2016-07-21 2 views
1

Я новичок в C++ и попытался ознакомиться с языком, реализовав LinkedList.C++ член класса, доступ к которому через getter дает мусор, прямой доступ ok, но std :: cout мешает?

class ListElement { 
public: 
    int val; 
    ListElement *next; 

    ListElement(int v, ListElement *n) {val = v; next = n;}; 
}; 

ListElement содержит значение val Int и указатель на следующий элемент списка (nullptr, если нет следующего элемента) и конструктор.

class MyLinkedList { 
public: 
    ListElement *head; 

    MyLinkedList() {head = nullptr;}; 

    ListElement* getHead(void){ 
     return head; 
    }; 

    void append(int i) { 
     head = &ListElement(i, head); 
    }; 
}; 

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

int main() { 
    MyLinkedList l; 
    l.append(1); 

    int x = l.head->val; 
    cout << "head: " << x << "\n"; 
    int y = l.getHead()->val; 
    cout << "getHead(): " << y << "\n"; 
    int z = l.head->val; 
    cout << "head: " << z << "\n"; 

    cin.get(); 
    return 0; 
} 

Выполнение этого кода (добавить #include <iostream> и using namespace std; для рабочего примера) печатает

head: 1 
getHead(): 18085840 
head: -858993460 

поэтому первый прямой доступ head работ так же, как ожидается, получая 1 в качестве значения первого элемента списка, но с использованием геттер возвращает мусор. Если head затем получить доступ непосредственно снова, она также дает мусор, который заставил меня думать «Хм, кажется, что с помощью getHead() как-то искажает объект ListMember», только чтобы обнаружить, что

int x = l.head->val; 
cout << "head: " << x << "\n"; 
int z = l.head->val; 
cout << "head: " << z << "\n"; 

печатает

head: 1 
head: -858993460 

, даже не касаясь газопоглотителя. Так просто получить доступ к l.head в любом случае достаточно, чтобы обмануть его?

Нет, как

int x = l.head->val; 
int z = l.head->val; 
cout << "head: " << x << "\n"; 
cout << "head: " << z << "\n"; 

возвращает (по их назначению) head: 1 два раза. Итак, используя cout между изменениями моих объектов или их указателей? И что случилось с getHead(), поскольку все, что он делает, это только return head;?


Так что я довольно утерян здесь и не мог найти никаких связанных вопросов. (This Question имеет многообещающее название, но не использует указатели). Неужели я полностью не использую указатели правильно? Или происходит автоматическое удаление объекта за кулисами? Или это как работает magiC++?

+2

'head = & ListElement (i, head);' - это не то, как вы выделяете новый элемент ListElement. Пойдите, прочитайте о 'new' (а затем для большинства случаев, постарайтесь избежать необходимости). – user2357112

+1

для хорошей меры, вы также можете узнать о [умных указателях] (http://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one) – jaggedSpire

+0

@jaggedSpire Правильно, мое плохое - слишком поздно думать прямо, я думаю: \ – jpw

ответ

1

В C++ вы должны управлять своей собственной памятью.Заявление

ListElement(i, head); 

Создает экземпляр ListElement локально в области видимости MyLinkedList :: Append(). Таким образом, когда эта функция выходит из этой переменной, она больше не существует, и указатель теперь указывает на недопустимую память.

Причина, по которой ваш первый оператор печати дает вам, по-видимому, правильный ответ - это небольшая красная селедка. Во всех случаях вы получаете доступ к свободной памяти, которая имеет неопределенное поведение. В первом случае память имеет только то значение, которое вы ранее задали.

Вы должны выделить свою собственную память в дополнение и очистить ее, когда закончите с ней. После того, как вы освоили «новый», обязательно посмотрите, как выполнять итерацию по структуре данных и удалять каждый элемент. С вашей связанной реализацией в списке это должно быть довольно тривиально.

1

изменение

void append(int i) { 
    head = &ListElement(i, head); 
}; 

к

void append(int i) { 
    head = new ListElement(i, head); 
}; 

Во-первых, если он собирает, принимает адрес временного стека выделенного объекта. Следовательно, head будет «указывать на мусор» после его разрушения.

2

В

void append(int i) { 
    head = &ListElement(i, head); 
}; 

ListElement(i, head) создает временную, безымянный ListElement и присваивает указатель на него в head. Затем, поскольку сам ListElement не был назначен ни на что, ListElementgoes out of scope и уничтожен. Это оставляет голову, указывающую на недопустимую память.

Руководитель может быть написана, чтобы использовать динамическую память для степени жизнь ListElement

void append(int i) { 
    head = new ListElement(i, head); 
}; 

но теперь кто-то должен взять на себя ответственность за обеспечение того, чтобы ListElement удаляется, когда больше не требуется.

Например:

void remove(int i) { 
    // find list element i and previous element. Special handling required for first element 
    prev.next = element.next; 
    delete element; 
}; 

Осторожное использование std::unique_ptr и std::move может получить автоматизировать управление памятью и устранить необходимость в delete.

Смежные вопросы