重新开始学stm32 FreeRTOS系统(6)——双向循环链表、自定义列表

 接着上一期的内容,我们来简单地从双向循环链表的角度深入学习列表,尝试写自己的列表。

我们在这里不详细地去完全去讲双向循环链表的一些东西,因为链表的知识比较多,在双向循环链表之前还有双向链表、循环链表,所以我们是基于我们上一期列表的知识来认识双向循环链表,再根据双向循环链表的知识反馈回列表的学习。好了废话不多说,进入正题。 

双向循环链表

我们直接看下面的双向循环链表的结构示意图。诶!是不是似曾相识,甚至都可以说是一模一样了。没错,这不就是 “ 列表和列表项 ” 吗?只是在这里每个 “ 列表项 ” 称之为 “ 结点 ” ,这头节点没有数据,不就跟列表的 xListEnd 很像吗?对比之下,两者几乎是没有什么区别,所以我们完全可以用我们对列表和列表项的知识和理解来看待双向循环链表。

 好了,现在我们算是简单地认识了双向循环链表这个 “ 高端 ” 的玩意儿了。下面我们就来结合我们上一期学的列表和列表项的知识来写我们的列表吧。


自定义列表

结点(列表项)结构体定义

参考着官方的列表项的结构体的定义,我们先来写我们的结点的定义吧。为了方便和官方的列表项作区分,下面我们都称自定义的列表项为结点、自定义的列表为链表吧。

typedef int Data_Type;

struct ListNode
{
    Data_Type Data;						//数据
	struct ListNode* Previous;		    //指向前一个结点的指针
	struct ListNode* Next;				//指向后一个结点的指针
};
typedef struct ListNode List_Node;


/************************官方迷你列表项定义*********************/
struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
	configLIST_VOLATILE TickType_t xItemValue;
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
/**************************************************************/

因为我们不需要完整的列表项那么全的功能,所以直接以迷你列表项为模板,同时也把完整性检查省去了。


动态申请一个结点

结点的结构体我们定义好了,自然就到了创建结点的函数编写了。

这里动态申请一个结点的函数我们使用到了C语言动态申请内存的一个函数 malloc()。当然我们也可以直接像官方那样的直接定义创建的方法。

/********************************方法一************************************/
List_Node* BuyListNode(Data_Type data)
{
	List_Node* newNode = (List_Node*)malloc(sizeof(List_Node));
	if(newNode == NULL)
	{
		free(newNode);
		return NULL;
	}
	newNode->Previous = newNode->Next = NULL;
	newNode->Data = data;
	return newNode;
}

/*********************************方法二*************************************/
List_Node* BuyListNode(Data_Type data)
{
	List_Node newNode;
    List_Node* pnewNode = &newNode;
	if(pnewNode == NULL)
	{
		return NULL;
	}
	pnewNode->Previous = pnewNode->Next = NULL;
	pnewNode->Data = data;
	return pnewNode;
}

注意:这里如果使用方法一,就是使用 malloc() 函数和方法,我们在后面写删除结点的函数的时候要记得使用 free() 函数释放相应的内存。

因为新创建的结点是还没有插入链表的,而且这里我们动态创建结点的函数在创建的同时还为结点写入数据,所以函数后面要把两个指针设置成空指针 NULL 并向 data 赋相应的值。


头结点创建和初始化

这里我们的函数功能是对头节点的初始化和创建,其实头结点我们可以自己另外单独创建,这样比较灵活,但是通常头结点创建和初始化也是一起操作的,所以在初始化函数加上创建的部分也是没问题的。后面其他的函数也是这样,都是带有创建结点的功能,这样能省去我们很多创建结点的操作。

List_Node* ListInit(List_Node* plist)
{
    //List_Node* plist = BuyListNode(0);        //头结点创建
	plist->Previous = plist->Next = plist;      //初始化
	return plist;
}

初始化部分跟列表的初始化的思想的一模一样的,就是把头结点的前结点和后结点都设置成其自身,原因的什么我们在上一期讲的非常清楚了,这里就不再赘述。


末尾插入结点

这个函数的功能是创建一个结点并在指定链表的末尾插入该结点。

void ListPushBack(List_Node* plist, Data_Type num)
{
	List_Node* newNode = BuyListNode(num);
	newNode->Next = plist;
    newNode->Previous = plist->Previous;
	plist->Previous->Next = newNode;
	plist->Previous = newNode;
}


/*********************************官方末尾插入函数***********************************/
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList->pxIndex;

	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;

	mtCOVERAGE_TEST_DELAY();

	pxIndex->pxPrevious->pxNext = pxNewListItem;
	pxIndex->pxPrevious = pxNewListItem;

	pxNewListItem->pvContainer = ( void * ) pxList;

	( pxList->uxNumberOfItems )++;
}

这里我们在函数里先创建一个新的结点,然后插入操作就是跟官方的代码一样的思路。

这里可能会有小伙伴有疑问,就是 plist 不是链表的地址吗,那按照上一期列表和列表项的内容,为什么用链表的地址而不是头结点的地址呢,那是因为我们这个自定义的列表在创建头结点的时候就把头结点当作链表使用了,按前面列表和列表项的内容就是把 xListEnd 当作列表用了,或者说是把两种融合在一起了


末尾删除结点

这个函数的功能是删除链表末尾的一个结点。

void ListPopBack(List_Node* plist)
{
	List_Node* tail = plist->Previous;
	plist->Previous->Previous->Next = plist;
	plist->Previous = plist->Previous->Previous;

	free(tail);            //如果不是使用malloc()函数创建的就不用释放
	tail = NULL;
}

末尾删除结点函数其实是非常简单的,删除的思路也是像官方的删除方法一样,就是把要删除的结点的前后结点直接相连。但是有一个点不一样,需要注意,所以在这里我要说一下,就是 tail 这个东西。诶!没错,就是有人会问为什么要创建这样一个指针变量,其实这个 tail 就是用来记录要删除的结点的位置的,因为我们把它前后两结点相连只是形式上的删除了它,但是相应的地址还是存储着原理的数据,所以我们通过 tail 来对那个结点所在的内存块的数据清除。而官方的那个删除是没有对原来的内存块进行清除的。

根据末尾插入和删除,我们还可以举一反三写出表头的插入和删除函数


结点查找

其实结点查找就是在列表的遍历的基础上加上了把找到的结点返回出来的部分罢了。

List_Node* ListFind(List_Node* plist, Data_Type findnum)
{
	List_Node* cur = plist->Next;
	while (cur != plist)
	{
		if (cur->Data == findnum)
		{
			return cur;
		}
		cur = cur->Next;
	}
	return NULL;
}

思路非常简单,就是从头结点的后一个结点开始查找,一个一个往后比较,直到找到数据的值符合查找的结点,就返回那个结点的地址,找一圈找不到就返回空指针 NULL。

这个函数虽然简单,但是却非常重要。想想,有了这个查找结点的函数,我们是不是还可以对找到的结点进行操作,比如在这个结点前后插入结点、删除这个结点,甚至是调换两个结点的位置等,随便我们发挥。


链表合并函数

这个函数是把两个链表合并起来的存放进第三个链表的功能(链表1在前,链表2在后)。

void ListMerge(List_Node* pxList1, List_Node* pxList2, List_Node* pxList3)
{
	TickType_t data;
	List_Node* cur = pxList1->Next;
	while(cur != pxList1)
	{
		data = cur->Data;
		ListPushBack(pxList3,data);
	}
	cur = pxList2->Next;
	while(cur != pxList2->xListEnd)
	{
		data = cur->Data;
		ListPushBack(pxList3,data);
	}
}

这个的思路也很简单,就是使用遍历的方法先获取链表 1 按顺序的各个结点的 Data 的值并按这个值使用末尾插入函数创建并插入一个一样的结点插入到链表 3 即可,然后就是链表 2 也同链表 1 一样 ” 复制 ” 进去就完成了。(前提是要保证链表 3 原本是个空链表)。


自定义列表的其它的一些功能的函数就随着我们的使用需求可以一点一点地扩展,这就是自定义列表最大的优势。

好了,今天的自定义列表就先到这里,再见。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值