如何全局而不是本地更改链接列表头指针

时间:2014-05-18 05:15:48

标签: c++ pointers linked-list

我有两个这样的实现。

为什么这个特定的实现不起作用?我有一个指向指针的指针,我改变了内部点,但它没有保留主函数的变化

#include <iostream>
using namespace std;

struct Node {
    int value = 4;
    Node* next;
};

void insertFront(Node*, Node**);

int main(){

    Node head;
    head.value = 32;
    head.next = nullptr;

    Node** headptr = new Node*;
    (*headptr) = new Node;
    *(*headptr) = head;
    (*headptr)->value = 32;//redundant, I know
    (*headptr)->next = nullptr;//redundant, I know
    cout << head.value << endl;
    insertFront(new Node, headptr);
    cout << head.value << endl;

    system("pause");
    return 0;
}

void insertFront(Node* newHead, Node** head2){
    cout << "Inside before swap " << (*head2)->value << endl;
    newHead->next = *head2;
    *head2 = newHead;
    cout << "Inside after swap " << (*head2)->value << endl;
}

为什么这个有效?有人可以在IN DETAIL中解释指针魔法在进行吗?我有一个模糊的想法,但我仍然有点困惑。我知道使用指向头指针的指针允许你全局改变头地址,但它仍然有点混浊。有人可以澄清,这两个实现中的指针是怎么回事?

#include <iostream>
using namespace std;

struct Node {
    int value = 4;
    Node* next;
};

void insertFront(Node*, Node**);

int main(){

    Node** head = new Node*;

    (*head) = new Node;
    (*head)->value = 32;
    (*head)->next = nullptr;
    cout << (*head)->value << endl;
    insertFront(new Node, head);
    cout << (*head)->value << endl;

    system("pause");
    return 0;
}

void insertFront(Node* newHead, Node** head2){
    cout << "Inside before swap " << (*head2)->value << endl;
    newHead->next = *head2;
    *head2 = newHead;
    cout << "Inside after swap " << (*head2)->value << endl;
}

2 个答案:

答案 0 :(得分:3)

两种实现都使用双间接错误,并且都是泄漏内存。你的问题似乎更多的是关于双重间接,而不仅仅是关于什么有效,什么无效(无论你是否意识到)。它是一个C问题,虽然也适用于C ++,但使用该语言的情况则不那么好,因为参考参数使这更容易(可以说)。

我可以简单地说&#34;使用对指针的引用&#34; (你可以这样做),但那就像你说的那样#34;为什么我的车没有工作?&#34;和我回答&#34;因为这辆车在这里工作&#34;。因此,我将提供一个C答案(令我对自己的常识感到沮丧,因为我能感觉到炉子从火焰喷射器中燃烧起来即将被送到我的路上)。如果我有时间,我将包含C ++答案(使用引用),但不保证。


指针指针与任何其他指针类型没有区别。所有指针类型都是将变量定义为&#34; point&#34;对于那种类型的东西(我知道,它重复和微不足道,但在这里忍受我)。简单的例子:

void foo(int x) 
{ 
    x = 5; 
}

显然在调用者方面没有改变x,你似乎非常清楚这一点。如果要使用指针更改输入/输出参数,则需要将形式参数声明为指针类型,在函数体中取消引用指针参数,并从调用者传递地址。即

void foo(int *p)
{
    *p = 5;
}

int main()
{
    int x = 0;
    foo(&x);
}

事实是参数都是C中的传值,甚至是指针参数。是的,再读一遍。你说什么?认真。这是真的。它恰好发生了&#34;值&#34;你传递的是一个地址而不是某个变量中的值,因此,接收者必须是准备通过该地址获取和操纵数据的东西:指针。

现在。 指针的指针也不例外。指针的指针包含(等待它......)指针的地址。就像我们的第一个例子一样,这个:

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

vod foo(Node* ptr)
{
    Node *p = new Node();
    p->data = 0;
    p->next = ptr;
    ptr = p;
}

int main()
{
    Node *root = nullptr;
    foo(root);
}

赢得了 。您可以通过多种方式解决此问题一种方法是使用指向指针(C方式)。另一个使用引用指针(C ++方式)。

首先是C方式,它演示了通过地址传递内容的全部咒语,意味着将参数声明为指针类型(在这种情况下是指向指针类型的指针),并传递要修改的东西的地址:

void foo(Node** ptr)
{
    Node *p = new Node();
    p->data = 0;
    p->next = *ptr;
    ptr = p;
}

int main()
{
    Node *root = nullptr;
    foo(&root); // LOOK: passing address of our root pointer
}

您是否知道如何使用intint*,我们必须传递我们正在修改的内容的地址一个带指针到类型的函数?在这种情况下,&#34;类型&#34;它本身就是指针类型。

现在,有争议的是,使用引用的C ++方式相比之下是琐事,但恕我直言,它并不清楚发生了什么,只是因为字面上有一个单一不起作用的版本与版本之间的字符差异。看看这个:

vod foo(Node*& ptr) // LOOK added &
{
    Node *p = new Node();
    p->data = 0;
    p->next = ptr;
    ptr = p;
}

int main()
{
    Node *root = nullptr;
    foo(root);
}

请注意其他所有内容的版本相同。每个人都有他们的偏好,并且知道要查找什么允许我使用任何一种方法,但我可以看到为什么有些人在编写和调试隐藏在引用类型中的本质上是双向间接代码时有这么困难。有些工程师喜欢将所有他们的out-params作为指针类型发送,而我通常是其中之一。


剥离您的代码

在完成所有这些之后,让我们剥离你的代码,看看事情到底发生了什么。我会剖析那些工作的那个,希望你能看出为什么 版本都不是真的非常好:

首先是你的类型:

struct Node
{
    int value = 4;
    Node* next;
};

这里没有什么值得怀疑的。结构定义中的默认值赋值。这将嘲笑非当前的C ++,所以很可能暂时抛出它。如果你想要一个默认值,那就做一个构造函数(无论如何你应该确保所有成员都正确地初始化为某些东西):

struct Node
{
    int value;
    Node* next;

    Node(int val = 4)
        : value(val)
        , next()
    {}
};

确定。接下来......

void insertFront(Node*, Node**);

您似乎想要使用纯节点接口。编写链表的大多数人都会这样做:

void insertFront(Node** ppRoot, int value);

但我们现在就开始使用您的版本。实际执行:

void insertFront(Node* newHead, Node** head2)
{
    newHead->next = *head2;
    *head2 = newHead;
}

是对的。是的它可以孤立newHead->next以前指出的任何东西,但这似乎不是你的担忧,所以我们现在就一直关注它。

最后是torrent:main()

int main()
{
    Node head;
    head.value = 32;
    head.next = nullptr;

    Node** headptr = new Node*;
    (*headptr) = new Node;
    *(*headptr) = head;
    (*headptr)->value = 32;//redundant, I know
    (*headptr)->next = nullptr;//redundant, I know
    cout << head.value << endl;
    insertFront(new Node, headptr);
    cout << head.value << endl;

    system("pause");
    return 0;
}

这有多个问题。首先,混合动态节点与非动态节点。

Node head;
head.value = 32;
head.next = nullptr;

糟糕的主意。调用代码(特别是 cleanup 代码从列表中删除每个节点)这是不合理的方式,有任何线索是否是指向的是动态的。 不要这样做。。使用上面的Node的构造函数版本,这应该只是:

Node* head = new Node(32);

接下来,您将动态分配指针; (不是节点; 指针

Node** headptr = new Node*;

糟糕的主意。没有必要这样做。你已经有了一个指向你的列表头的指针变量(它被称为,而不是巧合,head)。这看起来都是用于调用插入函数的设置。要做到这一点,{em>来自Node** headptr = new Node*;的所有都可以简单地替换为:

insertFront(new Node(10), &head); // LOOK: passing address of head pointer
cout << head->value << endl;

答案 1 :(得分:1)

你使用指针的方式是如此,错了。

我们来看看这段代码:

Node** headptr = new Node*;
(*headptr) = new Node;
*(*headptr) = head;
(*headptr)->value = 32;//redundant, I know
(*headptr)->next = nullptr;//redundant, I know
cout << head.value << endl;
insertFront(new Node, headptr);
cout << head.value << endl;

让我们先清理一下这段代码。没有理由在免费商店中分配Node *(使用new),然后通过Node **引用它。它可以而且应该只是一个局部变量并直接引用。为此,我们仅使用Node** headptr = new Node*;替换Node *phead,并仅使用(*headptr)替换phead的所有实例:

Node* phead;
phead= new Node; // #2
*phead= head;    // #3
phead->value = 32;//redundant, I know
phead->next = nullptr;//redundant, I know
cout << head.value << endl;
insertFront(new Node, &phead);  // here we are passing the address of phead so that insertFront() can modify it
cout << head.value << endl;

现在仔细看看这段代码。您在第2行为新Node分配了空间,并使phead指向它。您head的内容复制到第3行的新Node中。然后,您的insertFront()调用修改了新分配的节点并将phead设置为而是指向新节点。任何指针永远不会指向head,它的值永远不会被触及;当你检查head.value时,它们当然保持不变。