通讯录
老规矩笔记和代码欢迎自取~
链接:
文章目录
一、通讯录的组成
利用最近所学的知识,试着实现一个通讯录
手机里面的通讯录信息
最后我决定给通讯录增加以下信息
-
存放100个联系人的信息
信息包括:姓名 + 性别 + 年龄 + 电话 + 公司地址
(PS:节目效果,真不是跟踪狂)
-
增加/删除/修改/查找/打印/排序联系人
(功能是不是很强大?看完这篇你也可以实现!)
二、通讯录文件构成
为什么每一篇小项目都强调一遍文件构成?
- 文件的构成是每一个项目的思路
- 没有思路,直接上手容易手忙脚乱
- 分析好每个文件所包含的内容,有利于我们代码的书写
1. test.c
按照之前的设计小游戏一样,test.c文件是必要的,用来测试各种功能使用是否可以正常使用或者运行
还包括整个通讯录的框架,比如提供给使用者得选择菜单
2. correspondence.c
用来设计实现通讯录功能的文件,文件包含的基本上都是函数
函数包括实现增加/删除/修改/查找/打印/排序联系人
3. correspondence.h
包含 test.c 和 correspondence.c 中需要用到的库函数的头文件,以及全过程需要使用的变量
三、实现的代码步骤分析
1. 打印菜单
使用者上手即操作,我们需要提供给他们操作的菜单,也是我们功能的目录
菜单明细上面也分析了,主要包含:
void menu()//菜单
{
printf("==============通讯录=============\n");
printf(" 1.添加联系人(add) \n");
printf(" 2.查找联系人(seek) \n");
printf(" 3.删除联系人(delete) \n");
printf(" 4.修改联系人(modify) \n");
printf(" 5.排序联系人(sort) \n");
printf(" 6.打印联系人(print) \n");
printf(" 0.退出通讯录(EXIT) \n");
}
利用最近所学的枚举类型,我们试着这次的循环内的switch语句,判断不选择数字,而选择判断功能的内容
所以我们定义一个枚举类型,用单词代表各个功能
此过程在 test.c 文件中
enum menu
{
EXIT,//0
add,//1
seek,//2
delete,//3
modify,//4
sort,//5
print//6
};
把exit放前面正好代表0,每个枚举常量代表的数字都是对应的功能
到此为止,整个循环的大概模型是这样,代码如下
此过程在 test.c 文件中
int main()
{
int input = 0;
do
{
menu();//打印菜单提供使用者选择
printf("请选择您要使用的功能(输入选项括号内内容):");
scanf("%d", &input);//使用者输入选择
switch (input)
{
case add:break;
case seek:break;
case delete:break;
case modify:break;
case sort:break;
case print:break;
case exit:
printf("您选择了退出通讯录\n");
break;
default:
printf("输入错误信息,请重新输入:\n");
break;
}
} while (input != 0);
return 0;
}
运行的结果
分别测试了 正确信息 错误信息 退出功能
2. 联系人信息初始化
刚拿到新手机的大家,通讯录里面是没有联系人的信息
但可以通过自己填写联系人信息存储到手机中
我们需要提供给使用者,接收/存储信息的一个数据库
但因为人的信息是复杂的,是多种元素构成
信息包括:姓名 + 性别 + 年龄 + 电话 + 公司地址
使用类型:char char int int char
此过程在 correspondence.h 文件中
#define NAME_MAX 11
#define GENDER_MAX 5
#define TELE_MAX 12
#define EMAIL_MAX 20
//通讯录初始化个人信息
struct perinfmt //personal information
{
char name[NAME_MAX];//支持5个汉字(2个字节一个汉字)
char gender[GENDER_MAX];//男性 or 女性
int age;
char tele[TELE_MAX];//11位电话号码
char email[EMAIL_MAX];//19位邮箱
};
为了不用每次挨个修改个人信息的大小,我们用define定义每个信息的最大值
还没有结束,我们创建好的是每个人的信息填写位置,但还没有创建一个通讯录,有100个人,每个人还需要标记序号(这里序号不放在个人信息里是因为,创建联系人的时候不会去填每个人的信息),这是需要我们系统内部自己去偷偷记录的
此时100个人 + 序号 通讯录仍然是复杂信息 我们再次创建结构体变量
此过程在 correspondence.h 文件中
struct address_book //通讯录内容
{
struct perinfmt p[BOOK_MAX];//每个人信息,数组一共100个人
int ID;//统计每个人编号
};
创建通讯录的结构体变量时,我们仍然还没有初始化
新建一个函数,专门初始化它
此过程在 correspondence.c 文件中
void Initialize(struct address_book* ap)
{
ap->ID = 0;//序号初始值0
memset(ap->p, 0, sizeof(ap->p));//初始化整个结构体数组大小为0
}
记得在 correspondence.h 中声明
我们才能在 test.c 上使用 (记得在进入循环前就初始化好)
struct address_book alinfmt;//all information
Initialize(&alinfmt);//初始化通讯录
PS:但其实也可以不创建这个 函数初始化
(我们是为了方便,这样每次都可以,直接用函数初始化)
可以在创建变量的时候直接初始化
struct address_book alinfmt = { { 0 }, 0 };//all information
此过程在 test.c 中完成
3. 添加联系人
功能的铺垫已经写好了
我们开始写主要的功能
添加联系人函数
- 判断通讯录是否满了,如果满了,退出
- 如果没满,让使用者输入信息
//增加联系人
void add_contact(struct address_book* ap)
{
if (ap->ID == BOOK_MAX)
{
printf("通讯录满了,请删除一些联系人再添加!\n");
}
else
{
printf("请输入联系人姓名:");
scanf("%s", ap->p[ap->ID].name);
printf("请输入联系人性别:");
scanf("%s", ap->p[ap->ID].gender);
printf("请输入联系人年龄:");
scanf("%d", &(ap->p[ap->ID].age));
//这里是因为age是int类型变量需要传地址,其他数组类型变量名就是地址
printf("请输入联系人电话:");
scanf("%s", ap->p[ap->ID].tele);
printf("请输入联系人邮箱地址:");
scanf("%s", ap->p[ap->ID].email);
printf("-----添加完成-----\n");
ap->ID++;
}
}
scanf里面一大串都是什么?
图解
还有写这段代码时候必须包含等号,不能只写大于号
if (ap->ID == BOOK_MAX)
{
printf("通讯录满了,请删除一些联系人再添加!\n");
}
不然会警告
最后别忘了填写完一个人的信息的时候,记录的ID需要增加
4. 打印联系人
既然已经完成了添加联系人,那么我们就不按顺序,看看如何打印我们所有联系人的成员吧
打印联系人函数
直接用一个for循环搞定~
//显示所有联系人
void print_book(struct address_book* ap)
{
int i = 0;
for (i = 0; i < ap->ID; i++)
{
printf("联系人姓名:%s\n性别:%s\n年龄:%d\n电话:%s\n邮箱地址:%s\n",
ap->p[i].name,
ap->p[i].gender,
ap->p[i].age,
ap->p[i].tele,
ap->p[i].email);
}
}
搭配添加联系人食用~,结果如下图:
5. 删除联系人
我们发现,删除/查找/修改 联系人功能中,都会包含有 查找联系人这个动作
用for循环遍历一遍,查找是否有这个联系人
//查找联系人
int find_contact(const struct address_book* ap, const char* name)
{
int i = 0;
for (i = 0; i < ap->ID; i++)
{
if (strcmp(ap->p[i].name, name) == 0)
{
return 1;
}
}
return -1;
}
找到了之后图解:
//删除联系人
void del_contact(struct address_book* ap)
{
if (ap->ID == 0)//判断通讯录是否为空
{
printf("无可删除联系人");
return;
}
char name[NAME_MAX] = { 0 };//存放查找的姓名
printf("请输入删除的联系人姓名:");
scanf("%s", name);
int ret = find_contact(ap, name);
if (ret == -1)
{
printf("该联系人不存在");
}
else
{
//删除联系人
int j = 0;
for (j = ret; j < ap->ID - 1; j++)
{
ap->p[j] = ap->p[j + 1];
}
ap->ID--;//少了一个数字
printf("删除成功!\n");
}
}
代码运行效果:
我的舍友就这样成功被我删除了哈哈哈哈
6. 查找指定联系人
这个代码有了上面几个代码的铺垫,直接复制粘贴修改一些变量就可以!
看看代码是不是有点熟悉,copy就完事了
//查找指定联系人
void seek_contact(const struct address_book* ap)//我们只是查找不作修改
{
char name[NAME_MAX];
printf("请输入需要查找的联系人姓名:");
scanf("%s", &name);
int ret = find_contact(ap, name);
if (ret == -1)
{
printf("该联系人不存在");
}
else
{
printf("找到了!\n联系人姓名:%s\n性别:%s\n年龄:%d\n电话:%s\n邮箱地址:%s\n\n",
ap->p[ret].name,
ap->p[ret].gender,
ap->p[ret].age,
ap->p[ret].tele,
ap->p[ret].email);
}
}
运行效果:
7. 修改联系人
同理,也是copy + 稍微修改一下就完事了
//修改联系人
void modify_contact(struct address_book* ap)
{
char name[NAME_MAX];
printf("请输入需要查找的联系人姓名:");
scanf("%s", &name);
int ret = find_contact(ap, name);
if (ret == -1)
{
printf("该联系人不存在");
}
else
{
printf("开始修改联系人信息!\n");
printf("请重新输入联系人姓名:");
scanf("%s", ap->p[ret].name);
printf("请重新输入联系人性别:");
scanf("%s", ap->p[ret].gender);
printf("请重新输入联系人年龄:");
scanf("%d", &(ap->p[ret].age));
printf("请重新输入联系人电话:");
scanf("%s", ap->p[ret].tele);
printf("请重新输入联系人邮箱地址:");
scanf("%s", ap->p[ret].email);
printf("-----修改完成-----\n");
}
}
运行效果:
8. 排序联系人
我们用qsort快排进行联系人的姓名排序
int compare(const void* e1, const void* e2)
{
return (strcmp((const char*)e1, (const char*)e2));
}
//排序联系人
void sort_contact(struct address_book* ap)
{
qsort(ap->p->name, ap->ID, sizeof(ap->p[0]), compare);
printf("排序完成,请选择显示所有联系人进行查看!\n");
}
然后,我将前面的exit改成了大写
因为出现了问题!!!
在stdlib.h文件的存在情况下,exit是已经被使用的
而我再使用,与库函数冲突,会报错!!
在发现了原因的情况下,修改之后
效果如下: