通訊錄
- 【功能子產品】
-
- 1. 新增聯系人
- 2. 删除聯系人
- 3. 修改聯系人
- 4. 查詢聯系人
- 5. 顯示所有聯系人
- 6. 清空所有聯系人
- 【函數主體】
- 【功能測試】
-
楔子:
我們日常使用的手機,無論大小,應該都具備通訊錄這個實用的功能。仔細想想其實它的實作原理其實也很簡單,那麼本篇部落格就按照邏輯來實作一下一個簡單的通訊錄系統。
- 通訊錄可以抽象成為一下幾個子產品:
- 管理很多聯系人
- 每個聯系人的資訊:姓名 + 電話 (為主)
- 增删改查的功能
- 和使用者通過控制台進行互動
以下為程式主體:
【功能子產品】
1. 新增聯系人
void Add_Contact(telephone_directory* telephone_directory) {
assert(telephone_directory != NULL);
if (telephone_directory->size >= CONTACT_MAX_SIZE) {
printf("目前通訊錄已滿");
return;
}
printf("\n\n******開始新增聯系人:******\n");
//每次都把這個新的聯系人放到有效數組的最後一個元素上
Contact* p = &telephone_directory->person[telephone_directory->size]; //取出結構體指針友善下面幾行的代碼可讀性與簡潔性
//這裡如果取結構體變量,不使用結構體指針:Contact p = telephone_directory->person[telephone_directory->size];
//這個結構體變量相當于數組中對應元素的一份拷貝,修改結構體變量時隻會對副本修改,不會影響到原來的數組
printf("請輸入新增聯系人的姓名:");
scanf("%s", p->name);
printf("\n請輸入新增聯系人的電話号碼:");
scanf("%s", p->Cell_phone);
//新增完成後,需要更新 size
++telephone_directory->size;
printf("******插入聯系人成功!******\n\n");
printf("\n******目前通訊錄中共有%d條資料******\n\n", telephone_directory->size);
}
2. 删除聯系人
void Delete_Contact(telephone_directory* telephone_directory) {
//1. 将删除對象之後的所有元素都往前挪一位,然後将最後一位設為無效位,即--size;
//這樣的方法局限性太強,如果是一個大型公司的通訊錄,之後的元素如果也是數以億計,那麼這樣多次搬運方法就顯得很笨拙
//也可以使用連結清單,這裡主要講解下面這種方法
//2. 将最後一個元素覆寫掉删除對象元素,之後--size;
//這種方法是順序表的思路,因為順序表是有順序的,不可以打斷其中元素的順序關系。此時通訊錄的情況,就不要求實實在在的順序,對該方法的一個拓展可以很好地解決現在等問題
assert(telephone_directory != NULL);
//如果通過姓名删除,操作很危險,因為存在重名風險,可能删除多個聯系人
//如果通過電話号碼删除,雖然是唯一的,但是号碼太長難以記憶,不太科學
//是以此時選擇 通過通訊錄序号 進行删除.
printf("請輸入想要删除的聯系人序号:");
int id = 0;
scanf("%d", &id);
if (id < 0 || id >= telephone_directory->size) {
printf("您輸入的序号有誤!删除失敗!\n");
return;
}
Contact* p = &telephone_directory->person[id];
printf("确認删除[%d]\t%s\t%s?(使用 Y 确認)", id, p->name, p->Cell_phone);
char choice[1024]; // 使用者輸入的内容
scanf("%s", &choice);
if (strcmp(choice, "Y") != 0) {
printf("删除操作取消!\n");
return;
}
//開始删除邏輯
//1. 取出最後一個數組元素的結構體指針
Contact* from = &telephone_directory->person[telephone_directory->size - 1];
//2. 取出被删除元素結構體指針
Contact* to = p;
//3. 替換被删除元素
//【結構體之間允許同類型結構體的直接指派】【相當于from結構體中的内容拷貝到to結構體中】
*to = *from;
--telephone_directory->size;
printf("******删除操作完成!******\n");
}
3. 修改聯系人
void Modify_Contact(telephone_directory* telephone_directory) {
assert(telephone_directory != NULL);
printf("******開始修改聯系人******\n\n");
printf("請輸入需要修改的聯系人序号:");
int id = 0;
scanf("%d", &id);
if (id < 0 || id > telephone_directory->size) {
printf("您輸入的序号有誤!修改失敗!\n");
return;
}
Contact* p = &telephone_directory->person[id];
char input[1024];
printf("請輸入要修改的姓名;(不需要修改此項請輸入 !)");
scanf("%s", input);
if (strcmp(input, "!") != 0) {
strcpy(p->name, input);
}
printf("請輸入要修改的電話号碼;(不需要修改此項請輸入 !)");
scanf("%s", input);
if (strcmp(input, "!") != 0) { //如果使用者沒有選擇!,就是選擇修改内容,把使用者輸入的内容寫入結構體中。
//如果使用者輸入了!,表示不修改内容,那麼就不需要把輸入替換原來的内容了,if條件中的語句就不執行了
strcpy(p->Cell_phone, input);
}
printf("******修改成功!******\n");
}
4. 查詢聯系人
void Seek_Contact(telephone_directory* telephone_directory) {
assert(telephone_directory != NULL);
printf("******開始進行查找******\n\n");
printf("請輸入要查找的姓名:"); //一般都是按照姓名進行查找,是以此處設定為姓名索引
char name[1024] = { 0 };
scanf("%s", name);
int count = 0;
for(int i = 0;i < telephone_directory->size;++i){
Contact* p = &telephone_directory->person[i];
if (strcmp(p->name, name) == 0) { //表示找到了
printf("[%d]\t%s\t%s\n", i, p->name, p->Cell_phone);
//姓名是有可能重複的 是以此時不可以加上break;,展示所有内容
++count;
}
}
printf("\n\n******查找完畢!******\n");
printf("******共顯示了 %d 條資料!******\n",count);
}
5. 顯示所有聯系人
void Print_all_Contact(telephone_directory* telephone_directory) {
assert(telephone_directory != NULL);
for (int i = 0; i < telephone_directory->size; ++i) {
Contact* p = &telephone_directory->person[i];
//這時如果不設定為指針,其實是可以的,因為此時不涉及更改元素内容,而是簡單的讀取檢視,無論是副本還是原始資料都是一個值
//但是這個過程涉及對實參進行拷貝,如果實參結構體大小很小影響不大,如果結構體占存許多個G,那麼不使用指針的這個行為開銷就太大了,還是推薦使用指針
printf("\n\n");
printf("序号\t姓名\t聯系方式\n");
printf("[%d]\t%s\t%s\n", i, p->name, p->Cell_phone);
}
printf("\n******以上共顯示了%d條資料******\n\n", telephone_directory->size);
}
6. 清空所有聯系人
void Clear_all_Contact(telephone_directory* telephone_directory) {
assert(telephone_directory != NULL);
printf("确認清空所有聯系人:(輸入 Y 表示确認)"); //防止使用者誤觸點選清空
char input[1024];
scanf("%s", input);
if (strcmp(input, "Y") != 0) {
printf("\n******清空操作取消!******\n");
return;
}
telephone_directory->size = 0;
printf("\n******清空操作完成!******\n");
}
【函數主體】
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#define CONTACT_MAX_SIZE 500 //最大聯系人的宏定義,可以随時在這裡修改本參數在程式中的值
//建立聯系人結構體
typedef struct Contact {
char name[1024];
char Cell_phone[1024];
}Contact;
//聯系人結構體數組
typedef struct telephone_directory {
Contact person[CONTACT_MAX_SIZE];
int size; //描述前size個元素是有效的,[0 ,size)範圍是數組的有效範圍區間
}telephone_directory;
telephone_directory g_telephone_directory; //聲明一個全局變量作為設計的通訊錄的實體
//清空函數
void Init(telephone_directory* telephone_directory) { //這裡通過指針傳參,如果不使用指針,函數的形參就不會影響到實參
assert(telephone_directory != NULL);//設定斷言來校驗傳進來的指針的有效性
telephone_directory->size = 0;
//這個結構體數組是通過 size 控制數組元素的有效個數
//當把 size 設為 0 ,證明無論數組元素内容是什麼都是無效的,等同于清空。
}
//菜單函數
int menu() {
printf("=================\n");
printf("| < 選項清單 > |\n");
printf("| |\n");
printf("| 1. 增加 |\n");
printf("| 2. 删除 |\n");
printf("| 3. 修改 |\n");
printf("| 4. 查詢 |\n");
printf("| 5. 顯示全部 |\n");
printf("| 6. 清空全部 |\n");
printf("| 0. 退出 |\n");
printf("| |\n");
printf("=================\n");
printf("\n請輸入您的選項:");
int choice = 0;
scanf("%d", &choice);
return choice;
}
int main() {
//1. 對通訊錄進行初始化
Init(&g_telephone_directory);
//開始設定表驅動
typedef void(*pfunc_t)(telephone_directory*); //設定函數指針數組
pfunc_t table[] = {
Add_Contact,
Delete_Contact,
Modify_Contact,
Seek_Contact,
Print_all_Contact,
Clear_all_Contact
};
while (1) {
int choice = menu();
if (choice < 0 || choice >(int)(sizeof(table) / sizeof(table[0]))) { // 這裡不直接設定一個數字,而選擇麻煩的去設
//sizeof 的傳回值是無符号整數,而choice變量我們設定的是有符号的整數,我們選擇使無符号類型強制類型轉換為有符号的類型
//筆者建議盡量少使用無符号整數,因為unsigned的使用有很多注意點,比如兩個無符号整型作差,被減數如果小于減數就會變成一個非常大的數超乎平常預期等等,感興趣的讀者可以自行了解
printf("您的輸入有誤! 請重新輸入:\n");
continue;
}
if (choice == 0) {
printf("\n感謝您的使用,再見~\n");
break;
}
table[choice - 1](&g_telephone_directory); //使用者輸入的值和函數指針數組中對應的選項相差 1 ,是以這裡選擇将使用者輸入的數字 -1 ,對應到數組中正确的值
//if (choice == 1) {
// AddContact(&g_telephone_directory);
//}
//else if (choice == 2) {
// DelContact(&g_telephone_directory);
//}
//else if (choice == 3) {
// ModifyContact(&g_telephone_directory);
//}
...
//如果這樣設計,“圈複雜度”過高,通過使用函數指針數組實作的“表驅動”解決
}
system("pause");
return 0;
}
【功能測試】
- 運作程式,列印互動菜單:
- 新增功能
-
删除功能
(1) 聯系人序号輸入錯誤
(2) 确認密鑰輸入錯誤 (3) 正确删除 -
修改功能
(1) 聯系人序号輸入錯誤
(2) 姓名預設,修改電話号碼 【通訊錄中内容發生變化】: (3) 電話号碼預設,修改姓名 【通訊錄中内容發生變化】: -
查詢功能
(1) 輸入通訊錄不存在的聯系人
(2) 正确查找聯系人 (3) 添加多條聯系人資料,同時設定重名聯系人,再次查證 【查詢結果】: - 顯示所有聯系人功能
-
清空所有聯系人功能
(1) 确認密鑰輸入錯誤
(2) 正确清空 【清空結果】
- 小結:
- 每一個項目的完成都是多個子產品功能的加和,是以設計階段一定要把要實作的功能構思好,再化整為零,逐個攻破。
- 每個函數都要進行參數有效性檢驗,這是確定程式執行高效性的一個名額,及時發現傳參錯誤的問題。
- 程式參數一定要設定的足夠大,否則資料溢出或者數組越界就是非常棘手的問題了,防患于未然,注意要提前将參數留有餘地。
- 結構體成員通路的書寫有點複雜,是以一開始就選擇定義一個結構體指針,之後就可以簡化寫法,用指針來代表某段結構體成員通路。使用指針調用時就可以減少不必要的開銷,避免将整個結構體在函數中以形參的形式再拷貝一份。
- 對于重大的危險操作設定确認密鑰,以防使用者誤觸點選按鍵造成萬劫不複的行為,通過加上确認密鑰這種二重保證就可以防止這種事件的發生。