【问题标题】:Deleting node containing address of other node删除包含其他节点地址的节点
【发布时间】:2021-10-30 18:37:50
【问题描述】:

我知道下面给出的代码中的逻辑是错误的,但我怀疑当我们删除具有下一个节点地址的 p 时会发生什么。

析构函数会做什么? 它会转到所有节点使它们为空,直到节点 p 中的 Next 不变为空

还告诉我析构函数对内存的作用。 我已经阅读了很多关于析构函数、释放、删除和释放的文章,但我仍然感到困惑。 主要的困惑在于释放和析构函数。

class Node {
    public:
        int data;
        Node * next;
        Node(int data){
            this -> data = data;
            this -> next = NULL;
        }
    
        ~Node() {
            if(next) {
                delete next;
            }
        }
    };
void deleteAlternateNodes(Node *head) {
    //Write your code here
    Node *p =head;
    Node *q =NULL;
   if(p->next == NULL)
   {
       return;
   }
    while(p!=NULL)
    {
        q=p;
        p=p->next;
        q->next = p->next;
        delete p;
        p = q->next;
    }
}

【问题讨论】:

  • 如果您使用一种语言进行编程,请不要标记其他不相关的语言。

标签: c++ memory-management destructor singly-linked-list recursive-datastructures


【解决方案1】:

所以析构函数是在对象被销毁之前要调用的最后一个函数。它正在销毁(如调用相关的析构函数)传递给 delete 的对象实例,然后“释放”内存以便它可以用于其他目的。

对于您的代码问题,您释放了之前为节点“NEXT”保留的内存。

【讨论】:

    【解决方案2】:

    C++ 中的每个对象都以构造函数调用开始其生命,并以析构函数结束。对于原始类型,这是无操作的。对于类,您可以定义这两个调用的作用。

    这与存储的对象有两个正交:

    • 自动存储意味着对象的生命周期是根据定义的对象的范围自动推导出来的。 IE。 C++ 自动确保为对象调用构造函数和析构函数*。这包括:
      • 局部变量:执行时通过定义传递调用的 Ctor,在作用域的末尾调用 Dtor - 大致是“}”。
      • 本地静态变量:Ctor 与之前相同,但只是第一次,Dtor 位于程序末尾且仅当 Ctor 已被调用时。
      • 全局变量:Ctor 在main 之前调用,大部分是无序的,Dtor 在程序末尾。
      • 成员变量:类的初始化列表中的 ctor 在其 ctor 中。 Dtor 在类的 Dtor 末尾。即使在这里,这两个函数也总是被调用,这意味着你不应该也不应该在类的 dtor 中显式调用 dtor。
      • 成员静态变量:与全局变量相同。
    • 动态存储 - 必须通过使用 new 明确请求。 C++ 永远不会为您调用delete,您有责任准确地 这样做一次。 delete 调用析构函数 + 释放由 new 分配给存储的内存。仅仅显式调用析构函数是不够的。此外,delete 不会修改指针,如果代码依赖于此,则必须将其显式设置为 nullptr**。

    *除非程序崩溃。 **请勿使用NULL

    在您的示例中,Node::~Node 正确地销毁了之前由new 分配的next 内存。 int data 在 dtor 的末尾自动销毁,因为它是原始类型,所以它是无操作的。 dtor 完成后,Node 对象被销毁。

    这不是销毁链表的正确方法,因为析构函数是递归的,很容易溢出堆栈以获得更长的列表。正确的过程是手动遍历 dtor 中的列表和 delete 各个节点。

    【讨论】:

    • 你能告诉我堆栈将如何溢出
    • 你能告诉我析构函数如何递归删除它旁边的节点
    • @Abhinav1036 如果列表足够长,那么因为delete next; 调用next 的析构函数,这又是Node::~Node,其中包含delete next;,它调用next 的析构函数,又是@ 987654340@ 包含 delete next; 调用析构函数 next 再次是 Node::~Node 包含 delete next; 调用析构函数 next 再次是 Node::~Node 包含 delete next; 调用析构函数 @9876543这又是Node::~Node,包含delete next;,它调用next 的析构函数,又是Node::~Node 包含
    • Ups 我似乎已经用完了评论的堆栈空间。
    • 先生,当析构函数到达最后一个节点时会发生什么
    【解决方案3】:

    是的,Node 的析构函数将销毁它后面的所有节点,不,它不会使任何内容成为空指针(如果这样做也无济于事,因为在对象销毁后访问对象是未定义的)。

    您的 deleteAlternateNodes 在访问被破坏的节点时具有未定义的行为。
    一个快速而肮脏的解决方法是在销毁节点之前切断“尾巴”:

    q->next = p->next;  // Save the tail.
    p->next = nullptr;  // Cut off the tail.
    delete p; // This is safe now.
    

    修复剩余的错误作为练习。
    (首先使用笔和纸绘制列表。这是开发和调试基于指针的代码的最佳方法。)

    【讨论】:

    • 为什么Node的析构函数会销毁它后面的所有节点。
    • @Abhinav1036 因为delete next会破坏下一个节点,而那个节点的破坏会破坏它的个下一个节点,这会破坏那个节点的follower,以此类推。
    【解决方案4】:

    在这个语句中的操作符删除

    delete p;
    

    指针p指向的节点后面的所有节点(由于数据成员next)都将被删除。

    所以函数deleteAlternateNodes 调用未定义的行为,因为表达式q->next 指向的所有节点都被赋值为

        q->next = p->next;
    

    因此声明将被删除

        delete p;
    

    所以这个说法

        p = q->next;
    

    将指针 p 设置为无效指针。

    这是一个演示程序

    #include <iostream>
    
    class Node 
    {
    public:
        int data;
        Node * next;
        
        Node(int data)
        {
            this -> data = data;
            this -> next = NULL;
        }
        
        ~Node() 
        {
            std::cout << "The destructor is called for node with data equal to "
                      << data << '\n';
    
            if(next) 
            {
                delete next;
            }
        }
    };
    
    void display( const Node *head )
    {
        for ( ; head; head = head->next )
        {
            std::cout << head->data << " -> ";
        }
        
        std::cout << "null\n";
    }
    
    int main() 
    {
        Node *head = new Node( 1 );
        Node *current = head;
        current->next = new Node( 2 );
        current = current->next;
        current->next = new Node( 3 );
        
        display( head );
        
        delete head;
        
        return 0;
    }
    

    程序输出是

    1 -> 2 -> 3 -> null
    The destructor is called for node with data equal to 1
    The destructor is called for node with data equal to 2
    The destructor is called for node with data equal to 3
    

    其实析构函数中的if语句是多余的

            if(next) 
            {
                delete next;
            }
    

    你可以写

    delete next;
    

    没有 if 语句,因为对于空指针,不会调用析构函数。例如

    ~Node() 
    {
        std::cout << "The destructor is called for node with data equal to "
                  << data << '\n';
    
        delete next;
    }
    

    ~Node() 
    {
        delete next;
    }
    

    【讨论】:

    • 先生,但你使用的析构函数不是默认的,所以我们怎么知道它会为 next 调用 delete
    • @Abhinav1036 有明确的语句 delete next;在析构函数中。
    • 先生,我说的是当我们使用默认析构函数时会发生同样的事情
    • @Abhinav1036 默认析构函数不包含语句 delete next;。因此,它的行为与您问题中代码 sn-p 中的析构函数不同。
    • 先生还告诉我,如果析构函数不释放内存,那么它的用途是什么。
    猜你喜欢
    • 1970-01-01
    • 2011-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-20
    • 1970-01-01
    • 1970-01-01
    • 2021-06-11
    相关资源
    最近更新 更多