通讯录在我们日常生活中时常会被使用,那么今天我将用C语言实现一个通讯录
要实现一个通讯录,首先要知道这个通讯录需要的功能,下图是我绘制的通讯录功能的思维导图,接下来将按照此图来构造我们的通讯录
类似于我之前博客中实现游戏的流程,实现通讯录也需要3个文件
- 源文件test.c用于测试通讯录和流程
- 头文件contact.h用于存放需要的对库函数的引用、全局变量、对函数的声明、对类型的声明等
- 源文件contact.c用于存放对通讯录功能函数的定义代码
首先,要实现此通讯录的基本流程
根据所需功能编写一个菜单函数
void menu()
{
printf("*****************************\n");
printf("***** 1.add 2.del *****\n");
printf("***** 3.search 4.modify *****\n");
printf("***** 5.sort 6.print *****\n");
printf("***** 0.exit *****\n");
printf("*****************************\n");
}
编写测试函数
void test()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
break;
case DEL:
break;
case SEARCH:
break;
case MODIFY:
break;
case SORT:
break;
case PRINT:
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
}
此代码和之前游戏的基本测试函数大体相同,case标签有所不同,并没有使用数字常量,取而代之,使用了枚举常量,这里使用枚举常量增加了代码的可读性,在读者在读代码读到case标签时,case ADD 显然比 case 1更加容易让人理解
由于是类型声明,我们将其放入contact.h头文件中,代码如下
enum Option
{
EXIT, //0
ADD, //1
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
既然是一个通讯录,那么它存储的变量类型一定不是单一的,思维导图中可以看到,要实现的通讯录存储的信息包括名字,性别,年龄,电话,住址。对于类型不同的变量,可以采用结构体来实现,结构体的声明同样存放在contact.h中
typedef struct PeoInfo
{
char name[name_MAX];
char sex[sex_MAX];
int age;
char tele[tele_MAX];
char addr[addr_MAX];
}PeoInfo;
为了方便接下来的编写,我将struct PeoInfo类型重定义为PeoInfo。除此之外,为了后续方便维护,数组长度我使用了标识符常量,同样定义在contact.h中
#define name_MAX 20
#define sex_MAX 10
#define tele_MAX 12
#define addr_MAX 30
当有了存储联系人信息的结构体类型后,就该考虑创建一个通讯录,通讯录当然不会只有一个联系人,因此当有很多相同类型的元素需要存放到一起时,就需要使用数组
PeoInfo data[data_MAX];
为了方便后续维护,依旧使用了标识符常量
#define data_MAX 1000
对于接下来的通讯录功能函数的实现,如增加联系人,可以想到除了将存储联系人的通讯录数组地址作为参数传过去,还需要传递一个存储着通讯录已经存放的联系人个数的参数,因此我们还需要定义一个变量
//通讯录中已保存的联系人的个数
int sz;
由于这两个变量都与通讯录有关并且作为参数时很多时候是一起传递的,因此可以考虑将它们封装为一个结构体,实现如下
typedef struct Contact
{
PeoInfo data[data_MAX];
//通讯录中已经保存的信息个数
int sz;
}Contact;
将strcut Contact 类型重定义为 Contact,创建通讯录时就可以直接使用Contact结构体定义变量
Contact con;
至此,实现通讯录的第一阶段已经结束了
当基本框架搭建好后,就要开始实现通讯录的功能,也就是contact.c中的内容
当我们创建好了一个通讯录,它里面存储的其实是随机值,如果在增加1个联系人后,存储已保存的联系人个数的变量sz应该加1,但若是没进行初始化,那么就是随机值+1,这样是毫无意义的,因此我们先封装一个初始化通讯录的函数,要改变通讯录本身,要用传址传递
void Initcontact(Contact* pc)
{
memset(pc->data, 0, sizeof(pc->data));
pc -> sz = 0;
}
这里在给data中的数据赋0时,采用了memset库函数,它的函数原型如下
void * memset ( void * ptr, int value, size_t num );
表示将从ptr地址处开始向后的num个字节每个字节都置为value
当一个通讯录已经初始化以后,就应该考虑如何往里面传联系人的信息,把这个功能封装成函数,由于每次增加联系人的信息,通讯录本身都要被改变,因此在传参时,应该使用传址传递
void Addcontact(Contact* pc)
{
if (pc->sz == data_MAX)
{
printf("通讯录已满,无法继续添加\n");
return;
}
//录入信息
//在scanf时,由于名字,性别,电话,住址都是数组名表示地址,而年龄是int型,因此就需要取出它的地址&
printf("请输入名字:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入住址:>");
scanf("%s", pc->data[pc->sz].addr);
//每次存入一个联系人信息,sz都加1
pc->sz++;
}
增加联系人的功能函数已经被创建好了,为了查看效果,我们先将打印通讯录的函数创建出来,由于打印通讯录并不需要对通讯录进行修改,因此在传参时可以选择传值传递。但由于一个通讯录存储的信息过大,传值传递要创建一个和通讯录一样大的临时拷贝,花销较大,因此在这里依然选择将通讯录的起始地址进行传址传递,但由于不用对通讯录进行修改,可以在形参中将其用const修饰
void Printcontact(const Contact* pc)
{
int i = 0;
printf("%-20s %-10s %-6s %-12s %-30s\n", "名字", "性别", "年龄", "电话", "住址");
for (i = 0; i < pc->sz; i++)
{
//将联系人信息按照如下格式,左对齐打印
printf("%-20s %-10s %-6d %-12s %-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].tele, pc->data[i].addr);
}
}
测试
实现下一个删除联系人函数时,我们可以思考一下。平时我们删除联系人的时候,若是全部删除,那么直接将通讯录全部赋0即可,因此在删除联系人的函数中,我们应该实现的是删除指定联系人的功能,而删除指定联系人首先要找到这个指定联系人,而在通讯录所需功能中,删除联系人、查找联系人、修改联系人都需要先查找到指定联系人,因此我们需要先实现一个查找联系人的函数
如果找到了指定联系人,就返回其在data数组中的下标
如果找不到指定联系人,就返回-1
由于查找联系人并不需要对通讯录本身进行修改,因此形参在接收通讯录地址时可以用const修饰
int FindByName(const Contact* pc, char searchname[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, searchname) == 0)
{
//找到了
return i;
}
}
//找不到
return -1;
}
在删除指定联系人时,有3种情况
- 通讯录为空时(存储已保存的联系人个数的变量sz为0时)
- 通讯录不为空但是找不到要删除的联系人
- 通讯录不为空并且可以找到要删除的联系人
void Delcontact(Contact* pc)
{
//1.通讯录为空
if (pc->sz == 0)
{
printf("通讯录为空,无法删除\n");
return;
}
//数组存放的是要删除的联系人的姓名
char searchname[name_MAX] = { 0 };
printf("请输入要删除联系人的名字:>");
scanf("%s", searchname);
//找到,返回下标,找不到,返回-1
int ret = FindByName(pc, searchname);
//2.通讯录不为空但找不到
if (ret == -1)
{
printf("要删除的联系人不存在\n");
return;
}
//3.通讯录不为空并且可以找到,进行删除
int j = 0;
for (j = ret; j < pc->sz - 1; j++)
{
//将后一个联系人的信息赋给前一个联系人信息,相当于覆盖
pc->data[j] = pc->data[j + 1];
}
//最后将已存放联系人个数的sz-1,那么就不会通过sz访问到删除前联系人个数的位置
pc->sz--;
printf("删除成功\n");
}
测试
有了上面几种函数的基础,我们只需要稍作改动即可
void Searchcontact(const Contact* pc)
{
char searchname[name_MAX] = { 0 };
printf("请输入要查找的联系人的名字:>");
scanf("%s", searchname);
int ret = FindByName(pc, searchname);
if (ret == -1)
{
printf("要查找的联系人不存在\n");
return;
}
else
{
printf("%-20s %-10s %-6s %-12s %-30s\n", "名字", "性别", "年龄", "电话", "住址");
printf("%-20s %-10s %-6d %-12s %-30s\n", pc->data[ret].name, pc->data[ret].sex, pc->data[ret].age, pc->data[ret].tele, pc->data[ret].addr);
}
}
测试
void Modifycontact(Contact* pc)
{
char searchname[name_MAX] = { 0 };
printf("请输入要修改的联系人的名字:>");
scanf("%s", searchname);
int ret = FindByName(pc, searchname);
if (ret == -1)
{
printf("要修改的联系人不存在\n");
return;
}
else
{
printf("请输入名字:>");
scanf("%s", pc->data[ret].name);
printf("请输入性别:>");
scanf("%s", pc->data[ret].sex);
printf("请输入年龄:>");
scanf("%d", &(pc->data[ret].age));
printf("请输入电话:>");
scanf("%s", pc->data[ret].tele);
printf("请输入住址:>");
scanf("%s", pc->data[ret].addr);
printf("修改成功\n");
}
}
测试
在这里我设计有一个排序功能可以让通讯录按名字的字母顺序来进行排序,基于此,我使用了qsort函数
首先给出qsort函数使用时用户自定义的比较函数
int cmp(const void* e1, const void* e2)
{
//比较字符串用strcmp函数
return strcmp(((Contact*)e1)->data->name, ((Contact*)e2)->data->name);
}
接着是Sortcontact函数
void Sortcontact(Contact* pc)
{
//数组中一个元素的大小
size_t elementsz = sizeof(pc->data[0]);
qsort(pc->data, pc->sz, elementsz, cmp);
printf("排序成功\n");
}
如果对qsort函数不熟悉,大家可以浏览我之前的博客,这里附上链接
https://blog.csdn.net/Hhhh_Jy/article/details/126582433?spm=1001.2014.3001.5501
测试
最后给出此次实现通讯录所用到的库函数的头文件
#include
#include
#include
至此,通讯录的制作就结束了,感谢各位的阅读 !!!