在上一篇文章我们学会了用单链表来实现各种方法,在这一篇文章我们将在单链表的基础上实现通讯录。
0、准备工作
实现通讯录之前,我们还需要在单链表的基础上添加2个文件,头文件Contact.h和源文件Contact.c。Contact.c来实现通讯录方法的声明,而Contact.h来实现通讯录的具体方法。
通讯录的数据其实就是存储在单链表上的,只不过变成了一个结构体,因此我们需要在Contact.h定义一个联系人数据的结构体。
如下图所示:
代码如下:
typedef struct PersonInfo
{char name[NAME_MAX];char gender[GENDER_MAX];int age;char tel[TEL_MAX];char addr[ADDR_MAX];
}PeoInfo;
为了代码有更好的延展性,我们还可以对数组的内存大小进行宏定义,在Contact.h的头部定义:
#define NAME_MAX 10
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100
由于我们是再单链表的基础上进行的,所以我们可以对单链表重新起个名字叫做通讯录,在Contact.h中定义:typedef struct SListNode contact;
由于我们是将类型由整型类型改为结构体类型,那么我们还需要再SList,h中将类型重定义一下:typedef struct PersonInfo SLDataType;
接着就可以在Contact.h中声明一系列的方法:
//通讯录的初始化
void InitContact(contact** con);//添加通讯录数据
void AddContact(contact** con);//删除通讯录数据
void DelContact(contact** con);//展示通讯录数据
void ShowContact(contact** con);//查找通讯录数据
void FindContact(contact** con);//修改通讯录数据
void ModifyContact(contact** con);//销毁通讯录数据
void DestroyContact(contact** con);//读取文件内容到通讯录
void LoadContact(contact** con);//保存通讯录数据到文件
void SaveContact(contact** con);
接下来再在Contact.c进行方法的实现,在实现之前我们需要包含以下头文件:
#include"Contact.h"
#include"SList.h"
1、初始化通讯录
初始化代码:
void InitContact(contact** con)
{LoadContact(con);
}
通讯录的初始化实际上就是将文件的数据导入到通讯录,确保通讯录一开始就拥有之前的数据,而不是一个空壳。
将文件数据导入到通讯录代码如下:
void LoadContact(contact** con)
{//打开文件FILE* pf = fopen("contact.txt", "rb");if (pf == NULL){perror("fopen fail!");return;}//定义通讯录变量PeoInfo info;//依次读取文件数据while (fread(&info, sizeof(info), 1, pf)){//将文件数据尾插到通讯录中SLTPushBack(con, info);}printf("历史通讯录数据导入成功!\n");//有开有闭fclose(pf);
}
2、添加通讯录数据
思路:创建通讯录局部变量,输入对应信息后,再尾插到通讯录中。
void AddContact(contact** con)
{PeoInfo info;printf("请输入要添加的联系人姓名:");scanf("%s", info.name);printf("请输入要添加的联系人性别:");scanf("%s", info.gender);printf("请输入要添加的联系人年龄:");scanf("%d", &info.age);printf("请输入要添加的联系人电话:");scanf("%s", info.tel);printf("请输入要添加的联系人地址:");scanf("%s", info.addr);SLTPushBack(con, info);printf("添加联系人成功!\n");
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);return 0;
}
运行结果:
我们再进行调试观察是否真正的插入成功:
可以观察到确实成功插入数据了!
3、展示通讯录数据
为了方便我们观察方法是否成功进行,我们可以先编写展示通讯录数据的方法。
思路:先打印表头,再创建指针cur指向头结点,遍历整个通讯录,依次打印对应的信息。
void ShowContact(contact* con)
{printf("%-10s %-4s %-4s %-11s %-20s\n", "姓名", "性别", "年龄", "电话", "地址");contact* cur = con;while (cur){printf("%-10s %-4s %4s %-11s %-20s\n", cur->data.name,cur->data.gender,cur->data.age,cur->data.tel,cur->data.addr);cur = cur->next;}
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);ShowContact(con);return 0;
}

通讯录数据显示成功!
4、删除通讯录数据
在进行删除,修改等一系列操作的时候,我们首先需要知道删除或修改的对象是谁,因此还需要一个函数专门根据姓名来查找到联系人,再进行后续的一系列操作。
查找思路:传一个头结点,和要查找的姓名,先创建指针指向头结点,再遍历通讯录,看要查找的名字是否在通讯录中,在就返回下标,不在就返回空。
contact* FindByName(contact* con, char name[])
{contact* cur = con;while (cur){if (0 == strcmp(name, cur->data.name)){return cur;}cur = cur->next;}//没找到返回NULLreturn NULL;
}
接着就可以删除通讯录数据了。
思路:输入要查找的姓名,调用查找函数找到下标,如果下标为空就表示没有找到,并且终止程序,下标不为空就执行删除指定位置数据的操作。
void DelContact(contact** con)
{char name[NAME_MAX];printf("请输入要删除的联系人姓名:");scanf("%s", name);contact* pos = FindByName(*con, name);if (pos == NULL){printf("你要删除的联系人不存在!\n");return;}SLTErase(con, pos);printf("删除成功!\n");
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);AddContact(&con);ShowContact(con);DelContact(&con);ShowContact(con);return 0;
}
运行结果:
我们可以观察到删除成功!
5、查找通讯录数据
思路:输入要查找的姓名,调用查找函数找到下标,下标为空就没找到并终止程序,不为空就直接展示下标所对应的数据。
void FindContact(contact** con)
{char name[NAME_MAX];printf("请输入要查找的联系人姓名:");scanf("%s", name);contact* pos = FindByName(*con, name);if (pos == NULL){printf("你要查找的联系人不存在!\n");return;}printf("查找成功!\n");//打印下标对应的数据printf("%-10s %-4s %-4s %-11s %-20s\n", "姓名", "性别", "年龄", "电话", "地址");printf("%-10s %-4s %-4d %-11s %-20s\n",pos->data.name,pos->data.gender,pos->data.age,pos->data.tel,pos->data.addr);
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);AddContact(&con);FindContact(&con);return 0;
}
运行结果:
6、修改通讯录数据
思路:输入要查找的姓名,调用查找函数找到下标,下标为空就没找到并终止程序,不为空就直接修改下标所对应的数据。
void ModifyContact(contact** con)
{char name[NAME_MAX];printf("请输入要修改的联系人姓名:");scanf("%s", name);contact* pos = FindByName(*con, name);if (pos == NULL){printf("你要修改的联系人不存在!\n");return;}//修改下标对应的数据printf("请输入要修改的姓名:");scanf("%s", pos->data.name);printf("请输入要修改的性别:");scanf("%s", pos->data.gender);printf("请输入要修改的年龄:");scanf("%d", &pos->data.age);printf("请输入要修改的电话:");scanf("%s", pos->data.tel);printf("请输入要修改的地址:");scanf("%s", pos->data.addr);printf("修改成功!\n");
}
再进行测试:
int main()
{contact* con=NULL;AddContact(&con);ShowContact(con);ModifyContact(&con);ShowContact(con);return 0;
}
运行结果:
修改成功!
7、保存通讯录数据
在我们实现以上方法后,我们输入数据之后可以将其保存,下一次再直接导入使用,因此还需要写一个保存数据的函数。
思路:打开文件,创建指针cur指向头结点,遍历单链表,依次将cur对应的联系人数据写入文件中。
void SaveContact(contact* con)
{FILE* pf = fopen("contact.txt", "wb");if (pf == NULL){perror("fopen fail!");return;}contact* cur = con;while (cur){//将当前节点对应的联系人数据写入文件fwrite(&(cur->data), sizeof(PeoInfo), 1, pf);cur = cur->next;}printf("保存成功!\n");fclose(pf);
}
我们再进行测试:
int main()
{contact* con=NULL;AddContact(&con);SaveContact(con);return 0;
}

可以观察到,数据已经成功被保存到文件中了!
8、销毁通讯录
思路:先保存通讯录数据,再调用单链表的销毁函数。
void DestroyContact(contact** con)
{SaveContact(*con);SListDestroy(con);
}
再进行测试:
int main()
{contact* con = NULL;AddContact(&con);DestroyContact(&con);return 0;
}
可以观察到数据已经成功销毁了,同时数据已经被存储到文件中了。
9、通讯录的完整实现
再实现了上述方法之后,我们就可以在test.c中编写可视化的操作页面,通过调用通讯录的方法来完整实现通讯录了。
思路:先编写主界面函数,主函数:输入值先赋值为-1,通讯录初始化,再使用do while循环条件输入0终止,调用主界面,输入值,根据输入的值使用switch来执行对应的操作,最后再将通讯录销毁。
void menu()
{printf("****************** 通讯录 ******************\n");printf("********* 1、增加联系人 2、删除联系人 **************\n");printf("********* 3、查找联系人 4、修改联系人 **************\n");printf("********* 5、展示联系人 6、保存联系人 **************\n");printf("********* 0、退出通讯录 **************\n");
}int main()
{int input = -1;contact* con = NULL;InitContact(&con);do{menu();printf("请选择你的操作:");scanf("%d", &input);switch (input){case 1:AddContact(&con);break;case 2:DelContact(&con);break;case 3:FindContact(&con);break;case 4:ModifyContact(&con);break;case 5:ShowContact(con);break;case 6:SaveContact(con);break;case 0:printf("退出通讯录...\n");break;default:printf("输入错误,请重新选择你的操作:\n");break;}} while (input!=0);DestroyContact(&con);return 0;
}
再进行测试:
发现已经可以实现完整的通讯录功能了。
点击在gitee查看完整源代码