Удаление узла, содержащего адрес другого узла

#c #memory-management #destructor #singly-linked-list #recursive-datastructures

Вопрос:

Я знаю, что логика в приведенном ниже коде неверна, но у меня есть сомнения в том, что произойдет, когда мы удалим p, имеющий адрес следующего узла.

Что будет делать деструктор? будет ли он переходить ко всем узлам, делая их нулевыми, пока следующий узел p не станет нулевым

Также скажите мне, что деструкторы делают с памятью. Я прочитал много статей о деструкторе, освобождении ,удалении и освобождении, но я все еще в замешательстве. Основная путаница заключается в освобождении и деструкторе.

 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;
    }
}
 

Комментарии:

1. Если вы программируете на одном языке, пожалуйста, не помечайте другие не относящиеся к делу языки.

Ответ №1:

В этом заявлении оператора удалите

 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 << "nulln";
}

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;
}
 

Комментарии:

1. сэр, но используемый вами деструктор не используется по умолчанию, так как мы узнаем, что он вызовет удаление для следующего

2. @Abhinav1036 В деструкторе явно есть оператор delete next;.

3. сэр, я говорю о случае, когда мы используем деструктор по умолчанию, произойдет ли то же самое

4. @Abhinav1036 Деструктор по умолчанию не содержит инструкцию delete next;. Таким образом, его поведение отличается от поведения деструктора во фрагменте кода в вашем вопросе.

5. сэр, также скажите мне, если деструктор не освобождает память, тогда какой в этом смысл.

Ответ №2:

Таким образом, деструктор-это последняя функция, которая будет вызвана перед уничтожением объекта. Он уничтожает (как при вызове соответствующего деструктора) экземпляр объекта, переданный для удаления, а затем «освобождает» память, чтобы ее можно было использовать для других целей.

Для вашего вопроса о коде вы освобождаете память, которая ранее была зарезервирована для вашего узла «ДАЛЕЕ».

Ответ №3:

Каждый объект в C начинает свою жизнь с вызова конструктора и завершает ее деструктором. Для примитивных типов это не операция. Для классов вы можете определить, что делают оба вызова.

Это ортогонально хранению объекта, у которого есть два:

  • Автоматическое хранение означает, что время жизни объекта выводится автоматически на основе области действия определенного объекта. Т. е. 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 правильно уничтожается next память, которая была ранее выделена new . int data автоматически уничтожается в конце dtor, так как это примитивный тип, он не работает. После завершения Node dtor объект уничтожается.

Однако это неправильный способ уничтожения связанного списка, поскольку деструктор рекурсивен и легко переполнит стек для более длинных списков. Правильная процедура состоит в том, чтобы вручную выполнить итерацию по списку в dtor и delete отдельным узлам.

Комментарии:

1. можете ли вы сказать мне, как стек будет переполнен

2. можете ли вы также рассказать мне, как деструктор рекурсивно удаляет узел рядом с ним

3. @Abhinav1036 если список достаточно долго, то потому, что delete next; вызывает деструктор для next которых снова Node::~Node содержащих delete next; который вызывает деструктор next , который является вновь Node::~Node содержащих delete next; который вызывает деструктор next , который является вновь Node::~Node содержащих delete next; который вызывает деструктор next , который является вновь Node::~Node содержащих delete next; который вызывает деструктор next , который является вновь Node::~Node содержащих delete next; который вызывает деструктор next , который является вновь Node::~Node контаи

4. Ups У меня, кажется, закончилось место в стеке комментариев.

5. сэр, что произойдет, когда деструктор достигнет последнего узла

Ответ №4:

Да, деструктор Node уничтожит все узлы, которые следуют за ним, и нет, он ничего не делает нулевым указателем (и это не помогло бы, если бы это было так, поскольку доступ к объекту после его уничтожения не определен).

deleteAlternateNodes У вас неопределенное поведение при доступе к уничтоженным узлам.
Быстрое и грязное решение этой проблемы состоит в том, чтобы отрезать «хвост» перед уничтожением узла:

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

Исправление оставшихся ошибок осталось в качестве упражнения.
(Сначала разработайте это, составив списки с помощью ручки(cil) и бумаги. Это лучший метод как для разработки, так и для отладки кода на основе указателей.)

Комментарии:

1. почему деструктор узла уничтожит все узлы, которые следуют за ним.

2. @Abhinav1036, потому delete next что уничтожает следующий узел, и уничтожение этого узла уничтожит его следующий узел, который уничтожит последователя этого узла, и так далее.