【问题标题】:Why does using pointer of a pointer in linked list give me different value of the list head pointer为什么在链接列表中使用指针的指针会给我不同的列表头指针值
【发布时间】:2017-11-19 00:24:35
【问题描述】:

有两个函数适用于我的链表实现: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 的值在我添加新节点后或在函数中发生了变化。

我认为这是因为我在函数中使用了对指向指针的指针的引用。但是,如果我 dont use references, thepHeadfrommain()` 不会受到我的功能的影响...

【问题讨论】:

标签: c++ pointers dereference


【解决方案1】:

问题:

在您的代码中,您有一个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 作为引用传递,因此您的原始变量现在指向一个对象...分配在堆栈上在函数范围内这不是个好主意!。一旦你的函数returns,以前是pAdd 的内存块不再有效使用!

错误假设:

等一下……如果那部分内存的使用确实是无效的,那为什么当我们调用时:

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

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

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

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

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

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

解决办法是什么?

其实有很多。我们使用C++,所以我认为没有理由不将列表操作实现为成员函数,但我们不要对原始代码进行太多更改。首先,让我们删除一层“...points to...”,而不是:

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&lt;T&gt; 作为上述类中的私有类,以提高代码的可读性和一致性

【讨论】:

    【解决方案2】:
    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。行为未定义。

    【讨论】:

    • pAdd 是 AddToTail 函数中的自动变量。函数返回时自动变量被破坏。那么为什么我使用pHead = pAdd时pAdd不会破坏(pHead是ListNode的指针)
    • @Leslie pAdd 将被销毁。但是ListNode* pHead 并不指向pAdd,因此您最终不会取消引用指向无效对象的指针。
    【解决方案3】:

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

    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;
    }
    

    【讨论】:

    • 我之前做过,但我觉得不完美,因为我在使用列表时需要做更多的工作。
    • @Leslie 我不明白你的意思。虽然其他人建议通过引用传递指针,但这通过指针传递指针。否则它会做同样的事情。
    猜你喜欢
    • 1970-01-01
    • 2020-04-17
    • 1970-01-01
    • 2012-08-11
    • 2013-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-20
    相关资源
    最近更新 更多