/* 节点结构体定义 */
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
可以看作是一个站在队伍最后的标记节点,不是真正的队伍成员。
- A:队伍中的第一个小人,没有人站在他前面。
- B:A 后面的一个小人。
- pxIndex:一个标记队伍末尾的小人,没有其他人站在他后面。
此时,队伍中的人数为 2
(A 和 B),由变量 pxList->uxNumberOfItems
记录。
目标
现在,我们想让小人 C
加入队伍,站在 B
和 pxIndex
之间。最终目标是让队伍变成:
A <-> B <-> C <-> pxIndex
插入过程一步步拆解
1. 让 C
知道他后面是谁
代码的第一步是让 C
的 pxNext
指针指向 pxIndex
,也就是告诉 C
站在他后面的是 pxIndex
:
pxNewListItem->pxNext = pxIndex;
现在 C
的 pxNext
指针指向了 pxIndex
,表示他知道了自己后面是队伍的尾巴节点。
A <-> B C -> pxIndex
2. 让 C
知道他前面是谁
接下来,我们需要告诉 C
站在他前面的是 B
。我们通过 pxIndex->pxPrevious
找到 B
,然后把 B
的地址赋给 C
的 pxPrevious
指针:
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
现在 C
的 pxPrevious
指针指向了 B
,表示 C
知道了他前面是谁。
A <-> B <-> C -> pxIndex
3. 让 B
知道他后面多了一个人
在队伍中,双向链表是彼此双向连接的。所以我们还需要告诉 B
,让他知道后面站了一个新小人 C
。我们通过 pxIndex->pxPrevious->pxNext
找到 B
的 pxNext
,然后把 C
的地址赋给它:
pxIndex->pxPrevious->pxNext = pxNewListItem;
这样 B
的 pxNext
指针指向了 C
,表示 B
知道他后面站了一个新成员 C
。
A <-> B <-> C -> pxIndex
4. 更新队尾指针,表示 C
是最后一个人
队尾指针 pxIndex->pxPrevious
需要指向新插入的节点 C
,表示队伍的最后一个有效成员是 C
。因此,pxIndex->pxPrevious = pxNewListItem;
:
pxIndex->pxPrevious = pxNewListItem;
现在 pxIndex->pxPrevious
指向了 C
,表示 C
成为新的尾巴前的最后一个节点。
A <-> B <-> C <-> pxIndex
5. 记录新节点 C
的所属链表
我们把 pxList
的地址存储在 C->pvContainer
中,方便以后找到 C
属于哪个链表:
pxNewListItem->pvContainer = ( void * ) pxList;
6. 更新链表节点计数器
最后,链表节点数量增加了 1:
( pxList->uxNumberOfItems )++;
现在链表计数器变成了 3
,准确记录了链表中有 A
、B
、C
三个节点。
总结
整个过程简单来说,就是让新节点 C
找到他前后的人,同时告诉他前后的人有了新成员 C
,最后更新链表的头尾标记和计数器。
看到这里,你是不是已经完全理解和消化掉了如何插入新节点呢?会不会有一点点疑问,但是又不好意思问呢?我自我提问几个问题吧,因为太菜因为c学的不到位,所以就问题多多了一些。
Question One:
为什么pxIndex->pxPrevious代表的是B?????
答:
在这个链表结构中,pxIndex
是一个伪尾节点(或称标记节点),它作为链表的终点标记。因为 pxIndex
并不是真正的有效数据节点,它的作用只是指示链表的尾部。链表中的实际有效节点依次连接,而 pxIndex
在链表结构上只是方便操作的一个标记。
让我们来看看为什么 pxIndex->pxPrevious
会代表链表的最后一个有效节点 B
。
伪尾节点(pxIndex)的工作方式
在这个双向链表中:
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
之间。