尽管进行了相应的更正,为啥指向指针的指针与指针的处理方式不同

Posted

技术标签:

【中文标题】尽管进行了相应的更正,为啥指向指针的指针与指针的处理方式不同【英文标题】:Why is a pointer to pointer treated differently than a pointer in spite of corresponding corrections尽管进行了相应的更正,为什么指向指针的指针与指针的处理方式不同 【发布时间】:2021-09-23 09:37:48 【问题描述】:

我正在尝试从双向链表中删除一个节点。函数 deleteNode() 将接收链表的头部和要删除的节点。但是,根据我是将要删除的节点作为指针还是指向指针的指针传递,代码的行为会有所不同(尽管在语法上进行了相应的更正)。

这是我的双向链表节点结构 -

struct DNode 
int val;
struct DNode* next;
struct DNode* prev;
DNode(int val)  //constructor
    this->val = val;
    next = NULL;
    prev = NULL;

;

以下代码运行良好:

void deleteNode(struct DNode** head, struct DNode* deleteMe) 
//head is pointer to pointer, deleteMe is pointer

if(!(*head) || !deleteMe) 
    cout<<"\nDeletion not possible";
    return;


if(deleteMe->prev != NULL)
    deleteMe->prev->next = deleteMe->next;

if(deleteMe->next != NULL)
    deleteMe->next->prev = deleteMe->prev;

if(deleteMe == *head)
    *head = deleteMe->next;

delete deleteMe;

对应的驱动代码-

/*at this point, I have pushed elements into the Linked List to make it 4->3->2->1, where 4 is 
the head*/
deleteNode(&head, head);      
deleteNode(&head, head->next); 
deleteNode(&head, head->next); 

但是,当我将两个参数作为指向指针的指针发送时,我在运行时遇到了崩溃:

void deleteNode(struct DNode** head, struct DNode** deleteMe) 

if(!(*head) || !(*deleteMe)) 
    cout<<"\nDeletion not possible";
    return;


if((*deleteMe)->prev != NULL)
    (*deleteMe)->prev->next = (*deleteMe)->next;

if((*deleteMe)->next != NULL)
    (*deleteMe)->next->prev = (*deleteMe)->prev;

if(*deleteMe == *head)
    *head = (*deleteMe)->next;

delete *deleteMe;

对应的驱动代码:

/*at this point, I have pushed elements into the Linked List to make it 4->3->2->1, where 4 is 
the head*/
deleteNode(&head, &head);      
deleteNode(&head, &head->next);
deleteNode(&head, &head->next);

PS - 这不完全是删除的问题:

如果您在错误代码中注释行 delete *deleteMe,代码将运行但输出会有所不同。

工作代码的输出:3

注释删除行后错误代码的输出:3->1

【问题讨论】:

不要用评论告诉我们head 是什么,而是在代码中显示它。创建minimal reproducible example。 第一种情况,第二个参数是按值传递的。因此(在函数内部)*head 的更改不能与deleteMe 的更改(指向指向的对象)交互。将第二个参数作为指向指针的指针传递意味着(内部)函数*head*deleteMe(或(*deleteMe)-&gt;next(*deleteMe)-&gt;prev)可能是相同的对象。论点之间相互作用的差异解释了行为的差异。为main() 中的变量和deleteNode() 中的变量或参数赋予不同的名称可能会使交互在视觉上更加明显。 【参考方案1】:

这里,当你通过&amp;head&amp;head

if(*deleteMe == *head)
    *head = (*deleteMe)->next;

*deleteMe*head是同一个对象(你传入的地址的对象),赋值后还是同一个对象。 (即相当于*head = (*head)-&gt;next;*deleteMe = (*deleteMe)-&gt;next。)

所以这个,

delete *deleteMe;

在这种情况下与delete *head 做同样的事情,然后事情就发展了。

在第一种情况下,*headdeleteMe 是不同的对象,因此分配给*head 不会影响deleteMe

作为说明,这就是发生的情况(“main_head”是main 中的变量)。

当你进入函数时,你有这种情况:

  head
   |
   v          +---+   +---+
main_head---->|  ---->| ----> ...
   ^          +---+   +---+
   |                    ^
 deleteMe               |
                  (*deleteMe)->next

然后分配*head = (*deleteMe)-&gt;next:

  head    +-------------+
   |      |             |
   |      |             v
   v      |   +---+   +---+
main_head-+   |  ---->| ----> ...
   ^          +---+   +---+
   |
 deleteMe

您可以看到headdeleteMe 仍然指向mainhead,这就是您为其赋值的对象。

使用工作代码,您可以从以下内容开始:

  head        
   |           
   v          +---+   +---+
main_head---->|  ---->| ----> ...
              +---+   +---+
                ^       ^
                |       |
            deleteMe   deleteMe->next

最终得到

  head    +-------------+
   |      |             |
   |      |             v
   v      |   +---+   +---+
main_head-+   |  ---->| ----> ...
              +---+   +---+
                ^       ^
                |       |
            deleteMe   deleteMe->next

【讨论】:

等一下。当您说“分配后它们仍然是同一个对象”时,这怎么可能?我看到它的方式是,当我传递 &head 和 &head 时,(在函数中)*head 和 *delete 是开头同一节点的别名。当我说 *head = (*deleteMe)-&gt;next; 时, *head 现在指向链表中的第二个节点,而 *deleteMe 仍然指向第一个节点。所以delete *deleteMe;不可能和delete *head;意思一样 是的,*head*deleteMe 是同一个对象的“别名”。因此,当您分配给*head 时,这会影响*deleteMe,因为它是同一件事。我将添加一点 ASCII 艺术来说明。 兄弟,ASCII 艺术非常有帮助,谢谢我现在明白了

以上是关于尽管进行了相应的更正,为啥指向指针的指针与指针的处理方式不同的主要内容,如果未能解决你的问题,请参考以下文章

定义了一个常数组,为啥能用指针改变数组元素的值?

为啥指向成员函数的指针不像数据指针那样只是内存地址

C语言free释放内存后为啥指针里的值不变?竟然还可以输出

为啥指针+1包含的内存地址与被指向的值的地址+1不同

C语言全排列问题为啥用指针比数组慢很多?

C语言中 内存消亡 指向她的指针就一定消亡或成了空指针为啥是错的啊