Итак, я пытаюсь переопределить operator = в связанном классе списка, который я пишу, но по какой-то причине по-прежнему получаю эту странную проблему.
List& List::operator=(const List& copyList){
if(copyList.head != nullptr){
makeEmpty(); // clears *this from any previous nodes
cout << "if statement " << endl;
head = new Node; // create a new node for head
head -> data = copyList.head -> data; // copy the first data of copylist
Node* pnew = head; // a temp node to traverse the new linkedlist
assert(head != nullptr);
Node* current2 = copyList.head;
current2 = current2 -> next;
while(current2 != NULL && pnew != NULL){
cout << "entering while loop " << endl;
pnew-> next = new Node;
pnew -> next->data = current2 ->data;
cout << "pnew next data " << *(pnew -> next->data) << endl;
assert(pnew-> next != nullptr);
pnew = pnew -> next;
current2 = current2 -> next;
cout << "after current2" << endl;
}
pnew -> next = NULL;
}else{
cout << "else statement " << endl;
head = nullptr;
}
cout<< "printing out copylist"<< endl << copyList << endl;
cout<< "printing current list: " << endl << *this << endl;
return *this;
}
Итак, это код, который я должен проверить для переопределения оператора:
cout << "mylist:" << endl << mylist << endl;
cout << "mylist4:" << endl << mylist4 << endl;
mylist = mylist4;
cout << "mylist:" << endl;
cout << mylist << endl;
cout << "mylist4:" << endl;
cout << mylist4 << endl;
Это результат:
mylist:
10 f
16 u
20 n
25 !
mylist4:
14 s
15 t
16 u
18 f
19 f
25 !
if statement
entering while loop
pnew next data 15 t
after current2
entering while loop
pnew next data 16 u
after current2
entering while loop
pnew next data 18 f
after current2
entering while loop
pnew next data 19 f
after current2
entering while loop
pnew next data 25 !
after current2
printing out copylist
14 s
15 t
16 u
18 f
19 f
25 !
printing current list:
14 s
15 t
16 u
18 f
19 f
25 !
*crashes right here*
Я пытался выяснить эту проблему около 3 дней. Любая помощь будет высоко оценен. Заранее спасибо!
EDIT: вот конструктор (деструктор по умолчанию является компилятором):
NodeData::NodeData(int n, char c) {
num = n; ch = c;
}
EDIT2: Я нашел проблему после тщательного изучения. Проблема заключалась в том, что я не указывал последний узел головы, pnew после цикла while, на null. Это устранило проблему. Благодаря вашей поддержке.
У вас есть контроль над классом Node, и он должен поддерживать операции копирования и назначения операторов как прозрачно, так и при необходимости своими силами. Учитывая это, я согласен с Дитмаром, что использование механизма copy-ctor/swap/destructor является идеальным подходом.
Если вы настаиваете на том, чтобы сделать это вручную, это один из способов сделать это. Ваша реализация делает это намного сложнее, чем нужно.
List& List::operator=(const List& copyList)
{
List tmp;
Node **dst = &tmp.head;
const Node* src = copyList.head;
while (src)
{
*dst = new Node(*src); // invoke Node copy-ctor
src = src->next; // advance source
(*dst)->next = nullptr; // break link to original next
dst = &(*dst)->next; // move target to next pointer
}
// tmp now has a copy of the source list
// swap its head pointer with ours.
std::swap(tmp.head, head);
// upon return, the tmp object that now holds our
// original list will clean it up. we own the
// new list form this point on.
return *this;
}
Как это работает
Указатель-на-указатель dst
всегда содержит адрес следующего указателя, который будет заполнен новым узлом. Первоначально он содержит адрес локального указателя объекта List tmp
объекта List tmp
. , Когда мы добавляем узлы, он обновляется, чтобы всегда удерживать адрес next
указателя добавленного последнего узла. Поступая таким образом, мы получаем прямую цепочку автоматически. Как только мы закончим копирование, tmp
теперь является дубликатом источника. Затем мы меняем указатель на голову собственным. Это, в свою очередь, своп, которому принадлежит список. Когда tmp
уничтожается на функции-выходе, он уничтожит наш старый список. Мы сохраняем новый список, на который указывает наш head
указатель.
Вышеприведенное предполагает, что вы используете копию-ctor по умолчанию для Node
, что означает, что копирование скопирует как значение данных, так и следующую ссылку, последнее вы НЕ хотите, таким образом, встроенный разрыв ссылки. Если вы на самом деле реализуете Node::Node(const Node&)
чтобы всегда установить ссылку на NULL после копирования, разрыв ссылки может быть устранен. То есть, ваш экземпляр-конструктор Node
должен выглядеть примерно так:
Node::Node(const Node& arg)
: data(arg.data)
, next()
{
}
Это гарантирует, что чистая (так же чистая, как data
могут быть скопированы, в любом случае) копируется без случайной ссылки на исходный указатель arg
next.
Все сказанное, ответ Дитмара является наиболее правильным, и я проголосовал за него соответствующим образом. Если это поможет вам, рассмотрите вопрос о голосовании, но это его идеальное решение.
Не смотря на жесткую реализацию (я остановился, когда обнаружил makeEmpty()
который гарантирует, что оператор присваивания не будет строго безопасным без каких-либо оснований): самый простой способ реализовать оператор присваивания - использовать копию конструктор, деструктор и вообще тривиальная функция write swap()
. Реализация выглядит одинаково для всех классов, за исключением имени класса:
T& T::operator= (T other) {
other.swap(this);
return *this;
}
Обратите внимание, что намеренно передавать аргумент по значению: это то, где значение действительно происходит. По сравнению с копированием на T const&
имеет то преимущество, что фактическая копия может быть устранена в некоторых случаях.
swap()
некоторые данные узла! Вы вызываете swap()
для класса, который вы реализуете, и если у него нет такого метода, вы реализуете его! Аналогично с конструктором копирования: в вашем списке обязательно есть конструктор копирования!
if
совпадающееelse
, и есть, по крайней мере , две основные логические вопросы , которые я вижу только в этом коде, который не дотягивает большие надежды для остальной части списка.