目錄
1、通訊錄系統介紹
?2、代碼分裝
?3、代碼實現步驟
3.1制作菜單函數以及游戲運行邏輯流程
?3.2、封裝人的信息PeoInfo以及通訊錄Contact結構體類型
3.3、初始化通訊錄InitContact函數
3.4、增加聯系人AddContact函數
?3.5、顯示所有聯系人ShowContact函數
?3.6、刪除聯系人DelContact函數以及判斷是否存在FindByName函數
?3.7、查找指定聯系人SearchContact函數
3.8、修改指定聯系人ModifyContact函數
?3.9、以年齡排序聯系人SortContact函數
?4、使用動態規劃優化通訊錄?
1、通訊錄系統介紹
實現一個通訊錄:
- 可以保存100個人的信息(后續優化成動態開辟)
- 增加人的信息
- 刪除指定聯系人的信息
- 修改指定聯系人的信息?
- 查詢指定聯系人的信息
- 顯示所有聯系人的信息
- 排序通訊錄的信息
其中,人的信息包括:名字、年齡、性別、電話 、地址
?2、代碼分裝
源文件:tect.c? 測試通訊錄的基本功能
contact.c? 相關函數的實現
頭文件 contact.h相關函數和類型的聲明,包含要引用的頭文件和宏
?3、代碼實現步驟
3.1制作菜單函數以及游戲運行邏輯流程
在 test.c 中定義一個menu函數打印菜單,提示玩家進行選擇,增刪查改等選線;
scanf接收玩家輸入并用switch判斷,針對判斷進行相應操作,輸入錯誤時提示選擇錯誤,重新選擇。用到do while語句:為了能夠讓用戶重新選擇以及完成一個功能操作時再繼續下一個功能操作,需要使用do while語句將它們包含起來。
使用枚舉一一列舉菜單選擇的可能取值,便于后續使用時能夠見名知意,增加代碼可讀性,而且枚舉變量的值是默認從0開始。
【test.c】?
void menu()
{printf("***********************************\n");printf("****** 1.add 2.del ******\n");printf("****** 3.search 4.modify ******\n");printf("****** 5.show 6.sort ******\n");printf("****** 0.exit ******\n");printf("***********************************\n");
}enum Option //枚舉常量
{EXIT,ADD,DEL,SEARCH,MODIFY,SHOW,SORT
};int main()
{int input = 0;do{menu();printf("請輸入你的選擇:>");scanf("%d", &input);switch (input){case ADD:break;case DEL:break;case SEARCH:break;case MODIFY:break;case SHOW:break;case SORT:break;case EXIT:printf("退出通訊錄\n");break;default:printf("選擇錯誤,重新選擇\n");break;}} while (input);return 0;
}
?3.2、封裝人的信息PeoInfo以及通訊錄Contact結構體類型
一個人的信息包括很多,名字、年齡、性別、電話 、地址。這些成員是不同類型,所以創建一個結構體變量來保存人的信息Peoinfo。
同理通訊錄也是使用一個結構體,要包括人的信息,也要知道已經存入多少個人的信息了。
?【contact.h】
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDR_MAX 30#define MAX 100typedef struct PeoInfo
{char name[NAME_MAX];int age;char sex[SEX_MAX];char tele[TELE_MAX];char addr[ADDR_MAX];
}PeoInfo;typedef struct Contact
{PeoInfo data[MAX];int sz; //記錄的是當前通訊錄中存放的人的信息個數
}Contact;
Contact這個結構體類型中包含了PeoInfo類型的數組data,數組的元素是?PeoInfo類型。
還包括sz用于記錄當前保存了多少個聯系人的信息。
3.3、初始化通訊錄InitContact函數
【contact.h】
//初始化通訊錄
void InitContact(Contact* pc);
?【contact.c】?
void InitContact(Contact* pc)
{assert(pc);pc->sz = 0;memset(pc->data, 0, sizeof(pc->data));
}
?在contact.c中具體的實現這個函數,采用指針+箭頭操作符來訪問初始化con中的data成員。
將data和sz初始化為0
assert斷言,對傳入的指針進行判斷,防止對空指針進行操作。
使用?memset函數?初始化結構體中的數據。
?【test.c】?
int main()
{int input = 0;Contact con; //通訊錄InitContact(&con);//初始化通訊錄do{menu();printf("請輸入你的選擇:>");scanf("%d", &input);switch (input){case ADD:break;case DEL:break;case SEARCH:break;case MODIFY:break;case SHOW:break;case SORT:break;case EXIT:printf("退出通訊錄\n");break;default:printf("選擇錯誤,重新選擇\n");break;}} while (input);return 0;
}
創建了一個Contact類型的結構體變量con,調用初始化函數,把con的地址作為參數傳出
在contact.c中具體的實現這個函數,采用指針+箭頭操作符來訪問初始化con中的data成員。
3.4、增加聯系人AddContact函數
同樣是函數的聲明
函數的實現
函數的調用
函數的聲明【contact.h】
//增加聯系人
void AddContact(Contact* pc);
?函數的實現【contact.c】
void AddContact(Contact* pc)
{assert(pc);if (pc->sz == MAX){printf("通訊錄已滿,無法增加\n");return;}//增加信息printf("請輸入名字:");scanf("%s", pc->data[pc->sz].name);printf("請輸入年齡:");scanf("%d", &(pc->data[pc->sz].age));printf("請輸入性別:");scanf("%s", pc->data[pc->sz].sex);printf("請輸入電話:");scanf("%s", pc->data[pc->sz].tele);printf("請輸入地址:");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("增加成功\n");}
pc->data[pc->sz].name
就是訪問data數組中的第sz個元素的name。也就是用戶要添加的是第幾個聯系人,他的名字,年齡,性別....
?3.5、顯示所有聯系人ShowContact函數
【contact.h】
//顯示所有聯系人
void ShowContact(const Contact* pc);
【contact.c】
void ShowContact(const Contact* pc)
{assert(pc);if (pc->sz == 0){printf("通訊錄為空,無需打印\n");return;}int i = 0;printf("%-20s%-5s%-5s%-12s%-30s\n", "名字", "年齡", "性別", "電話", "地址"); //標題行for ( i = 0; i < pc->sz; i++){printf("%-20s%-5d%-5s%-12s%-30s\n",pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);}
}
- 名為ShowContact的函數,用于打印通訊錄對象的內容。函數的參數是一個指向const Contact類型的指針。
- 在函數內部,首先使用assert宏對傳入的指針進行斷言,確保指針非空。然后判斷通訊錄的大小(sz字段)是否為0,如果為0則說明通訊錄為空,打印提示信息并返回。
- 接下來使用一個循環來遍歷通訊錄的每個聯系人對象,并使用printf函數按照一定的格式打印聯系人的信息。第一次循環打印標題行,后續循環每次打印一個聯系人的信息。
- 打印的信息包括:名字(name)、年齡(age)、性別(sex)、電話(tele)和地址(addr)。
?【test.c】
在switch語句中的case show調用函數
switch (input){case ADD:AddContact(&con);break;case DEL:break;case SEARCH:break;case MODIFY:break;case SHOW:ShowContact(&con);break;case SORT:break;case EXIT:printf("退出通訊錄\n");break;default:printf("選擇錯誤,重新選擇\n");break;}
?3.6、刪除聯系人DelContact函數以及判斷是否存在FindByName函數
【contact.h】
//刪除聯系人
void DelContact(Contact* pc);
?【contact.c】
- 因為通過名字判斷此人是否存在的FindByName函數這個功能在其他操作上也需要使用到,所以最好將它封裝成一個函數,減少代碼冗余并且提高寫代碼效率。
- 當FindByName函數在通訊錄中找到此人時返回此人在data數組中的下標,找不到是返回-1。返回-1就是為假
- 又因為FindByName函數只在contact.c中被使用,因此可以加上static關鍵字修飾。
static int FindByName(Contact* pc, char name[])
{assert(pc);int i = 0;for ( i = 0; i < pc->sz; i++){if (strcmp(pc->data[i].name, name) == 0) //兩個字符串比較用strcmpreturn i;}return -1;
}void DelContact(Contact* pc)
{char name[NAME_MAX];assert(pc);if (pc->sz == 0){printf("通訊錄為空,無法刪除\n");return;}printf("請輸入要刪除人的名字:");scanf("%s", name);//查找名字為name的人int ret = FindByName(pc, name);if (ret == -1){printf("要刪除的人不存在\n");return;}//刪除這個人int i = 0;for (i = ret; i < pc->sz - 1; i++) //注意此處的sz - 1 {pc->data[i] = pc->data[i + 1];}pc->sz--;printf("刪除成功\n");
}
- DelContact函數中創建的char類型的數組name是為了存儲要刪除的聯系人的名字。這是因為在循環中進行聯系人比較時,我們需要將要刪除的聯系人的名字與contacts數組中的每個聯系人的名字進行比較,以找到要刪除的聯系人的索引。
- 如果沒有name數組并且直接在循環中將contacts[i].name與要刪除的聯系人的名字進行比較,那么在找到要刪除的聯系人之后,我們將無法在后續的循環中對其進行操作。因為我們只能訪問到contacts[i]和contacts[i-1],找不到要刪除的聯系人的索引。
- 通過將要刪除的聯系人的名字存儲在name數組中,我們可以在找到要刪除的聯系人之后,繼續使用它的索引進行相應操作,例如移動后面的聯系人、更新sz等等。這樣就可以正確地刪除聯系人,并保持contacts數組的完整性。
- 在代碼中,DelContact函數的最后一個循環是為了從contacts數組中刪除指定的聯系人。循環通過將指定位置后面的聯系人逐個向前移動一位來實現。因此,循環條件是i < sz-1,即i的值需要小于sz-1。
- 這是因為刪除聯系人時,需要將后面的聯系人向前移動一個位置。而不是用后面的一個聯系人來覆蓋要刪除的聯系人。如果循環條件是i < sz,那么最后一個聯系人將無法被移動,因為沒有后面的聯系人可以填充它的位置。比如現在是10個聯系人
- 因此,循環條件為i < sz-1確保了所有聯系人都能被正確地向前移動一位,保證了聯系人信息的完整性。
?將ret后面的所有內容向前平移一位,就可以覆蓋掉要ret指向的內容,然后再對sz--,這樣變相地完成了對信息的刪除。等將來又有新聯系人加入時,因為sz--了所以寫入信息時會覆蓋掉下標為ret中的內容。
?3.7、查找指定聯系人SearchContact函數
【contact.h】
//查找指定聯系人
void SearchContact(Contact* pc);
?【contact.c】
void SearchContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("請輸入要查找人的名字:");scanf("%s", name);int ret = FindByName(pc, name);if (ret == -1){printf("要查找的人不存在\n");return;}//顯示查查找到的信息printf("%-20s%-5s%-5s%-12s%-30s\n", "名字", "年齡", "性別", "電話", "地址");printf("%-20s%-5d%-5s%-12s%-30s\n",pc->data[ret].name, pc->data[ret].age, pc->data[ret].sex, pc->data[ret].tele, pc->data[ret].addr);}
3.8、修改指定聯系人ModifyContact函數
?【contact.h】
//修改指定聯系人
void ModifyContact(Contact* pc);
?【contact.c】
void ModifyContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("請輸入要修改人的名字:");scanf("%s", name);int ret = FindByName(pc, name);if (ret == -1){printf("要修改的人不存在\n");return;}//修改printf("請輸入名字:");scanf("%s", pc->data[ret].name);printf("請輸入年齡:");scanf("%d", &(pc->data[ret].age));printf("請輸入性別:");scanf("%s", pc->data[ret].sex);printf("請輸入電話:");scanf("%s", pc->data[ret].tele);printf("請輸入地址:");scanf("%s", pc->data[ret].addr);
}
調用FindByName函數進行判斷,有此人則返回下標,沒有此人則返回-1。
找到之后直接將該下標下的所有信息都重新接收并覆蓋,就完成了修改操作。
?3.9、以年齡排序聯系人SortContact函數
?【contact.h】
//排序聯系人
void SortContact(Contact* pc);
?【contact.c】
int cmp_age(const void* e1, const void* e2)
{return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}void SortContact(Contact* pc)
{assert(pc);if (pc->sz == 0){printf("通訊錄為空,無需排序\n");return;}qsort(pc, pc->sz, sizeof(PeoInfo), cmp_age);printf("排序成功\n");
}
?仍然存在的問題:
1.錄入的信息,等程序結束后就不存在了,這是因為數據存放在內存中的。為了解決這個問題,需要使用到文件存儲的知識。
2.通訊錄的大小是固定的100個元素,只能最多存放100個人。當信息太少時,就會導致空間剩余過大浪費空間,而當信息太多時空間又太小了無法進行存入,而解決這個問題需要使用到動態內存管理的知識。下面就來優化一下通訊錄。
?4、使用動態規劃優化通訊錄?
規定:
- 通訊錄剛開始時可以存放3個人的信息。#define DEFAULT_SZ 3
- 當空間放滿時,自動增加容量,每次增加2個信息的空間。#define DEFAULT_INC 2
?定義通訊錄Contact結構體:
?首先是通訊錄這個結構體要修改,因為原來定死了是100人,這里使用一個PeoInfo類型的指針,空間是malloc開辟的,方便我們使用realloc來調整大小
?【靜態】
靜態版本
typedef struct Contact
{PeoInfo data[MAX];int sz; //記錄的是當前通訊錄中存放的人的信息個數
}Contact;
?【動態】
動態版本
typedef struct Contact
{PeoInfo* data;int sz; //記錄的是當前通訊錄中存放的人的信息個數int capacity; //記錄的時通訊錄當前的最大容量
}Contact;
?sz記錄的是當前通訊錄中存放的人的信息個數
?capacity記錄的時通訊錄當前的最大容量當sz==capacit就要考慮增容了
?初始化通訊錄InitContact函數:?
使用calloc對data指針進行動態開辟空間,如果開辟失敗則用perror打印錯誤信息
【靜態】?
靜態版本
void InitContact(Contact* pc)
{assert(pc);pc->sz = 0;memset(pc->data, 0, sizeof(pc->data));
}
【動態】
初始化sz,capacity,data(使用calloc來開辟)就要判斷返回的指針是不是為NULL
void InitContact(Contact* pc)
{assert(pc);pc->sz = 0;pc->capacity = DEFAULT_SZ; //#define DEFAULT_SZ 3pc->data = (PeoInfo*)calloc(pc->capacity, sizeof(PeoInfo));if (pc->data == NULL){perror("InitContact->calloc");return;}
}
??增加聯系人AddContact函數:
人滿了就要增加容量判斷條件是當sz==capacit就要考慮增容了。使用if語句。使用realloc函數增容,記得要先創建一個指針tmp來判斷返回的指針是不是為NULL不為NULL是賦值給p
【靜態】
靜態版本
void AddContact(Contact* pc)
{assert(pc);if (pc->sz == MAX){printf("通訊錄已滿,無法增加\n");return;}printf("請輸入名字:");scanf("%s", pc->data[pc->sz].name);printf("請輸入年齡:");scanf("%d", &(pc->data[pc->sz].age));printf("請輸入性別:");scanf("%s", pc->data[pc->sz].sex);printf("請輸入電話:");scanf("%s", pc->data[pc->sz].tele);printf("請輸入地址:");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("增加成功\n");}
?【動態】
void AddContact(Contact* pc)
{assert(pc);if (pc->sz == pc->capacity){PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + DEFAULT_INC) * sizeof(PeoInfo));if (tmp != NULL){pc->data = tmp;pc->capacity += DEFAULT_INC; //#define DEFAULT_INC 2printf("增容成功\n");}else{perror("AddContact->realloc");return;}}printf("請輸入名字:");scanf("%s", pc->data[pc->sz].name);printf("請輸入年齡:");scanf("%d", &(pc->data[pc->sz].age));printf("請輸入性別:");scanf("%s", pc->data[pc->sz].sex);printf("請輸入電話:");scanf("%s", pc->data[pc->sz].tele);printf("請輸入地址:");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("增加成功\n");}
?pc->data就是指針這個data是一個PeoInfo類型的指針
?也可以把這個增容的操作單獨封裝成一個函數
if (pc->sz == pc->capacity){PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + DEFAULT_INC) * sizeof(PeoInfo));if (tmp != NULL){pc->data = tmp;pc->capacity += DEFAULT_INC; //#define DEFAULT_INC 2printf("增容成功\n");}else{perror("AddContact->realloc");return;}}
在EXIT退出通訊錄時候記得對動態開辟的空間進行free操作:
【contact.h】
//銷毀通訊錄
void DestroyContact(Contact* pc);
【contact.c】
void DestroyContact(Contact* pc)
{free(pc->data);pc->data = NULL;pc->sz = 0;pc->capacity = 0;
}
?結束