FreeRTOS(二):数据结构—列表与列表项

4 列表与列表项

链表:列表(list)
一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

节点:列表项(list item)
表示某个网络中的一个连接点。

一个列表(list)下面可能有很多个列表项(list item),每个列表项都有一个指针指向列表。
在这里插入图片描述

4.1链表

单向链表与双向链表

4.1.1单向链表

链表:链表中共有 n 个节点,前一个节点都有一个箭头指向后一个节点。

节点:单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。在这里插入图片描述

/* 线性表的单链表存储结构 */
typedef struct LNode
{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;

野火的结构体定义:除了 struct node *next 这个节点指针之外,剩下的成员都可以理解为节点携带的数据,但是这种方法很少用。通常的做法是节点里面只包含一个用于指向下一个节点的指针。要通过链表存储的数据内嵌一个节点即可,这些要存储的数据通过这个内嵌的节点即可挂接到链表中。

/* 节点定义 */
struct node
{
  struct node *next; /* 指向链表的下一个节点 */
}
struct userstruct
{
  /* 在结构体中,内嵌一个节点指针,通过这个节点将数据挂接到链表 */
struct node *next;
  /* 各种各样......,要存储的数据 */
}

在这里插入图片描述
链表的操作:
插入、删除
插入:在这里插入图片描述
删除:
在这里插入图片描述

4.1.2双向链表

双向链表与单向链表的区别:节点中有两个节点指针,分别指向前后两个节点,其他完全一样。在这里插入图片描述

4.1.3链表与数组的对比

链表优点:
1、插入删除速度快
2、内存利用率高,不会浪费内存
3、大小没有固定,拓展很灵活。

链表缺点:
1、不能随机查找,必须从第一个开始遍历,查找效率低

数组优点:
1、随机访问性强
2、查找速度快

数组缺点:
1、插入和删除效率低
2、可能浪费内存
3、内存空间要求高,必须有足够的连续内存空间。
4、数组大小固定,不能动态拓展

数组的每个成员对应链表的节点,成员和节点的数据类型可以是标准的 C 类型或者是用户自定义的结构体。数组有起始地址和结束地址,而链表是一个圈,没有头和尾之分,但是为了方便节点的插入和删除操作会人为的规定一个根节点。
在这里插入图片描述

4.2FreeRTOS中链表的实现

FreeRTOS 中与链表相关的操作均在 list.h 和 list.c 这两个文件中实现

4.2.1实现链表节点

1.定义链表节点数据结构

                 /*链表节点数据结构定义*/
struct xLIST_ITEM
{
  TickType_t xItemValue;             /* 辅助值,用于帮助节点做顺序排列 */
  struct xLIST_ITEM * pxNext;        /* 指向链表下一个节点 */
  struct xLIST_ITEM * pxPrevious;    /* 指向链表前一个节点 */
  void * pvOwner;                    /* 指向拥有该节点的内核对象,通常是 TCB */
  void * pvContainer;                /* 指向该节点所在的链表 */
};
typedefstruct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */

            /*链表精简节点结构体定义*/
struct xMINI_LIST_ITEM
2 {
3 TickType_t xItemValue;                        /* 辅助值,用于帮助节点做升序排
列 */
4 struct xLIST_ITEM * pxNext;                   /* 指向链表下一个节点 */
5 struct xLIST_ITEM * pxPrevious;               /* 指向链表前一个节点 */
6 };
7 typedefstruct xMINI_LIST_ITEM MiniListItem_t; /* 精简节点数据类型重定义 */

在这里插入图片描述

2.链表节点初始化

链表节点 ListItem_t 总共有 5 个成员,但是初始化的时候只需将 pvContainer 初始化为空即可,表示该节点还没有插入到任何链表。

                  /*链表节点初始化*/
void vListInitialiseItem( ListItem_t * const pxItem )
{
 /* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
 pxItem->pvContainer = NULL;(1)
}

4.2.2实现链表根节点

1.定义链表根节点数据结构

                 /*链表根节点数据结构定义*/
typedefstruct xLIST
2 {
3 UBaseType_t uxNumberOfItems;     /* 链表节点计数器 */(1)
4 ListItem_t * pxIndex;            /* 链表节点索引指针 */(2)
5 MiniListItem_t xListEnd;         /* 链表最后一个节点 */(3)
6 } List_t;

(1):链表节点计数器,用于表示该链表下有多少个节点,根节点除外。
(2):链表节点索引指针,用于遍历节点。
(3):链表最后一个节点。我们知道,链表是首尾相连的,是一个圈。链表的最后一个节点,实际也就是链表的第一个节点。
在这里插入图片描述

2.链表根节点初始化

                  /*链表节点初始化*/
void vListInitialise( List_t * const pxList )
{
 /* 将链表索引指针指向最后一个节点 */(1)
 pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
 /* 将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */(2)
 pxList->xListEnd.xItemValue = portMAX_DELAY;
 /* 将最后一个节点的 pxNext 和 pxPrevious 指针均指向节点自身,表示链表为空 */(3)
 pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
 pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
 /* 初始化链表节点计数器的值为 0,表示链表为空 */(4)
 pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}

(1):将链表索引指针指向最后一个节点,即第一个节点,或者第零个节点更准确,因为这个节点不会算入节点计数器的值。
(2):将链表最后(也可以理解为第一)一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点(也可以理解为第一)。
(3):将最后一个节点(也可以理解为第一)的 pxNext 和 pxPrevious 指针均指向节点自身,表示链表为空。
(4):初始化链表节点计数器的值为 0,表示链表为空。
在这里插入图片描述

3.将节点插入链表的尾部

将节点插入到链表的尾部(可以理解为头部)就是将一个新的节点插入到一个空的链表。

                  /*将节点插入到链表的尾部*/
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
   //取列表项索引指针,此时该指针指向最后一个节点
	ListItem_t * const pxIndex = pxList->pxIndex;
	//新插入的列表项前驱指针指向最后一个节点
	pxNewListItem->pxNext = pxIndex;//新插入的列表项的后继指针也指向最后一个节点
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;//列表的最后一个节点的后继指针指向新插入的列表项
	pxNewListItem->pxPrevious->pxNext = pxNewListItem;//列表的最后一个节点的前驱指针也指向新插入的列表项
	pxIndex->pxPrevious = pxNewListItem;//新插入的列表项是输入该列表的
	pxNewListItem->pvContainer = (void*) pxList;//该列表中列表项数目+1
	(pxList->uxNumberOfItems)++;

在这里插入图片描述

4.将节点按照升序排列插入链表

将节点按照升序排列插入到链表,如果有两个节点的值相同,则新节点在旧节点的后面插入。

                  /*将节点按照升序排列插入链表*/
/*pxList:列表项要插入的列表;pxNewListItem:要插入的列表项*/
void vListInsert(List_t * const pxList,ListItem_t * const pxNewListItem)
{
/*pxIterator为要插入的插入点位置。*/
	ListItem_t *pxIterator;
/*获取新插入列表项的辅助值xItemValue(1、2、3),确定列表项要插入的位置。*/
	const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
/*获取列表项插入到什么位置,如果插入列表项的值等于portMAX_DELAY,即列表项值为最大值,此时插入的位置为列表最末尾*/
	if(xValueOfInsertion == portMAX_DELAY)
	{
/*获取要插入点,列表中xListEnd表示列表末尾,初始化列表时xListEnd的列表值也是portMAX_DELAY,尽管两个值一样,但是要把插入的列表项放在xListEnd前面。*/
			pxIterator = pxList->xListEnd.pxPrevious;
	}
	else
	{
/*如果列表项的值不等于portMAX_DELAY那么就需要在列表中遍历,寻找插入位置,用for循环遍历列表寻找插入点。*/
			for(pxIterator = (ListItem_t*) &(pxList->xListEnd);
		      pxIterator->pxNext->xItemValue <=xValueOfInsertion ;
		      pxIterator = pxIterator->pxNext)
			{
			}
	}
	/*对于下图此时pxIterator指向List_item1*/
	/*此时pxIterator->pxNext是List_item3地址*/
	pxNewListItem->pxNext = pxIterator->pxNext;
	/*pxNewListItem->pxNext->pxPrevious是item3的前驱指针指向新列表项item2*/
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	/*新列表项item2的前驱指针指向pxIterator即item1*/
	pxNewListItem->pxPrevious = pxIterator;
	/*pxIterator即item1的后继指向新列表项item2*/
	pxIterator->pxNext = pxNewListItem;
	pxNewListItem->pvContainer = (void*) pxList;
	(pxList->uxNumberOfItems)++;
}

在这里插入图片描述

5.将节点从链表删除

                  /*将节点按照升序排列插入链表*/
UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove)
{
	/* 获取节点所在的链表 */
	List_t * const pxList = (List_t*)pxItemToRemove->pvContainer;
	/* 将指定的节点从链表删除 */
	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
	/* 调整链表的节点索引指针 */
	if(pxList->pxIndex == pxItemToRemove)
	{
			pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
	pxItemToRemove->pvContainer = NULL;
	/* 链表节点计数器-- */
	(pxList->uxNumberOfItems)--;
	/* 返回链表中剩余节点的个数 */
	return pxList->uxNumberOfItems;
}

在这里插入图片描述

4.3链表节点插入实验

新建一个根节点(也可以理解为链表)和三个普通节点,然后将这三个普通节点按照节点的排序辅助值做升序排列插入到链表中。

#include"list.h"
/* 定义链表根节点 */
List_t List_Test;
/* 定义节点 */
ListItem_t List_Item1;
ListItem_t List_Item2;
ListItem_t List_Item3;

int main(void)
{
		
	/* 链表根节点初始化 */
    vListInitialise(&List_Test);
		
    //初始化列表项
    vListInitialiseItem(&List_Item1);
	List_Item1.xItemValue = 1;
		
    vListInitialiseItem(&List_Item2);
	List_Item2.xItemValue = 2;
		
    vListInitialiseItem(&List_Item3);
	List_Item3.xItemValue = 3;
/* 将节点插入链表,按照升序排列 */
    vListInsert(&List_Test,&List_Item2);
	vListInsert(&List_Test,&List_Item1);
	vListInsert(&List_Test,&List_Item3);
		
    for (;;)
    {
      /* 啥事不干 */
    }
}

仿真结果:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值