一只脚踏入数据结构的大门,如何用C语言实现一个单链表(超超超详解,我的灵魂受到了升华)

目录

0.前言

1.什么是链表

1.1链表简介

 1.2链表的分类

1.3为什么要有链表(vs顺序表)

1.3.1顺序表的缺点

1.3.2 链表的优点

1.3.3 顺序表的优点是链表的缺点

1.4.为什么选择实现结构最简单的单链表

2* 什么是单链表(两种理解逻辑)

2.实现单链表

2.1 如何代表一个单链表

2.2* 单链表的尾插

2.3* 单链表的尾删

2.4 单链表的头插

2.5 单链表的头删

2.6 单链表的销毁

2.7 单链表的插入(Insert--在目标节点之前插入)

2.8* 单链表的删除

2.9 单链表的pos之后插入(更适合单链表)

2.10 单链表的pos之后删除(更适合单链表)

2.11单链表的查找

3. 单链表的测试


0.前言

本文所有代码都汇总至gitee网站当中:

3单链表实现 · onlookerzy123456qwq/data_structure_practice_primer - 码云 - 开源中国 (gitee.com)https://gitee.com/onlookerzy123456qwq/data_structure_practice_primer/tree/master/3%E5%8D%95%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0

1.什么是链表

1.1链表简介

我们首先想象火车,火车是由一节一节车厢链接起来的,每一个车厢作为一个节点,和周围旁边的两个车厢链接起来其实一个链表就是一辆火车

就像火车是以一节节车厢为基本单位一样,链表是以一个一个节点为基本单位的,火车相邻的两个车厢与车厢之间,是链接起来的,链表的两个相邻的节点之间也存在链接关系。如下图我们看最简单的一个单链表结构:

 1.2链表的分类

链表是以一个一个节点为基本单位的,相邻节点之间有链接关系,而链接的方式分为两种,一个是单向链接,一个是双向链接,前者是只有前一个节点链接到后一个节点,而没有后一个节点却没有链接到前一个节点,只能往后找,不能往前找;而后者是相邻的两个节点,都是相互链接指向的,可以后节点往前找,也可以前节点往后找。 

同时链表一开始可以就固定有一个节点,我们称之为哨兵位的头结点以后插入的节点都在这个哨兵头结点的后面。而一开始定义一个链表也可以没有这个头节点,即从一开始链表是NULL,没有任何节点,这个我们称之为不带头哨兵位头结点的有无,可以分为带头/不带头。

首节点和尾节点之间是否存在链接关系,这也是很重要的一个点。如果首节点和为尾节点之间不存在链接关系,则是非循环链表;如果首节点和尾节点之间存在链接关系,则是循环链表。

根据单向链接vs双向链接 和 有头节点vs无头结点 和 循环vs非循环,这三对矛盾,我们就可以对链表做如下的分类

1.3为什么要有链表(vs顺序表)

为什么要有链表结构,其实链表是根据顺序表的缺点设计出来的:

1.3.1顺序表的缺点

顺序表的缺点是由其基本性质决定的,首先顺序表,即开辟的数组的空间必须是连续的;同时顺序表中的数据必须是在物理地址上是连续存储的,不能有间隔和跳跃

第一个缺点:因为所开辟的空间必须是连续的空间,所以有时就会非常影响扩容的效率。在扩容realloc的时候,我们必须开辟出一段连续的空间,而随着顺序表数组的增大,要求在内存中开辟的连续的空间就会越来越大,我们知道realloc的原理,存在原地扩容和异地扩容两种情况,如果在原空间的后面还有足够的空间够原空间扩容两倍,那就可以直接原地扩容,这样的成本是很低的;而如果原来的空间后面已经有空间是被征用的了,我们realloc要开辟的连续空间就需要异地扩容,即首先会在堆区当中寻找新的空间找到足够大的两倍连续空间之后,会花费O(N)的时间先把原来空间的数据拷贝到这块新空间上去,然后使用再free销毁掉原来的空间,这个异地扩容的成本很高的。

void CheckSLCapacity(SeqList* ps)
{
	if (ps->_size == ps->_capacity)
	{
		int next_cap = (ps->_capacity == 0) ? 4 : ps->_capacity * 2;
		SLDataType* ptmp = (SLDataType*)realloc(ps->_a, sizeof(SeqList) * next_cap);
		if (ptmp == NULL)
		{
			//申请空间失败
			perror("realloc");
			exit(1);
		}
		ps->_a = ptmp;
		ps->_capacity = next_cap;
	}
}

 为了解决频繁扩容而导致的扩容效率问题,我们通常是采用一次扩容两倍空间。可是这样扩容两倍就会产生一个浪费空间的问题,比如我们想插入101个数据,而现在的数组空间是100的容量,所以只能是扩容到200,这样就会致使99个空间浪费,所以说扩容是存在空间浪费问题的。

不只是开辟连续的空间,顺序表要求存储的数据也必须是连续的,数据必须没有间隔和跳跃,所以这就导致顺序表在头插和头删的时候效率是极低的,必须涉及所有元素的挪动,这就达到了O(N)的时间复杂度。

总结一下顺序表的缺点:

1.空间不够了需要增容,增容需要付出代价。

2.为避免频繁扩容,一次一搬是按照倍数去扩容(2倍),可能存在一定空间浪费。

3.头部或者中间位置的插入/删除,需要挪动数据,挪动数据也是有消耗的。

1.3.2 链表的优点

针对顺序表的三个缺点,我们设计出链表。首先链表是以节点为基本单位的,各节点之间在堆区的任意位置存储,并不需要开辟连续的空间,所以扩容(异地扩容概率极小)消耗小。而且链表是以按需申请空间,不用了就可以释放空间,更加合理的使用了空间。同时按需申请和释放的特点可以使得链表存在空间浪费的情况给多少数据就存多少数据。在头部或中间插入/删除的时候,不需要挪动数据,就可以提高效率

总结一下链表的优点

1.按需申请空间,不用了就释放空间(更合理的使用了空间)。

2.不存在空间浪费。

3.头部/中间,插入/删除数据,不需要挪动数据。

1.3.3 顺序表的优点是链表的缺点

顺序表是数组,数组的每一个元素都是 有下标的,可以直接*(a+i),通过下标进行访问,直接找到数据实体,所以说顺序表是支持随机访问的,而链表只能通过链接关系一个一个的往前/往后找,没有下标,并不支持随机访问。而有些算法,需要存储结构支持随机访问,比如:二分查找,比如:优化的快排等。

1.4.为什么选择实现结构最简单的单链表

我们选择实现的是结构最简单的,不带头节点的,单向的单链表。单链表的缺陷还是很多的,单纯单链表的增删查改的意义并不大。那为什么我们还要学习并实现这种结构呢?

1. 很多OJ题考查并使用的都是单链表

2. 单链表更多的是去作为更复杂数据结构的子结构,例如哈希表中的哈希桶,图中的邻接表等。

因为以上两个点我们需要学习结构最简单的单链表结构。

补充一点,实际上,双向带头链表,才是更有意义的数据结构,我们在实战当中想使用链表这种数据结构的话,使用最多的其实是双向带头链表list。

2* 什么是单链表(两种理解逻辑)

我们首先形象的理解这个链表其实就是一个一个的节点,通过一个个箭头链接起来,每一个前面的节点都通过箭头可以链接找到后面的一个节点。下面这个我们理解的图叫做逻辑结构图。

 那具体是如何让前面一个节点链接找到后面一个节点呢?

让每一个节点存储下一个节点的地址,即每个节点都存储着指向下一个节点的指针,就可以通过这个指针指向找到下一个节点实体即可达到这种链接的效果。下面这个理解的图叫做物理逻辑图。

所以我们大致就可以定义出单链表的节点的基本结构,struct SListNode一个节点里面存储着这个节点存储的数据data,然后还存储下一个节点的地址即指向下一个节点的指针

所以我们理解一下下面这段代码:

typedef struct SListNode
{
	SLDataType _data;
	struct SListNode* _next;
}SListNode;
void SListPrint(SListNode* phead)
{
    SListNode* cur = phead;
	//如果phead==NULL,空链表不打印
	while (cur)
	{
		printf("%d->", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}

传入了一个phead,也就是一个单链表的头节点的指针,我们要打印这个单链表中的所有数据,所以就需要我们遍历每一个节点,找到每一个节点的存储的数据data进行打印。

我们定义一个指针cur指向第一个节点head,然后让cur一直往后走,遍历每一个节点。第一句printf("%d->", cur->_data);好理解,我们知道cur是指向一个节点的指针,然后cur->_data就是解引用找到这个指向节点里存储的数据_data。

cur=cur->_next;这一句代码,我们知道cur指向的就是一个节点,cur指针可以访问现在指向的节点的数据成员。而我们知道节点里面存储着下一个节点的地址/指向下一个节点的指针。所以cur->_next获取到的就是下一个节点的指针。cur = cur->_next;就是让cur这个指针更新指向下一个节点的指针

所以cur=cur->_next我们从逻辑结构图上理解就是,通过节点之间的链接cur指向到了下一个节点。从物理结构图上理解,就是cur的地址值不断更新更新成这节点存储着的下一个节点的地址。

2.实现单链表

2.1 如何代表一个单链表

我们之前封装一个结构体struct SeqList来表示一个顺序表,我们用一个struct SingleList来表示一个单链表也是没有问题的,可是没有必要

表示一个顺序表,我们需要描述这个顺序表的数组实体,大小以及容量,这是需要用一个struct结构体封装在一起的,才能够代表描述一个顺序表。可是一个普通的单链表,只需知道首元素即第一个节点的指针,就可以代表描述一个单链表。因为我们只要知道这个首节点的指针,就可以往后通过链接关系,找到后面的所有的节点。

//单链表的定义
/*typedef struct SingleList
{
	SListNode* plist;
}SingleList;*/

/*我们并不需要单独创建一个结构体来描述单链表, 因为
单链表只需要一个成员变量,也就是首元素地址,即第一个节点的指针,即可代表表示一个单链表*/
/*SListNode* plist1 = NULL; SListNode* plist2 = NULL;*/
//定义一个单链表
SListNode* plist = NULL;

2.2* 单链表的尾插

我们要知道一件事,我们在main函数中,是使用一个首节点的指针来代表整个单链表,如果我们调用一个函数接口,对这个单链表实体进行操作,是有可能改变这个单链表首节点的地址的。例如我们的尾插,很多时候都是修改plist后面的节点,并不需要改变首节点的指针值,可是如果一开始单链表是一个空链表,即plist==NULL的时候,在尾插之后,那plist值一定必须要修改成新插入节点的地址。比如下面这个操作,我们如果是这样传参实现单链表的尾插的话:

void SListPushBack(SListNode* phead, SLDataType x)
{
    SListNode* newnode=(SListNode*)malloc(sizeof(SListNode));
    newnode->_data = x;
    newnode->_next = NULL;
    if(phead == NULL)
    {
        phead=newnode;
    }
    else{
        SListNode* cur = phead;
        while(cur->_next)
        {
            //找尾
		    SListNode* tail = *pphead;
		    while (tail->_next)
		    {
			    tail = tail->_next;
		    }
		    //尾插链接
		    tail->_next = newnode;
		    newnode->_next = NULL;
        }
    }
}
int main()
{
    //定义一个单链表(单链表首节点的地址)
    SListNode* plist = NULL;
    //给单链表plist插入数据为6的节点
    SListPushBack(plist,6);
}

那就会造成一个严重的后果:

传入的phead是plist的拷贝,所以我们要改变phead的时候,并不能改变到plist实体的值! 

所以我们必须让该链表(首节点指针plist)在调用接口的时候,必须要修改的是这个单链表的代表----首节点的指针plist实体。做到这一点我们就要传入plist指针的地址,即指向这个指针变量plist指针,才能找到plist实体!即我们要传入二级指针

PS:plist作为指针指向的一个个的节点实体,我们通过一级指针可以找到每一个节点实体,从而修改每一个节点的值。我们可以通过二级指针pphead指向的是一级指针实体plist,从而通过二级指针修改plist实体的值。

由此我们也得出一个结论:只要对单链表进行操作的接口有可能修改传入的plist,即这个单链表的代表,也即这个首节点的指针实体,就需要传入二级指针。

SListNode* BuySListNode(SLDataType x)
{
	SListNode* ptmp = (SListNode*)malloc(sizeof(SListNode));
	//检查申请
	if (ptmp == NULL)
	{
		perror("SListNode malloc");
		exit(1);
	}
	//初始化节点
	ptmp->_next = NULL;
	ptmp->_data = x;
	return ptmp;
}

void SListPushBack(SListNode** pphead, SLDataType x)
{
	//创建新节点
	SListNode* newnode = BuySListNode(x);

	//如果链表为空,需要修改单链表的代表实体,首节点的指针*pphead
	if (*pphead == NULL)
	{
		//现在首节点成为了这个第一个创建的节点
		*pphead = newnode;
	}
	else //链表不为空
	{
		//找尾
		SListNode* tail = *pphead;
		while (tail->_next)
		{
			tail = tail->_next;
		}
		//尾插链接
		tail->_next = newnode;
		newnode->_next = NULL;
	}
}

任何插入,只要是有效的插入,都要创建节点,所以我们对创建节点的接口做了封装。

然后尾插大多数情况都是要找到最后一个节点,然后在最后一个节点后链接上新创建的节点;但是有一种情况是链表压根就没有节点,即空链表的情况,这时候就需要我们直接对首节点指针实体进行修改,赋值为新创建节点的指针值

2.3* 单链表的尾删

尾部删除也有可能改变plist实体,即在只有一个节点的单链表的情况下,我们尾删,就会使得链表变成空链表,所以我们必须改变外部的plist实体为NULL,所以一定要传入链表首节点指针plist的二级指针!

然后我们需要考虑各种情况,这是我们写代码之前所要考虑的,要做尾删,那我们能删除空链表吗?显然不能,这是一个应该额外考虑的情况。如果链表当中只有一个节点,那删除之后,链表为空,这个情况我们需要直接修改plist实体为NULL。如果链表中有多个节点,我们需要在删除一个节点之后,对删除的尾部节点的前一个节点,需要把前一个节点的_next置空,这又是另一种情况。

void SListPopBack(SListNode** pphead)
{
	//须有元素才可以进行删除
	assert(*pphead);
	//找尾
	SListNode* prv = NULL;
	SListNode* tail = *pphead;
	while (tail->_next)
	{
		prv = tail;
		tail = tail->_next;
	}
	//删除尾节点
	//需要尾节点前面的节点_next置空
	if (prv == NULL)
	{
		//没有尾前节点,即该单链表只有一个节点 *pphead==tail
		free(tail);
		*pphead = NULL;
	}
	else //tail前面有节点
	{
		free(tail);
		prv->_next = NULL;
	}
}

2.4 单链表的头插

头插一定会改变首节点的地址,因为现在头结点的地址会变成新节点的地址,所以在实现单链表的头插接口时,我们一定要传入首节点的指针plist的地址,即plist的指针,以此来修改plist实体值。

同时我们继续考虑情况,头插需要把plist变成新节点的指针,然后把新节点的指针连接到原来的首节点上。这样无论是对于空链表和有节点的链表实现方法都是一样的,所以不用区分别的情况。

void SListPushFront(SListNode** pphead, SLDataType x)
{
	//创建新节点
	SListNode* newnode = BuySListNode(x);
	//头插一定会改变首节点为新节点
	//新节点链接到原来的首节点
	newnode->_next = *pphead;
	*pphead = newnode;
}

2.5 单链表的头删

头删也一定会改变单链表首节点的指针,会变成当前节点的下一个节点的指针,所以我们必须要传入首节点指针plist的地址,即二级指针。

可是我们要考虑另一种情况,那就是当链表为空的时候,空链表是不能进行删除的,所以我们要禁止这种情况。

void SListPopFront(SListNode** pphead)
{
	//必须保证链表不为空才可以删除
	assert(*pphead);
	//头删一定会改变plist首节点指针实体,为第二个节点/空
	SListNode* head_next = (*pphead)->_next;
	free(*pphead);
	*pphead = head_next;
}

2.6 单链表的销毁

我们知道所有的空间都是在堆区上开辟的,所以在链表结束使用或程序退出的时候,我们需要销毁free掉每一个节点的空间。这时候我们要注意,我们还是要传入链表首元素地址plist的指针的,因为我们需要对plist实体进行置空,以防止野指针的出现。

然后单链表的销毁我们应该一个一个节点的删除,直至删除到空NULL销毁结束

void SListDestroy(SListNode** pphead)
{
	//在一个一个释放所有节点之后,需要对plist链表指针实体置空
	//传入二级指针
	
	SListNode* cur = *pphead;
	//对于空链表,就没有free操作
	while (cur)
	{
		SListNode* cur_next = cur->_next;
		free(cur);
		cur = cur_next;
	}
	*pphead = NULL;
}

2.7 单链表的插入(Insert--在目标节点之前插入)

我们在给定的pos位置,之前插入新节点,我们还是需要改变首节点的指针plist实体,所以还是要传二级指针:这是因为如果pos就是首节点的话,我们此时就要做头插,而头插就必须改变plist,所以我们遇到头插的情况就要改变plist实体。而如果非头结点之前插入,那我们就让前一个节点的_next指向这个新节点,让新节点指向这个插入的pos位置的节点即可。

同时我们知道,如果传入pos是NULL的时候,我们是不允许这种情况出现的,因为我们不能在NULL之前插入!然后Insert在pos节点之前插入,这个Insert接口的特性(在Pos位置之前插入)实际上就限制了Insert接口不能进行尾插最后一个节点之后没有节点了。同时Insert不能对空链表进行插入,因为对空链表的插入需要一个pos作为参数,而空链表一个节点都没有。

void SListInsert(SListNode** pphead, SListNode* pos, SLDataType x)
{
	//在pos位置之前插入新节点,pos为空,找不到该位置之前进行插入
	assert(pos);
	//创建新节点,把pos位置前一个节点链接到新节点,新节点链接到pos
	SListNode* newnode = BuySListNode(x);
	//可pos位置之前不一定有节点,即pos可能是首节点
	//同时Insert接口不存在对空链表插入的情况,因为我们不能对空位置之前的节点插入
	SListNode* prv = NULL;
	SListNode* cur = *pphead;
	//找pos以及pos前节点
	while (cur!=pos)
	{
		prv = cur;
		cur = cur->_next;
	}
	//不合法的pos位置
	if (cur == NULL)
	{
		printf("Illegal pos Insert\n");
		exit(2);
	}
	//prv->newnode->pos/cur
	if (prv == NULL)
	{
		//在首节点之前插入
		//新创建节点变成新头节点
		newnode->_next = *pphead;
		*pphead = newnode;
	}
	else //前面有一个节点
	{
		prv->_next = newnode;
		newnode->_next = cur;
	}
}

2.8* 单链表的删除

删除pos位置的这个节点。这个情况就复杂的多。

情况一:如果这个链表是空链表,那就不允许删除,我们需要禁止这种情况。

情况二:传入的pos位置不属于这个单链表或者就是NULL空位置,属于非法删除。

情况三:把pos这个节点删除,就需要把pos之前的节点链接到pos之后的那个节点链接起来。可是pos之前一定有节点吗?如果pos这个节点是首节点的话就没有pos_prv这个节点,即pos是首节点的话,那就不能有这个链接环节了,这个其实就是情况四。本情况三,pos之前之后都有一个节点。

情况四:删除的这个节点是首节点,所以需要改变首节点的指针实体plist,使之变成首节点的next下一个节点的地址,所以这种情况就决定了我们必须传入二级指针,即首节点指针plist的指针。

void SListErase(SListNode** pphead, SListNode* pos)
{
	//必须有元素才可以进行删除
	assert(*pphead);
	//删除的pos位置一定不为空且合法
	assert(pos);

	SListNode* prv = NULL;
	SListNode* cur = *pphead;
	
	//寻找pos位置
	while (cur != pos)
	{
		prv = cur;
		cur = cur->_next;
	}

	//不存在合法的pos位置
	if (cur == NULL)
	{
		printf("Illegal pos Erase\n");
		exit(3);
	}
	else
	{
		//删除的pos是头结点
		if (prv == NULL)
		{
			*pphead = cur->_next;
			free(cur);
		}
		else //pos不是头结点
		{
			SListNode* cur_next = cur->_next;
			free(cur);
			prv->_next = cur_next;
		}
	}
}

2.9 单链表的pos之后插入(更适合单链表)

适合单链表的插入,并不是在pos位置之前插入Insert,而是在pos位置之后插入InsertAfter。

因为我们Insert,需要付出O(N)的时间复杂度,因为在pos之前插入,需要遍历单链表找到pos的前一个节点pos_prv,使得pos_prv节点指向新创建的节点。而InsertAfter就可以直接在给定的pos位置的后面插入一个位置,同时pos_next节点也可以直接由pos往后找到,所以就只需要O(1)的时间复杂度就可以实现了。

不仅如此,我们InsertAfter接口还不需要传入二级指针:因为在pos位置之后插入,压根不存在修改头结点的机会,因为我们是在pos之后插入,不存在头插的情况,同时,也不存在对空链表的插入,因为空链表没有pos位置节点进行传入。所以也就没有对plist首节点指针修改的情况出现。

//在pos之后插入不可能改变首节点,所以不需要传入pphead,只需要传入插入位置pos
void SListInsertAfter(SListNode* pos, SLDataType x)
{
	//要插入的位置pos(后)必须是有效的,不可为空
	assert(pos);
	//创建新节点
	SListNode* newnode = BuySListNode(x);
	SListNode* pos_next = pos->_next;
	pos->_next = newnode;
	newnode->_next = pos_next;
}

2.10 单链表的pos之后删除(更适合单链表)

pos位置之后删除位置,比删除pos位置的节点,更加适合单链表!

因为删除pos位置的单链表,需要遍历单链表处理pos位置之前和pos位置之后的节点的链接关系,这就需要付出O(N)的时间复杂度。而且删除pos位置有可能改变首节点的指针实体plist,所以就需要传入二级指针

EraseAfter就相对简单了,因为我们不需要遍历单链表了,就可以直接在pos位置之后进行插入,因为此时我们通过pos直接找到下一个要删除的节点,以及找到pos的下下个节点,不用遍历链表就你可以处理链接关系了,时间复杂度只需要O(1)。同时EraseAfter也杜绝了头删的可能,所以从这个情况下说,EraseAfter不可能改变头节点的指针,所以也就没有必要传入二级指针了。

//删除pos后面的的节点,也不会改变单链表的首节点指针实体
void SListEraseAfter(SListNode* pos)
{
	//非法情况删除(NULL位置之后不存在位置NULL->_next)
	assert(pos);
	//非法情况删除(pos之后为空NULL,不可删除)
	assert(pos->_next);

	SListNode* pos_erase = pos->_next;
	SListNode* erase_next = pos_erase->_next;
	
	free(pos_erase);
	pos->_next = erase_next;
}

2.11单链表的查找

对于单链表的查找,我们只是需要遍历寻找一个对应的节点地址,所以并不需要修改头节点的指针plist实体,所以就没有必要传入二级指针只需要传入一级指针,即外部首节点指针的拷贝即可。

SListNode* SListFind(SListNode* phead, SLDataType x)
{
	SListNode* cur = phead;
	while (cur)
	{
		if (cur->_data == x)
		{
			return cur;
		}
		cur = cur->_next;
	}
	return NULL;
}

3. 单链表的测试

我们对每一个接口进行单元测试,就可以对点找错,找到问题所在。同时也要写一个接口就测试一个接口,所以我们分模块Test1,Test2.....分函数对不同的函数接口进行分块测试。 

#include"singlelist.h"
void TestSList1()
{
	//定义一个单链表
	SListNode* plist = NULL;
	//测试尾插,尾删
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 0);
	SListPushBack(&plist, 0);
	SListPushBack(&plist, 8);
	SListPushBack(&plist, 6);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListDestroy(&plist);
}
void TestSList2()
{
	SListNode* plist = NULL;
	//测试头插,头删
	SListPushFront(&plist, 6);
	SListPushFront(&plist, 8);
	SListPushFront(&plist, 0);
	SListPushFront(&plist, 0);
	SListPushFront(&plist, 1);
	SListPopFront(&plist);
	SListPrint(plist);
	SListDestroy(&plist);
}
void TestSList3()
{
	//测试在pos位置之前插入Insert测试
	SListNode* plist = NULL;

	SListPushFront(&plist, 8);
	SListPushFront(&plist, 0);
	SListInsert(&plist, plist , 1);
	SListInsert(&plist, plist->_next, 7);

	SListPrint(plist);
	SListDestroy(&plist);
}
void TestSList4()
{
	//测试在pos位置进行删除Erase测试
	SListNode* plist = NULL;

	SListPushBack(&plist, 1);
	SListPushBack(&plist, 0);
	SListPushBack(&plist, 0);
	SListPushBack(&plist, 8);
	SListPushBack(&plist, 6);
	SListErase(&plist,plist->_next);
	SListErase(&plist, plist);
	SListPrint(plist);
	SListDestroy(&plist);
}
void TestSList5()
{
	//测试在pos之后插入以及在pos之后删除
	SListNode* plist = NULL;

	SListPushBack(&plist, 1);
	SListPushBack(&plist, 0);
	SListPushBack(&plist, 0);
	SListPushBack(&plist, 8);
	SListPushBack(&plist, 6);
	SListInsertAfter(plist, 999);
	SListInsertAfter(plist->_next->_next->_next, 66);
	//SListEraseAfter(plist->_next->_next->_next);
	SListPrint(plist);
	SListDestroy(&plist);
}
int main()
{
	//TestSList1();
	//TestSList2();
	//TestSList3();
	//TestSList4();
	TestSList5();
	return 0;
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值