如何在链接列表中对destuructor进行递归调用

How is the recursive call to destructor made in linked list?

本文关键字:destuructor 递归 调用 链接 列表      更新时间:2023-10-16

我正在浏览此博客文章并遇到此代码。

#include <iostream>
#include <string>
using namespace std;
struct LNode {
   int data;
   LNode* next;
   LNode(int n){data = n; next = nullptr;}
   void add_to_end(int n) {
      if (next)
         next->add_to_end(n);
      else
         next = new LNode(n);
   }
   ~LNode() { cout << " I am from LNode Destructor " << endl; delete next; }
};
int main()
{   
   LNode root(1);
   root.add_to_end(2);
   root.add_to_end(3);
   root.add_to_end(4);
}

此代码的输出是

 I am from LNode Destructor 
 I am from LNode Destructor 
 I am from LNode Destructor 
 I am from LNode Destructor 

由于某种原因,我一直认为我必须使用某种> while或for loop ,然后继续删除我使用New动态分配的所有节点。但是在上面的代码中,如何调用驱动器四次,以及它如何自动穿越链接列表创建此Domino效果(完全删除了分配的内存本身)。

此代码:

~LNode() { delete next; } // destructor

如果将next设置为0,则NULLnullptr将不会发生任何事情。但是,如果将其设置为以下LNode的地址,它将导致以下LNode's destructor 运行。它的 destructor 将做同样的事情,从而导致LNode被销毁之后,依此类推,直到遇到等于 nullptrnext变量。

它基本上是链。

从根节点开始(这是失去范围的一个,因此,其解构器被自动称为),当调用delete next;时,您正在调用next元素的解构器,等等。当nextNULL0nullptr时,该过程将停止。

您还可以在节点中添加索引,并自己查看破坏顺序。

delete和destructor调用是两个微妙的东西。但是,每次删除(非零)指向LNode的指针都称为destructor。

有关详细信息,请参见此问题:删除调用destructor吗?

root实例的破坏者在 main()末尾出现范围时调用:

int main()
{   
   LNode root(1);
   root.add_to_end(2);
   root.add_to_end(3);
   root.add_to_end(4);
/* Destructor of root is called here. Imagine this is present here: */
   root.~LNode(); // calling destructor on object root
}

~LNode destructor调用 delete next;,它依次调用该对象的destructor,然后在NULL指针上调用delete next;时停止整个递归

您的呼叫列为最后一个破坏者时,将看起来像这样:

root.~LNode()
delete root.next
root.next.~LNode()
delete root.next->next
root.next->next->~LNode()
delete root.next->next->next
root.next->next->next->~LNode()

实际上在NULL上调用了delete next;,然后递归停止。

一个微小的澄清:删除Next; 呼叫会导致destructor在节点上从第一到最后 - 上调用,但是实际的存储回收发生了从最后到第一个。即,这是序列:

 1) destructor invoked on node 1.
 2)   delete called with pointer to node 2.
 3)   destructor invoked on node 2.
 4)     delete called with pointer to node 3.
 5)     destructor invoked on node 3.
 6)       delete called with pointer to node 4.
 7)       destructor invoked on node 4.
 8)         delete called with nullptr.
 9)       destructor call at (7) reclaims node 4 and returns.
10)     destructor call at (5) reclaims node 3 and returns.
11)   destructor call at (3) reclaims node 2 and returns.
12) destructor call at (1) reclaims node 1 and returns.

驱动器是如何被调用四次的? 自动穿越链接列表

这是递归的一种常见形式。


链接列表是递归数据结构。

我认为您可能有兴趣在(家庭构建和简单的)单链接列表中看到更多的递归示例。


这是CTOR(简化)。参数A_MAX指定要创建多少个节点元素。

LMBM::Node::Node(uint8_t a_max) :
   m_nodeId            (++M_nodeCount),
   m_next              (0), // linked list
   //... other node initializers items
{
   if(a_max > 1)
   {
      uint8_t nmax = static_cast<uint8_t>(a_max - 1);
      m_next = new Node(nmax); // recurse create another Node
      dtbAssert(m_next)(a_max); // confirm 
   }
   else // (1 >= a_max)
   {
      dtbAssert(1 == a_max)(a_max); // at least one node
      // all requested nodes created
   }
   if (1 == m_nodeId) //i.e. the 1st node
   {
      M_firstNode   = this;   // capture list anchor to static
      M_MAX_THREADS = a_max;  // capture list size to static
      // ...  a few more actions
   }
} // LMBM::Node::Node(uint8_t a_max)

以上是从运行代码中提取的,但是我现在认为此代码不适合发货,因为当新的失败(由于任何原因)时,代码断言。尽管我的DTBASSERT提供了调试器支持,但不适合客户使用。

是的,这里没有循环。

也许当您习惯它时,简单性和易用性是递归的典型代表。

CTOR用简单的新列表扩展了列表(通常由许多喜欢智能指针的同行劝阻)。

每个节点都会分配一个唯一的m_node_id,以帮助调试。

创建此列表的调用简单:

LMBM::Node nodes(LMBM::Node::DEFAULT_MAX_NODES);

MAIN中的代码(带有限制检查)可以通过用户值传递,以获取更大或较小的列表。


DTOR很像您的博客查找(尽管在反向序列中)

LMBM::Node::~Node(void)
{
   if(m_next) // delete objects and list
      delete m_next; // recurse down the list
   // ... clean up actions, if any 
   m_nodeId = 0;
} // Node::~Node(void)

此DTOR首先旋转到列表的末端,然后放松堆栈时进行清理和删除活动。


此init()方法再次使用递归初始化了一些资源(信号量等),并首先完成了最后一个节点的init()。

void LMBM::Node::init(void)
{
   if(m_next)
      m_next->init(); // tail first
   // 1. ALLOCATE resource, such as a semaphore
   m_semIn = new Sem_t; // create 1 semaphore per thread
   dtbAssert(m_semIn)(m_nodeId);
   //std::cout << "m_semIn " << m_nodeId << " = " << (void*)m_semIn << "n";
   // 2. INITIALIZE default Sem_t ctor is ok
} // void LMBM::Node::init(void)

(这里也没有循环。)

fyi -lmbm :: sem_t具有4行C 代码,并且通过Linux API包装单个POSIX进程信号量,设置为本地模式(未命名,未共享)。

事实证明,此POSIX信号量确实与STD ::线程以及Posix线程一起使用 - 我在此处测试的一件事。


这是startapp()。我不会显示出所用的递归...在此代码中查看此代码的示例中,线程执行每个节点的活动。

void LMBM::Node::startApp(void)   // activate threads
{
   if(m_next)
      m_next->startApp(); // recurse to end of linked list
   // 4. start my thread
   m_thread = new std::thread (LMBM::Node::threadEntry, this);
   dtbAssert(m_thread);
   // ... confirm thread running state
} // void LMBM::Node::startApp(void)   // activate threads

所有线程以一种或另一种方式进行合作。


在这一点上,我有n个节点;每个节点都有一个线程;线程的作用可以通过m_node_id确定;可以用1..10自然ID而不是系统ID的线程ID来增强调试器cout的cout。