#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
что уничтожает следующий узел, и уничтожение этого узла уничтожит его следующий узел, который уничтожит последователя этого узла, и так далее.