/* 节点结构体定义 */
struct xLIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
void * pvOwner; /* 指向拥有该节点的内核对象,通常是TCB */
void * pvContainer; /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */
/* 链表结构体定义 */
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 链表节点计数器 */
ListItem_t * pxIndex; /* 链表节点索引指针 */
MiniListItem_t xListEnd; /* 链表最后一个节点 */
} List_t;
/* 将节点插入到链表的尾部 */
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
pxNewListItem->pxNext = pxIndex; //1
pxNewListItem->pxPrevious = pxIndex->pxPrevious; //2
pxIndex->pxPrevious->pxNext = pxNewListItem; //3
pxIndex->pxPrevious = pxNewListItem; //4
/* 记住该节点所在的链表 */
pxNewListItem->pvContainer = ( void * ) pxList; //5
/* 链表节点计数器++ */
( pxList->uxNumberOfItems )++; //6
}
学习过“野火”freeRTOS的童鞋们一定会对这些代码眼熟,我刚学,只是发表一下个人的观点和见解,大神可以直接跳过,写的理解可能只适用于刚接触链表有关知识点的小伙伴,不敢说通俗易懂,但是对于我本身这种菜鸟很容易接受和理解。重点讲解一下第三段代码的逻辑!!!!不喜欢可以敬请喷,我会及时当勉励的。
假设链表当前结构如下(以便于理解,假设链表已有两个有效节点 A
和 B
),并且 pxIndex
是伪头节点,它指向链表的末尾:
NULL <- A <-> B <-> pxIndex -> NULL
我们可以将链表节点看成是互相握手的小人,他们按照一定的顺序排列。我们一步步看看新小人 C
是如何加入到一群已经排好队的小人 A
、B
和尾巴节点 pxIndex
中的。
假设我们有一个双向链表队伍,它的排列如下:
A <-> B <-> pxIndex
这里的每个箭头 <->
表示一个节点和它相邻节点之间的双向关系。pxIndex
可以看作是一个站在队伍最后的标记节点,不是真正的队伍成员。
此时,队伍中的人数为 2
(A 和 B),由变量 pxList->uxNumberOfItems
记录。
现在,我们想让小人 C
加入队伍,站在 B
和 pxIndex
之间。最终目标是让队伍变成:
A <-> B <-> C <-> pxIndex
C
知道他后面是谁代码的第一步是让 C
的 pxNext
指针指向 pxIndex
,也就是告诉 C
站在他后面的是 pxIndex
:
pxNewListItem->pxNext = pxIndex;
现在 C
的 pxNext
指针指向了 pxIndex
,表示他知道了自己后面是队伍的尾巴节点。
A <-> B C -> pxIndex
C
知道他前面是谁接下来,我们需要告诉 C
站在他前面的是 B
。我们通过 pxIndex->pxPrevious
找到 B
,然后把 B
的地址赋给 C
的 pxPrevious
指针:
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
现在 C
的 pxPrevious
指针指向了 B
,表示 C
知道了他前面是谁。
A <-> B <-> C -> pxIndex
B
知道他后面多了一个人在队伍中,双向链表是彼此双向连接的。所以我们还需要告诉 B
,让他知道后面站了一个新小人 C
。我们通过 pxIndex->pxPrevious->pxNext
找到 B
的 pxNext
,然后把 C
的地址赋给它:
pxIndex->pxPrevious->pxNext = pxNewListItem;
这样 B
的 pxNext
指针指向了 C
,表示 B
知道他后面站了一个新成员 C
。
A <-> B <-> C -> pxIndex
C
是最后一个人队尾指针 pxIndex->pxPrevious
需要指向新插入的节点 C
,表示队伍的最后一个有效成员是 C
。因此,pxIndex->pxPrevious = pxNewListItem;
:
pxIndex->pxPrevious = pxNewListItem;
现在 pxIndex->pxPrevious
指向了 C
,表示 C
成为新的尾巴前的最后一个节点。
A <-> B <-> C <-> pxIndex
C
的所属链表我们把 pxList
的地址存储在 C->pvContainer
中,方便以后找到 C
属于哪个链表:
pxNewListItem->pvContainer = ( void * ) pxList;
最后,链表节点数量增加了 1:
( pxList->uxNumberOfItems )++;
现在链表计数器变成了 3
,准确记录了链表中有 A
、B
、C
三个节点。
整个过程简单来说,就是让新节点 C
找到他前后的人,同时告诉他前后的人有了新成员 C
,最后更新链表的头尾标记和计数器。
看到这里,你是不是已经完全理解和消化掉了如何插入新节点呢?会不会有一点点疑问,但是又不好意思问呢?我自我提问几个问题吧,因为太菜因为c学的不到位,所以就问题多多了一些。
Question One:
为什么pxIndex->pxPrevious代表的是B?????
答:
在这个链表结构中,pxIndex
是一个伪尾节点(或称标记节点),它作为链表的终点标记。因为 pxIndex
并不是真正的有效数据节点,它的作用只是指示链表的尾部。链表中的实际有效节点依次连接,而 pxIndex
在链表结构上只是方便操作的一个标记。
让我们来看看为什么 pxIndex->pxPrevious
会代表链表的最后一个有效节点 B
。
在这个双向链表中:
pxIndex
总是指向链表的尾部,并且它的 pxNext
通常指向 NULL
或自己,以表示链表的终止。pxIndex->pxPrevious
是链表最后一个有效节点。这是因为链表的逻辑结构让 pxIndex
成为尾节点标记,那么它的 pxPrevious
指针自然指向链表中真正的最后一个有效节点。假设链表中有两个有效节点 A
和 B
,链表的结构如下:
NULL <- A <-> B <-> pxIndex
在这个结构中:
A
是链表的第一个有效节点,所以 A->pxPrevious
是 NULL
。B
是链表中最后一个有效节点,所以 B->pxNext
指向 pxIndex
。pxIndex
是链表的伪尾节点,所以 pxIndex->pxPrevious
指向 B
。当我们向链表末尾插入新节点 C
时,步骤如下:
C
的 pxPrevious
被设置为当前最后一个有效节点,即 pxIndex->pxPrevious
,也就是 B
。B->pxNext
被更新为 C
,表示 B
的后面现在是 C
。pxIndex->pxPrevious
更新为 C
,表示 C
成为链表中的新最后一个有效节点。最终,链表结构变为:
NULL <- A <-> B <-> C <-> pxIndex
因此,pxIndex->pxPrevious
总是指向链表的最后一个有效节点。这是因为 pxIndex
是一个伪尾节点,它自己没有有效数据,只是标记链表的终点。
Question Two:
pxIndex->pxPrevious->pxNext = pxNewListItem;为什么pxNewListItem是c的地址,pxNewListItem不是包括C的next和previous嘛?
答:
在这段代码中,pxNewListItem
是一个指向新节点的指针,表示整个新节点 C
的地址。这个指针本身包含了新节点的所有信息,包括它的 pxNext
和 pxPrevious
指针。
当我们写 pxIndex->pxPrevious->pxNext = pxNewListItem;
时,我们在做的实际上是更新链表中前一个节点的 pxNext
指针,使它指向新节点 C
。这一步的关键在于理解双向链表节点之间的指针如何相互指向,并构成链表结构。
让我们深入理解一下这里的具体含义。
pxNewListItem
的作用pxNewListItem
作为新节点的整体指针pxNewListItem
指向整个节点 C
,即包含了 C
的 pxNext
和 pxPrevious
等信息。因此,pxNewListItem
并不是指向 C
的某个特定成员,而是指向节点 C
本身的起始地址。
在链表中建立前后关系
在链表中,每个节点的 pxNext
和 pxPrevious
指针都用于指向相邻节点。这段代码:
pxIndex->pxPrevious->pxNext = pxNewListItem;
等效于 B->pxNext = pxNewListItem
,其中 B
是 pxIndex->pxPrevious
指向的最后一个有效节点。这一步就是让 B->pxNext
指向新节点 C
的整体地址(即 pxNewListItem
),表示 B
的下一个节点现在是 C
。通过这一指向关系的设置,B
和 C
之间的链接建立起来了。
为什么是整个 C
的地址pxNewListItem
的确包含 C
的 pxNext
和 pxPrevious
指针,但在这行代码中,我们只关心将 pxNewListItem
作为一个完整的节点地址赋给 B->pxNext
,表示 B
后面的节点是 C
。pxNext
和 pxPrevious
的详细信息只有在访问节点 C
本身时才会被读取和使用。
pxNewListItem->pxNext = pxIndex;
:将 C->pxNext
指向 pxIndex
,表示 C
后面是伪尾节点 pxIndex
。pxNewListItem->pxPrevious = pxIndex->pxPrevious;
:将 C->pxPrevious
指向 B
(原最后一个节点)。pxIndex->pxPrevious->pxNext = pxNewListItem;
:将 B->pxNext
指向 C
,即 B
现在知道他后面是 C
。pxIndex->pxPrevious = pxNewListItem;
:将 pxIndex->pxPrevious
更新为 C
,即 C
成为新的最后一个有效节点。在 pxIndex->pxPrevious->pxNext = pxNewListItem;
中,pxNewListItem
表示整个新节点 C
的地址,而不只是它的 pxNext
或 pxPrevious
。我们通过这个赋值操作让 B->pxNext
指向 C
的地址,从而把 C
插入到 B
和 pxIndex
之间。