行业知识
数据结构:链表及其应用
一、前言
�� 在我们使用顺序表时,我们会遇到以下问题:
1.中间/头部的插入/删除,时间复杂度为O(n)
2.增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。
3.增容一般是呈二倍增长,势必会有一定的空间浪费,假如当前的空间为100,增容之后为200,但是我们只需插入几个数据,那么剩下的空间就浪费了。
那么如何解决以上问题呢?
二、链表的概念和结构
概念:链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。车厢是独立存在的,且每节车厢都有车门。想象⼀下这样的场景,假设每节车厢的车门都是锁上的状
态,需要不同的钥匙才能解锁,每次只能携带⼀把钥匙的情况下如何从车头走到车尾?最简单的做法:每节车厢里都放⼀把下⼀节车厢的钥匙。
在链表⾥,每节“车厢”是什么样的呢?
与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“结点/节点”节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。图中指针变量plist保存的是第⼀个节点的地址,我们称plist此时“指向”第⼀个节点,如果我们希望 plist “指向”第⼆个节点时,只需要修改plist保存的内容为0x0012FFA0。为什么还需要指针变量来保存下⼀个节点的位置?链表中每个节点都是独立申请的(即需要插入数据时才去申请⼀块节点的空间),我们需要通过指针变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点。
当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)。当我们想要从第⼀个节点走到最后⼀个节点时,只需要在前⼀个节点拿上下⼀个节点的地址(下⼀个节点的钥匙)就可以了。
三、链表的分类
如上图所示,链表有三个属性:单向或者双向,带头或者不带头,循环或者不循环。
所谓带头,就是带有头节点的链表。带头的链表中不存储数据只存储第一个节点的地址,只起到标志的作用,标志着这里是链表的头部。
单向链表中一个节点只存储它的下一个节点的地址,而双向链表中一个节点分别存储它的上一个节点的地址和下一个节点的地址。
循环链表顾名思义是一个圈,它的尾结点存储着它的头结点的地址。而不循环链表就是一条链。
他们三个进行随意组合,就构成了链表。那么随意组合之后,共有八中组合,其中单链表为最简单的链表,是不带头单向不循环链表。本文只介绍两种链表,分别为不带头单向不循环链表(单链表)和带头双向循环链表(双向链表)。
四、单链表的实现
和顺序表类似,存储数据时我们也用到一个结构体:
typedef struct SListNode{SLDataType data;//要存储的数据struct SListNode* next;//指向下一个节点的地址}LL; 我们定义一个结构体并命名为LL。
1.链表的初始化
链表的初始化很简单,只需将第一个节点的地址置为空即可。
//链表初始化oid LLInit(LL** pph){*pph = NULL;} 在使用链表时,我们需要创建一个节点,这个节点需要被初始化,我们可也写一个测试函数来测试一下:
void test05(){LL* phead;//创建一个头节点LLInit(&phead);} 在主函数中调用,调试运行,我们可以看到如图所示的结果:
在右边可以看到,头节点已经被置为空了。
2.链表节点空间申请
我们使用malloc函数申请一块新节点然后将其连接到链表中。
LL* LLApply(SLDataType x){LL* pnew = (LL*)malloc(sizeof(LL));assert(pnew != NULL);pnew->data = x;pnew->next = NULL;return pnew;} 3.链表的按顺序插入数据
按顺序插入有两个思路:第一个思路是先创建一个新节点,把想要的数据插入到新节点上,然后再将新节点连接至链表中。第二个思路是只创建节点,连接上链表之后再将新数据插入链表。
void LLPut(LL** pph, SLDataType x){assert(pph != NULL);LL* pnew = LLApply(x);//开辟新内存而不直接在新内存中赋值LL* pcur = *pph;//将phead指向的结构体的地址传给pcurif (*pph == NULL)//这里需要加判断的原因是,如果pph为空,那么pcur也为空,pcur找不到next,会报错。{*pph = pnew;(*pph)->data = x;(*pph)->next = NULL;//将新数据插入到链表里面return;}while (pcur->next != NULL)//这里应该是判断pcur->next为不为空,不是pcur{pcur = pcur->next;}pcur->next = pnew;} 要注意,这里采用的是传址调用,我们在主函数中创建了一个头节点后,传过来的是头结点自身的地址,而不是头结点指向的地址。我们想要改变结构体中的内容,就要通过对头节点地址解引用来改变。如果传的是头节点的值,也就是一个结构体指针,虽然也可以获得同样的效果,但是第一次赋值时,头结点里面的值是空指针,链表不存在,还要给头节点开辟一块新空间作为链表的第一个节点,所以需要获得头结点的地址才能给链表赋第一个值。当头节点有了非空地址之后,采用传值或者传址都可以。下面是采用传值调用的方式:
void LLPut1(LL* ph, SLDataType x){LL* pcur = ph;if (ph == NULL){LL* pnew = LLApply(x);pcur = pnew;return;}while (pcur->next != NULL){pcur = pcur->next;}pcur->next = LLApply(x);} 不过一般采用传址调用,因为第一个函数可以获得头结点的地址,所以包含了头节点为空指针时的赋值情况。而传值调用无法访问空指针,只有在链表存在一个或者以上个节点时才可以调用。
4.链表的打印
我们可以使用for循环来使一个指针变量从头开始访问知道最后一个节点。同时调用printf函数对每个数据进行打印。
void LLPrint(LL* phead){if (phead == NULL){printf("无元素,请先添加元素");}LL* pcur = phead;while (pcur!=NULL){printf("%d ", pcur->data);pcur = pcur->next;//next中存放的是下一个节点的地址,意思是把下一条节点的地址赋给pcur,pcur指向下一个节点。}} 5.链表的任意位置插入数据
想要实现任意位置插入数据,我们可以先申请一块新节点,然后将数据插入到新节点中,最后断开链表该位置的连接并将新节点连接上去。
//链表的任意位置插入数据void LLAnywherePut(LL** pph, int pos, SLDataType x){assert(pph != NULL);LL* pcurFront = *pph;//定义一个节点指向被插入的节点之前的位置//在头部插入数据if (*pph == NULL){LL* pnew = LLApply(x);*pph = pnew;return;}//如果链表不存在,则开辟一个新节点if (pos == 1){LL* pnew1 = LLApply(x);pnew1->next = pcurFront;*pph = pnew1;}//如果插入的位置在第一个节点处,就将新开辟的节点连接至原来的第一个节点之前,再将新开辟的节点地址作为头节点else{for (size_t i = 0; i next;}LL* pcurBack = pcurFront->next;//定义一个指针变量保存原来节点的地址LL* pnew1 = LLApply(x);pcurFront->next = pnew1;pcurFront->next->next = pcurBack;}} 6.链表任意位置查找/删除
想要实现链表数据删除,那么就要先找到这个元素,我们需要先实现一个查找函数,另外还要实现一个能够返回链表中存储数据数量的函数。
//链表元素查找int LLFind(LL** pph, SLDataType x,int size){assert(pph != NULL);LL* pcur = *pph;int i,count = 0;for ( i = 0; i data == x){count++;pcur = pcur->next;}elsepcur = pcur->next;}return count;} //统计链表元素个数int LLSize(LL* ph){LL* pcur = ph;int count = 0;while (pcur->next!=NULL){count++;pcur = pcur->next;}return count+1;} //链表任意位置删除void LLAnywhereDel(LL** pph, SLDataType x){assert(pph != NULL);LL* pcur = *pph;int size = LLSize(*pph);int ret = LLFind(pph, x, size);if (pcur == NULL){printf("链表未定义!");return;}if (ret == 0){printf("链表中无元素!");}else{for (int i = 0; i next->data != x){pcur = pcur->next;}else{LL* pnew = pcur->next->next;free(pcur->next);pcur->next = pnew;return;}}}} 删除元素只需将要删除的节点之前的节点与之后的节点链接即可。同时将删除掉的节点空间释放掉。
7.一键清空链表
我们可以定义一个快捷删除链表全部元素的功能,当我们想要清空掉这个链表时,就可以直接调用这个函数。
//一键清空链表void LLClear(LL** pph){printf("你确定要清空链表吗?\n如果是,请在键盘中按1,按其他键则代表放弃。\n");char arr[10] = { '0' };scanf("%c", arr);if (arr[0] != '1'){printf("取消清空操作。\n");return;}LL* pcur = *pph;int size = LLSize(*pph);int sizetmp = size;for (int i = 0; i next;}sizetmp--;free(pcur);pcur = *pph;}(*pph) = NULL; printf("已清空。\n");} 我们先实现一个判断,防止用户错误操作。我们使用for循环嵌套,外层for循环是删除的循环次数,当有size个元素时就循环size次。内层for循环是通过循环来找到链表的末尾,释放掉这个末尾之后就再次寻找链表新的末尾。
至此我们已经实现完了单链表的功能。
五、双向链表的实现
1.双向链表的定义
双向链表的全名是带头循环双向链表,它带有一个头节点,不存储任何数据,它是首尾相连的,并且它的每个节点都保存着上一个节点和下一个节点的地址。链表的定义如下:
typedef int SLDataType;typedef struct DoubleLinkedList{struct DoubleLinkedList* last;//上一个节点的地址SLDataType data;//要存储的数据struct DoubleLinkedList* next;//下一个节点的地址}DL; 2.双向链表的初始化
//双向链表的初始化和销毁int DLInit(DL** pphead){DL* pcur = *pphead;pcur = (DL*)malloc(sizeof(DL)); if (pcur == NULL)//还要判断一下空间是否开辟成功。如果开辟失败,那么p将会是空指针。{perror("Initmalloc");return 1;//返回1代表函数出问题}//给头结点赋初值//初始时,头节点的前一个节点和后一个节点均指向自己。pcur->data = -1;pcur->next = pcur;pcur->last = pcur;*pphead = pcur;return 0;} 链表的初始化使链表里面只有一个头结点,并让头结点自身首尾相连。
3.双向链表节点的空间申请
接下来我们就要实现一个空间申请函数。
DL* DLApply(SLDataType x){DL* pnew = (DL*)malloc(sizeof(DL));if (pnew == NULL){perror("malloc");}else{pnew->data = x;}return pnew;} 如果申请成功,就将数据插入到新节点当中,再返回这个新节点的地址,再将原来的链表连接上这个节点即可。
4.双向链表按顺序插入数据
//链表的按顺序插入void DLPut(DL** pphead,SLDataType x){DL* pcur = (*pphead);DL* phead = (*pphead);DL* pnew = DLApply(x);//调用空间申请函数得到新节点的地址//链表中没有元素if (pcur->next == phead){pnew->last = pcur;pnew->next = pcur;pcur->next = pnew;pcur->last = pnew;//将新节点与头节点进行双向链接}//链表中有元素else{while (1)//通过循环来找到链表的尾结点,直到找到为止{if (pcur->next == phead){break;}else{pcur = pcur->next;}}pnew->last = pcur;pnew->next = phead;pcur->next = pnew;phead->last = pnew;}} 5.双向链表的打印
//链表的打印void DLPrint(DL* phead){DL* pcur = phead;//通过循环来遍历链表,同时打印其中的元素while (1){if (pcur->next == phead){break;}else{pcur = pcur->next;printf("%d ", pcur->data);}}} 6.双向链表的任意位置插入数据
第一个参数为链表结构体变量的地址,第二个参数为要插入数据的位置,第三个参数为要插入的数据。
//链表的任意位置插入数据void DLAnywherePut(DL** pphead, int pos, SLDataType x){DL* pcur = (*pphead);DL* phead = (*pphead);DL* pnew = DLApply(x);if (pcur->next == phead){pnew->last = pcur;pnew->next = pcur;pcur->next = pnew;pcur->last = pnew;}//链表中只有一个元素else if (pos == 1){DL* ptmp = pcur->next;pcur->next = pnew;pnew->last = pcur;pnew->next = ptmp;ptmp->last = pnew;}//链表中有一个以上个元素else{for (size_t i = 0; i next;}DL* ptmp = pcur->next;pcur->next = pnew;pnew->last = pcur;pnew->next = ptmp;ptmp->last = pnew;}} 7.双向链表元素个数统计
//统计链表元素个数int DLSize(DL* phead){int j = 0;DL* pcur = phead;while (1){if (pcur->next==phead){return j;}else{pcur = pcur->next;j++;}}}//通过循环来统计链表中的所有元素的个数,知道遍历整个链表为止,返回元素的个数。 8.双向链表中单个元素个数统计
//链表任意元素个数查找int DLFind(DL* phead, SLDataType x){int j = 0;DL* pcur = phead;while (1){if (pcur->next->data == x){j++;pcur = pcur->next;}else if (pcur->next == phead){return j;}else{pcur = pcur->next;}}}//通过循环来找到要查找元素的位置,直到遍历整个链表为止,返回该元素的个数。 9.双向链表中任意单个元素删除
//链表任意位置删除void DLAnywhereDel(DL** pphead, SLDataType x)//删除单一链表元素{DL* pcur = (*pphead);DL* phead = (*pphead);int ret = DLFind(pcur, x);//首先查找一下该元素是否存在,不存在则直接返回if (pcur->next == phead){printf("链表中无元素!\n");return;}if (ret == 0){printf("链表中无该元素!\n");return;}while (1){if (pcur->next->data == x){DL* ptmp = pcur->next;pcur->next = pcur->next->next;pcur->next->last = pcur;free(ptmp);return;//删除一次直接返回}else{pcur = pcur->next;}}}//通过循环来找到要删除的元素,直接将该节点的上一个节点与下一个节点相连,并释放掉这个空间 10.双向链表中所有相同元素删除
//链表所有相同元素删除void DLEverywhereDel(DL** pphead, SLDataType x){DL* pcur = (*pphead);DL* phead = (*pphead);int ret = DLFind(pcur, x);if (pcur->next==phead){printf("链表中无元素!\n");return;}if (ret == 0){printf("链表中无该元素!\n");return;}int size = DLSize(pcur);for (size_t i = 0; i next->data == x){DL* ptmp = pcur->next;pcur->next = pcur->next->next;pcur->next->last = pcur;free(ptmp);}//连续删除直到该元素删除完为止else{pcur = pcur->next;}}} 11.清空双向链表
//链表的销毁void DLClear(DL** pphead){DL* pcur = (*pphead)->last;DL* pnew = pcur;while(1){pnew = pcur->last;free(pcur);pcur = pnew;if (pcur == *pphead)break;}free(pcur);} 六、基于单链表实现通讯录
在顺序表那一章,我们已经使用顺序表实现过了通讯录,但是当我们存储下联系人数据之后,关闭通讯录的时候,数据就丢失了,并没有达到真正的存储。并且顺序表扩容是有弊端的,那么现在我们来通过链表的知识,结合文件管理的操作的知识,再次实现一遍通讯录项目吧!
1.通讯录菜单打印
这个通讯录菜单的打印在顺序表实现通讯录时就已经做出了详细的介绍,这里不再过多的解释。直接给出源码:
#define _CRT_SECURE_NO_WARNINGS 1#include"Address_book+.h"int main(){LL* ps;//创建一个链表结构体变量InfoInit(&ps);ImportContactData(&ps);printf("历史数据导入通讯录成功!\n");PrintMenu();int n;int i;int size = 0;for (i = 0;;){scanf("%d", &n);switch (n){case 0://退出通讯录InfoExit(&ps);return 0;case 1://查看已有联系人InfoView(&ps);break;case 2://添加新的联系人InfoAdd(&ps);break;case 3://删除已有联系人InfoDel(&ps);break;case 4://修改联系人信息InfoView(&ps);InfoMod(&ps);break;default:printf("操作无效,请重新输入。\n");break;}PrintMenu();}return 0;} 2.统计联系人个数
//统计联系人个数int LLSize(LL* ps){LL* pcur = ps;int count = 0;while (pcur->next != NULL){count++;pcur = pcur->next;}return count + 1;} 3.退出通讯录
void InfoExit(LL** pps){if ((*pps) == NULL)//如果通讯录本来就是空的,那么就直接退出,省去执行下面的步骤。{//printf("通讯录已退出\n");return;}int size = LLSize(*pps);//退出通讯录需要将之前malloc过得空间都释放掉。LL* pcur = (*pps);int sizetmp = size;for (int i = 0; i next;}sizetmp--;free(pcur);pcur = (*pps);}*pps = NULL;printf("通讯录已退出\n");} 要释放掉链表的空间,可以先从链表的尾部开始释放,也可以从链表的头部开始释放。上面的代码给的是从尾部开始释放。下面给出从头部释放的代码:
void InfoExit(LL** pps){if ((*pps) == NULL)//如果通讯录本来就是空的,那么就直接退出,省去执行下面的步骤。{//printf("通讯录已退出\n");return;}int size = LLSize(*pps);//退出通讯录需要将之前malloc过得空间都释放掉。LL* pcur = (*pps);int sizetmp = size;/*for (int i = 0; i next;}sizetmp--;free(pcur);pcur = (*pps);}*/for (size_t i = 0; i next;free(pcur);pcur = ptmp;}*pps = NULL;printf("通讯录已退出\n");} 相比于尾部,头部释放的代码更为简单。
4.申请一块新空间用以存储联系人数据
//联系人信息结构体空间申请LL* LLApply(Info info){LL* pnew = (LL*)malloc(sizeof(LL));//先求出要申请的空间的大小if (pnew == NULL){perror("malloc");return NULL;}pnew->data = info;pnew->next = NULL;return pnew;} 5.添加联系人
//添加联系人void InfoAdd(LL** pps){Info info;LL* pcur = *pps;printf("执行添加联系人的操作。\n\n");printf("请输入联系人的姓名:\n");scanf("%s", info.name);printf("\n请输入联系人的年龄:\n");scanf("%d", &info.age);printf("\n请输入联系人的性别:\n");scanf("%s", info.gender);printf("\n请输入联系人的电话:\n");scanf("%s", info.tel);printf("\n请输入联系人的住址:\n");scanf("%s", info.addr);LL* pnew = LLApply(info);if (pcur == NULL)//当链表为空的情况{*pps = pnew;//将pnew作为第一个节点ContactDataSaving(pps);printf("\n联系人添加成功!\n\n");return;}while (pcur->next != NULL)//链表不为空的情况{pcur = pcur->next;}pcur->next = pnew;ContactDataSaving(pps);printf("\n联系人添加成功!\n\n");} 思路是先创建一个结构体,里面存储着联系人的各种数据,然后再将这个结构体作为链表的一个元素按顺序插入至链表中。
添加联系人的函数中,出现了ContactDataSaving(pps)这个函数,它的作用是将联系人数据保存到本地。将在下文详细介绍。
6.保存联系人数据到本地
//保存联系人数据到本地void ContactDataSaving(LL** pps){FILE* pf = fopen("Address_book.txt", "wb");if (pf == NULL) {perror("fopen error!\n");return;}//将通讯录数据写⼊⽂件LL* pcur = *pps;while (pcur)//如果pcur不为空,就连续写入{fwrite(&(pcur->data), sizeof(pcur->data), 1, pf);//将data中大小为sizeof(pcur->data)的一个数据写入到文件中pcur = pcur->next;}fclose(pf);} 当添加、删除、修改完联系人后,使用fopen函数打开文件时选择wb(以二进制形式写),文件中的内容就会清空, 这时候在调用该函数将新的联系人数据保存到本地即可。
7.导入本地数据
//导入历史联系人数据void ImportContactData(LL** pps){FILE* pf = fopen("Address_book.txt", "rb");//以二进制读的方式打开文件if (pf == NULL) {perror("fopen error!\n");return;}//循环读取⽂件数据LL* pcur = *pps;Info info;while (fread(&info, sizeof(info), 1, pf))//将文件里一个结构体数据读取到一个结构体变量中,然后将结构体变量按顺序尾插到链表中{LL* pnew = LLApply(info);if (pcur == NULL)//通讯录中没有联系人的情况{*pps = pnew;pcur = *pps;}else{while (pcur->next != NULL){pcur = pcur->next;}pcur->next = pnew;}}fclose(pf);} 导入联系人的思路是使用循环读取文件中的数据,存入到一个结构体变量中,然后按顺序尾插到通讯录中。
8.查看已有联系人
//查看联系人void InfoView(LL** pps){printf("你的联系人信息:\n\n");if ((*pps) == NULL){printf("通讯录中无联系人!请先添加联系人。\n\n");return;}int size = LLSize(*pps);if (size == 0){printf("通讯录中无联系人!请先添加联系人。\n\n");return;}int i = 0;LL* pcur = (*pps);printf("姓名 性别 年龄 电话 住址\n");for (i = 0; i data.name,pcur->data.gender,pcur->data.age,pcur->data.tel,pcur->data.addr);pcur = pcur->next;}} 9.按名字查找联系人
void InfoDel(LL** pps){assert((*pps) != NULL);int size = LLSize(*pps);printf("请输入要删除的联系人姓名:\n");char name[NAME_MAX];scanf("%s", name);LL* pcur = (*pps);int ret = InfoFind(pcur,name,size);//找出该联系人的下标位置if (ret == -1){printf("没有此联系人!\n");return;}else if (size == 0){printf("通讯录中没有联系人!无法执行删除联系人操作,请先添加联系人。\n");return;}else if (ret == 1 && (size) == 1){free(pcur);*pps = NULL;(size)--;FILE* pf = fopen("Address_book.txt", "wb");fclose(pf);}else if (ret == 1){LL* tmp = pcur->next;free(*pps);(*pps) = tmp;(size)--;FILE* pf = fopen("Address_book.txt", "wb");ContactDataSaving(pps);fclose(pf);}else{for (int i = 0; i next;}LL* tmp = pcur->next->next;free(pcur->next);pcur->next = tmp;size--;FILE* pf = fopen("Address_book.txt", "wb");ContactDataSaving(pps);fclose(pf);}printf("删除成功!\n");} 当我们想要实现删除、修改信息的操作,首先要找出该联系人。我们可以采用按姓名来查找。单独实现一个查找联系人的函数,当我们需要查找联系人时,直接调用这个函数就可以。
//根据名字查找联系人int InfoFind(LL* ps, char* name,int size){int i = 0;int j = 0;LL* pcur = ps;for (int i = 0; i data.name, name) == 0)//找到了{j = i;j++;return j;}else{pcur = pcur->next;}}if (strcmp(ps->data.name, name) != 0)//如果找到了,就将该联系人的下标返回给j,如果找不到,j就为0,然后退出该程序{return -1;}return 0;} 10.查找指定联系人在通讯录中的位置
//根据名字查找联系人int InfoFind(LL* ps, char* name,int size){int i = 0;int j = 0;LL* pcur = ps;for (int i = 0; i data.name, name) == 0)//找到了{j = i;j++;return j;}else{pcur = pcur->next;}}if (strcmp(ps->data.name, name) != 0)//如果找到了,就将该联系人的下标返回给j,如果找不到,j就为0,然后退出该程序{return -1;}return 0;} 通过使用strcmp函数比对要查找的联系人名字和通讯录里面存储的联系人名字,来判断通讯录中是否存在该联系人。如果存在该联系人,就返回该联系人在通讯录中的位置。如果是第一位,就返回数字1。
11.删除联系人
//删除联系人信息void InfoDel(LL** pps){assert((*pps) != NULL);int size = LLSize(*pps);printf("请输入要删除的联系人姓名:\n");char name[NAME_MAX];scanf("%s", name);//根据姓名来查找该联系人LL* pcur = (*pps);int ret = InfoFind(pcur,name,size);//找出该联系人的下标位置if (ret == -1){printf("没有此联系人!\n");return;}else if (size == 0){printf("通讯录中没有联系人!无法执行删除联系人操作,请先添加联系人。\n");return;}else if (ret == 1 && (size) == 1)//通讯录中只有一位联系人并且是要查找的联系人{free(pcur);*pps = NULL;(size)--;FILE* pf = fopen("Address_book.txt", "wb");fclose(pf);}else if (ret == 1)//通讯录中存储着大于等于一位联系人并且是要查找的联系人是第一个{LL* tmp = pcur->next;free(*pps);(*pps) = tmp;(size)--;FILE* pf = fopen("Address_book.txt", "wb");ContactDataSaving(pps);fclose(pf);}else//通讯录中存储着大于等于一位联系人并且是要查找的联系人不是第一个{for (int i = 0; i next;}//将pcur指针定位到要查找的联系人位置处LL* tmp = pcur->next->next;free(pcur->next);pcur->next = tmp;size--;FILE* pf = fopen("Address_book.txt", "wb");ContactDataSaving(pps);fclose(pf);}printf("删除成功!\n");} 12.修改联系人信息
//修改联系人信息void InfoMod(LL** pps){assert((*pps) != NULL);int size = LLSize(*pps);printf("请输入要修改的联系人姓名:\n");char name[NAME_MAX];scanf("%s", name);//按姓名查找联系人LL* pcur = *pps;int i = 0;if (size == 0){printf("通讯录中没有联系人!无法执行修改联系人信息的操作,请先添加联系人。\n");}int j = InfoFind(pcur, name,size);if (j == -1){printf("该联系人不存在!\n");return;}else{for ( i = 0; i next;}printf("请输入修改后联系人的名字:\n");scanf("%s", pcur->data.name);printf("请输入修改后联系人的年龄:\n");scanf("%d", &pcur->data.age);printf("请输入修改后联系人的性别:\n");scanf("%s", pcur->data.gender);printf("请输入修改后联系人的电话:\n");scanf("%s", pcur->data.tel);printf("请输入修改后联系人的住址:\n");scanf("%s", pcur->data.addr);printf("联系人信息修改成功!\n\n");//重新输入正确的联系人信息}FILE* pf = fopen("Address_book", "wb+");ContactDataSaving(pps);fclose(pf);} 当我们想要改变通讯录里面联系人的信息时,就使用fopen函数使用wb模式打开文件,这时文件里面的内容会被清空,这时我们就调用ContactDataSaving(pps)函数将内存中新的联系人数据写入文件里面即可。