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

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

一.单链表

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.mfbz.cn/a/445078.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker容器的数据卷

1配置数据卷 docker run --namen01 -d --restartalways -p 80:80 -v /qy172/data/nginx/html:/usr/share/nginx/html nginx 2Docker应用部署 1搜索mysql镜像 docker search mysql 2拉取mysql镜像 docker pull mysql:5.6 3创建容器&#xff0c; 设置端口映射、目录映射 d…

Pycharm安装,环境初次配置与运行第一个简单程序

一、Pycharm安装 1.在PyCharm官网中&#xff0c;找到社区版下载链接&#xff0c;下载Pycharm社区版&#xff0c;社区版免费 2.下载成功后&#xff0c;双击下载好的安装包&#xff0c;点击下一步后&#xff0c;点击“浏览”更改安装路径到C盘以外其他硬盘&#xff0c;点击“下…

6 种 卷积神经网络压缩方法

文章目录 前言 1、低秩近似 2、剪枝与稀疏约束 3、参数量化 4、二值化网络 &#xff08;1&#xff09;二值网络的梯度下降 &#xff08;2&#xff09;两个问题 &#xff08;3&#xff09;二值连接算法改进 &#xff08;4&#xff09;二值网络设计注意事项 5、知识蒸馏 6、浅层 …

Pulsar 社区周报 | No.2024.03.08 Pulsar-Spark Connector 助力实时计算

关于 Apache Pulsar Apache Pulsar 是 Apache 软件基金会顶级项目&#xff0c;是下一代云原生分布式消息流平台&#xff0c;集消息、存储、轻量化函数式计算为一体&#xff0c;采用计算与存储分离架构设计&#xff0c;支持多租户、持久化存储、多机房跨区域数据复制&#xff0c…

SpringCloudGateway理论与实践

文章目录 网关介绍为什么需要网关Gateway 使用gateway pom依赖yml 配置重启测试总结 断言过滤器工厂路由过滤器的种类请求头过滤器默认过滤器全局过滤器总结 Gateway解决跨域 网关介绍 Spring Cloud Gateway 是一个基于Spring Framework 5&#xff0c;由Spring Cloud团队开发的…

定制repo(不再切换python和google源)

文章目录 定制repo&#xff08;不再切换python和google源&#xff09;前言各用各的repo定制repo2/repo3源码自动识别repo2/repo3项目完整解决方案&#xff1a; 定制repo&#xff08;不再切换python和google源&#xff09; 众知&#xff0c;Android/AOSP/ROM系统开发&#xff0c…

垃圾回收:JavaScript内存管理的利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【SpringCloud】微服务重点解析

微服务重点解析 1. Spring Cloud 组件有哪些&#xff1f; 2. 服务注册和发现是什么意思&#xff1f;Spring Cloud 如何实现服务注册和发现的&#xff1f; 如果写过微服务项目&#xff0c;可以说做过的哪个微服务项目&#xff0c;使用了哪个注册中心&#xff0c;常见的有 eurek…

vue实现购物车功能

实现功能 CSS部分 <style>.tr {display: flex;}.th {margin: 10px;width: 20%;height: 50%;}.td {display: flex;margin: 10px;width: 20%;height: 100px;align-items: center;}.app-container .banner-box {border-radius: 20px;overflow: hidden;margin-bottom: 10px;}…

docker-swarm集群搭建

目录 一、docker swarm介绍 二、部署docker 三、搭建集群 3.1 工作模式 3.2 将当前主机作为leader 3.3 将第二个节点slave1加入到worker 3.4 将第三个节点slave2也加入到worker 3.5 将第四个节点(slave3)加入到manager 四、总结 一、docker swarm介绍 Docker Swarm…

CSS顶部与JS后写:网页渲染的奥秘

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

动态规划(算法竞赛、蓝桥杯)--数位DP度的数量

1、B站视频链接&#xff1a;E38 数位DP 度的数量_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N34; int a[N];//把B进制数的每一位抠出存入数组 int f[N][N];//f[i][j]表示在i个位置上&#xff0c;放置j个1的组合数 int K,B;void init(…

11.Node.js入门

一.什么是 Node.js Node.js 是一个独立的 JavaScript 运行环境&#xff0c;能独立执行 JS 代码&#xff0c;因为这个特点&#xff0c;它可以用来编写服务器后端的应用程序 Node.js 作用除了编写后端应用程序&#xff0c;也可以对前端代码进行压缩&#xff0c;转译&#xff0c;…

Java 数据结构之链表

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA null || headB null) return null;ListNode pA headA, pB headB;while (pA ! pB) {pA pA null ? headB : pA.next;pB pB null ? headA : pB.next;}return pA;} public ListNode rev…

2024.3.6每日一题

LeetCode 找出数组中的 K -or 值 题目链接&#xff1a;2917. 找出数组中的 K-or 值 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 nums 中的 K-or 是一个满足以下条件的非负整数&#xff1a; 只有在 nums 中&…

【开源】SpringBoot框架开发教学资源共享平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课程资源模块2.4 课程作业模块2.5 课程评价模块 三、系统设计3.1 用例设计3.2 类图设计3.3 数据库设计3.3.1 课程档案表3.3.2 课程资源表3.3.3 课程作业表3.3.4 课程评价表 四、系统展…

遗传算法GA求解机器人栅格地图最短路径规划,可以自定义地图及起始点(提供MATLAB代码)

一、原理介绍 遗传算法是一种基于生物进化原理的优化算法&#xff0c;常用于求解复杂问题。在机器人栅格地图最短路径规划中&#xff0c;遗传算法可以用来寻找最优路径。 遗传算法的求解过程包括以下几个步骤&#xff1a; 1. 初始化种群&#xff1a;随机生成一组初始解&…

先进电机技术 —— 高速电机与低速电机

一、背景 高速电机是指转速远高于一般电机的电动机&#xff0c;通常其转速在每分钟几千转至上万转甚至几十万转以上。这类电机具有功率密度高、响应速度快、输出扭矩大等特点&#xff0c;在航空航天、精密仪器、机器人、电动汽车、高端装备制造等领域有着广泛的应用。 高速电…

【Pytorch】新手入门:基于sklearn实现鸢尾花数据集的加载

【Pytorch】新手入门&#xff1a;基于sklearn实现鸢尾花数据集的加载 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望…

学习和认知的四个阶段,以及学习方法分享

本文分享学习的四个不同的阶段&#xff0c;以及分享个人的一些学习方法。 一、学习认知的四个阶段 我们在学习的过程中&#xff0c;总会经历这几个阶段&#xff1a; 第一阶段&#xff1a;不知道自己不知道&#xff1b; 第二阶段&#xff1a;知道自己不知道&#xff1b; 第三…