【问题标题】:Why should we use double pointer for adding node in front/back, but not in the middle?为什么我们应该使用双指针在前/后添加节点,而不是在中间?
【发布时间】:2021-07-07 13:55:03
【问题描述】:

我有这段代码在最前面创建新节点:

void push(struct Node** head_ref, int new_data)
{
    struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));
   
    new_node->data  = new_data;
   
    new_node->next = (*head_ref);
   
    (*head_ref)    = new_node;
}

我有这段代码在另一个节点之后创建一个新节点:

void insertAfter(struct Node* prev_node, int new_data) 
{ 
    if (prev_node == NULL) 
    { 
       printf("the given previous node cannot be NULL");     
       return; 
    } 
          
    struct Node* new_node =(struct Node*) malloc(sizeof(struct Node)); 
  
    new_node->data = new_data; 
  
    new_node->next = prev_node->next; 
  
    prev_node->next = new_node; 
}

我知道如果我们在第一种情况下传递一个单独的指针 a.k.a.head,则更改将仅在 push 函数中生效,而不是在整个程序中生效。这就是为什么我们使用对head 的引用来更改它所指向的内容。

不过,在第二种情况下,我们传递了一个指针。我只是不明白为什么当我们不使用对前一个节点的引用时,即使我们将 next 更改为指向新节点,代码仍然可以工作。

这些更改不应该只保留在insertAfter 函数中吗?

【问题讨论】:

  • 可以insertAfter(struct Node** prev_node, ...,没问题。但是当您调用InsertAfter 时,您实际上不需要修改调用方的prev_node.pointer。

标签: c pointers pass-by-reference singly-linked-list function-definition


【解决方案1】:

在第一个函数中

void push(struct Node** head_ref, int new_data)
{
    struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));
   
    new_node->data  = new_data;
   
    new_node->next = (*head_ref);
   
    (*head_ref)    = new_node;
}

指向头节点的指针本身已更改。所以函数需要处理原始指针而不是其值的副本。

所以你需要通过引用将指针传递给头节点。

在 C 中,通过引用传递意味着通过指向对象的指针间接传递对象。在这种情况下,像函数的这个语句一样取消引用指针

    (*head_ref)    = new_node;

该函数可以直接访问原始传递的对象。

在第二个函数中

void insertAfter(struct Node* prev_node, int new_data) 
{ 
    if (prev_node == NULL) 
    { 
       printf("the given previous node cannot be NULL");     
       return; 
    } 
          
    struct Node* new_node =(struct Node*) malloc(sizeof(struct Node)); 
  
    new_node->data = new_data; 
  
    new_node->next = prev_node->next; 
  
    prev_node->next = new_node; 
}

指针 prev_node 本身没有被改变。正在更改的是指针指向的节点。所以不需要通过引用将原始指针传递给函数。另一方面,指针prev_node 指向的节点由于该指针而通过引用传递。并且使用指针可以改变指向节点的数据成员。

【讨论】:

  • 很好的解释,谢谢!还有一件事——“(*head_ref)”指的是指针“head”,所以它可以指向新的节点?
  • @zaro 假设您声明了一个指向头节点的指针,如 struct Node *head = NULL;然后像 push( &head, new_data)l 这样通过引用传递指针,该函数通过取消引用现在指向原始指针头的参数(指针)*head_ref 来直接访问指针头。
【解决方案2】:

对这两种情况的解释在于,指针与 C 中的任何其他变量一样,都是按值传递的。

当您更改作为参数传递的指针的 时,在函数内,您需要将指针传递给该指针,因为您将更改指针本身。

如果你想改变它指向的变量的值,你可以只传递一个指针的副本。它仍然可以工作,因为您将更改存储在该指针中存储的地址中的值,地址的副本就可以了。

例子:

void func(int *ptr)
{
    static int a = 20;
    *ptr = 10; // changing the value stored in the address pointed by ptr
    ptr = &a;  // changing ptr itself, (spoiler, won't reflect in the original)
}
int main()
{
    int var;
    int *ptr = &var;

    func(ptr);
    printf("%d", *ptr);
}

您会看到ptr 指向的变量仍然是var,而不是a,并且输出将是10,因为您可以再次更改指针指向的变量。


而如果您将指针传递给指针:

void func(int **ptr)
{
    static int a;
    *ptr = &a;  // changing the value of the pointer
    **ptr = 20; // changing the value of the variable stored int the address stored in ptr
}
int main()
{
    int var = 10;
    int *ptr = &var;
  
    func(&ptr);
    printf("%d", *ptr);
}

现在ptr 将指向a,而不是var,输出将是20,我们能够更改指针值和存储在指针中存储的地址中的值。

【讨论】:

  • 这是第一种情况,是的。但是第二个呢?
  • @zaro,这就是我试图提出的观点,第二种情况,当你传递一个指针时,你正在传递一个副本,当你改变它的值时它不会反映在原始参数中,我会用一个例子来编辑。
【解决方案3】:

在第一种情况下,您正在修改指针head_ref 所指向的内容。

在第二种情况下,您正在修改指针prev_node 所指向的内容。 (A->B 表示(*A).B

这两个函数都指向应该修改的内容和修改指向的内容。

【讨论】:

  • 那么在第二种情况下我可以修改prev_node中的元素,但不能修改节点本身,对吧?
  • 修改节点中的元素不是修改节点吗?
  • 是的。我的意思是,我可以修改节点中的元素,但不能修改指向它的指针?
  • 你是对的。传递了指向节点的指针的副本,因此您无法从被调用函数更改调用者传递的指针。
猜你喜欢
  • 2011-11-08
  • 2021-09-17
  • 2015-06-15
  • 1970-01-01
  • 2017-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多