printList函数中原始头指针没有改变,但插入节点时列表确实发生了变化

Posted

技术标签:

【中文标题】printList函数中原始头指针没有改变,但插入节点时列表确实发生了变化【英文标题】:Original head pointer not being changed in the printList function, but the list does change when inserting a node 【发布时间】:2022-01-07 14:07:51 【问题描述】:

我已经阅读了这两个关于使用双指针/通过引用传递的帖子/答案

When printing out a linked list, why is the original head pointer not changed

Linked list head double pointer passing

但有一件事仍然让我感到困惑。

printList 函数中的头指针(head = head->next 遍历)在 main 中没有改变,因为即使我们通过引用传递它,函数也会接收指针/地址的副本。 我能理解。

但是当插入一个像这样的节点时,整个列表怎么会改变(更新)

struct node* addLast(struct node* head, struct node* new_node) 
    if (head == NULL)
    
        head = new_node;
        return head;
    

    struct node* current = head;
    while (current->next != NULL)
    
        current = current->next;
    

    current->next = new_node;

    return head;
 

我们在 main 中调用它

head = addLast(head, node)

我知道该原理适用于 head == NULL 的情况(因为我们返回“新”头),但如果不是,则我们再次遍历列表并插入节点。

那么列表是如何更新的(不一定只在这个特定的添加函数中)?那么new_node(另一个函数用malloc()创建的节点)不也是一个“副本”吗?

【问题讨论】:

【参考方案1】:

为什么插入节点时整个列表会发生变化(更新)?

正如你所说的,这里有两种情况:

我知道该原则适用于 head == NULL 的情况(因为我们返回“新”头)

的确如此。所以你的问题是关于列表不为空并且附加一个节点的剩余情况。

在这种情况下,返回的值与作为参数给出的 指针 相同:head 不会改变。

但是,head 指向的节点有自己的next 指针,而那个 指针可能已从NULL 更改为指向新节点的指针。所以,虽然head 没有改变,next 指针链会变得更长

想象一下当我们从一个空列表开始时会发生什么,然后使用以下脚本向其中添加节点:

node* createNode(int value) 
    node* newNode = malloc(sizeof(node));
    newNode->value = value;
    newNode->next = NULL;
    return newNode;


int main() 
    node* head = NULL;
    head = addLast(head, createNode(1));
    head = addLast(head, createNode(2));
    head = addLast(head, createNode(3));
    // ...
    return 0;

我刚刚添加了一个函数来创建节点实例:这并不奇怪(我希望!)

所以当脚本启动时,我们有head:

head: NULL

然后我们调用createNode(1),它返回一个指向节点的指针。我们可以用一个框来表示那个节点:

┌────────────┐
│ value: 1   │
│ next: NULL │
└────────────┘

指向这个节点的指针作为第二个参数传递给addList,所以在那个函数中我们有:

 new_node         head: NULL
  ↓
┌────────────┐
│ value: 1   │
│ next: NULL │
└────────────┘

正如您正确注意到的,这个节点引用从函数addList 返回给调用者,调用者将它分配给它自己的head 变量。所以在主程序中我们现在有这个状态:

 head
  ↓
┌────────────┐
│ value: 1   │ 
│ next: NULL │
└────────────┘

现在到第二个节点:它使用createNode(2) 创建,然后使用这些参数调用addList

 head                new_node
  ↓                   ↓
┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │ 
│ next: NULL │      │ next: NULL │
└────────────┘      └────────────┘

addList 然后创建另一个变量current,它以与head 相同的引用开始:

 current
 head                new_node
  ↓                   ↓
┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │ 
│ next: NULL │      │ next: NULL │
└────────────┘      └────────────┘

while 循环条件不成立,因此不会迭代。然后current->next = new_node被执行,这是最重要的赋值:它在列表的last节点与新节点之间建立链接:

 current
 head                new_node
  ↓                   ↓
┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │ 
│ next: ──────────> │ next: NULL │
└────────────┘      └────────────┘

最后,head 被返回给调用者,调用者将其分配给他们的head 变量——这实际上是一个虚拟分配,因为head 没有改变。 改变的是head指向的链表长度。

这应该可以解释了,但让我们再添加一个节点:create_node(3) 被传递给addList,所以在addList 中我们有这个状态:

 current
 head                                    new_node
  ↓                                       ↓
┌────────────┐      ┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │      │ value: 3   │ 
│ next: ──────────> │ next: NULL │      │ next: NULL │
└────────────┘      └────────────┘      └────────────┘

这次while 条件为真,所以current = current->next 会将我们带入这种状态:

 head                current             new_node
  ↓                   ↓                   ↓
┌────────────┐      ┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │      │ value: 3   │ 
│ next: ──────────> │ next: NULL │      │ next: NULL │
└────────────┘      └────────────┘      └────────────┘

while 循环将退出,current->next = new_node 被执行:

 head                current             new_node
  ↓                   ↓                   ↓
┌────────────┐      ┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │      │ value: 3   │ 
│ next: ──────────> │ next: ──────────> │ next: NULL │
└────────────┘      └────────────┘      └────────────┘

addList 通过返回(未更改的)head 指针而终止。主程序再次执行分配给它自己的head,即使那个指针没有改变。

我希望这能澄清即使head 不再改变,next 指针链确实 改变:尾节点的next 指针从NULL 更改为地址新节点。

【讨论】:

感谢您提供如此直观且非常详细的解释!我明白,这就是为什么我对 printList 函数感到困惑;因为在该函数中,在遍历时,我们确实将 head->next 分配给了我们的头指针(head = head->next)。在这种情况下,头指针是否保持不变,因为我们通常不使用返回值(在大多数情况下我遇到它是一个 void 函数),或者 printList 函数中指向头指针的指针,我们宁愿只是“通过”是吗? 它保持不变,因为head是一个局部变量,分配给它不会修改main中另一个同名变量,它的值是作为参数传递的。 啊,我明白了。现在很清楚了,我再次感谢您花时间和详细的澄清。

以上是关于printList函数中原始头指针没有改变,但插入节点时列表确实发生了变化的主要内容,如果未能解决你的问题,请参考以下文章

为啥传递给函数的指针的值没有改变? [复制]

段错误:链表节点插入函数 [C++]

c怎样在文件中插入数据

Redis数据结构

插入排序

5-链表