目录
一、引言
二、单链表的基本概念
三、通讯录项目的需求分析
四、通讯录的数据结构
五、通讯录的接口
1.通讯录初始化 / 导入外部数据
2.添加联系人信息
3.删除联系人信息
4.查找联系人信息
5.修改联系人信息
6.展示联系人信息
7.导出数据到文件
8.通讯录销毁
六、主函数中通讯录操作
1.通讯录菜单
2.通讯录人机交互操作
七、各文件的实现代码
SLinkList.h
SLinkList.c
Contact.h
Contact.c
test.c
八、测试与验证
九、写在最后
一、引言
在数字化时代,通讯录作为我们日常生活中不可或缺的一部分,扮演着记录和管理联系人信息的重要角色。随着智能手机的普及,人们对于通讯录的功能和性能要求也在不断提高。为了更好地满足这些需求,我们有必要对通讯录的实现方式进行深入研究和探索。
在众多的数据结构中,单链表以其独特的优势成为了实现通讯录的一种理想选择。单链表是一种线性数据结构,它通过每个节点中的指针链接在一起,形成一个有序的链表。相比于数组等其他数据结构,单链表在插入、删除操作上具有更高的效率,因为它不需要像数组那样移动大量的元素。此外,单链表在内存使用上也更加灵活,可以根据需要动态地分配和释放内存空间。
因此,本文旨在探讨如何使用单链表来实现一个高效、灵活的通讯录项目。我们将首先介绍单链表的基本概念和基本操作,然后分析通讯录项目的需求,并设计相应的数据结构和接口。接下来,我们将详细实现通讯录类的各个功能,并进行测试和验证。最后,我们将对项目进行总结和反思,并提出改进方向。
通过本文的介绍和实践,读者将能够深入理解单链表在通讯录项目中的应用,掌握使用单链表实现通讯录的基本方法和技巧。同时,本文也为读者提供了一个实际的项目案例,有助于提升读者的编程能力和解决问题的能力。
二、单链表的基本概念
通讯录项目的实现直接借用了单链表实现的头文件SLinkList.h 和 源文件SLinkList.c
关于单链表的问题请参照前置文章
对单链表有了深入的理解之后才能更好的实现通讯录项目
三、通讯录项目的需求分析
- 能够存储较多的联系人信息,并且能够高效地管理内存,避免不必要的内存浪费
- 能够保存用户信息:名字、性别、年龄、电话、地址等
- 增加联系⼈信息
- 删除指定联系⼈
- 查找指定联系⼈
- 修改指定联系⼈
- 显示联系⼈信息
- 实现数据的导入导出
四、通讯录的数据结构
以下是用结构体记录通讯录单个联系人信息的信息,对应单链表单个节点的数据部分的数据类型
同时,单链表头文件中对单链表结构的数据部分定义有所修改
注意:
因为单链表的头文件需要用到通讯录的头文件的联系人结构体定义,所以在单链表头文件中包含了通讯录头文件。
但通讯录头文件中又需要用到单链表中对节点的定义,头文件不能互相包含,所以应当在通讯录头文件中包含一条对单链表的结构体的前置声明
接下来,就可以来实现通讯录项目的方法了
五、通讯录的接口
通讯录的基本方法接口包括
- 初始化与销毁通讯录
- 数据的导入导出
- 对联系人的增删改查
- 以及展示通讯录中的联系人信息
需要对单链表数据进行修改的函数,应该传址调用,实参传递地址,形参使用二级指针接收
1.通讯录初始化 / 导入外部数据
单链表实现的通讯录因为不带有额外的头节点,并且链表每个节点都是独立的,所以初始化不需要额外的操作,只需要从外部文件导入通讯录数据即可
这里将导入数据单独封装成一个函数,以便代码复用
由初始化函数来调用导入数据函数
导入外部数据的函数功能:
- 以二进制读方式打开文件(注意:以读的方式打开文件,必须保证文件存在,否则会出错)
- 判断是否成功
- 循环读取数据,每读取一条就尾插到单链表
2.添加联系人信息
添加联系人信息的函数功能:
- 创建一个联系人的结构体变量
- 逐个录入信息至该变量
- 将该结构体变量和通讯录链表的首节点地址作为参数,一起传给底层的单链表尾插函数
3.删除联系人信息
删除联系人记录需要封装一个查找联系人函数(单独实现查找,无其他功能)
单独的查找函数的函数功能:
- 这里以姓名作为关键值查找,接收一个链表首地址和关键值信息
- 遍历链表,如果找到该联系人,返回节点地址
- 否则返回空指针
删除联系人记录的函数功能:
- 录入要删除的联系人姓名
- 调用封装的查找函数
- 如果找到,调用单链表实现的删除指定节点函数
- 如果找不到,报错
4.查找联系人信息
查找联系人信息的函数功能:
- 这里不同于上面的功能单一的查找函数
- 作用是根据录入的关键值查找并打印该联系人信息或报错
- 这里依然以姓名作为关键值查找
5.修改联系人信息
修改联系人信息的函数功能:
- 依然以姓名作为关键值(因为可以重复利用封装的查找函数)
- 录入姓名,调用单独的查找函数
- 若找到指定联系人,依次修改该联系人的各部分信息,赋值给该节点
- 否则,该联系人不存在,报错
6.展示联系人信息
展示联系人信息的函数功能:
- 先打印表头信息
- 创建一个遍历链表的指针
- 逐个访问链表的每一个节点,每行打印该节点的数据部分
7.导出数据到文件
数据导出的函数功能:
- 以二进制写方式打开文件
- 循环遍历链表,将每个节点的数据(每个联系人信息)输出到外部文件
8.通讯录销毁
通讯录销毁的函数功能:
- 销毁之前需要先调用导出数据的函数,将联系人信息保存下来
- 调用单链表中已实现的链表销毁函数——循环遍历链表,释放每一个动态申请空间的节点
六、主函数中通讯录操作
1.通讯录菜单
菜单函数的功能:
- 封装一个函数向用户展示通讯录项目的功能以及每项功能对应的选项
2.通讯录人机交互操作
通讯录人机交互部分功能:
- 首先主函数运行,初始化通讯录,从外部载入数据
- 然后通过一个 do while循环(保证程序至少运行一次)和 swtich语句(用户选择指定选项对应指定的功能)配合完成对通讯录的操作
- 最后,用户选择结束操作后,将数据保存至外部文件,销毁通讯录
七、各文件的实现代码
单链表中已有文件——
SLinkList.h
单链表结构定义及函数声明头文件
SLinkList.c
单链表方法实现源文件
Contact.h
通讯录结构定义及函数声明头文件
Contact.c
通讯录方法实现源文件
//Contact.c
#include"Contact.h"
#include"SLinkList.h"
//导入数据到通讯录
void LoadContact(contact** con)
{
FILE* pf = fopen("contact.txt", "rb");//以二进制读方式打开文件
if (pf == NULL)//判空
{
perror("fopen");
return;
}
PeoInfo po;
while (fread(&po, sizeof(po), 1, pf))//循环读取数据
{
SLTPushBack(con, po);//尾插到单链表
}
printf("数据载入成功!\n");
fclose(pf);
pf = NULL;
}
//初始化通讯录
void InitContact(contact** con)
{
LoadContact(con);//导入外部数据
}
//添加通讯录数据
void AddContact(contact** con)
{
assert(con);//二级指针判空
PeoInfo po;
printf("请按提示输入要添加的联系人信息\n");
printf("请输入姓名:\n");
scanf("%s", po.name);
printf("请输入性别:\n");
scanf("%s", po.sex);
printf("请输入年龄:\n");
scanf("%d", &po.age);
printf("请输入电话:\n");
scanf("%s", po.tel);
printf("请输入地址:\n");
scanf("%s", po.addr);
SLTPushBack(con, po);//调用单链表函数尾插
printf("添加联系人成功\n");
}
//封装的单独查找函数
contact* FindByname(contact*con,char name[])
{
assert(con);//二级指针判空
contact* pcur = con;//遍历链表的指针
while (pcur)
{
if (strcmp(pcur->data.name, name) == 0)//字符串比对
return pcur;
pcur = pcur->next;
}
return NULL;
}
//删除通讯录数据
void DelContact(contact** con)
{
assert(con&&*con);//二级指针判空,链表判空
printf("请输入要删除的联系人姓名:");
char name[NAME_MAX];
scanf("%s", name);
contact* del = FindByname(*con, name);//调用单独的查找函数
if (del == NULL)
{
printf("要删除的联系人不存在!\n");
return;
}
SLTErase(con, del);//调用单链表删除指定元素
printf("删除联系人成功!\n");
}
//展示通讯录数据
void ShowContact(contact* con)
{
if (con == NULL)//对空链表的特殊处理
{
printf("NULL\n");
return;
}
printf("%-10s%-10s%-10s%-10s%-10s\n",
"姓名", "性别", "年龄", "电话", "地址"
);//表头
contact* pcur = con;
while (pcur)//遍历链表,打印每个节点的联系人信息
{
printf("%-10s%-10s%-10d%-10s%-10s\n",
pcur->data.name,
pcur->data.sex,
pcur->data.age,
pcur->data.tel,
pcur->data.addr
);
pcur = pcur->next;
}
}
//查找通讯录数据
void FindContact(contact* con)
{
printf("请输入要查找的联系人姓名:");
char name[NAME_MAX];
scanf("%s", name);
contact* pcur = FindByname(con, name);//调用已经实现的查找函数
if (pcur == NULL)
{
printf("要查找的联系人不存在!\n");
return;
}
printf("%-10s%-10s%-10s%-10s%-10s\n",
"姓名", "性别", "年龄", "电话", "地址"
);//打印该联系人信息
printf("%-10s%-10s%-10d%-10s%-10s\n",
pcur->data.name,
pcur->data.sex,
pcur->data.age,
pcur->data.tel,
pcur->data.addr
);
}
//修改通讯录数据
void ModifyContact(contact** con)
{
assert(con);
printf("请输入要修改的联系人姓名:");
char name[NAME_MAX];
scanf("%s", name);
contact* pcur = FindByname(*con, name);//调用已经实现的查找函数
if (pcur == NULL)
{
printf("要修改的联系人不存在!\n");
return;
}
printf("请输入修改后的联系人姓名: ");
scanf("%s", pcur->data.name);
printf("请输入修改后的联系人性别: ");
scanf("%s", pcur->data.sex);
printf("请输入修改后的联系人年龄: ");
scanf("%d", &pcur->data.age);
printf("请输入修改后的联系人电话: ");
scanf("%s", pcur->data.tel);
printf("请输入修改后的联系人地址: ");
scanf("%s", pcur->data.addr);
printf("修改联系人信息成功!\n");
}
//导出数据
void SaveData(contact* con)
{
FILE* pf = fopen("contact.txt", "wb");//以二进制写方式打开文件
if (pf == NULL)
{
perror("fopen\n");
exit(1);
}
contact* pcur = con;
while (pcur)//遍历链表,将通讯录数据输出到文件中
{
fwrite(pcur, sizeof(contact), 1, pf);
pcur = pcur->next;
}
fclose(pf);
free(pf);
pf = NULL;
}
//销毁通讯录数据
void DestroyContact(contact** con)
{
SaveData(*con);//调用函数导出数据到文件
SListDesTroy(con);//调用单链表函数销毁通讯录
}
test.c
主函数测试文件
八、测试与验证
编辑
编辑
编辑
九、写在最后
本文所有代码已经过多轮测试,可以直接复制使用。
如果您发现某处代码存在问题以及对程序的改进意见,欢迎私信或评论指点。
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
评论(0)