为什么在链表中使用指针指针给出了列表头指针

时间:2017-11-19 00:24:36

标签: c++ pointers dereference

我的链接列表实现有两个功能:AddToTailPrintListAddTotail会将节点添加到尾部。PrintList将打印列表中的第一个节点。

我试图实现这个想法:

#include <iostream>

struct ListNode
{
    int m_nKey;
    ListNode* m_pNext;
};


void AddToTail(ListNode** &pHead, int value)
{
    ListNode* pAdd = new ListNode();
    pAdd->m_nKey = value;
    pAdd->m_pNext = nullptr;

    if(pHead==nullptr)
    {
        pHead = &pAdd;
    }
    else
    {
        ListNode* pTmp = *pHead;
        while(pTmp->m_pNext!=nullptr)
        {
            pTmp = pTmp->m_pNext;
        }
        pTmp->m_pNext = pAdd;
    }
}


void PrintList(ListNode** &pHead) {
  if (pHead==nullptr) return;

  std::cout << (*pHead)->m_nKey << std::endl;
}


int main() {
  ListNode** pHead = nullptr;
  AddToTail(pHead, 1);
  std::cout << (*pHead)->m_nKey << std::endl;
  AddToTail(pHead, 2);
  std::cout << (*pHead)->m_nKey << std::endl;
  PrintList(pHead);
  return 0;
}

输出如下:

1
2
-306755624

与预期的不同:

1
1
1

问题是在添加新节点或在函数中更改了*pHead的值。

我认为是因为我在函数中使用了指向指针的指针。但是,如果我不t use references, the pHead from main()`不会受我的职能影响......

3 个答案:

答案 0 :(得分:2)

问题:

在您的代码中,您有一个ListNode** pHead = nullptr,它是指向变量的指针。现在让我们看看你在做什么:

AddToTail(pHead, 1);

好的,这似乎很好。你的AddToTail函数需要一个指向ListNode指针的指针,这正是你给它的!到目前为止没有什么可疑的......但是我们不必为了这里的错误而进一步了解......

更好地了解您的功能,我们可以看到您:

void AddToTail(ListNode** &pHead, int value)
{
    ListNode* pAdd = new ListNode();
    pAdd->m_nKey = value;
    pAdd->m_pNext = nullptr;

    if(pHead==nullptr)
    {
        pHead = &pAdd;
    }

    // the rest of the code...
}

在这里,事情开始变得肮脏......付出关闭关注你刚才写的内容:

pHead = &pAdd;

由于您已将pHead作为参考传递,因此您的原始变量现在指向一个对象... 在函数范围内分配 这不是个好主意!。只要你的函数return s,之前为pAdd的内存块就不再有效了!

错误的假设:

等一下......如果该部分内存的使用确实无效,那么为什么我们打电话时:

std::cout << (*pHead)->m_nKey << std::endl;

AddToTail(pHead, 1)之后,它给出了正确的数字? 我的输出显示为1

是。您的输出显示了您希望在列表的第一个节点中具有的内容。如果使用该内存无效,为什么不打印一些垃圾值呢?因为包含该部分内存的堆栈尚未清除。

请记住,我所指的内存的部分是一个指针 - 一个表示地址的简单值。这就是为什么只要这个块没有被清除,我们就能看到它并推断出我们真正想要指向的对象在哪里。一旦堆栈被清除,地址就变成了垃圾值,并且正在查看某个随机值指向的随机值。调用std::cout << (*pHead)->m_nKey << std::endl后,您可以看到堆栈将被清除,因为如果您尝试运行:

std::cout << (*pHead)->m_nKey << std::endl;
std::cout << (*pHead)->m_nKey << std::endl;

而不是一次调用std:: << ...,第二次调用实际打印一些垃圾值

解决方案是什么?

实际上,有很多。我们正在使用C++,所以我认为没有理由不将列表操作实现为成员函数,但是我们不要更改原始代码。首先,让我们放一个级别的“ ...指向... ”,而不是:

ListNode** pHead = nullptr;

我们将与之合作:

ListNode* pHead = nullptr;

因此,我们改变了我们的职能:

void AddToTail(ListNode* &pHead, int value) // the arguments have changed
{
    ListNode* pAdd = new ListNode();
    pAdd->m_nKey = value;
    pAdd->m_pNext = nullptr;

    if(pHead==nullptr)
    {
        pHead = pAdd; // notice the difference!
    }
    else
    {
        ListNode* pTmp = pHead; // here too!
        while(pTmp->m_pNext!=nullptr)
        {
            pTmp = pTmp->m_pNext;
        }
        pTmp->m_pNext = pAdd;
    }
}

当然,您的PrintList也需要更改,以及main()中的一些代码,但现在您的工作就是这样做。只是为了给你一些见解,这个:

ListNode* pHead = nullptr;
AddToTail(pHead, 1);
std::cout << pHead->m_nKey << ' ';
AddToTail(pHead, 2);
std::cout << pHead->m_nKey << std::endl;

打印 1 1

Post Scriptum:我会高度鼓励你改变你的实现方式,使得函数(而不是免费的)成为ListNode类的一部分,并且可能在所提到的内部使用node<T>作为私有类来提高代码的可读性和一致性

答案 1 :(得分:0)

void AddToTail(ListNode** &pHead, int value)

pHead是一个参考。它指的是函数AddToTail之外的变量。

ListNode* pAdd = ...

pAddAddToTail函数中的自动变量。函数返回时会破坏自动变量。

pHead = &pAdd;

将自动变量的地址存储在pHead引用的指针中。

AddToTail(pHead, 1);

AddToTail返回后,pHead指向已销毁的本地变量。取消引用pHead会有未定义的行为,因为它没有指向有效的对象。

std::cout << (*pHead)->m_nKey << std::endl;

在这里,您取消引用不指向有效对象的pHead。行为未定义。

答案 2 :(得分:0)

或者也可以尝试这样的事情:

struct ListNode
{
    int m_nKey;
    ListNode* m_pNext;
};


void AddToTail(ListNode **pHead, int value)
{
    ListNode* pAdd = new ListNode();
    pAdd->m_nKey = value;
    pAdd->m_pNext = nullptr;

    if (*pHead == nullptr)
    {       
        *pHead = pAdd;
    }
    else
    {
        ListNode *pTmp = *pHead;
        while (pTmp->m_pNext != nullptr)
        {
            pTmp = pTmp->m_pNext;
        }
        pTmp->m_pNext = pAdd;
    }
}


void PrintList(ListNode *pHead) {
    if (pHead == nullptr) return;

    std::cout << "list key:" << pHead->m_nKey << std::endl;
    PrintList(pHead->m_pNext);
}


int main()
{ 
    ListNode *pHead = nullptr;
    AddToTail(&pHead, 1);
    std::cout << pHead->m_nKey << std::endl;
    AddToTail(&pHead, 2);
    std::cout << pHead->m_nKey << std::endl;
    PrintList(pHead);

    return 0;
}