文章目录
一.C语言链表
链表作为C语言中的一种基础的数据结构,在平时的程序中使用次数比较少,但在操作系统中链表中的使用次数比较多。链表的最大作用就是可以把离散的数据链接在一起,这和数组是不一样的,数组的存储空间是一段连续的内存,而链表可以把内存中不连续的数据联系起来。学习链表主要是学习链表和节点。链表和节点之间的关系就像是晾衣架和钩子的关系:

链表就像是最上面的挂钩,节点就像是下面的小钩子,通过上面的钩子可以找到所有的小钩子,节点之间是首位相连的,每个节点上可以挂载数据,这样的话一个链表就可以把许多数据联系在一起。
1.单向链表
单向链表节点本身必须包含一个节点指针,用于指向下一个节点,当然节点中还可以携带其他的数据。节点是一个自定义类型的数据结构,在节点中可以有单个的数据、数组、指针数据或结构体数据等。
例如:
struct node
{
struct node *next; /* 指向链表的下一个节点 */
/* 节点携带的数据可以是如下内容 */
char data1; /* 单个的数据 */
unsigned char array[]; /* 数组 */
unsigned long *prt /* 指针数据 */
struct userstruct data2; /* 自定义结构体类型数据 */
/* 其他数据类型 */
}
在单向链表中,最后一个节点的指针指向第一个节点,代表链表是循环的,如图所示:

2.双向链表
双向链表和单向链表的区别就是:双向链表中有俩个节点指针,分别指向前一个节点和后一个节点,其余部分和单向链表一样。
双向链表的结构如示:

3.链表与数组
链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现对数据的存取。而数组是通过开辟一段连续的内存来存储数据,这是数组和链表最大的区别。 数组的每个成员对应链表的节点,成员和节点的数据类型可以是标准的 C 类型或者是用户自定义的结构体。数组有起始地址和结束地址,而链表是一个圈,没有头和尾之分,但是为了方便节点的插入和删除操作会人为的规定一个根节点。
二.FreeRTOS中链表实现
1.定义节点结构体
定义链表节点数据结构如下:
/* 定义节点的数据结构 */
struct xLIST_ITEM
{
/* 辅助值,相当于节点的序号 */
TickType_t xItemValue;
/* 指向下一个节点 */
struct xLIST_ITEM * pxNext;
/* 指向前一个节点 */
struct xLIST_ITEM * pxPrevious;
/* 指向拥有该节点的内核对象,通常为TCB */
void * pvOwner;
/* 指向该节点所在的链表 */
void * pvContainer;
};
/* 重定义节点数据类型 */
typedef struct xLIST_ITEM ListItem_t;
2.定义精简节点结构体
/* 定义链表精简节点结构体 */
struct xMINI_LIST_ITEM
{
/* 辅助值,帮助节点做升序排序 */
TickType_t xItemValue;
/* 指向链表下一个节点 */
struct xLIST_ITEM * pxNext;
/* 指向节点前一个节点 */
struct xLIST_ITEM * pxPrevious;
};
/* 重定义精简节点结构体 */
typedef struct xMINI_LIST_ITEM MiniListItem_t;
3.定义根节点结构体
/* 定义节点链表(根节点)的数据结构 */
typedef struct xLIST
{
/* 链表节点计数器,记录链表中节点的个数 */
UBaseType_t uxNumberOfItemsl;
/* 链表节点索引指针,用于遍历节点 */
ListItem_t * pxIndex;
/* 链表最后一个节点,也是第一个节点,此节点数据结构是精简结构 */
MiniListItem_t xListEnd;
}List_t;
4.节点初始化
/* 链表节点初始化 */
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 将节点指向的链表为空,表示节点还没有插入任何链表 */
pxItem->pvContainer = NULL;
}
5.根节点初始化
/* 链表根节点初始化 */
void vListInitialise( List_t * const pxList )
{
/* 将链表索引指针指向最后一个节点 */
pxList->pxIndex = ( ListItem_t * ) & ( pxList->xListEnd );
/* 将链表最后一个节点的辅助排序设置为最大,确保该节点就是链表的最后节点 */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 将最后一个节点的前后节点指针都指向自身,表示链表为空 */
pxList->xListEnd.pxNext = ( ListItem_t * ) & ( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) & ( pxList->xListEnd );
/* 初始化链表节点计数器为0,表示链表为空 */
pxList->uxNumberOfItemsl = ( UBaseType_t ) 0;
}
6.将节点插入到链表尾部
/* 将节点插入到链表尾部,即插入到最后一个节点的前面,充当最后节点 */
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->uxNumberOfItemsl )++;
}
7.将节点插入到固定位置
/* 将节点按照升序插入到链表中,插在序号节点前面 */
/* 比如插入节点的序号为7,代表要插在链表原来的6与7之间,成为新的7号 */
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
{
/* 直到迭代节点是6号节点 */
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext)
{
/* 什么都不做,不断迭代只为寻找节点要插入的位置 */
}
}
/* 将节点插入到迭代节点后面,即插在6号节点后面 */
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* 将插入的节点归入链表中 */
pxNewListItem->pvContainer = ( void * ) pxList;
/* 链表中节点数加一 */
( pxList->uxNumberOfItemsl )++;
}
8.删除节点
/* 将节点从链表删除,并返回链表中的剩余节点个数 */
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->uxNumberOfItemsl )--;
/* 返回链表中剩余的节点个数 */
return ( pxList->uxNumberOfItemsl );
}
三.仿真验证
运行下面的代码,利用仿真检验链表节点之间的关系是否正确,创建一个根节点、三个节点,将三个节点顺序插入链表中。
#include "list.h"
/* 一个根节点 */
struct xLIST List_Test;
/* 三个节点 */
struct xLIST_ITEM List_Item_1;
struct xLIST_ITEM List_Item_2;
struct xLIST_ITEM List_Item_3;
int main(void)
{
/* 初始化根节点 */
vListInitialise( & List_Test );
/* 初始化节点 */
vListInitialiseItem( & List_Item_1 );
List_Item_1.xItemValue = 1;
vListInitialiseItem( & List_Item_2 );
List_Item_2.xItemValue = 2;
vListInitialiseItem( & List_Item_3 );
List_Item_3.xItemValue = 3;
/* 将节点插入到链表中 */
vListInsert( & List_Test, & List_Item_1 );
vListInsert( & List_Test, & List_Item_2 );
vListInsert( & List_Test, & List_Item_3 );
while( 1 )
{
;
}
}
仿真结果如示:

从结果中看出,各节点之间的对应关系是正确的。

本文介绍了C语言中的链表基础知识,包括单向链表和双向链表,并详细阐述了FreeRTOS操作系统中链表的实现,包括节点结构体定义、初始化及插入删除操作。最后通过仿真验证了链表操作的正确性。

1682

被折叠的 条评论
为什么被折叠?



