一、前言
本文介绍用C语言模拟一个通讯录,用来存储联系人的信息。采取数据结构中的动弹顺序表来实现。
每个联系人的信息包括:姓名、性别、电话、住址。
通讯录的功能包括:
- 新增联系人信息
- 按姓名查找联系人
- 删除联系人信息
- 修改指定联系人的指定信息
- 查看所有联系人信息
- 按指定关键字排序联系人信息
二、实现通讯录
1.定义联系人
通讯录存放联系人的信息,因此顺序表中的每一个元素应是一个“联系人”的结构体类型。
代码如下:
typedef struct People { char name[20]; //姓名 char sex[6]; //性别 char tel[15]; //电话 char addr[50]; //住址 }People;
2.定义通讯录顺序表
采用顺序表来实现通讯录。一个通讯录包含:存储的元素内容、通讯录表长与通讯录表容量。表长用于记录已存储的联系人个数,表的容量用于控制通讯录能否正常使用、是否需要增容等。
代码如下:
typedef struct Contact { People* arr; int size; int capacity; }Contact;
注意,这里的数组arr的类型是联系人类型,也就是People类型,因为它存放的内容是每一个联系人的信息。
3.通讯录菜单及主函数
菜单与main函数
菜单包含了我们要实现的功能提示。将菜单封装成函数,在主函数中调用。
代码如下:
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"); printf("\n"); }
用户通过选择功能前不同的数字代号,来进入对应的功能。在主函数中,我们可以通过switch-case语句来实现多分支,如当用户输入数字1时,进入add功能。
枚举主函数内部选项
为了增强代码的可读性,我们也可以用枚举类型定义以上菜单中的各个功能。在如下的枚举中,从texit到sort这七个变量分别代表整型数字0到数字6.
enum Choice { texit, //0 exit与c语言函数重名,因此需更改一下变量名 add, //1 del, //2 search, //3 modify, //4 show, //5 sort //6 };
因此,在main函数的switch-case控制中,我们可以这样写:用枚举中定义的7个变量分别代替0到6这七个数字作为switch-case的入口。
int input = 0; do { menu(); printf("请输入选项:>"); scanf("%d", &input); switch (input) { case add: //add break; case del: //del break; case search: //search break; case modify: //modify break; case show: //show break; case sort: //sort break; case texit: //texit break; default: printf("请输入正确的选项!\n"); break; } fflush(stdin); printf("\n"); } while (input); return 0; }
fflush(stdin)清空缓冲区
在上面代码中,在单个功能结束后调用了fflush(stdin)函数。该函数是C语言的清空缓冲区函数。
在输入input选项时,scanf("%d",&input)语句只有从缓冲区中读取到整型数字时,读取成功。但如果用户误触,没有输入整型数字,而输入了浮点数或其它非法数据,scanf会发生读取错误,即实际读入的数据与%d不匹配。而此时,缓冲区中的非法数据并没有因为scanf的读取而被清除。在第一次do-while循环结束、第二次循环进入时,scanf仍然会因为读取到缓冲区中的非法数据而发生读取错误。
只要缓冲区内的非法数据不清楚,scanf就永远无法读到正确的数据。这里可能会发生do-while的死循环,因此用fflush(stdin)进行规避。
如输入7.3时,会发生死循环
输入1.2,虽然不会发生死循环,但也会因为读取错误导致程序无法正常运行
这里对fflush(stdin)函数作简要介绍,并且提醒一下大家scanf的数据读取问题。
4.初始化与销毁通讯录
初始化
初始化通讯录可以有两个版本:初始化并分配顺序表空间或仅初始化,不分配顺序表空间(分配空间的任务在对顺序表进行插入时再执行)。
代码如下:
版本一:初始化并分配顺序表空间
#define DEFAULT_SZ 3 void Init(Contact* con) { assert(con); con->arr = (People*)malloc(DEFAULT_SZ * sizeof(People)); if (con->arr == NULL) { perror("Init()"); return; } con->size = 0; con->capacity = DEFAULT_SZ; }
版本二:仅初始化不分配空间
1.void Init(Contact* con) { con->arr = NULL; con->size = con->capacity = 0; }
*下面将采取版本二对应的思路实现剩余代码。
销毁
1.//7-销毁 void destroyContact(Contact *con) { assert(con); free(con->arr); con->arr = NULL; con->size = con->capacity = 0; printf("销毁成功!\n"); }
在exit之前,必须先销毁,否则会造成内存泄漏。
5.新增联系人
判断增容
若表长size与表容量capacity相等时,意味着顺序表满,应当增容。
增容代码如下:
//判断增容 static void CheckCapacity(Contact* con) { assert(con); if (con->capacity == con->size) //判断通讯录是否已满 { //由于初始化表时,并未给表分配空间,因此若capacity为0,则应先分配空间 int newCapacity = con->capacity == 0 ? 4 : 2 * con->capacity; //realloc原地增容 People* tmp = (People*)realloc(con->arr, sizeof(People) * newCapacity); if (tmp == NULL) { perror("CheckCapacity"); exit(-1); } else { con->arr = tmp; con->capacity = newCapacity; } } }
增容的思路是当表满后,顺序表的容量扩展为原表的两倍。但由于初始化通讯录顺序表时,并未给顺序表分配空间,因此最开始表capacity为0.此时2*0结果还是0,无法达到增容的效果,程序会出错。因此先对newCapacity进行判断,若newCapacity为0,则先给它赋一个初始值。若newCapacity不为0,那么扩容到原来的两倍。
此外,扩容应当是在原数组的基础上扩张。因此要用realloc来原地扩容。realloc的原理是当原数组后还有连续可用空间时,直接在原数组后开辟空间,仍然返回原数组地址。
若用malloc,则会重新开辟一片内存空间,原数组中的内容会丢失,因此不妥。
添加联系人
在判断增容过后,就能安全地添加联系人了。我们通过con->arr[con->size].成员名 来输入联系人信息。
con是一个指向通讯录的指针,通讯录中的arr数组存储的是联系人People的信息。
con->arr即可访问到通讯录中的People信息。
由于是顺序表,通过con->size即可访问表中元素。表中每个元素都是People类型,因此con->arr[con->size]表示的正是一个People类型的结构体,这时通过成员访问符 . 即可对相应的信息进行输入。
添加联系人的代码如下:
//1-添加 void addContact(Contact *con) { //判断增容 CheckCapacity(con); //添加内容 printf("姓名:"); scanf(" %s", con->arr[con->size].name); printf("性别:"); scanf(" %s", con->arr[con->size].sex); printf("电话:"); scanf(" %s", con->arr[con->size].tel); printf("住址:"); scanf(" %s", con->arr[con->size].addr); con->size++; //添加完毕后个数要+1 printf("添加联系人成功!\n"); }
C语言实验-动态顺序表实现简易通讯录(二)
+https://developer.aliyun.com/article/1519325?spm=a2c6h.13148508.setting.15.25634f0e0MQf1l