我的链接列表实现有两个功能:AddToTail
和PrintList
。
AddTotail
会将节点添加到尾部。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()`不会受我的职能影响......
答案 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 = ...
pAdd
是AddToTail
函数中的自动变量。函数返回时会破坏自动变量。
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;
}