【问题标题】:Clarification on passing a pointer by reference关于通过引用传递指针的说明
【发布时间】:2018-05-23 00:04:31
【问题描述】:

这有点愚蠢,但我无法真正解释为什么会发生这种情况。作为练习,我想反转一个单链表,我通过定义方法来做到这一点:

class solution {
    void reverseLinkedList(Node*& head) {
      Node* curr = head;
      Node* prev = NULL;
      while (curr != NULL) {
        Node* _next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = _next;
      }
      head = prev;
}

在我的主要功能中,我拨打电话

solution s;
s.reverseLinkedList(head);

Node* iterator = head;
while (iterator != NULL) {
    std::cout<<iterator->data<<std::endl;
    iterator = iterator->next;
}

我之前定义了指向某个链表的头指针。 while 循环用于打印我的链表,该函数完成了它的工作。这仅在我通过引用传递头节点后才有效;我最初尝试在开始时传递 Node* head 而不是 Node*& head,它只打印了我的链表的第一个元素(并且没有反转它)。例如,如果我没有通过引用传递列表 1->2->3,我将只打印出 1。

我认为传递一个指针就足够了?为什么我没有通过引用得到这种奇怪的行为>

【问题讨论】:

  • 如果按值传递指针,则看不到指针的修改(head = prev),而通过引用则可以。

标签: c++ pointers linked-list


【解决方案1】:

C++ 中的局部变量(存储在堆栈中)具有块作用域,即它们在定义它们的块被执行后超出作用域。

当您传递一个指向函数的指针时,会创建一个指针的副本,并且该副本就是传递的内容。一旦函数被执行,函数工作区中的变量就会超出范围。函数内创建的任何非静态自动变量都将被销毁。

当您通过引用传递时,您不会传递变量的副本,而是传递实际变量,因此对变量所做的任何更改都会反映在传递给函数的实际变量上(通过引用)。

我想指出,指向下一个节点的指针存储在内存中,并且有一个指向它存储位置的地址。所以如果你不想通过引用传递,你可以这样做:

  1. 使用指向指针的指针,该指针指向存储指向下一个节点的指针变量(地址)的内存位置
  2. 将 this 传递给函数(不是通过引用)
  3. 取消引用指针并存储您要指向的新地址。

我知道这有点令人困惑,但请查看将节点添加到链表的一小段代码。

void addNode(Node** head, int newData)
{
    Node* newNode = new Node;
    newNode->data = newData; // Can also be done using (*newNode).data
    newNode->next = *head;
    *head = newNode;
}

【讨论】:

  • 什么意思:“一旦函数执行完毕,我们就超出了函数的作用域”?
  • @codekaizer : 程序的内存被划分为段——数据段、堆、栈等。数据是存储初始化的全局和静态变量的地方。堆用于动态内存分配,栈用于存储函数参数、局部变量和函数相关信息。当我们输入一个函数时,它会在堆栈中分配一定的内存,并在这个工作空间中创建函数中定义的变量。当我们退出函数时,函数中创建的变量将被删除(如果是非静态的),您将无法再访问它。
  • 是的,我不断在 MATLAB、C++ 和 python 之间切换,只是有点困惑。感谢您指出。
  • 推荐编辑:“因此,在函数中创建的任何变量(非静态)都将被销毁。” ->“在函数内创建的任何非静态自动变量都将被销毁。”原始允许动态变量被破坏,这是不正确的。
【解决方案2】:

我认为传递一个指针就足够了吗?

void reverseLinkedList(Node* head) // pass pointer by value
                                   // head is a copy here

按值传递pointer 会创建要在函数内部使用的副本。

在函数内部对该指针所做的任何更改只会反映在函数范围内。
因为这些更改只反映在指针的副本中,而反映在原始指针中。

一旦pointer(副本)超出范围,由于生命周期结束,这些更改将被“丢弃”。

因此,您需要参考。

void reverseLinkedList(Node&* head) // changes made in head will be
                                    // reflected in original head pointer

【讨论】:

    【解决方案3】:

    当你定期传递一个指针(IE 按值)时,它会创建一个指针的副本。对此指针所做的任何更改都不会影响原始指针。

    通过引用传递指针是发送对该指针的引用(非常类似于将指针传递给指针),因此对该指针所做的任何更改都会影响其“原始”状态。

    例如:

    //WRONG does not modify the original pointer, causes memory-leak.
    void init(Object* ptr, int sz) {
        ptr = new T[sz]; 
    }
    

     //Correct original pointer is a set to a new block of memory
    void init(Object*& ptr, int sz) {
        ptr = new T[sz]; 
    }
    

    【讨论】:

    • **有点切线,实际上很少有理由反转双向链表,因为您可以简单地向后迭代它并为自己节省 O(n)。虽然它总是很好练习!
    • 以上假设您有一个尾指针来记住列表的末尾。没有它,您将遭受 O(n) 查找列表末尾以便向后迭代的痛苦。
    • 大多数双向链表实现都包含Node* last,因此您可以将O(1)添加到列表的末尾
    【解决方案4】:

    void reverseLinkedList(Node* head)
    

    指针是按值传递的。

    这听起来很傻,这是一个该死的指针,对吧?通过引用传递的定义类型。嗯,被指向Node 是通过引用传递的。 指针本身head 只是另一个变量,恰好包含其他变量的地址,它不是通过引用传递的。

    所以head 包含用于调用reverseLinkedListNode 指针的副本,并且与所有按值传递的参数一样,对副本的任何修改,指向其他地方的head,都不会在调用函数。

    【讨论】:

      猜你喜欢
      • 2019-06-11
      • 2014-09-08
      • 2014-09-11
      • 1970-01-01
      • 2014-07-27
      • 2023-03-03
      • 2019-10-15
      • 2021-02-28
      相关资源
      最近更新 更多