【数据结构】吃透单链表!!!(详细解析~)

前言:

上篇文章介绍了顺序表,这篇文章开始着重讲解链表了。
链表有很多种:单、双链表,循环、非循环链表还有带头、不带头的链表。本篇的主要内容是单链表(无头,单向,非循环)
链表对比顺序表有哪些不同之处,接下来会带大家一起了解~

一.顺序表的缺陷 && 介绍链表

1.顺序表的缺陷

1.头部和中间的插入删除效率都较低,时间复杂度为O(N)。需要挪动数据。
2.空间不够用了,增容需要申请新空间拷贝数据释放旧空间。会有不小的消耗。(尤其是异地扩容)
3.扩容会有一定的空间浪费。(例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间)

2.介绍链表

针对顺序表的缺陷,就有了链表这个数据结构

(1)链表的概念

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
特点:按需申请释放
在这里插入图片描述

顺序表是数组存储数据的,空间是连续的(可通过一个指针找到所有的值),通过size标记直到没有数据(前面的为size的个数即有效数据)。
链表的每个节点的大小没有关系,也不连续(多次malloc开辟出来的空间是随机的)。它是通过一个头指针(phead)先找到第一个节点,然后通过第一个节点的指针找到第二个节点,第二个节点的指针找到第三个节点,以此类推(通过指针链接)。每个位置的节点都有指针指向下一个,当下一个为空指针的时候,就结束。

(2)链表的结构

物理图:
在这里插入图片描述
逻辑图:
在这里插入图片描述
链表的节点组成(单链表):
在这里插入图片描述

注意:链表的最后一个节点的next指向空

看到这有些小伙伴可能有些疑惑,链表的每个节点是不连续的,为什么上面的两个图中每个节点都有线连接起来变成看似连续的呢?其实不是这样的,以上的两张图是为了方便理解。实际在内存中每个节点的地址是随机的,只不过用这个节点的指针(next)找到了下一个节点的地址,所以才能实现链接。

(3)链表的功能

链表的功能与顺序表类似,无非是增删查改,在某位置的插入与删除,对数据内容进行管理和操作。

二.单链表的实现

还是以多文件的形式分模块写

SList.h——函数和类型的声明
SList.c——函数的实现
Test.c——进行测试

1.创建节点的结构

单链表一个节点的结构:

存放数据:data
结构体指针:next

注意:不能这样写:

typedef int SListDataType;//方便更改存储的数据类型
typedef struct SListNode
{
    
    
	SListDataType data;
	SLTNode* next;
}SLTNode;//  <-重定义开始生效的位置

因为typedef重定义结构体类型的名称是在上面有箭头的一行开始生效,生效了才能使用,在前面就提前使用就会出现错误。

正确写法:

typedef int SListDataType;//方便更改存储的数据类型
typedef struct SListNode
{
    
    
	SListDataType data;
	struct SListNode* next;
}SLTNode;

2.头文件函数的声明

1.打印单链表
2.创建一个节点
3.尾插
4.头插
5.尾删
6.头删
7.查找(包含修改)
8.在pos位置前插入
9.在pos位置后插入
10.删除pos位置的节点
11.删除pos位置后一个的节点
12.清理单链表

//打印单链表
void SLTPrint(SLTNode* phead);
//创建一个节点
SLTNode* BuySLTNode(SListDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SListDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SListDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SListDataType x);
//在pos位置前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SListDataType x);
//在pos位置后插入
void SLTInsertAfter(SLTNode* pos, SListDataType x);
//删除pos位置的节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置后一个的节点
void SLTEraseAfter(SLTNode* pos);
//清理单链表
void SLTDestroy(SLTNode** pphead);

3.函数的实现

(1)打印单链表

创建一个结构体指针变量(cur),使它指向第一个节点(把头指针覆给cur)。利用循环如果cur不是空指针,就打印cur所指向的数据,然后cur往后走(到下一个节点)。直到cur为空跳出,最后打印NULL(最后一个节点为空指针)。

逻辑图:
在这里插入图片描述
物理图:
在这里插入图片描述
注意:与顺序表不同,顺序表传过来的指针一定不为空;链表传过来的指针可能为空,比如链表没有节点,头指针指向的就是NULL,所以不需要断言头指针。

所以在测试的文件里(Test.c)刚开始要让头指针指向NULL

SLTNode * plist = NULL;

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

(2)创建一个节点

为了方便后面的尾插、头插等操作,所以写个函数来创建一个新节点。
新节点的类型也是结构体指针,用malloc函数开辟一个新节点。如果新节点为空就报错。然后给新节点的data赋值,next为空,返回这个节点(方便其他的函数使用)

SLTNode* BuySLTNode(SListDataType x)
{
    
    
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

(3)尾插

尾插一个新节点,假设有多个节点,首先要找到尾,定义一个变量tail去遍历链表找到尾
注意:用tail遍历找尾再尾插时不能写成:

       SLTNode* tail = *pphead;
       while (tail)
		{
    
    
			tail = tail->next;
		}
		tail = newnode;		

这段代码看似没有什么问题,其实是与正确的代码差别很大。
tail刚开始指向第一个节点,如果不为空,到下一个节点;当tail为空时跳出循环,把newnode的值(新节点的地址)赋给tail。
如图:
在这里插入图片描述
这里有一个问题,tail里面存放的是新节点的地址,但是原来链表的最后一个节点的next指针并没有存放新节点的地址,也就是说最后一个节点没有与新节点连接起来,就没有尾插了。其次还有可能存在内存泄漏,新创建的节点丢了。

因为tail是局部变量,newnode和phead也是,它们出这个函数就销毁了,所以给tail这个变量赋新节点的地址没有用。

要成功完成尾插,就必须改变结构体的内容,让最后一个节点的next指针指向新节点的地址。

这里大家可能有些疑惑,既然tail销毁了,那么链表的这些节点会不会销毁呢?
答案是不会,因为这些节点是malloc出来的,malloc在堆上开辟的空间,只有自己主动free释放掉才能销毁。

正确的思路:
首先想到的是要改变结构体(节点)的内容,那么tail这个指针变量就不能到空结束,而是到最后一个节点结束(tail的next为空就结束,tail的位置指向最后一个节点)。

此时尾节点的next为空,我们要做的是让尾节点的next存放新节点的地址。让tail的next存放newnode的值(新节点的地址),就可以改变结构体的内容。
在这里插入图片描述

找尾尾插正确的一小段代码:

		SLTNode* tail = *pphead;
		while (tail->next)
		{
    
    
			tail = tail->next;
		}
		tail->next = newnode;

还有一种情况,如果刚开始链表没有节点,就不需要找尾了。直接将新节点的地址给头指针(plist)就行

但是这种情况要注意什么呢?
以下是错误示范:

	if (phead == NULL)
	{
    
    
		phead = newnode;
	}

这个代码的意思如图所示:
在这里插入图片描述
有两个问题:
一:plist没有改变,还是指向空指针;新节点丢了,可能造成内存泄漏。
二:newnode和phead是形参,形参是实参的拷贝,出这个函数就销毁了,改变phead并没有改变plist。

注意!!!:plist是一级指针,改变一级指针需要用到二级指针,并且有解引用操作。所以在函数的参数应该用二级指针来接收(传参时plist要有取地址符才能与二级指针类型对应)
在这里插入图片描述
正确的一小段代码:

	if (*pphead == NULL)
	{
    
    
		*pphead = newnode;
	}

总结:
1.改变结构体,要用结构体指针
2.改变结构体指针,要有结构体指针的指针(即二级指针)

最后一点:什么时候要断言指针
当一级指针(* pphead)为空时不需要断言,因为如果刚开始链表没有节点,* pphead所指向的就是空指针。二级指针pphead存放的是一级指针的地址,一级指针的地址不可能为空,所以二级指针需要断言。

void SLTPushBack(SLTNode** pphead, SListDataType x)
{
    
    
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	//原来没有节点,改变结构体指针,用二级指针
	if (*pphead == NULL)
	{
    
    
		*pphead = newnode;
	}
	//原来有节点,改变结构体,用结构体指针
	else
	{
    
    
		SLTNode* tail = *pphead;
		while (tail->next)
		{
    
    
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

(4)头插

头插也需要用到二级指针,因为每次头插头指针(plist)都要连接新的节点。(改变了头指针)
头插时原来链表没有节点与原来链表有节点的思路是一样的
在这里插入图片描述
新节点连接第一个节点或者空指针,然后plist连接新节点

注意:两者的顺序不能换,因为如果先让plist连接newnode,那么原来链表plist头指针后面的节点就找不到了。newnode再连接plist所指向的下一个节点就是自己,导致死循环。

void SLTPushFront(SLTNode** pphead, SListDataType x)
{
    
    
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

(5)尾删

前面的尾插、头插都有用到二级指针,那么尾删需不需要二级指针呢?接下来我们一点一点的分析:

尾删的大体思路是:找到尾,然后free释放掉尾节点就行。

但是链表有一个很重要的点:前后关联

这里我们定义一个指针变量tail去找尾,把尾节点删掉了,那么原来前一个节点变成新的尾节点,还需要用另一个变量当作原来尾节点的前一个节点,新的尾节点next指针就必须指向NULL只需要改变结构体),否则就访问野指针了。
有两种写法,这里只展现一种,就用tail一个指针变量,让它的下一个的下一个指针为空时停下(tail->next->next==NULL),此时tail->next就是最后一个节点,tail是前一个节点,修改新的尾节点的next,让tail->next为NULL(改变结构体)就行了。

以上只是包括一类情况:一个以上节点的时候是这样的
如果尾删把节点只删到剩下一个节点时,还是如此吗?

在这里插入图片描述
按前面的思路来走,遇到尾节点就把它的前一个节点的next置空。

依图分析,只有一个节点时,前一个节点就不是节点了,是头指针。要让头指针指向NULL,即改变头指针,就要用到二级指针了。
让 * pphead置空,就可以改变头指针

plist 等价于 * pphead

没有节点的情况:
断言 * pphead,为空就不能再删了

void SLTPopBack(SLTNode** pphead)
{
    
    
	assert(pphead);
	//空
	assert(*pphead);
	//一个节点
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
	}
	//一个以上的节点
	else
	{
    
    
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
    
    
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

(6)头删

通过前面的分析发现,有改变头指针所指向的内容就要用到二级指针,头删是把第一个节点除去,让头指针指向新的头节点。

画图分析:
在这里插入图片描述
当链表没有节点时不能再删了,所以要对 * pphead断言( * pphead等价于plist即第一个节点)

只有一个节点和有多个节点不需要分开处理,定义一个变量记录原来链表的第二个节点(新的头节点),free释放掉第一个节点,让头指针连接新的头节点

void SLTPopFront(SLTNode** pphead)
{
    
    
	assert(pphead);
	//空
	assert(*pphead);
	//非空
	SLTNode* newhead = (*pphead)->next;//注意优先级
	free(*pphead);//不需要置空,因为头指针直接连接新的头
	*pphead = newhead;
}

(7)查找

定义一个变量cur遍历链表,先判断cur所指向的数据是否等于x,如果相等,返回cur,否则往后走;找不到返回空指针。

SLTNode* SLTFind(SLTNode* phead, SListDataType x)
{
    
    
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
    
    
		if (cur->data == x)
		{
    
    
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

查找可以包含修改这个节点的数据

	SLTNode* pos1 = SLTFind(plist, 2);//测试查找+修改
	if (pos1 != NULL)
	{
    
    
		printf("找到了\n");
		pos1->data *= 100;
		SLTPrint(plist);
	}
	else
	{
    
    
		printf("找不到\n");
	}

(8)在pos位置前插入

要在pos位置前插入一个新节点,首先pos这个位置的节点必须存在,所以要断言pos(后面有pos位置插入删除的函数也要用到)
pos可能在任意一个位置,如果pos在第一个节点,就相当于头插了。头插要改变头指针所指向的内容,所以要用二级指针。直接调用头插的函数即可。

pos不在第一个节点的情况:
首先要定义一个变量prev,遍历链表找到并指向pos的前一个节点,因为插入新的节点必须前后连接起来(单链表的不足之处,后期文章用双向循环带头链表就非常简单)。
当prev->next != pos,往后走;==pos时跳出循环,让prev->next连接新节点,新节点的next连接pos,完成插入。
在这里插入图片描述

void SLTInsert(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
    
    
	assert(pphead);
	assert(pos);
	//pos在第一个节点就是头插
	if (pos == *pphead)
	{
    
    
		SLTPushFront(pphead, x);
	}
	else
	{
    
    
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
    
    
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

(9)在pos位置后插入

因为在pos位置后插入新的节点,所以可以不用头指针了,找到pos位置的下一个节点即可。可以定义一个变量posNext为pos位置的下一个节点,让新节点的next连接posNext,pos->next连接新节点
不需要考虑是不是尾插,因为在哪插入都是一样的
在这里插入图片描述

void SLTInsertAfter(SLTNode* pos, SListDataType x)
{
    
    
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* posNext = pos->next;
	newnode->next = posNext;
	pos->next = newnode;
}

(10)删除pos位置

删除pos位置的节点,必须把它的前一个节点与后一个节点连接起来,这里就要有头指针,去找pos位置的前一个节点。
我们要考虑一些情况,pos在第一个节点、中间某个节点和尾节点

当pos在第一个节点时,就是头删,要改变头指针指向的内容,所以要用二级指针,然后调用头删的函数即可

如果pos是在中间的某个节点或者尾节点呢?
其实两者的思路是一致的,把pos位置的节点删除,让前一个节点连接后一个节点就行(是尾节点的话,让前一个节点连接空指针)
在这里插入图片描述

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    
    
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
    
    
		SLTPopFront(pphead);
	}
	else
	{
    
    
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
    
    
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

(11)删除pos位置后的节点

要删除pos位置的后一个节点,除了pos这个位置要存在之外,pos位置的后一个节点也必须存在,所以pos->next要断言。假如pos是在尾节点,就没有意义了。

定义一个变量posNext为pos的下一个节点,然后使pos->next指向posNext->next,即把pos位置的节点与posNext的下一个节点连接起来,最后释放掉posNext

在这里插入图片描述

void SLTEraseAfter(SLTNode* pos)
{
    
    
	assert(pos);
	assert(pos->next);//检查是否为尾节点
	SLTNode* posNext = pos->next;
	pos->next = posNext->next;
	free(posNext);
	posNext = NULL;
}

(12)清理单链表

清理(销毁)链表,必须要一个一个节点清理,因为链表在物理结构上是不连续的。

定义一个变量cur遍历链表,每到一个节点把它释放掉。但是这里又有一个问题,当前节点被释放了,怎么到下一个节点呢?所以我们循环里再定义一个变量next为cur的下一个节点,释放完当前的cur,然后把next赋给cur,这样cur就能到下一个节点了。

最后全部节点释放完,头指针要指向空,这里又有改变头指针了,所以有二级指针。
在这里插入图片描述

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

三.全部代码

1.SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SListDataType;//方便更改存储的数据类型
typedef struct SListNode
{
    
    
	SListDataType data;
	struct SListNode* next;
}SLTNode;
//打印单链表
void SLTPrint(SLTNode* phead);
//创建一个节点
SLTNode* BuySLTNode(SListDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SListDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SListDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SListDataType x);
//在pos位置前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SListDataType x);
//在pos位置后插入
void SLTInsertAfter(SLTNode* pos, SListDataType x);
//删除pos位置的节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置后一个的节点
void SLTEraseAfter(SLTNode* pos);
//清理单链表
void SLTDestroy(SLTNode** pphead);

2.SList.c

#include "SList.h"
//打印
void SLTPrint(SLTNode* phead)
{
    
    
	SLTNode* cur = phead;
	while (cur)
	{
    
    
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
//创建一个节点
SLTNode* BuySLTNode(SListDataType x)
{
    
    
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SListDataType x)
{
    
    
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	//原来没有节点,改变结构体指针,用二级指针
	if (*pphead == NULL)
	{
    
    
		*pphead = newnode;
	}
	//原来有节点,改变结构体,用结构体指针
	else
	{
    
    
		SLTNode* tail = *pphead;
		while (tail->next)
		{
    
    
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
//头插
void SLTPushFront(SLTNode** pphead, SListDataType x)
{
    
    
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
    
    
	assert(pphead);
	//空
	assert(*pphead);
	//一个节点
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
	}
	//一个以上的节点
	else
	{
    
    
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
    
    
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
    
    
	assert(pphead);
	//空
	assert(*pphead);
	//非空
	SLTNode* newhead = (*pphead)->next;//注意优先级
	free(*pphead);//不需要置空,因为头指针直接连接新的头
	*pphead = newhead;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SListDataType x)
{
    
    
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
    
    
		if (cur->data == x)
		{
    
    
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos位置前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
    
    
	assert(pphead);
	assert(pos);
	//pos在第一个节点就是头插
	if (pos == *pphead)
	{
    
    
		SLTPushFront(pphead, x);
	}
	else
	{
    
    
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
    
    
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
//在pos位置后插入
void SLTInsertAfter(SLTNode* pos, SListDataType x)
{
    
    
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* posNext = pos->next;
	newnode->next = posNext;
	pos->next = newnode;
}
//删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    
    
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
    
    
		SLTPopFront(pphead);
	}
	else
	{
    
    
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
    
    
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//删除pos位置后的节点
void SLTEraseAfter(SLTNode* pos)
{
    
    
	assert(pos);
	assert(pos->next);//检查是否为尾节点
	SLTNode* posNext = pos->next;
	pos->next = posNext->next;
	free(posNext);
	posNext = NULL;
}
//清理
void SLTDestroy(SLTNode** pphead)
{
    
    
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
    
    
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

3.Test.c

#include "SList.h"
test()
{
    
    
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);//测试尾插
	SLTPrint(plist);

	SLTPushFront(&plist, 10);
	SLTPushFront(&plist, 20);
	SLTPushFront(&plist, 30);
	SLTPushFront(&plist, 40);//测试头插
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPopBack(&plist);
	SLTPopBack(&plist);//测试尾删
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPopFront(&plist);//测试头删
	SLTPrint(plist);

	SLTNode* pos1 = SLTFind(plist, 2);//测试查找+修改
	if (pos1 != NULL)
	{
    
    
		printf("找到了\n");
		pos1->data *= 100;
		SLTPrint(plist);
	}
	else
	{
    
    
		printf("找不到\n");
	}

	SLTNode* pos2 = SLTFind(plist, 10);//测试pos位置前插入
	if (pos2)
	{
    
    
		SLTInsert(&plist, pos2, 66);
		SLTPrint(plist);
	}

	SLTNode* pos3 = SLTFind(plist, 20);//测试pos位置后插入
	if (pos3)
	{
    
    
		SLTInsertAfter(pos3, 77);
		SLTPrint(plist);
	}

	SLTNode* pos4 = SLTFind(plist, 1);//测试删除pos位置
	if (pos4)
	{
    
    
		SLTErase(&plist, pos4);
		SLTPrint(plist);
	}

	SLTNode* pos5 = SLTFind(plist, 66);//测试删除pos位置的后一个节点
	if (pos5)
	{
    
    
		SLTEraseAfter(pos5);
		SLTPrint(plist);
	}

	SLTDestroy(&plist);
}
int main()
{
    
    
	test();
	return 0;
}

在这里插入图片描述
总算把最费劲的写完了,感谢铁子们的观看,期待大家的支持~

猜你喜欢

转载自blog.csdn.net/2301_77459845/article/details/132338090