前言:小伙伴们又见面啦!这几天通过我们对自定义数据类型的学习,我们已经掌握了如何同时对多种数据类型进行管理,那么今天这篇文章,我们就来干一件大事——实现简易的通讯录。
先来想想通讯录有哪些功能:
添加联系人信息
删除联系人信息
查找联系人信息
修改联系人信息
显示联系人信息
排序联系人信息
清空所有联系人
基本上都会有以上的7种功能,而且我们需要一个菜单来让用户进行选择。
制作一个大的项目,往往都要将代码写得规范整洁。
所以我们要跟之前写三子棋和扫雷游戏一样,将宏定义常量,函数声明与定义,以及主函数分开来写,方便日后对代码的维护。

Contact即为联系人,通讯录。
Contact.h 用来管理宏定义常量、函数的声明。
Contact.c 是管理函数的定义实现。
test.c 是用来进行测试代码的功能。
下面我们就先来一步一步的实现通讯录的基本框架。
- #include "Contact.h"
- void menu()
- {
- printf("*********************************\n");
- printf("***** 1.add 2.del *****\n");
- printf("***** 3.search 4.revize *****\n");
- printf("***** 5.show 6.sort *****\n");
- printf("***** 7.empty 0.exit *****\n");
- printf("*********************************\n");
- }
- int main()
- {
- int input;
- do {
- menu();
- printf("请选择->:");
- scanf("%d", &input);
- switch (input)
- {
- case 0:
- printf("退出通讯录\n");
- break;
- case 1:
- break;
- case 2:
- break;
- case 3:
- break;
- case 4:
- break;
- case 5:
- break;
- case 6:
- break;
- case 7:
- break;
- default:
- printf("输入错误,请重新输入\n");
- }
- } while (input);
- return 0;
- }
目录的做法对于现在的我们来说应该算是手到擒来,但是现在我们想要进行一个小小的改进:
如果我们做这个代码要交给其他的程序员看的话,他可能看到Switch-case语句时会很难分辨出各个case语句的数字都代表的是哪一项功能,还得不停地回到菜单函数去查看。
所以我们希望用各项功能的名字取代数字,这样就会更加方便。
那么这时候,就要用到枚举啦:
- #include "Contact.h"
- void menu()
- {
- printf("*********************************\n");
- printf("***** 1.add 2.del *****\n");
- printf("***** 3.search 4.revize *****\n");
- printf("***** 5.show 6.sort *****\n");
- printf("***** 7.empty 0.exit *****\n");
- printf("*********************************\n");
- }
- enum Function
- {
- EXIT,//默认从0开始
- ADD,
- DEL,
- SEARCH,
- REVIZE,
- SHOW,
- SORT,
- EMPTY,
- };
- int main()
- {
- int input;
- do {
- menu();
- printf("请选择->:");
- scanf("%d", &input);
- switch (input)
- {
- case EXIT:
- printf("退出通讯录\n");
- break;
- case ADD:
- break;
- case DEL:
- break;
- case SEARCH:
- break;
- case REVIZE:
- break;
- case SHOW:
- break;
- case SORT:
- break;
- case EMPTY:
- break;
- default:
- printf("输入错误,请重新输入\n");
-
- }
- } while (input);
- return 0;
- }
枚举常量默认从0开始,所以将exit放在第一位,而后逐个递增。
最后再将数字进行替换就好啦。
通讯录里会保存联系人的哪些信息呢???
名字、电话、住址等等,这些显然不会是一种数据类型。
所以创建联系人变量,就要用到结构体啦。
- struct Peomessage
- {
- char name[20];
- int tele[12];
- char addr[30];
- };
简单创建一个联系人信息的结构体,这时候又会有问题:
一个人的电话的不会超过12个数字,但是名字不会超过20个字符吗???
某一天,我认识了一个外国朋友,它的名字特别长,就要修改name数组的大小,地址同样如此。
那么为了方便高效,我们将这两个数组的大小进行宏定义。
- #define NAME_MAX 20
- #define ADDR_MAX 30
- typedef struct Peomessage
- {
- char name[NAME_MAX];
- int tele[12];
- char addr[ADDR_MAX];
- }Peomessage;
同时我们使用typedef类型重定义关键字,将此结构体类型的名字定义为Peomessage,方便我们后续的使用。
创建完联系人类型之后呢,我们就要开始创建联系人列表,这显然需要一个结构体类型的数组。
那么数组的大小是多少呢???这个又是一个无法估量的问题,所以我们仍然使用宏定义常量,
先默认能够存放100个联系人。
Peomessage data[DATA_MAX];
int sz;
我们建立了这样一个数组。除此之外,我们还需要一个整型变量来帮助我统计通讯录里有多少个联系人了,所以我们定义sz。
不难看出,这两者我们也需要同时管理,所以干脆就将它们两个也用一个结构体来管理:
- typedef struct Contact
- {
- Peomessage data[DATA_MAX];
- int sz;
- }Contect;
我们创建了Contect结构体之后,还需要将其内部数据初始化为0,以免出现异常情况。
- void InitContact(Contact* pc)
- {
- assert(pc);
- pc->sz = 0;
- memset(pc->data, 0, sizeof(pc->data));
- }
memset是我们之前讲过的内存函数,可以将任意位置的任意数量的数据设置成我们想要的值。
这里要讲的一点是assert函数,这个函数的作用就是帮助我们检查代码是否有错,有错误则会立即终止程序并返回错误信息。
这里主要是帮助我们判断pc指针的可用性,如果pc指针不可用,那么就会造成巨大的问题。
通过上述的步骤,我们已经实现了通讯录的基本框架,下面我们开始实现各种功能。
每一个功能的实现,必然少不了对于函数的运用。
再添加信息之前,有一点非常值得注意:那就是我们的通讯录有没有存满。
所以我们得先进行一个判断。
- void AddContact(Contact* pc)
- {
- assert(pc);
- //判断是否已存满
- if (pc->sz == DATA_MAX)
- {
- printf("通讯录已满,无法添加\n");
- return;
- }
- //没有存满则进行存放
- printf("请输入名字:");
- scanf("%s", pc->data[pc->sz].name);
- printf("请输入电话:");
- scanf("%s", pc->data[pc->sz].tele);
- printf("请输入住址:");
- scanf("%s", pc->data[pc->sz].addr);
- pc->sz++;
- printf("添加成功\n");
- }
添加过一个联系人之后,我们的sz就需要+1,用来记录我们已经存放了多少个联系人的信息。
来看实践:

添加完联系人之后,我们想马上看一下我们到底有没有存进去。
下面我们就实现显示联系人信息。
我们这里要解释一点,显示联系人信息是显示当前已经存放过的所有联系人的信息。
而之后要讲的查找联系人信息才是针对某个联系人来显示。
那么既然要打印所有人的信息,就必然少不了对于循环的运用。
同样的,在显示之前,我们还要判断一下通讯录是否为空。
- void ShowContact(Contact* pc)
- {
- assert(pc);
- if (pc->sz == 0)
- {
- printf("通讯录为空\n");
- return;
- }
- int i = 0;
- for (i = 0; i < pc->sz; i++)
- {
- printf("%s %s %s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
- }
- }
这样我们就可以显示出我们的联系人信息啦。

但是发现我们的信息上边没有像名字,电话,住址这样的列表名,而且我们每一行的信息排列并不整齐。
所以我们进行几处修改和补充:
- void ShowContact(Contact* pc)
- {
- assert(pc);
- if (pc->sz == 0)
- {
- printf("通讯录为空\n");
- return;
- }
- printf("%-20s%-12s%-30s\n","姓名","电话","住址");
- int i = 0;
- for (i = 0; i < pc->sz; i++)
- {
- printf("%-20s%-12s%-30s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
- }
- }
这种打印方式不知道小伙伴们了不了解:
%s是我们常规的方式,那么%20s就是指在打印的数据之前先打印20个空格。
%-20s则代表着先打印数据,在打印20个空格,这样我们就能实现对齐啦。

这里我们先讲查找联系人信息。
因为删除联系人信息、修改联系人信息实现的前期是:我们必须得先找到这个人才行。
那么我们该怎么查找呢???显然通过名字是最直接的方法。而且我们需要用到循环。
既然使用名字查找这种方式到处都要用到,所以我们干脆给它也写成一个函数:
- int FindByName(Contact* pc, char* name)
- {
- assert(pc && name);
- int i = 0;
- for (i = 0; i < pc->sz; i++)
- {
- if (strcmp(name, pc->data[i].name) == 0)
- {
- return i;
- }
- }
- return -1;
- }
这里我们这个函数的返回值为什么要是整型呢???
而且要是找到的话还返回它在通讯录列表里的位置,往下看就知道啦。
- void SearchContact(Contact* pc)
- {
- assert(pc);
- char name[NAME_MAX];
- printf("请输入要查找人的名字:");
- scanf("%s", name);
- int ret = FindByName(pc, name);
- if (ret == -1)
- {
- printf("找不到该联系人\n");
- return;
- }
- printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
- printf("%-20s%-12s%-30s\n", pc->data[ret].name, pc->data[ret].tele, pc->data[ret].addr);
- }
通过定义ret来接收FindByName函数的返回值,这样我们就能得到要查找的联系人的位置,能够轻松的打印出他的信息啦。

删除某个联系人信息,必然也是要先查找一下这个联系人存不存在。
同时想要删除一个联系人信息,必然是通过他的名字来删除。
- void DelContact(Contact* pc)
- {
- assert(pc);
- char name[NAME_MAX];
- printf("请输入要查找人的名字:");
- scanf("%s", name);
- int ret = FindByName(pc, name);
- if (ret == -1)
- {
- printf("找不到该联系人\n");
- return;
- }
- }
接下来就是要思考怎么来删除了。
我们使用的通讯录本质上是一个数组,想要删除某个位置的信息,首先想到的就是把它置为0。
但是这样一来我们就会浪费一个空间,因为你不知道这个位置的数组下标,也不可能把一个新的联系人信息补充到这个位置上来。
所以我们采取逐个覆盖的方法,用后边的信息逐一向前一位进行覆盖。
- void DelContact(Contact* pc)
- {
- assert(pc);
- char name[NAME_MAX];
- printf("请输入要删除联系人的名字:");
- scanf("%s", name);
- int ret = FindByName(pc, name);
- if (ret == -1)
- {
- printf("该联系人信息不存在\n");
- return;
- }
- //删除联系人
- int i = 0;
- for (i = ret;i < pc->sz - 1; i++)
- {
- pc->data[i] = pc->data[i + 1];
- }
- pc->sz--;
- printf("删除成功\n");
- }
用 i 接收 FindByName 的返回值 ret ,这样我们就得到了要删除的联系人的位置,从这里开始我们通过循环进行逐一覆盖。
删除一个联系人之后,不要忘记我们的sz要-1。


修改联系人信息之前,我们还是要先查找一下这个联系人存不存在,如果存在则进行修改。
修改联系人信息则需要让用户选择出要具体修改哪个信息。
所以我们还需要一个小目录来供用户选择,这里的操作就和刚开始的制作目录的方法一样啦。
- void menu2()
- {
- printf("*******************************\n");
- printf("***** 0.exit 1.name *****\n");
- printf("***** 2.tele 3.addr *****\n");
- printf("*******************************\n");
- }
- enum Type
- {
- QUIT,
- NAME,
- TELE,
- ADDR
- };
这里为了不发生类型多次重定义,我们将“退出”用它的另一个英文“quit”来定义。
- void ReviseContact(Contact* pc)
- {
- assert(pc);
- char name[NAME_MAX];
- printf("请输入要修改联系人的名字:");
- scanf("%s", name);
- int ret = FindByName(pc, name);
- if (ret == -1)
- {
- printf("该联系人信息不存在\n");
- return;
- }
- int input;
- do
- {
- menu2();
- printf("请选择要修改的信息类型:");
- scanf("%d", &input);
- switch (input)
- {
- case QUIT:
- printf("退出修改\n");
- break;
- case NAME:
- printf("请输入名字:");
- scanf("%s", pc->data[ret].name);
- printf("修改成功\n");
- break;
- case TELE:
- printf("请输入电话:");
- scanf("%s", pc->data[ret].tele);
- printf("修改成功\n");
- break;
- case ADDR:
- printf("请输入地址:");
- scanf("%s", pc->data[ret].addr);
- printf("修改成功\n");
- break;
- default:
- printf("选择错误,请重新选择\n");
- break;
- }
- } while (input);
- }
来看实战:



排序联系人信息我们这里需要用到一个函数——qsort

使用这个函数需要头文件:#include
qsort函数有4个参数,分别是:
base 排序的首地址
num 排序的数据数量
size 排序的数据字节大小
compar 排序的方式函数
通讯录的排序都是通过名字的首字母来排序的,所以这里我们要比较的是两个名字的字符串,
所以我们强制类型转换之后要调用name成员来比较。
- int compare(const void* a, const void* b)
- {
- return strcmp(((Peomessage*)a)->name, ((Peomessage*)b)->name);
- }
这里的强制类型转换用法我们在之前的文章——内存函数中已经讲到,不熟悉的小伙伴建议再去补习补习哦。
- void SortContact(Contact* pc)
- {
- assert(pc);
- qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare);
- printf("排序成功\n");
- }
我们要排序的是data这个结构体类型的数组,所以要传入它的首地址、大小、以及每个结构体数据的字节大小,最后传入我们的比较函数compare。
来看实战:


那么最后,如何清空所有的联系人呢???
其实这个超级简单,所谓清空,不就是再初始化一次吗,我们一开始就已经搞定啦。
- void EmptyContact(Contact* pc)
- {
- assert(pc);
- pc->sz = 0;
- memset(pc->data, 0, sizeof(pc->data));
- }


- #include
- #include
- #include
- #include
-
- #define NAME_MAX 20
- #define ADDR_MAX 30
- #define DATA_MAX 100
- //选项
- enum Function
- {
- EXIT,//默认从0开始
- ADD,
- DEL,
- SEARCH,
- REVISE,
- SHOW,
- SORT,
- EMPTY
- };
- enum Type
- {
- QUIT,
- NAME,
- TELE,
- ADDR
- };
- //联系人信息
- typedef struct Peomessage
- {
- char name[NAME_MAX];
- char tele[12];
- char addr[ADDR_MAX];
- }Peomessage;
- typedef struct Contact
- {
- Peomessage data[DATA_MAX];
- int sz;
- }Contact;
- //初始化通讯录
- void InitContact(Contact* pc);
- //增加联系人信息
- void AddContact(Contact* pc);
- //显示联系人信息
- void ShowContact(Contact* pc);
- //查找联系人信息
- void SearchContact(Contact* pc);
- //删除联系人信息
- void DelContact(Contact* pc);
- //修改联系人信息
- void ReviseContact(Contact* pc);
- //排序联系人信息
- void SortContact(Contact* pc);
- //清空所有联系人
- void EmptyContact(Contact* pc);
- #include"Contact.h"
- //初始化通讯录
- void InitContact(Contact* pc)
- {
- assert(pc);
- pc->sz = 0;
- memset(pc->data, 0, sizeof(pc->data));
- }
- //增加联系人信息
- void AddContact(Contact* pc)
- {
- assert(pc);
- //判断是否已存满
- if (pc->sz == DATA_MAX)
- {
- printf("通讯录已满,无法添加\n");
- return;
- }
- //没有存满则进行存放
- printf("请输入名字:");
- scanf("%s", pc->data[pc->sz].name);
- printf("请输入电话:");
- scanf("%s", pc->data[pc->sz].tele);
- printf("请输入住址:");
- scanf("%s", pc->data[pc->sz].addr);
- pc->sz++;
- printf("添加成功\n");
- }
- //显示联系人信息
- void ShowContact(Contact* pc)
- {
- assert(pc);
- if (pc->sz == 0)
- {
- printf("通讯录为空\n");
- return;
- }
- printf("%-20s%-12s%-30s\n","姓名","电话","住址");
- int i = 0;
- for (i = 0; i < pc->sz; i++)
- {
- printf("%-20s%-12s%-30s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
- }
- }
- //通过名字查找
- int FindByName(Contact* pc, char* name)
- {
- assert(pc && name);
- int i = 0;
- for (i = 0; i < pc->sz; i++)
- {
- if (strcmp(name, pc->data[i].name) == 0)
- {
- return i;
- }
- }
- return -1;
- }
- //查找联系人信息
- void SearchContact(Contact* pc)
- {
- assert(pc);
- char name[NAME_MAX];
- printf("请输入要查找人的名字:");
- scanf("%s", name);
- int ret = FindByName(pc, name);
- if (ret == -1)
- {
- printf("找不到该联系人\n");
- return;
- }
- printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
- printf("%-20s%-12s%-30s\n", pc->data[ret].name, pc->data[ret].tele, pc->data[ret].addr);
- }
- //删除联系人信息
- void DelContact(Contact* pc)
- {
- assert(pc);
- char name[NAME_MAX];
- printf("请输入要删除联系人的名字:");
- scanf("%s", name);
- int ret = FindByName(pc, name);
- if (ret == -1)
- {
- printf("该联系人信息不存在\n");
- return;
- }
- //删除联系人
- int i = 0;
- for (i = ret;i < pc->sz - 1; i++)
- {
- pc->data[i] = pc->data[i + 1];
- }
- pc->sz--;
- printf("删除成功\n");
- }
- void menu2()
- {
- printf("*******************************\n");
- printf("***** 0.exit 1.name *****\n");
- printf("***** 2.tele 3.addr *****\n");
- printf("*******************************\n");
- }
- //修改联系人信息
- void ReviseContact(Contact* pc)
- {
- assert(pc);
- char name[NAME_MAX];
- printf("请输入要修改联系人的名字:");
- scanf("%s", name);
- int ret = FindByName(pc, name);
- if (ret == -1)
- {
- printf("该联系人信息不存在\n");
- return;
- }
- int input;
- do
- {
- menu2();
- printf("请选择要修改的信息类型:");
- scanf("%d", &input);
- switch (input)
- {
- case QUIT:
- printf("退出修改\n");
- break;
- case NAME:
- printf("请输入名字:");
- scanf("%s", pc->data[ret].name);
- printf("修改成功\n");
- break;
- case TELE:
- printf("请输入电话:");
- scanf("%s", pc->data[ret].tele);
- printf("修改成功\n");
- break;
- case ADDR:
- printf("请输入地址:");
- scanf("%s", pc->data[ret].addr);
- printf("修改成功\n");
- break;
- default:
- printf("选择错误,请重新选择\n");
- break;
- }
- } while (input);
- }
- //排序联系人信息
- int compare(const void* a, const void* b)
- {
- return strcmp(((Peomessage*)a)->name, ((Peomessage*)b)->name);
- }
- void SortContact(Contact* pc)
- {
- assert(pc);
- qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare);
- printf("排序成功\n");
- }
- //清空所有联系人
- void EmptyContact(Contact* pc)
- {
- assert(pc);
- pc->sz = 0;
- memset(pc->data, 0, sizeof(pc->data));
- printf("已清空通讯录\n");
- }
- #include "Contact.h"
- void menu1()//菜单
- {
- printf("*********************************\n");
- printf("***** 1.add 2.del *****\n");
- printf("***** 3.search 4.revise *****\n");
- printf("***** 5.show 6.sort *****\n");
- printf("***** 7.empty 0.exit *****\n");
- printf("*********************************\n");
- }
- int main()
- {
- Contact con;
- //初始化通讯录
- InitContact(&con);
- int input;
- do {
- menu1();
- printf("请选择->:");
- scanf("%d", &input);
- switch (input)
- {
- case EXIT:
- printf("退出通讯录\n");
- break;
- case ADD:
- AddContact(&con);
- break;
- case DEL:
- DelContact(&con);
- break;
- case SEARCH:
- SearchContact(&con);
- break;
- case REVISE:
- ReviseContact(&con);
- break;
- case SHOW:
- ShowContact(&con);
- break;
- case SORT:
- SortContact(&con);
- break;
- case EMPTY:
- EmptyContact(&con);
- break;
- default:
- printf("输入错误,请重新输入\n");
-
- }
- } while (input);
- return 0;
- }
到这里,对于如何用C语言实现简易通讯录的讲解终于完结撒花啦!!!
喜欢博主文章的小伙伴一定要点个关注不迷路哦。
最后还是要记得一键三连呀!
祝大家中秋节快乐,我们下期再见啦!
