一、总体设计思路
1、设计背景
在前面的文章中我们依次学习了结构体、动态内存管理以及文件操作的相关知识,所以我们今天可以分别来编写静态版、动态版和文件版的通讯录,用于对已学知识的应用与巩固,和对我们前面学习效果的检测。
2、设计框架
与正常的项目设计一样,我们把程序封装在三个文件中:
test.c:通讯录的总体逻辑,主要用于对通讯录各功能的测试;
contact.c:通讯录各种功能的具体实现;
contact.h:各种必要的声明,包括库函数头文件的声明、自定义结构的声明以及自定义函数的声明;
3、功能概述
我们这里设计的通讯录应包含如下功能:
静态版:通讯录内部联系人的增删查改、对联系人按姓名进行排序、显示通讯录中所有联系人、清空通讯录、退出通讯录;
动态版:动态版的通讯录与静态版通讯录功能一样,但是内部实现由固定大小改为动态增容;
文件版:文件版的通讯录与动态版功能与实现方式一样,但是会在程序退出时把联系人信息保存在文件中,在程序运行时把文件中的联系人信息加载到通讯录中;
二、通讯录(静态版)
1、结构体设计
这里我们设计两个结构体:一个用于管理联系人的各种属性,比如姓名、年龄、性别、电话号码、住址;另一个结构体用于管理通讯录,它由一个联系人结构体数组加上一个记录联系人数量的变量构成;由于我们这里设计的是静态版的通讯录,所以联系人结构体数组的大小是固定的。
#define MAX 100 //联系人的最大数量 #define MAX_NAME 20 //宏定义各种联系人信息变量的大小,方便以后修改 #define MAX_SEX 10 #define MAX_TELE 12 #define MAX_ADDR 30 //联系人信息结构体 typedef struct PeoInfo { char name[MAX_NAME]; //姓名 char sex[MAX_SEX]; //性别 int age; //年龄 char tele[MAX_TELE]; //电话 char addr[MAX_ADDR]; //住址 }PeoInfo; //结构体重命名 //通讯录结构体 typedef struct Contact { PeoInfo date[MAX]; //储存联系人信息(固定大小) int count; //记录实际联系人数量 }Contact;
2、初始化通讯录
我们可以使用前面学习的 memset 函数来吧通讯录中存放联系人信息的数组的内容全部初始化为0,然后再把用于记录实际联系人数量的变量置为0,完成通讯录得初始化。
void InitContact(Contact* pc) //初始化通讯录 { assert(pc); memset(pc->date, 0, sizeof(pc->date)); //通讯录里面的信息全部初始化为0 pc->count = 0; }
3、添加联系人信息
这里由于通讯录大小是固定的,所以我们在添加联系人的时候要注意检查通讯录是否已满,如果没满才能正常添加,满了就打印提示信息并直接 return。
void AddPeoInfo(Contact* pc) //添加联系人信息 { assert(pc); if (pc->count == MAX) //检查通讯录是否满了 { printf("通讯录已满,无法添加联系人\n"); return; } else { printf("请输入姓名:>"); scanf("%s", pc->date[pc->count].name); printf("请输入性别:>"); scanf("%s", pc->date[pc->count].sex); printf("请输入年龄:>"); scanf("%d", &(pc->date[pc->count].age)); printf("请输入电话:>"); scanf("%s", pc->date[pc->count].tele); printf("请输入住址:>"); scanf("%s", pc->date[pc->count].addr); pc->count++; //联系人数量++ printf("添加联系人成功\n"); } }
4、删除联系人信息
这里有两个需要注意的地方:一是需要检查通讯录是否为空,如果为空提示后直接返回;二是我们要检查我们希望删除的这个人是否存在,所以我们需要设计一个find函数来查找联系人,根据find函数的结果来进行后续操作。
void DeletePeoInfo(Contact* pc) //删除联系人信息 { assert(pc); if (pc->count == 0) //通讯录为空直接返回 { printf("通讯录为空\n"); return; } else { char name[MAX_NAME]; printf("请输入要删除的联系人的姓名:>"); scanf("%s", &name); int pos = find_by_name(pc, name); //检查通讯录中是否有该联系人 if (pos == -1) { printf("该联系人不存在\n"); } else { int i = 0; for (i = pos; i < pc->count - 1; i++) { pc->date[i] = pc->date[i + 1]; } pc->count--; printf("删除联系人成功\n"); } } }
5、查找联系人(按姓名)
这里函数返回值的设计应该注意,如果我们查找到了就返回该联系人所在位置的下标,但是如果找不到我们不应该返回0,而是应该返回一个无意义的数,比如-1,因为数组是从0下标开始的,我们所查找的联系人可能在0位置处。
//在通讯录中查找指定联系人,找到返回下标,找不到返回-1 static int find_by_name(const Contact* pc, char name[]) //函数用static修饰是为了让该函数只能在本文件内部被使用 { assert(pc && name); int i = 0; for (i = 0; i < pc->count; i++) { if (strcmp(pc->date[i].name, name) == 0) return i; //找到返回所在位置下标 } return -1; //找不到返回-1 }
6、查找指定联系人
和删除联系人一样,我们需要检查通讯录是否为空,调用find函数判断该联系人是否存在。
void SearchPeoInfo(const Contact* pc) //查找指定联系人信息并打印 { assert(pc); if (pc->count == 0) { printf("通讯录为空\n"); } else { char name[MAX_NAME]; printf("请输入要查找的联系人的姓名:>"); scanf("%s", name); int pos = find_by_name(pc, name); //检查通讯录中是否有该联系人 if (pos == -1) { printf("该联系人不存在\n"); } else //打印联系人信息 { printf("%-20s\t%-10s\t%-5s\t%-12s\t%-30s\n", "姓名", "性别", "年龄", "电话", "住址"); //打印表头 printf("%-20s\t%-10s\t%-5d\t%-12s\t%-30s\n", pc->date[pos].name, //打印数据 pc->date[pos].sex, pc->date[pos].age, pc->date[pos].tele, pc->date[pos].addr); } } }
7、修改联系人信息
void ModifyPeoInfo(Contact* pc) //修改联系人信息 { assert(pc); if (pc->count == 0) { printf("通讯录为空\n"); } else { char name[MAX_NAME]; printf("请输入要修改的联系人的姓名:>"); scanf("%s", name); int pos = find_by_name(pc, name); //检查通讯录中是否有该联系人 if (pos == -1) { printf("该联系人不存在\n"); } else { printf("请输入修改后的姓名:>"); scanf("%s", pc->date[pos].name); printf("请输入修改后的性别:>"); scanf("%s", pc->date[pos].sex); printf("请输入修改后的年龄:>"); scanf("%d", &(pc->date[pos].age)); printf("请输入修改后的电话:>"); scanf("%s", pc->date[pos].tele); printf("请输入修改后的住址:>"); scanf("%s", pc->date[pos].addr); printf("成功修改联系人信息\n"); } } }
8、排序联系人(按姓名)
我们可以通过调用qsort函数,然后给定排序规则来实现对联系人信息的排序,这里我实现的是按姓名排序,大家通过可以修改排序方法来实现按年龄等其他规则的排序,甚至可以实现出所有排序方法的函数,然后将其放入函数指针数组中,最后通过回调函数的方式实现任意方式的排序。
int cmp_name(const void* e1, const void* e2) //qsort函数的排序函数 { assert(e1 && e2); return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name); } void SortContact(Contact* pc) //对通讯录进行排序(按姓名) { assert(pc); //qsort的使用:pc->date表示要排序数据的地址,pc->count表示待排序的元素个数,sizeof(PeoInfo)表示一个元素的大小,cmp_name表示排序的方法 qsort(pc->date, pc->count, sizeof(PeoInfo), cmp_name); }
9、显示所有联系人信息
void ShowPeoInfo(const Contact* pc) //显示所有联系人信息 { assert(pc); if (pc->count == 0) { printf("当前通讯录为空\n"); } else { int i = 0; printf("%-20s\t%-10s\t%-5s\t%-12s\t%-30s\n", "姓名", "性别", "年龄", "电话", "住址"); //打印表头 for (i = 0; i < pc->count; i++) { printf("%-20s\t%-10s\t%-5d\t%-12s\t%-30s\n", pc->date[i].name, //打印数据 pc->date[i].sex, pc->date[i].age, pc->date[i].tele, pc->date[i].addr); } } }
10、清空通讯录
对于静态版的通讯录来说,清空通讯录就相当于初始化通讯录,直接调用该函数即可。
void ClearContact(Contact* pc) //清空所有联系人 { assert(pc); InitContact(pc); //清空相当于把通讯录初始化 }
11、完整代码
静态版通讯录的完整代码及相关数据文件我放在 GitHub 和 Gitee 上了,有需要的可以自取。