对指针的引用在 C++ 中是如何工作的,我们什么时候需要它们(在链表的情况下)

How does reference to pointer exactly work in C++, and when do we need them (in the case of linked list)

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

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

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

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* &head) 函数中,函数接受参数 Node* &head。但是,即使参数为 Node* head,代码也能正常工作。在什么情况下我们需要在链表中传递 Node* & 而不是 Node*

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

,它的工作原理是一样的

所以,

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

关于 deleteHead(Node* &head) - 您使用包含 Node 指针的实变量的引用。函数 returns head 在同一变量中的新值和 return 值也是如此。

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

让我用一个更简单的例子

#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->next
  • 函数也return这个"new"头(指向head->next)并且分配给head

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

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

...或者反过来说:如果您 return 函数中的 "new" 头部 (head->next) 并将其分配给 head,则如果通过引用传递指针并不重要,因为在函数内部完成的赋值具有相同的效果。

您的代码类似于

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

然后通过

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

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

int moo(int& x) {
    x = 0;
    return x;
}

int x = 42;
x = moo(x);     // same as `moo(x)`

PS:你不需要两者(return 指针并在函数中分配它),所以最好使函数 return void.

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

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

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

也就是说,相当于你的函数的C(rought)是Node* deleteHead(Node** head)。 请注意,由于您传递了引用,因此修改了原始 head 变量,因此您的函数变得有点奇怪,因为它同时修改了 head 和 returns 它的值。 您可以使用以下选项之一:

(1) 删除 head(如果列表大小非空)和 returns 指向下一个元素的指针,这是不可取的,因为它会使 head 成为悬空指针。这是您的原始功能,但没有收到参考。

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

(2) 和你的函数一样,但是returns没有值(因为你已经修改了head)。如果不传递参考,这个将无法工作。

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

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