数据结构—列表与列表项7.24

数据结构—列表与列表项

列表和列表项是直接从 FreeRTOS 源码的注释中的 list 和 list item 翻译过来的,其实就是对应我们 C 语言当中的链表和节点,即链表就是列表,节点就是列表项。

1、C 语言链表简介

链表的节点本身不能存储太多东西,或者说链表的节点本来就不是用来存储大量数据的,但是节点可以挂很多数据。

链表分为单向链表和双向链表,单向链表很少用,使用最多的还是双向链表。

(1)单向链表

前一个节点都有一个箭头指向后一个节点,首尾相连,组成一个圈。

在这里插入图片描述

节点本身必须包含一个节点指针,用于指向后一个节点,除了这个节点指针是必须有的之外,节点本身还可以携带一些私有信息。

1)节点结构体定义
struct node
{
  struct node *next; /* 指向链表的下一个节点 */
  char data1; /* 单个的数据 */
  unsigned char array[]; /* 数组 */
  unsigned long *prt /* 指针数据 */
  struct userstruct data2; /* 自定义结构体类型数据 */
  /* ...... */
}

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

2)节点内嵌在一个数据结构中
struct node
{
    struct node *next; /* 指向链表的下一个节点 */
}

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

在这里插入图片描述

链表常规的操作就是节点的插入和删除,为了顺利的插入,通常一条链表我们会人为地规定一个根节点,这个根节点称为生产者。通常根节点还会有一个节点计数器,用于统计整条链表的节点个数。

(2)双向链表

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

(3)链表与数组的对比

链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现对数据的存取。

而数组是通过开辟一段连续的内存来存储数据,这是数组和链表最大的区别。

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

在这里插入图片描述

2、FreeRTOS 中链表的实现

FreeRTOS 中与链表相关的操作均在 list.h 和 list.c 这两个文件中实现,list.h 第一次使用需要在 include 文件夹下面新建然后添加到工程 freertos/source 这个组文件,list.c 第一次使用需要在 freertos 文件夹下面新建然后添加到工程 freertos/source 这个组文件。

(1)链表节点数据结构定义

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

凡是涉及到数据类型的地方,FreeRTOS 都会将标准的 C 数据类型用 typedef 重新取一个类型名。这些经过重定义的数据类型放在 portmacro.h

(2)链表节点初始化

链表节点初始化函数在 list.c 中实现

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

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

(3)定义链表根节点数据结构

链表根节点的数据结构在 list.h 中定义,具体实现下:

typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 链表节点计数器 ,用于表示该链表
                                                              下有多少个节点,根节点除外*/ 
ListItem_t * pxIndex; /* 链表节点索引指针,用于遍历节点 */ 
MiniListItem_t xListEnd; /* 链表最后一个节点 */ 
} List_t;

链表的最后一个节点,实际也就是链表的第一个节点,我们称之为生产者。该生产者的数据类型是一个精简的节点。

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

(4)链表根节点初始化

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

(5)将节点插入到链表的尾部

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

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
pxNewListItem->pxNext = pxIndex; 
pxNewListItem->pxPrevious = pxIndex->pxPrevious; 
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem; 

/* 记住该节点所在的链表 */
pxNewListItem->pvContainer = ( void * ) pxList;

/* 链表节点计数器++ */
( pxList->uxNumberOfItems )++;
}

(6)将节点按照升序排列插入到链表

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

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;

/* 获取节点的排序辅助值 */
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; 

/* 寻找节点要插入的位置 */ 
if ( xValueOfInsertion == portMAX_DELAY ){
    pxIterator = pxList->xListEnd.pxPrevious;
}
else{
   for ( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
      pxIterator->pxNext->xItemValue <= xValueOfInsertion; 
      pxIterator = pxIterator->pxNext ){
      /* 没有事情可做,不断迭代只为了找到节点要插入的位置 */
    }
}
/* 根据升序排列,将节点插入 */ 
pxNewListItem->pxNext = pxIterator->pxNext; 
pxNewListItem->pxNext->pxPrevious = pxNewListItem; 
pxNewListItem->pxPrevious = pxIterator; 
pxIterator->pxNext = pxNewListItem; 

/* 记住该节点所在的链表 */
pxNewListItem->pvContainer = ( void * ) pxList; 

/* 链表节点计数器++ */
( pxList->uxNumberOfItems )++;
}

(7)将节点从链表删除

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;
 }

(8)节点带参宏小函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值