【问题标题】:How does reference to pointer exactly work in C++, and when do we need them (in the case of linked list)对指针的引用在 C++ 中是如何工作的,我们什么时候需要它们(在链表的情况下)
【发布时间】:2020-05-13 05:09:26
【问题描述】:

我知道指针保存变量的地址。并且引用指向符号表中的相同地址(即,它们被分配到的变量的相同地址)。

我的问题是,对指针的引用究竟是如何工作的。我们什么时候需要它们,而不是单独使用指针(而不是使用对指针的引用)。如果您能向我解释相对于单链表使用对指针的引用,那将会很有帮助。

我有以下代码使用函数删除链表的头指针:

struct Node
{
    int data;
    Node* next;
};

struct Node* newNode(int data)
{
    Node* temp = new Node;
    temp->data = data;
    temp->next = nullptr;
    return temp;
}

Node* deleteHead(Node* &head)
{
    if (head)
    {
        Node* temp = head;
        head = head->next;
        delete temp;
    }
    return head;
}

int main()
{
    Node* head = newNode(1);
    head->next = newNode(6);
    head->next->next = newNode(4);
    head->next->next->next = newNode(8);

    head = deleteHead(head);
    Node* temp = head;
    while (temp != nullptr)
    {
        cout << temp->data << " " << endl;
        temp = temp->next;
    }
    return 0;
}

deleteHead(Node* &amp;head) 函数中,该函数接受参数Node* &amp;head。但是,代码工作正常,即使参数是Node* head。在什么情况下我们需要在链表中传递Node* &amp; 而不是Node*

下面是上面的deleteHead(Node* &amp;head)函数,如果我们只使用Node* head作为参数,而不是Node* &amp;head-

【问题讨论】:

  • head = deleteHead(head); 有效,但没有参考 deleteHead(head); 不会。归根结底,指针只是另一个变量(该值恰好是一个地址),如果您想更改该变量的值并使更改粘在函数之外,则需要通过引用传递变量。
  • “对指针的引用在 C++ 中是如何工作的”它的工作方式与对任何其他类型的变量的工作方式完全相同
  • 附带说明,您所做的看起来很像链表的 C 实现。 C++™ 的奇妙魔法触手可及——创建一个 List 类并将所有 Node 逻辑封装在其中,这样您的列表用户就不必担心任何问题!
  • 您真的需要参与到链表中来了解何时使用对指针的引用吗?像这样简单的事情呢? void foo(int *x) { x = new int;} int main() { int *p=nullptr; foo(p); } -- 请注意,在调用 foo 之后,p 仍然是 nullptr。为什么?你是怎么解决的?

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


【解决方案1】:

所以,

引用和指针都包含变量/内存的地址。 引用具有变量的语义(如果设置值,则将数据写入引用的内存), 指针具有指针的语义(如果您设置了值,则指向的内存不会改变),您可以设置指向其他内存的指针。

关于 deleteHead(Node* &head) - 你使用包含 Node 指针的真实变量的引用。该函数在同一个变量中返回head的新值,也作为返回值。

【讨论】:

    【解决方案2】:

    通过引用传递指针的原因与通过引用传递非指针的原因相同:让函数修改其值。

    让我用一个更简单的例子

    #include <iostream>
    
    void foo(int*& x) {
        *x = 42;        // change the value of the int x points to
        x = nullptr;    // change the value of x
    }
    

    第一行修改了x指向的值(但不修改x)。第二行修改x 本身。

    int main() {
        int y = 42;
        int* y_ptr = &y;
        foo(y_ptr);
        if (y_ptr == &y) std::cout << "cannot happen";
    }
    

    因为我们设置了x = nullptr,所以调用后y_ptr 将不再指向y

    现在,如果我们修改 foo 以不获取引用,我们会得到:

    #include <iostream>
    
    void foo(int* x) {
        *x = 42;        // change the value of the int x points to
        x = nullptr;    // change the value of x
    }
    

    第一行再次修改了x 指向的int。但是,现在第二行只对函数本地的x 有影响。

    int main() {
        int y = 42;
        int* y_ptr = &y;
        foo(y_ptr);
        if (y_ptr == nullptr) std::cout << "cannot happen";
    }
    

    y_ptr 的值不能通过传递给foo 来改变,因为它是按值传递的。

    在你的代码中你有

    Node* deleteHead(Node* &head)
    {
        if (head)
        {
            Node* temp = head;
            head = head->next;
            delete temp;
        }
        return head;
    }
    

    当你写head = deleteNode(head) 时,发生了两件事:

    • 函数修改head(因为它是通过引用传递的)指向head-&gt;next
    • 该函数还返回这个“新”头(指向head-&gt;next)并分配给head

    所以你基本上分配给headtwice。因为head 是通过引用传递的,deleteNode 会在不使用返回值的情况下做正确的事情:

    deleteNode(head);  // this already does modify head 
    

    ...或者反过来说:如果您从函数返回“新”头(head-&gt;next)并将其分配给head,那么您是否通过引用传递指针并不重要,因为在函数内部完成的赋值具有相同的效果。

    你的代码类似于

    int* bar(int*& x) {
       x = nullptr;
       return x;
    }
    

    然后通过调用它

    int y = 42;
    int* y_ptr = &y;
    y_ptr = bar(y_ptr);
    

    不使用返回值bar(y_ptr)也可以达到同样的效果。或者没有指针也一样(因为指针在这里真的没有区别):

    int moo(int& x) {
        x = 0;
        return x;
    }
    
    int x = 42;
    x = moo(x);     // same as `moo(x)`
    

    PS:你不需要两者(返回指针并已经在函数中分配它),所以最好让函数返回void

    【讨论】:

    • 您能否指出一些我应该在函数中(例如,在链表中)使用指针引用的场景?
    • @VikramSingh 你的deleteNode 是最好的例子。你应该让它什么都不返回(void),因为这只是打开了错误使用它的大门(调用者忘记使用返回值是什么?)
    【解决方案3】:

    引用是与值语义一起使用的“安全指针”(这在运算符重载上下文中非常有用),因此引用的用法与 C 中指针的用法非常相似,除了以下几点:

    1. 引用保存单个值而不是数组
    2. 引用不为空(这并不总是可取的)

    这意味着您可以(或应该)在想要更改原始传递的变量(而不是它的副本)时传递引用。

    也就是说,与您的函数等效的 C(粗略)是 Node* deleteHead(Node** head)。 请注意,由于您传递了一个引用,原来的 head 变量被修改了,因此您的函数变得有点奇怪,因为它既修改了 head 又返回了它的值。 您可以使用以下选项之一:

    (1) 删除 head (如果列表大小非空)并返回指向下一个元素的指针,这是不可取的,因为它将 head 作为悬空指针。这是您的原始函数,但它没有收到引用。

    Node* deleteHead(Node* head)
    {
        if (head)
        {
            Node* temp = head; // You might want to use auto
            head = head->next;
            delete head;
        }
        return head;
    }
    

    (2) 与您的函数相同,但不返回任何值(因为您已经修改了 head)。如果不传递引用,这个将无法工作。

    void deleteHead(Node* &head)
    {
        if (head)
        {
            Node* temp = head->next; 
            delete head; // deletes the content of head
            head = temp;
        }
    }
    

    【讨论】:

    • *References are "safe pointers" 是有问题的。通常根本不涉及指针。描述引用的更好方法是将其称为另一个对象的别名。另请注意,您可以通过引用传递数组。
    • 引用是任何体面的编译器(clang、g++和cl)中的指针,你可以在windbg/gdb中自己检查。确实,该标准没有说明任何关于引用实现的内容,但根本没有其他方便的方法可以做到这一点。当您说我可以将数组作为参考传递时,您可能是指 T[] 修改了底层元素,但这又是一次,因为 T[] 是 T* const 的 C++ 别名。
    【解决方案4】:

    对于指针deleteHead函数的引用,你可以使用head = deleteHead(head)或deleteHead(head),因为head只是main函数中head的引用,deleteHead函数中对head的任何改变实际上都应用在主函数中的头变量。对于指针版本,您必须使用 head = deleteHead(head)。

    【讨论】:

      猜你喜欢
      • 2010-10-28
      • 2013-07-14
      • 2012-01-04
      • 2020-11-08
      • 1970-01-01
      • 2013-04-06
      • 1970-01-01
      • 2016-01-16
      相关资源
      最近更新 更多