链表中插入新的节点

/* 节点结构体定义 */
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的童鞋们一定会对这些代码眼熟,我刚学,只是发表一下个人的观点和见解,大神可以直接跳过,写的理解可能只适用于刚接触链表有关知识点的小伙伴,不敢说通俗易懂,但是对于我本身这种菜鸟很容易接受和理解。重点讲解一下第三段代码的逻辑!!!!不喜欢可以敬请喷,我会及时当勉励的。

初始链表结构

假设链表当前结构如下(以便于理解,假设链表已有两个有效节点 AB),并且 pxIndex 是伪头节点,它指向链表的末尾:

NULL <- A <-> B <-> pxIndex -> NULL

我们可以将链表节点看成是互相握手的小人,他们按照一定的顺序排列。我们一步步看看新小人 C 是如何加入到一群已经排好队的小人 AB 和尾巴节点 pxIndex 中的。

初始状态

假设我们有一个双向链表队伍,它的排列如下:

A <-> B <-> pxIndex

这里的每个箭头 <-> 表示一个节点和它相邻节点之间的双向关系。pxIndex 可以看作是一个站在队伍最后的标记节点,不是真正的队伍成员。

  • A:队伍中的第一个小人,没有人站在他前面。
  • B:A 后面的一个小人。
  • pxIndex:一个标记队伍末尾的小人,没有其他人站在他后面。

此时,队伍中的人数为 2(A 和 B),由变量 pxList->uxNumberOfItems 记录。

目标

现在,我们想让小人 C 加入队伍,站在 BpxIndex 之间。最终目标是让队伍变成:

A <-> B <-> C <-> pxIndex

插入过程一步步拆解

1. 让 C 知道他后面是谁

代码的第一步是让 CpxNext 指针指向 pxIndex,也就是告诉 C 站在他后面的是 pxIndex

pxNewListItem->pxNext = pxIndex;

现在 CpxNext 指针指向了 pxIndex,表示他知道了自己后面是队伍的尾巴节点。

A <-> B     C -> pxIndex
2. 让 C 知道他前面是谁

接下来,我们需要告诉 C 站在他前面的是 B。我们通过 pxIndex->pxPrevious 找到 B,然后把 B 的地址赋给 CpxPrevious 指针:

pxNewListItem->pxPrevious = pxIndex->pxPrevious;

现在 CpxPrevious 指针指向了 B,表示 C 知道了他前面是谁。

A <-> B <-> C -> pxIndex
3. 让 B 知道他后面多了一个人

在队伍中,双向链表是彼此双向连接的。所以我们还需要告诉 B,让他知道后面站了一个新小人 C。我们通过 pxIndex->pxPrevious->pxNext 找到 BpxNext,然后把 C 的地址赋给它:

pxIndex->pxPrevious->pxNext = pxNewListItem;

这样 BpxNext 指针指向了 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,准确记录了链表中有 ABC 三个节点。

总结

整个过程简单来说,就是让新节点 C 找到他前后的人,同时告诉他前后的人有了新成员 C,最后更新链表的头尾标记和计数器。

看到这里,你是不是已经完全理解和消化掉了如何插入新节点呢?会不会有一点点疑问,但是又不好意思问呢?我自我提问几个问题吧,因为太菜因为c学的不到位,所以就问题多多了一些。

Question One:

为什么pxIndex->pxPrevious代表的是B?????

答:

在这个链表结构中,pxIndex 是一个伪尾节点(或称标记节点),它作为链表的终点标记。因为 pxIndex 并不是真正的有效数据节点,它的作用只是指示链表的尾部。链表中的实际有效节点依次连接,而 pxIndex 在链表结构上只是方便操作的一个标记。

让我们来看看为什么 pxIndex->pxPrevious 会代表链表的最后一个有效节点 B

伪尾节点(pxIndex)的工作方式

在这个双向链表中:

  • pxIndex 总是指向链表的尾部,并且它的 pxNext 通常指向 NULL 或自己,以表示链表的终止。
  • pxIndex->pxPrevious 是链表最后一个有效节点。这是因为链表的逻辑结构让 pxIndex 成为尾节点标记,那么它的 pxPrevious 指针自然指向链表中真正的最后一个有效节点。

当前链表的结构示例

假设链表中有两个有效节点 AB,链表的结构如下:

NULL <- A <-> B <-> pxIndex

在这个结构中:

  • A 是链表的第一个有效节点,所以 A->pxPreviousNULL
  • B 是链表中最后一个有效节点,所以 B->pxNext 指向 pxIndex
  • pxIndex 是链表的伪尾节点,所以 pxIndex->pxPrevious 指向 B

插入新节点的操作

当我们向链表末尾插入新节点 C 时,步骤如下:

  1. 新节点 CpxPrevious 被设置为当前最后一个有效节点,即 pxIndex->pxPrevious,也就是 B
  2. B->pxNext 被更新为 C,表示 B 的后面现在是 C
  3. 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 的地址。这个指针本身包含了新节点的所有信息,包括它的 pxNextpxPrevious 指针。

当我们写 pxIndex->pxPrevious->pxNext = pxNewListItem; 时,我们在做的实际上是更新链表中前一个节点的 pxNext 指针,使它指向新节点 C。这一步的关键在于理解双向链表节点之间的指针如何相互指向,并构成链表结构。

让我们深入理解一下这里的具体含义。

理解 pxNewListItem 的作用

  1. pxNewListItem 作为新节点的整体指针
    pxNewListItem 指向整个节点 C,即包含了 CpxNextpxPrevious 等信息。因此,pxNewListItem 并不是指向 C 的某个特定成员,而是指向节点 C 本身的起始地址

  2. 在链表中建立前后关系
    在链表中,每个节点的 pxNextpxPrevious 指针都用于指向相邻节点。这段代码:

pxIndex->pxPrevious->pxNext = pxNewListItem;
  1. 等效于 B->pxNext = pxNewListItem,其中 BpxIndex->pxPrevious 指向的最后一个有效节点。这一步就是让 B->pxNext 指向新节点 C 的整体地址(即 pxNewListItem),表示 B 的下一个节点现在是 C。通过这一指向关系的设置,BC 之间的链接建立起来了。

  2. 为什么是整个 C 的地址
    pxNewListItem 的确包含 CpxNextpxPrevious 指针,但在这行代码中,我们只关心将 pxNewListItem 作为一个完整的节点地址赋给 B->pxNext,表示 B 后面的节点是 CpxNextpxPrevious 的详细信息只有在访问节点 C 本身时才会被读取和使用。

代码工作流程小结

  1. pxNewListItem->pxNext = pxIndex;:将 C->pxNext 指向 pxIndex,表示 C 后面是伪尾节点 pxIndex
  2. pxNewListItem->pxPrevious = pxIndex->pxPrevious;:将 C->pxPrevious 指向 B(原最后一个节点)。
  3. pxIndex->pxPrevious->pxNext = pxNewListItem;:将 B->pxNext 指向 C,即 B 现在知道他后面是 C
  4. pxIndex->pxPrevious = pxNewListItem;:将 pxIndex->pxPrevious 更新为 C,即 C 成为新的最后一个有效节点。

总结

pxIndex->pxPrevious->pxNext = pxNewListItem; 中,pxNewListItem 表示整个新节点 C 的地址,而不只是它的 pxNextpxPrevious。我们通过这个赋值操作让 B->pxNext 指向 C 的地址,从而把 C 插入到 BpxIndex 之间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李解49

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值