我最近收到了一份数据结构课程的大学作业,要求我用 C++ 创建一个双向链表。我想开门见山。在处理我的双向链表时,我需要实现各种功能,但特别引起我注意的一种方法是“clear()”。该方法负责清除双向链表中的所有元素:
void clear(Node* head_ptr)
{
Node* previous_ptr = nullptr;
while(head_ptr != nullptr)
{
previous_ptr = head_ptr; // Store previous node.
head_ptr = head_ptr->next;
delete previous_ptr;
}
};
方法非常简单;它只是迭代所有元素并为每个节点释放内存。然后我在我的析构函数中
调用此方法,如下所示:
~List()
{
clear_list(m_head_ptr);
};
然后我开始思考。这种释放内存的方法很好如果我的节点元素位于堆上
,如下所示:
int main()
{
List list;
Node* node_1 = new Node(3, nullptr); // The tail node.
Node* node_2 = new Node(1, node_1);
Node* node_3 = new Node(5, node_2);
Node* node_4 = new Node(7, node_3); // The head node.
list.add(node_1);
list.add(node_2);
list.add(node_3);
list.add(node_4);
// Then do some stuff with the list...
} // The list goes out of scope and the destructor is called...
但是,一旦我创建堆栈上的节点
并将指针传递给堆栈对象,就会中断,如下所示:
int main()
{
List list;
Node* node_1(3, nullptr); // The tail node.
Node* node_2(1, node_1);
Node* node_3(5, node_2);
Node* node_4(7, node_3); // The head node.
list.add(&node_1);
list.add(&node_2);
list.add(&node_3);
list.add(&node_4);
// Then do some stuff with the list...
} // The list goes out of scope and the destructor is called and the program crashes because it attempts to free stack objects...
原因是因为我正在尝试释放堆栈对象,这不是一个好主意
。当然,我们通常不会使用基于堆栈的节点,因为我们通常希望节点数据在它们创建的范围之外持续存在。尽管如此,这还是让我想到了我的问题: 我该如何应对? (有没有办法检查
内存中的某个节点是否在我的函数中的堆或堆栈上,然后相应地释放它?或者是否有更好的方法来解决这个问题?)
让列表来管理节点本身的分配。用户甚至不需要知道 node
类型的存在,更不用说分配它们等等。
List list;
list.add_head(1);
list.add_head(3);
list.add_head(5);
list.add_head(7);
您通常还可以使用
add_tail
将项目添加到列表末尾。每个
add
(头或尾)通常应该返回一个抽象 iterator
类型(它可能是指向节点的指针的包装器),因此您可以执行以下操作:auto pos = list.add_head(7);
list.add_tail(5);
list.add_after(pos, 3);
...这会在开头添加 7,在结尾添加 5,并在
3
之后添加
7
。这样,列表本身就分配了所有节点,并知道如何处置它们。您可以更进一步,将分配和处置委托给 Allocator
类。这当然很有用,但可能有点超出了基本练习的意义(在实际使用中,您可能想使用标准库中的容器——虽然标准库确实提供了单向和双向——链表,它们
很少有用)。