通讯录(基于单链表)
我们知道 链表是由一个个节点组成的,我们让节点的数据域去存储一个结构体
这个结构体是存储联络人数据的一个结构体,里面放着许多信息:
// 要在链表的每一个节点中存储联系人数据
// 那我们就要定义联系人的结构体
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100
typedef struct personInfo
{
char name[NAME_MAX]; // 名字
char gender[GENDER_MAX]; // 性别
int age; // 年龄
char tel[TEL_MAX]; // 电话
char addr[ADDR_MAX]; // 地址
}peoInfo;
由于有了前面单链表的知识 我们这边直接放代码:
代码:
我们一共分为五个文件来完成这个通讯录,分别是之前单链表的SList.c,SList.h,
test.c(测试文件),Contact.h(通讯录头文件),Contact.c(通讯录功能实现)
SList.c:
# include"SList.h"
// 链表的功能函数实现都在这里
// 链表节点的申请
ListNode* SLTBuyNode(SLTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
链表的打印
//void SLTPrint(ListNode* phead)
//{
// // 遍历链表
// ListNode* pcur = phead;
// while (pcur)
// {
// printf("%d->", pcur->data);
// pcur = pcur->next;
// }
// printf("NULL\n");
//}
// 链表的尾插
void SLTPushBack(ListNode** pphead, SLTDataType x)
{
assert(pphead);
ListNode* newnode = SLTBuyNode(x); //申请节点
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
ListNode* pcur = *pphead;
while (pcur)
{
if (pcur->next == NULL)
{
// 尾插
pcur->next = newnode;
break;
}
pcur = pcur->next;
}
}
}
// 链表的头插
void SLTPushFront(ListNode** pphead, SLTDataType x)
{
assert(pphead);
ListNode* newnode = SLTBuyNode(x); // 申请节点
// 头插
ListNode* pcur = *pphead;
newnode->next = pcur;
*pphead = newnode;
}
// 链表的尾删
void SLTPopBack(ListNode** pphead)
{
assert(pphead);
assert(*pphead);
if ((*pphead)->next == NULL) // 只有一个节点
{
free(*pphead);
*pphead = NULL;
}
else
{
ListNode* pcur = *pphead;
ListNode* prev = *pphead;
// 遍历链表找到尾节点
while (pcur->next != NULL)
{
prev = pcur;// prev指向pcur的前一个节点
pcur = pcur->next;
}
// 走到这里说明pcur指向最后一个节点
prev->next = NULL;
free(pcur);
pcur = NULL;
}
}
// 链表的头删
void SLTPopFront(ListNode** pphead)
{
assert(pphead);
assert(*pphead);
//头删
ListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
链表的查找
//ListNode* SLTFind(ListNode* phead, SLTDataType x)
//{
// assert(phead);
//
// ListNode* pcur = phead;
// // 遍历链表 查找
// while (pcur)
// {
// if (pcur->data == x)
// {
// return pcur;
// }
// pcur = pcur->next;
// }
// // 走到这里说明没找到
// return NULL;
//}
// 在链表的指定位置之前插入数据
void SLTInsert(ListNode** pphead, ListNode* pos, SLTDataType x)
{
assert(pphead);
assert(*pphead);
assert(pos);
// 申请节点
ListNode* newnode = SLTBuyNode(x);
// 要分成头插 和其他情况
if (pos == *pphead)
{
SLTPushFront(pphead, x); // 引用头插
}
else
{
ListNode* pcur = *pphead;
ListNode* prev = *pphead;
while (pcur != pos)
{
if (pcur == NULL)
{
printf("不存在pos这个节点,请重新输入合法的值\n");
return;
}
prev = pcur;// prev 指向pcur的前一个节点
pcur = pcur->next;
}
// 走到这里说明pcur指向的就是pos节点
// 完成链接 prev->newnode->pos
prev->next = newnode;
newnode->next = pos;
}
}
// 在链表指定位置之后插入数据
void SLTInsertAfter(ListNode* pos, SLTDataType x) // 不需要改变第一个节点的位置 无需传入第一个节点的指针
{
assert(pos);
// 申请节点
ListNode* newnode = SLTBuyNode(x);
// 尾插
// 完成 pos->newnode->pos的下一个节点的链接
newnode->next = pos->next;
pos->next = newnode;
}
// 删除链表指定位置的节点
void SLTErase(ListNode** pphead, ListNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
// 要分成第一个节点 和其他情况
if (pos == *pphead)
{
// 头删
SLTPopFront(pphead);
}
else
{
ListNode* pcur = *pphead;
ListNode* prev = *pphead;
// 遍历链表 找到pos节点
while (pcur != pos)
{
if (pcur == NULL)
{
printf("没有找到pos节点,请重新输入合法值\n");
return;
}
prev = pcur;
pcur = pcur->next;
}
// 走到这里说明pcur指向的就是pos节点
prev->next = pcur->next;
free(pcur);
pcur = NULL;
}
}
// 删除链表指定位置之后的节点
void SLTEraseAfter(ListNode* pos) // 不需要改变第一个节点的位置 无需传入**pphead
{
assert(pos);
assert(pos->next); // pos的下一个节点不能没有 不然怎么删除
// 删除 链接 pos节点 和 pos的下个节点所存储的next指针
ListNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
// 下面这段代码 也可以实现删除pos之后的节点的功能
//SLTNode* next = pos->next->next;
//free(pos->next);
//pos->next = NULL;
//pos->next = next;
}
// 销毁链表
void SListDesTroy(ListNode** pphead)
{
assert(pphead);
assert(*pphead);
ListNode* pcur = *pphead;
ListNode* next = NULL;
// 遍历链表 一个节点一个节点的销毁
while (pcur)
{
next = pcur->next;
free(pcur);
pcur = next;
}
// 走到这里说明删除完了
*pphead = NULL;
}
SList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<string.h>
# include"Contact.h"
// 节点的结构体定义
typedef peoInfo SLTDataType;
typedef struct ListNode
{
SLTDataType data;
struct ListNode* next;
}ListNode;
// 节点的申请
ListNode* SLTBuyNode(SLTDataType x);
// 链表的打印
void SLTPrint(ListNode* phead);
//头部插⼊删除/尾部插⼊删除
void SLTPushBack(ListNode** pphead, SLTDataType x);
void SLTPushFront(ListNode** pphead, SLTDataType x);
void SLTPopBack(ListNode** pphead);
void SLTPopFront(ListNode** pphead);
//查找
ListNode* SLTFind(ListNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(ListNode** pphead, ListNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(ListNode* pos, SLTDataType x); // 不需要改变第一个节点的位置 无需传入第一个节点的指针
//删除pos节点
void SLTErase(ListNode** pphead, ListNode* pos);
//删除pos之后的节点
void SLTEraseAfter(ListNode* pos); // 不需要改变第一个节点的位置 无需传入**pphead
//销毁链表
void SListDesTroy(ListNode** pphead);
Contact.h:
#pragma once
// 要在链表的每一个节点中存储联系人数据
// 那我们就要定义联系人的结构体
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100
typedef struct personInfo
{
char name[NAME_MAX]; // 名字
char gender[GENDER_MAX]; // 性别
int age; // 年龄
char tel[TEL_MAX]; // 电话
char addr[ADDR_MAX]; // 地址
}peoInfo;
//前置声明
typedef struct ListNode contact; // 将链表改个名字 改成通讯录的名字
//初始化通讯录
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 "SList.h"
# include "Contact.h"
// 通讯录的功能函数实现都在这里
// 从文件中读取联系人数据到通讯录
void LoadContact(contact** con)
{
// 打开文件
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL)
{
perror("fopen");
exit(1);
}
// 开始读取数据
peoInfo info;
// while循环每次从pf中拿出一个联络人数据放到info中
while (fread(&info, sizeof(peoInfo), 1, pf))
{
// 将info尾插到链表中 也就是放到通讯录中
SLTPushBack(con, info);
}
// 关闭文件
fclose(pf);
pf = NULL;
}
// 初始化通讯录
void InitContact(contact** con)
{
LoadContact(con); // 从文件中读取联络人数据到通讯录中
}
//展示通讯录数据
void PenInfoPrint(peoInfo x)
{
printf("%s %s %d %s %s\n", x.name, x.gender, x.age, x.tel, x.addr);
}
void ShowContact(contact* con)
{
contact* cur = con;
printf("该通讯录的联系人信息如下:\n");
printf("姓名 性别 年龄 电话 地址\n"); // 表头
while (cur)
{
PenInfoPrint(cur->data);
cur = cur->next;
}
}
//添加通讯录数据
void AddContact(contact** con) // 不能传未初始化的con进来
{
peoInfo info;
// 输入联络人数据
printf("请输入联络人的姓名:\n");
scanf("%s", info.name);
printf("请输入联络人的性别:\n");
scanf("%s", info.gender);
printf("请输入联络人的年龄:\n");
scanf("%d", &(info.age));
printf("请输入联络人的电话:\n");
scanf("%s", info.tel);
printf("请输入联络人的地址:\n");
scanf("%s", info.addr);
// 将数据添加到通讯录中
SLTPushBack(con, info);
printf("插入成功\n");
}
//删除通讯录数据
contact* FindByName(contact* con,char name[])
{
contact* cur = con;
// 遍历通讯录 查看是否有名字相同的
while (cur)
{
if (strcmp(cur->data.name, name) == 0)
{
return cur;
}
cur = cur->next;
}
// 走到这里说明没找到
return NULL;
}
void DelContact(contact** con) //链表的删除指定位置的数据
{
// 输入要删除的联络人的姓名
char name[NAME_MAX] = { 0 };
printf("请选择你要删除的联络人姓名\n");
scanf("%s", name);
// 我们根据输入的姓名去查找通讯录中是否有该姓名的存在 有就删除
contact* ret = FindByName(*con, name);
if (ret != NULL)
{
SLTErase(con, ret);
printf("你所要删除的联系人:%s,已经成功删除\n", name);
}
else
{
printf("你所要删除的联系人,不存在于通讯录中!!!\n");
return;
}
}
//查找通讯录数据
void FindContact(contact* con)
{
// 输入要查找的联络人姓名
char name[NAME_MAX] = { 0 };
printf("请输入你要查找的联络人姓名\n");
scanf("%s", name);
contact* ret = FindByName(con, name);
if (ret != NULL)
{
printf("你所查找的联络人:%s,信息如下:\n", name);
printf("姓名 性别 年龄 电话 地址\n"); // 表头
PenInfoPrint(ret->data);
}
else
{
printf("你所查找的联络人:%s,不存在!!!\n", name);
return;
}
}
//修改通讯录数据
void ModifyContact(contact** con)
{
char name[NAME_MAX] = { 0 };
printf("请输入你要修改的联络人姓名\n");
scanf("%s", name);
contact* ret = FindByName(*con, name);
if (ret == NULL)
{
printf("你所查找的联络人:%s,不在通讯录中\n", name);
return;
}
else
{
// 找到了
printf("该联系人的信息如下:\n");
printf("姓名 性别 年龄 电话 地址\n"); // 表头
PenInfoPrint(ret->data);
// 修改 让用户自主选择修改那一部分
int n = 0;
do {
printf("请选择你需要修改的数据\n");
printf("*****************************************\n");
printf("****1.姓名*******2.性别******3.年龄*******\n");
printf("****4.电话*******5.地址******0.退出*******\n");
printf("*****************************************\n");
scanf("%d", &n);
switch (n)
{
case 1:
printf("请输入新的姓名\n");
scanf("%s", ret->data.name);
break;
case 2:
printf("请输入新的性别\n");
scanf("%s", ret->data.gender);
break;
case 3:
printf("请输入新的年龄\n");
scanf("%d", &(ret->data.age));
break;
case 4:
printf("请输入新的电话\n");
scanf("%s", ret->data.tel);
break;
case 5:
printf("请输入新的地址\n");
scanf("%s", ret->data.addr);
break;
case 0:
printf("已退出修改\n");
break;
default:
printf("输入不合法,请重新输入\n");
break;
}
} while (n);
printf("修改成功\n");
}
}
// 将通讯录数据写入文件中
void SaveContact(contact* con)
{
// 打开文件
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
perror("fopen");
exit(1);
}
// 将通讯录数据写入文件中
// 遍历通讯录
contact* cur = con;
while (cur)
{
fwrite(&(cur->data), sizeof(peoInfo), 1, pf);
cur = cur->next;
}
printf("成功保存联络人数据!\n");
// 关闭文件
fclose(pf);
pf = NULL;
}
//销毁通讯录数据
void DestroyContact(contact** con)
{
SListDesTroy(con);
}
test.c:
# include "SList.h"
void menu()
{
printf("******************通讯录**********************\n");
printf("********1.增加联系人 2.删除联系人************\n");
printf("********3.修改联系人 4.查找联系人************\n");
printf("********5.展示联系人 0.退出******************\n");
}
int main()
{
//Test01();
int n = 0;
contact* con = NULL;
while (1)
{
int m = 0;
printf("是否要导入历史数据?\n");
printf("1.yes,2.no\n");
scanf("%d", &m);
if (m == 1)
{
LoadContact(&con);
printf("导入历史数据成功\n");
break;
}
else if (m == 2)
{
printf("已退出......\n");
break;
}
else
{
printf("输入不合法,请重新输入\n");
}
}
do
{
menu();
printf("请输入你要选择的操作\n");
scanf("%d", &n);
switch (n)
{
case 1:
AddContact(&con);
break;
case 2:
DelContact(&con);
break;
case 3:
ModifyContact(&con);
break;
case 4:
FindContact(con);
break;
case 5:
ShowContact(con);
break;
case 0:
printf("已退出......\n");
break;
default:
printf("操作不合法,请重新输入\n");
break;
}
} while (n);
while (1)
{
int m = 0;
printf("是否要保存联系人数据?\n");
printf("1.yes,2.no\n");
scanf("%d", &m);
if (m == 1)
{
SaveContact(con); // 将联络人信息存储到文件中
break;
}
else if (m == 2)
{
printf("已退出......\n");
break;
}
else
printf("输入不合法,请重新输入\n");
}
DestroyContact(&con);// 将通讯录销毁
return 0;
}
这里我们优化了一下代码,让用户使用的时候更有体验。