有人可以运行此代码并告诉我为什么插入中的节点不断覆盖吗?
#ifndef LinkedList_hpp
#define LinkedList_hpp
#include <stdio.h>
#include <utility>
template<class T>class LinkedList{
public:
LinkedList(){
head = nullptr;
tail = nullptr;
size = 0;
}
//void insert(T val);
class Node{
public:
Node* next;
T* value;
Node* prev;
Node(T* value){
this->value = value;
}
Node(){
}
Node(T* value,Node* prev, Node* next){
this->value = value;
this->next = next;
this->prev = prev;
}
Node* operator=(const Node& node){
this->value = node.value;
this->prev = node.prev;
this->next = node.next;
return *this;
}
};
public:
Node* head;
Node* tail;
int size;
void insert(T val){
在此行中,如果前一个 head 为 10,则当前 val 40 将覆盖旧的 head 值并插入一个 val 为 40 的新节点
Node* temp = new Node(&val);
if(head==nullptr){
head = temp;
tail = temp;
}else{
temp->next = head;
head->prev = temp;
head = temp;
}
size++;
}
#endif
#include <iostream>
#include "LinkedList.hpp"
int main(int argc, const char * argv[]) {
// LinkedList<int> t;
int h = 7;
int j = 10;
int k = 40;
LinkedList<int>* list1 = new LinkedList<int>();
list1->insert(h);
list1->insert(j);
list1->insert(k);
return 0;
}
每次调用 insert 并构造一个新节点时,它都会覆盖旧值,并且所有内容都变为当前 Val
void insert(T val){
val
是此函数的参数。这个对象,这个val
,只存在到这个函数返回。此时它被销毁,就像在函数内部的非静态作用域中声明的其他所有内容一样。这就是C++的工作方式。一旦insert()
回来,这val
就不再了。它不复存在。它去见它的制造者。它变成了一个前对象,不再存在,完全是过去。
您的insert()
函数执行以下操作:
Node* temp = new Node(&val);
您将指向此val
参数的指针传递给Node
的构造函数,然后Node
将指向参数的指针保存到insert()
,作为它自己的类成员。
这很好,但是一旦insert()
返回,new
-edNode
中保存的指针就会变成指向已销毁对象的指针,取消引用此指针将成为未定义的行为。
然后,您稍后尝试取消引用不再指向有效对象的原始指针。
这解释了代码中观察到的未定义行为。
最重要的是,您的类和模板的设计存在根本缺陷。Node
使用指针没有明显的目的。Node
应该简单地将T
存储为自己的类成员,作为value
,而不是value
指向存在于某处的其他T
的指针,并且可以随时被销毁,这不受Node
的控制。
所示代码中的另一个问题是Node
的两个构造函数无法初始化next
并prev
指向NULL
的指针。这也将导致未定义的行为。
void insert(T val)
按值获取其参数,因此val
是本地副本而不是原始副本。
Node* temp = new Node(&val);
存储指向此本地副本的指针。副本超出范围,因此插入退出后您看到的是内存中存在的不再有效的幽灵。在这种情况下,重影似乎始终持有最后一个值集。
溶液:
智能方式:直接存储Node::value
,而不是作为需要与节点一起保持活动状态的指针。这种方式的内存管理要少得多。
T* value;
成为
T value;
和
Node(T* value){
this->value = value;
}
成为
Node(T value){
this->value = value;
}
value
的其他用途必须相应地更新。一般来说,new
非常令人头疼,应该谨慎使用。
愚蠢的方式:通过引用传递
void insert(T &val)
以便指针指向寿命更长的原始文件。