我正在学习c++,所以我写了一个带有一些基本实用程序的链表实现。我的插入函数接受一个节点指针,并迭代到列表的末尾,以附加一个新初始化的Node
对象,p。令我惊讶的是,对insert
方法的重复调用似乎共享节点p
!因此,在多次调用之后,insert
会将p->next
赋值给自己,从而引发一个无限循环。
#include <iostream>
#include <string>
using namespace std;
struct Node {
Node *next;
int val;
};
void insert(Node *n, int val) {
Node * cur = n;
while (cur->next) {
cout << "Val " << cur->val << "n";
cur = cur->next;
}
Node p = {0, val};
cur->next = &p;
}
int main() {
Node n1;
n1.val = 1;
insert(&n1, 2);
insert(&n1, 3);
insert(&n1, 4);
return 0;
}
我通过gdb运行这个命令来查看insert中发生了什么。实际上,在对insert
的第二次调用中,我发现p
在执行Node p = {0, val};
之前已经初始化,并且以后的调用保留了p->next
的相同的值,尽管显式地将其设置为0。我似乎在我的理解上有一个差距,因为把它翻译成C会产生同样的错误。我只能用malloc来修复,这不是我想要的。
为什么会发生?p
在每次调用时分配到堆栈上。
在insert
函数返回后,局部变量cur
和p
被销毁,内存空间可供重用。在变量被销毁之后,即使使用指针也不允许访问它,因为内存空间可以被其他东西重用。编译器不会聪明到阻止你这样做。
正如你所看到的,这不仅是理论上的可能性,而且实际上一直在发生。如果你只是再次调用完全相同的函数,你很可能会得到完全相同的内存安排,所以变量p
第二次将使用它第一次使用的完全相同的内存空间。如果调用不同的函数,如printf
(或cout <<
)而不是insert
,您可能会发现空间被printf
中的一些变量覆盖。
这就是为什么每个单独的链表教程告诉你使用malloc
。除了全局变量之外,这基本上是创建一些变量的唯一方法,当函数返回时不会被删除。一个"variable"由malloc
创建的直到您free
它才会被删除。(它也没有名称,所以指针是使用该变量的唯一方式,这一事实似乎使许多人感到困惑。)
@user53751非常正确,您的问题源于可变生命周期问题。然而,malloc
和free
并不是用来实现动态分配内存的合适的c++工具。相反,您应该使用new
和delete
来实现此目的,特别是因为它们不仅为对象分配正确大小的内存,而且还运行默认构造函数,或者像我在下面写的那样,初始化指向nullptr
的next
指针。
您可能还希望将insert
作为Node
的成员函数,因为c++允许这样做。
struct Node {
Node *next = nullptr;
int val;
void insert(int val) {
Node *temp;
for (temp = this; temp->next; temp = temp->next);
temp->next = new Node;
temp->next->val = val;
}
};
int main() {
Node *a = new Node;
a->val = 1;
a->insert(2);
a->insert(3);
for (Node *temp = a; temp; temp = temp->next) {
std::cout << temp->val << std::endl;
}
return 0;
}
还建议定义析构函数,以便您可以轻松地清理动态分配的内存。以简单、朴素的递归方式显示,用于演示。
struct Node {
Node *next = nullptr;
int val;
void insert(int val) {
Node *temp;
for (temp = this; temp->next; temp = temp->next);
temp->next = new Node;
temp->next->val = val;
}
~Node() {
if (next) delete next;
}
};