我最近收到了我的数据结构课程的大学作业,要求我用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;
}
};
字符串
该方法非常简单;它只是迭代所有元素并为每个Node
释放内存。然后我在析构函数中**调用此方法,如下所示:
~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...
型
但是,当我在堆栈上创建**Node
s**并传递一个指向堆栈对象的指针时,这个过程就被打破了,就像这样:
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
s,因为我们通常希望Node
数据持久化到它们创建的范围之外。然而,这导致了我的问题:
我该怎么反击?有没有办法检查内存中的一些Node
是否在我的函数中的堆或堆栈上,然后相应地释放它?或者,有没有更好的方法来解决这个问题?
2条答案
按热度按时间xkrw2x1b1#
最简单和最有效的解决方案是 * 通常 * 让列表自己管理节点的分配。用户甚至不需要知道
node
类型的存在,更不用说分配它们等等。所以,对于用户来说,你在问题中展示的内容的等价物是这样的:
字符串
你通常还需要一个
add_tail
来将元素添加到列表的末尾。每个add
(头或尾)通常应该返回一个抽象的iterator
类型(它可能是指向节点的指针的 Package 器),所以你可以这样做:型
.这将增加7在开始,5在结束,和
3
后的7
。这样,列表本身就分配了所有节点,并知道如何处置它们。您可以更进一步,让它将分配和处置委托给
Allocator
类。这当然很有用,但可能有点超出了基本练习的意义(在实际使用中,您可能希望使用标准库中的容器--尽管标准库确实提供了单链表和双链表,#21518;很少有用)。ngynwnxp2#
因为你刚刚开始学习动态内存,所以你必须从头开始学习(必须有人编写库-有一天可能是你!)。
所以,你会使用链表的一个原因是因为你不知道有多少节点会被提前添加。同样,在基于堆栈的节点数据的链表上放置“MAX_SIZE”的实现也不好(MAX_SIZE足够大吗?对于你的链表的一些客户端来说,它是否太大了?)
因此,链表几乎总是使用动态内存实现的。在C中,这将是malloc/free。在C++中,这将是new/delete。最终,客户端代码将利用标准库模板化实现(就像许多人指出的那样)。
为了回答你的问题,在你的教育中,使用动态内存,只为你的链表节点使用new/delete。
你很快就会明白为什么你(最终)会使用标准库实现,因为你必须了解使用/操作手工链表时的所有边缘情况(解引用删除的指针,内存泄漏等)。
很多时候,你通过失败学到更多(例如如何使用调试器!)。