数据结构入门篇 之 【单链表】的实现讲解(附单链表的完整实现代码以及用单链表完成通讯录的实现代码)

在这里插入图片描述
虽然封面是顶针,但是我们还是要好好学习

一.单链表

1.单链表的概念

2.单链表的结构

3.单链表的实现

1).尾插函数 SLTPushBack

2).打印函数 SLPrint

3). 头插函数 SLTPushFront

4).尾删函数 SLTPopBack

5).头删函数 SLTPopFront

6).查找函数 SLTFind

7).在指定位置之前插入数据函数 SLTInsert

8).在指定位置之后插入数据函数 SLTInsertAfter

9).删除指定位置节点 SLTErase

10).删除pos节点之后的节点函数 SLTEraseAfter

11).销毁函数 SListDesTroy

二.单链表完整代码实现

三.用单链表完成通讯录的实现代码

四.完结撒❀

前言:

在学习之前大家可以先思考两个问题:

1、单链表的作用是什么?
2、有了顺序表为什么还要实现单链表?
3、学习单链表需要用到哪些知识?

–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–
1、单链表的作用是什么?

我们在之前的学习中学习了顺序表的实现,同为数据结构的知识,单链表与顺序表所发挥的作用都是一样的——实现更好的管理所存储的数据,但是单链表的实现原理与顺序表是不同的。

2、有了顺序表为什么还要实现单链表?

同为数据结构,顺序表的使用是有缺点的:

1、中间/头部的插入删除,时间复杂度为O(N)。
2、增容需要申请新的空间,拷贝数据,释放旧的空间。会有不小的消耗。
3、增容一般是呈2倍增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

因此是否存在一种数据结构能够解决以上顺序表表现出来的问题呢?
也即:

1、中间/头部的插入删除,可以一步到位,不需要挪动数据。
2、不需要扩容。(扩容指对空间进行连续的开辟,单链表的空间不一定是连续的)。
加粗样式
3、不会造成空间浪费。

单链表的实现就能解决顺序表的这些缺点。

3、学习单链表需要用到哪些知识?

单链表的实现运用到了结构体,指针(一级指针、二级指针、指针传参)、结构体指针、动态内存管理这些知识,在掌握这些知识之后我们就可以进行单链表的学习了。

一、单链表

1.单链表的概念

概念:链表是一种物理存储结构非连续,非顺序的存储结构,数据元素的逻辑顺序是通过指针链接以此实现的。

链表可以抽象看作成火车
在这里插入图片描述链表的结构与火车类似。在增添车厢或者减少车厢时只需要将火车里的某节车厢去掉,因为车厢之间的独立存在的所以并不会影响到其他车厢。
我们想象一下这个场景:假如每节车厢的车门都是锁着的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下应如何从车头走向车尾呢?
最简单的方法:在每节车厢里放上一把下一节车厢的钥匙
那么我们只需要第一节车厢的钥匙便可从车头走向车尾。


在链表里,每节车厢是什么样的呢?
在这里插入图片描述与顺序表不同的是,链表里的每节“车厢”都是独立申请下来的空间,我们称之为“节点
节点的组成只要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)
图中指针变量plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的地址内容为0x0012FFA0。

为什么还需要指针变量来保存下一个节点的位置?

链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点的位置才能从当前节点找到下一个节点

2.单链表的结构

结合前面前面顺序表的讲解以及学到的结构体知识,我们可以给出每个节点对应的结构体代码:
假设当前保存的节点为整形

typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;

当我们想保存一个整形数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整形数据,也需要保存下一个节点的地址(当下一个节点为时保存的地址为)。
当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿上下一个节点的地址(下一个节点的钥匙)就可以了。

给定的链表结构中,如何实现节点从头到尾的打印呢?

3.单链表的实现

我们在实现链表时也是创建三个文件进行实现:头文件SList.h,实现文件SList.c,测试文件test.c

1).尾插 SLTPushBack

链表的结构上面已经讲过了,之后我们就来实现尾插操作。
尾插思路:
在这里插入图片描述假如上面两个是我们设置的链表,要分两种情况:一个不为空一个为空
链表不为空的时候我们进行尾插,进行的操作是要把尾插的节点的地址保存到上一个节点里,再把尾插的节点里存放地址的数据改成空指针。
链表为空的时候,我们需要将创建的指向头节点的结构体指针指向第一个节点,也就是尾插的节点,之后再将尾插的节点里所存的地址设置为空指针NULL。
这里注意,我们是要对原来链表进行改变,所以这里涉及到使用二级指针传参,因为只有用二级指针才能找到原链表进行修改打印。下面只要涉及到修改链表都要使用二级指针进行传参。

代码如下:

SList.h:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;
//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

SList.c:

#include "SList.h"
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}

在实现的文件中我们把申请节点空间独自写了一个函数SLTBuyNode,方便我们之后直接调用开辟空间。
我们在测试尾插函数正确与否之前先把打印函数实现了。

2).打印函数 SLPrint

对于链表的打印我们看下图:
在这里插入图片描述指针plist指向我们创建的第一个节点,因为对于节点来说我们是通过存储对应节点的地址来查找各个节点的,所以我们需要将形参设置为指针变量进行接收,通过结构体指针变量来找到对应节点存储的数值,之后再将指针指向指针内部所存储下一个节点的地址,让指针指向下一个节点。
对于上面代码进行解读:

1).pcur指针变量保存第一个节点的地址。
2).对pcur解引用拿到next指针变量中的地址(下一个节点的地址)
3).赋值给pcur,此时pcur保存的地址为0x0012FFA0,即pcur“指向了下一个节点”。

这里可能有人会问:直接使用指针变量phead进行打印不就行了吗?为什么要创建结构体变量plist进行操作?

因为我们需要保证存储的头部节点地址(第一个节点的地址)不被改变,所以需要另外创建一个变量进行存储。

代码如下:

void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}

头插测试

实现好了打印函数,那么我们来测试之前所创建的头插函数是否正确:

test.c:

#include "SList.h"
void SLTest01()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLPrint(plist);}int main()
{SLTest01();return 0;
}

打印结果:
在这里插入图片描述到此,尾插以及打印函数实现完成。

3). 头插函数 SLTPushFront

头插函数的实现逻辑与尾插函数不同,但是在两种情况下链表为空和链表不为空时,其实现逻辑是一样的。
在这里插入图片描述我们头插时也需要去进行空间申请,创造新节点,上图表示为newnode,在链表不为空的时候我们进行头插需要将newnode的地址存进创建的结构体指针变量phead中去,之后在节点newnode中存储原头节点的地址,而当链表为空时,我们执行的操作是一样的。
代码如下:

SList.h:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}

下面我们可以在测试函数中测试所写的头插代码是否正确。
test.c:

#include "SList.h"
void SLTest01()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLPrint(plist);SLTPushFront(&plist, 8);SLTPushFront(&plist, 9);SLTPushFront(&plist, 10);SLPrint(plist);}int main()
{SLTest01();return 0;
}

打印结果:
在这里插入图片描述下面我们实现尾删函数。

4).尾删函数 SLTPopBack

因为所有节点都是由内存申请的空间进行存储的,所以我们要对节点进行删除的话就必须涉及到销毁函数free的操作。
头删原理:
在这里插入图片描述我们在进行尾删操作当链表为空时是不能进行删除的,我们直接断言链表即可,那么我们需要考虑的是链表节点有多个链表节点只有一个的情况,当链表节点有多个时我们需要对链表进行遍历找到尾节点,然后将尾节点销毁,并且我们还需要创建结构体变量prev来记录保存尾节点之前节点的地址,因为后面我们还需要将prev指向的节点里的地址赋值为空指针。
链表不为空总结2部:

1).遍历找到尾节点进行销毁。
2).将尾节点前的节点里保存的地址改为空指针。

当链表节点为一个时,这时尾节点与头节点是一个,所以我们直接进行销毁节点即可。
代码如下:
SList.h:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);//实现头删和尾删
void SLTPopBack(SLTNode** pphead);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//不能为空列表assert(*pphead);//有节点分为1个节点或多个节点//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}//多个节点遍历SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//ptail最后节点prev->next = NULL;free(ptail);ptail = NULL;
}

我们再对代码进行测试:
test.c:

#include "SList.h"
void SLTest01()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLPrint(plist);SLTPushFront(&plist, 8);SLTPushFront(&plist, 9);SLTPushFront(&plist, 10);SLPrint(plist);SLTPopBack(&plist);SLTPopBack(&plist);SLPrint(plist);}int main()
{SLTest01();return 0;
}

打印结果:
在这里插入图片描述

5).头删函数 SLTPopFront

头删我们需要考虑的也是链表节点只有一个或链表节点有多个这两种情况,至于链表为空的话是不能删除的,所以我们直接断言链表即可。
在这里插入图片描述当链表为多个和链表为一个是一样的,我们需要创建一个结构体指针变量del保存我们第一个节点的地址,然后将头指针指向第二个节点,再free掉del。
代码如下:
SList.h:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);//实现头删和尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//不能为空列表assert(*pphead);//有节点分为1个节点或多个节点//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}//多个节点遍历SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//ptail最后节点prev->next = NULL;free(ptail);ptail = NULL;
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空列表不能删assert(*pphead);//头删一个或者多个逻辑一样SLTNode* ptail = *pphead;(*pphead) = (*pphead)->next;free(ptail);ptail = NULL;
}

下面进行测试
test.c:

#include "SList.h"
void SLTest01()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLPrint(plist);SLTPushFront(&plist, 8);SLTPushFront(&plist, 9);SLTPushFront(&plist, 10);SLPrint(plist);SLTPopBack(&plist);SLTPopBack(&plist);SLPrint(plist);SLTPopFront(&plist);SLTPopFront(&plist);SLPrint(plist);
}int main()
{SLTest01();return 0;
}

打印结果:
在这里插入图片描述

6).查找函数 SLTFind

我们要对链表进行查找,之后如果有那么返回该节点的地址,如果没有则返回空指针NULL,那么就需要对链表进行遍历,然后判断相等。
代码如下:
SList.h:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);//实现头删和尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//不能为空列表assert(*pphead);//有节点分为1个节点或多个节点//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}//多个节点遍历SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//ptail最后节点prev->next = NULL;free(ptail);ptail = NULL;
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空列表不能删assert(*pphead);//头删一个或者多个逻辑一样SLTNode* ptail = *pphead;(*pphead) = (*pphead)->next;free(ptail);ptail = NULL;
}SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead);//不能查找空列表if (*pphead == NULL){return NULL;}//遍历SLTNode* ptail = *pphead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}

这个代码的测试大家可以下去实验一下。

7).在指定位置之前插入数据函数 SLTInsert

我们要实现在指定位置之前进行数据的插入,需要考虑两个方面,在头节点之前插入,在头节点之后插入,在头节点之前插入的话我们直接调用头插即可实现
那么我们需要创建结构体指针变量,进行遍历查找指定位置的前一个节点,然后将申请好的新节点地址存进指定位置的前一个节点里面,之后再将指定位置节点的地址存进申请好的新节点里,完成三节点的联系。
代码如下:
SList.h:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);//实现头删和尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//不能为空列表assert(*pphead);//有节点分为1个节点或多个节点//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}//多个节点遍历SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//ptail最后节点prev->next = NULL;free(ptail);ptail = NULL;
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空列表不能删assert(*pphead);//头删一个或者多个逻辑一样SLTNode* ptail = *pphead;(*pphead) = (*pphead)->next;free(ptail);ptail = NULL;
}SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead);//不能查找空列表if (*pphead == NULL){return NULL;}//遍历SLTNode* ptail = *pphead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//链表也不能为空assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//如果在头节点之前if ((*pphead) == pos){SLTPushFront(pphead, x);return;}//位置在头节点之后SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev为pos前面节点//将三者进行关联prev->next = newnode;newnode->next = pos;
}

测试大家可以下去自主进行。

8).在指定位置之后插入数据函数 SLTInsertAfter

原理与在指定位置之前插入数据类似,我们只需要完成三个节点的联系,即可完成函数的操作。
代码如下:
SList:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);//实现头删和尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//不能为空列表assert(*pphead);//有节点分为1个节点或多个节点//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}//多个节点遍历SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//ptail最后节点prev->next = NULL;free(ptail);ptail = NULL;
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空列表不能删assert(*pphead);//头删一个或者多个逻辑一样SLTNode* ptail = *pphead;(*pphead) = (*pphead)->next;free(ptail);ptail = NULL;
}SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead);//不能查找空列表if (*pphead == NULL){return NULL;}//遍历SLTNode* ptail = *pphead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//链表也不能为空assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//如果在头节点之前if ((*pphead) == pos){SLTPushFront(pphead, x);return;}//位置在头节点之后SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev为pos前面节点//将三者进行关联prev->next = newnode;newnode->next = pos;
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}

测试大家可以下去自主进行。

9).删除指定位置节点 SLTErase

我们要执行删除指定位置节点需分两步

1.完成销毁节点前后节点的联系。
2.销毁节点。

如果我们指定的位置是头节点的话,那么我们直接调用头删函数即可,其他节点操作原理是一样的,遍历节点找到对应位置,将要销毁的节点里所存储的下一和节点的地址赋值给上一个节点里的结构体指针变量里,这样就实现了销毁节点前后节点的联系。
特别提醒,所传形参不能为空,链表不能为空,指定删除位置不能为空,所以要进行三次断言,*assert(pphead);assert(pos);assert(pphead);

代码如下:
SList.h:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);//实现头删和尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//不能为空列表assert(*pphead);//有节点分为1个节点或多个节点//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}//多个节点遍历SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//ptail最后节点prev->next = NULL;free(ptail);ptail = NULL;
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空列表不能删assert(*pphead);//头删一个或者多个逻辑一样SLTNode* ptail = *pphead;(*pphead) = (*pphead)->next;free(ptail);ptail = NULL;
}SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead);//不能查找空列表if (*pphead == NULL){return NULL;}//遍历SLTNode* ptail = *pphead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//链表也不能为空assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//如果在头节点之前if ((*pphead) == pos){SLTPushFront(pphead, x);return;}//位置在头节点之后SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev为pos前面节点//将三者进行关联prev->next = newnode;newnode->next = pos;
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//删除节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (pos = *pphead){SLTPopFront(pphead);return;}SLTNode* patil = *pphead;while (patil->next != pos){patil = patil->next;}//patil为pos前一个节点patil->next = pos->next;free(pos);pos = NULL;
}

下面进行函数测试:
test.c:

#include "SList.h"
void SLTest01()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLPrint(plist);SLTPushFront(&plist, 8);SLTPushFront(&plist, 9);SLTPushFront(&plist, 10);SLPrint(plist);SLTPopBack(&plist);SLTPopBack(&plist);SLPrint(plist);SLTPopFront(&plist);SLTPopFront(&plist);SLPrint(plist);SLTErase(&plist, SLTFind(&plist, 8));SLPrint(plist);}int main()
{SLTest01();return 0;
}

打印结果:
在这里插入图片描述

10).删除pos节点之后的节点函数 SLTEraseAfter

这个相比于删除指定位置节点就简单了,需要特别注意的是我们指定删除位置的节点后面不能为空,所以我们需要对其进行断言。

代码如下:
SList.h:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);//实现头删和尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//不能为空列表assert(*pphead);//有节点分为1个节点或多个节点//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}//多个节点遍历SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//ptail最后节点prev->next = NULL;free(ptail);ptail = NULL;
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空列表不能删assert(*pphead);//头删一个或者多个逻辑一样SLTNode* ptail = *pphead;(*pphead) = (*pphead)->next;free(ptail);ptail = NULL;
}SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead);//不能查找空列表if (*pphead == NULL){return NULL;}//遍历SLTNode* ptail = *pphead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//链表也不能为空assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//如果在头节点之前if ((*pphead) == pos){SLTPushFront(pphead, x);return;}//位置在头节点之后SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev为pos前面节点//将三者进行关联prev->next = newnode;newnode->next = pos;
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//删除节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (pos = *pphead){SLTPopFront(pphead);return;}SLTNode* patil = *pphead;while (patil->next != pos){patil = patil->next;}//patil为pos前一个节点patil->next = pos->next;free(pos);pos = NULL;
}//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos);//不能删最后一个后面assert(pos->next);SLTNode* prov = pos->next;pos->next = prov->next;free(prov);prov = NULL;
}

进行测试代码:
test.c:

#include "SList.h"
void SLTest01()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLPrint(plist);SLTPushFront(&plist, 8);SLTPushFront(&plist, 9);SLTPushFront(&plist, 10);SLPrint(plist);SLTPopBack(&plist);SLTPopBack(&plist);SLPrint(plist);SLTPopFront(&plist);SLTPopFront(&plist);SLPrint(plist);SLTErase(&plist, SLTFind(&plist, 8));SLPrint(plist);SLTEraseAfter(SLTFind(&plist, 1));SLPrint(plist);}int main()
{SLTest01();return 0;
}

打印结果:
在这里插入图片描述

11).销毁函数 SListDesTroy

我们既然开辟了动态内存空间,那么在使用结束后就必要要对应进行释放,释放一个动态内存空间好写,但这里要实现的是直接释放一串链表的释放函数,这其实也不难,只要按照每个节点所存的地址进行逐个销毁即可实现。
我们要进行销毁就必须要不为空的链表,所以直接进行断言。
代码如下:

void SListDesTroy(SLTNode** pphead)
{assert(pphead);assert(*pphead);while (*pphead){SLTNode* next = (*pphead)->next;free(*pphead);(*pphead) = next;}
}

这里还必须要明确先后顺序,执行这行代码时SLTNode* next = (pphead)->next;此时*(pphead)还不能为空*,所以必须在(*pphead)没被销毁之前把里面所存的下一个节点的地址保存下来,然后再对其进行销毁。

二.单链表完整代码实现

一共分两个文件进行实现,一个用来包含头文件SList.h,一个用来实现函数文件SList.c
当然如果你要进行使用单链表还要进行使用文件的创建,下面就不展示了。

SList.h:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
//链表是由节点组成
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLPrint(SLTNode* phead);//实现头插和尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);//实现头删和尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);//销毁链表
void SListDesTroy(SLTNode** pphead);

SList.c:

#include "SList.h"void SLPrint(SLTNode* phead)
{SLTNode* pnode = phead;while (pnode){printf("%d->", pnode->data);pnode = pnode->next;}printf("NULL\n");
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL; return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请空间SLTNode* newnode = SLTBuyNode(x);//判断是否为空列表if (*pphead == NULL){*pphead = newnode;return;}//不为空指针//为了不改变头指针的位置SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}//这里ptail就是尾节点ptail->next = newnode;}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//头插有无数据是同样逻辑SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//不能为空列表assert(*pphead);//有节点分为1个节点或多个节点//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}//多个节点遍历SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//ptail最后节点prev->next = NULL;free(ptail);ptail = NULL;
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空列表不能删assert(*pphead);//头删一个或者多个逻辑一样SLTNode* ptail = *pphead;(*pphead) = (*pphead)->next;free(ptail);ptail = NULL;
}SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead);//不能查找空列表if (*pphead == NULL){return NULL;}//遍历SLTNode* ptail = *pphead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//链表也不能为空assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//如果在头节点之前if ((*pphead) == pos){SLTPushFront(pphead, x);return;}//位置在头节点之后SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev为pos前面节点//将三者进行关联prev->next = newnode;newnode->next = pos;
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//删除节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (pos = *pphead){SLTPopFront(pphead);return;}SLTNode* patil = *pphead;while (patil->next != pos){patil = patil->next;}//patil为pos前一个节点patil->next = pos->next;free(pos);pos = NULL;
}//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos);//不能删最后一个后面assert(pos->next);SLTNode* prov = pos->next;pos->next = prov->next;free(prov);prov = NULL;
}//销毁链表
void SListDesTroy(SLTNode** pphead)
{assert(pphead);assert(*pphead);while (*pphead){SLTNode* next = (*pphead)->next;free(*pphead);(*pphead) = next;}
}

三.用单链表完成通讯录的实现代码

SList.h:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"contact.h"
typedef struct PersonInfo SLTDataType;
//typedef int SLTDataType;
void SLTPrint(SLTNode* phead);
//头部插⼊删除/尾部插⼊删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

contact.h:

#pragma once
#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100
//前置声明
typedef struct SListNode contact;
//⽤⼾数据
typedef struct PersonInfo
{char name[NAME_MAX];char sex[SEX_MAX];int age;char tel[TEL_MAX];char addr[ADDR_MAX];
}PeoInfo;
//初始化通讯录
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);

contach.c:

#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"
#include"SList.h"
void LoadContact(contact** con) {FILE* pf = fopen("contact.txt", "rb");if (pf == NULL) {perror("fopen error!\n");return;}//循环读取⽂件数据PeoInfo info;while (fread(&info, sizeof(info), 1, pf)){SLTPushBack(con, info);}printf("历史数据导⼊通讯录成功!\n");
}
void InitContact(contact** con) {LoadContact(con);
}
void AddContact(contact** con) {PeoInfo info;printf("请输⼊姓名:\n");scanf("%s", &info.name);printf("请输⼊性别:\n");scanf("%s", &info.sex);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];printf("请输⼊要删除的⽤⼾姓名:\n");scanf("%s", name);contact* pos = FindByName(*con, name);if (pos == NULL) {printf("要删除的⽤⼾不存在,删除失败!\n");return;}SLTErase(con, pos);printf("删除成功!\n");
}
void ShowContact(contact* con) {printf("%-10s %-4s %-4s %15s %-20s\n", "姓名", "性别", "年龄", "联系电话", contact* cur = con;while (cur){printf("%-10s %-4s %-4d %15s %-20s\n",cur->data.name,cur->data.sex,cur->data.age,cur->data.tel,cur->data.addr);cur = cur->next;}
}
void FindContact(contact* con) {char name[NAME_MAX];printf("请输⼊要查找的⽤⼾姓名:\n");scanf("%s", name);contact* pos = FindByName(con, name);if (pos == NULL) {printf("要查找的⽤⼾不存在,查找失败!\n");return;}printf("查找成功!\n");printf("%-10s %-4s %-4d %15s %-20s\n",pos->data.name,pos->data.sex,pos->data.age,pos->data.tel,pos->data.addr);
}
void ModifyContact(contact** con) {char name[NAME_MAX];printf("请输⼊要修改的⽤⼾名称:\n");scanf("%s", &name);contact* pos = FindByName(*con, name);if (pos == NULL) {printf("要查找的⽤⼾不存在,查找失败!\n");return;}printf("查找成功!\n");printf("%-10s %-4s %-4d %15s %-20s\n",pos->data.name,pos->data.sex,pos->data.age,pos->data.tel,pos->data.addr);}void ModifyContact(contact** con) {char name[NAME_MAX];printf("请输⼊要修改的⽤⼾名称:\n");scanf("%s", &name);contact* pos = FindByName(*con, name);if (pos == NULL) {printf("要查找的⽤⼾不存在,修改失败!\n");return;}printf("请输⼊要修改的姓名:\n");scanf("%s", pos->data.name);printf("请输⼊要修改的性别:\n");scanf("%s", pos->data.sex);printf("请输⼊要修改的年龄:\n");scanf("%d", &pos->data.age);printf("请输⼊要修改的联系电话:\n");scanf("%s", pos->data.tel);printf("请输⼊要修改的地址:\n");scanf("%s", pos->data.addr);printf("修改成功!\n");
}
void SaveContact(contact* con) {FILE* pf = fopen("contact.txt", "wb");if (pf == NULL) {perror("fopen error!\n");return;}//将通讯录数据写⼊⽂件contact* cur = con;while (cur){fwrite(&(cur->data), sizeof(cur->data), 1, pf);cur = cur->next;}printf("通讯录数据保存成功!\n");
}
void DestroyContact(contact** con) {SaveContact(*con);SListDesTroy(con);
}

四.完结撒❀

如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,据说点赞的都能找到漂亮女朋友
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/526007.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring-cloud-openfeign 3.0.0(对应spring boot 2.4.x之前版本)之前版本feign整合ribbon请求流程

在之前写的文章配置基础上 https://blog.csdn.net/zlpzlpzyd/article/details/136060312 下图为自己整理的

IPsec VPN之安全联盟

一、何为安全联盟 IPsec在两个端点建立安全通信&#xff0c;此时这两个端点被称为IPsec对等体。安全联盟&#xff0c;即SA&#xff0c;是指通信对等体之间对某些要素的约定&#xff0c;定义了两个对等体之间要用何种安全协议、IP报文的封装方式、加密和验证算法。SA是IPsec的基…

MySQL-锁:共享锁(读)、排他锁(写)、表锁、行锁、意向锁、间隙锁,锁升级

MySQL-锁&#xff1a;共享锁&#xff08;读&#xff09;、排他锁&#xff08;写&#xff09;、表锁、行锁、意向锁、间隙锁 共享锁&#xff08;读锁&#xff09;、排他锁表锁行锁意向锁间隙锁锁升级 MySQL数据库中的锁是控制并发访问的重要机制&#xff0c;它们确保数据的一致性…

《剑指 Offer》专项突破版 - 面试题 76 : 数组中第 k 大的数字(C++ 实现)

目录 详解快速排序 面试题 76 : 数组中第 k 大的数字 详解快速排序 快速排序是一种非常高效的算法&#xff0c;从其名字可以看出这种排序算法最大的特点是快。当表现良好时&#xff0c;快速排序的速度比其他主要对手&#xff08;如归并排序&#xff09;快 2 ~ 3 倍。 快速排…

linux多线程编程使用互斥量的原理分析和应用实例

目录 概述 1 保护对共享变量的访问&#xff1a;互斥量 1.1 认识互斥量 1.2 互斥锁API 1.2.1 互斥锁初始化函数 1.2.2 互斥锁函数 1.2.3 互斥锁变体函数 1.3 互斥锁使用方法 1.4 互斥锁死锁 2 互斥量的应用介绍 2.1 创建与销毁 2.1.1 创建互斥量 2.1.2 销毁互斥量 …

React组件(函数式组件,类式组件)

函数式组件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>React Demo</title> <!-- 引…

【牛客】VL69 脉冲同步器(快到慢)

描述 sig_a 是 clka&#xff08;300M&#xff09;时钟域的一个单时钟脉冲信号&#xff08;高电平持续一个时钟clka周期&#xff09;&#xff0c;请设计脉冲同步电路&#xff0c;将sig_a信号同步到时钟域 clkb&#xff08;100M&#xff09;中&#xff0c;产生sig_b单时钟脉冲信号…

基于php的用户登录实现(v2版)(持续迭代)

目录 版本说明 数据库连接 登录页面&#xff1a;login.html 登录处理实现&#xff1a;login.php 用户欢迎页面&#xff1a;welcome.php 密码修改页面&#xff1a;change_password.html 修改执行&#xff1a;change_password.php 用户注册页面&#xff1a;register.html …

推荐算法中经典排序算法GBDT+LR

文章目录 逻辑回归模型逻辑回归对于特征处理的优势逻辑回归处理特征的步骤 GBDT算法GBDTLR算法GBDT LR简单代码实现 逻辑回归模型 逻辑回归&#xff08;LR,Logistic Regression&#xff09;是一种传统机器学习分类模型&#xff0c;也是一种比较重要的非线性回归模型&#xff0…

FPGA的时钟资源

目录 简介 Clock Region详解 MRCC和SRCC的区别 BUFGs 时钟资源总结 简介 7系列FPGA的时钟结构图&#xff1a; Clock Region&#xff1a;时钟区域&#xff0c;下图中有6个时钟区域&#xff0c;用不同的颜色加以区分出来 Clock Backbone&#xff1a;从名字也能看出来&#x…

【C语言】linux内核ip_local_out函数

一、讲解 这个函数 __ip_local_out 是 Linux 内核网络子系统中的函数&#xff0c;部分与本地出口的 IPv4 数据包发送相关。下面讲解这段代码的每一部分&#xff1a; 1. 函数声明 int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)&#xff1a; -…

Hive安装教程-Hadoop集成Hive

文章目录 前言一、安装准备1. 安装条件2. 安装jdk3. 安装MySQL4. 安装Hadoop 二、安装Hive1. 下载并解压Hive2. 设置环境变量3. 修改配置文件3. 创建hive数据库4. 下载MySQL驱动5. 初始化hive数据库6. 进入Hive命令行界面7. 设置允许远程访问 总结 前言 本文将介绍安装和配置H…