2016-07-21 98 views
1

Ich bin neu in C++ und habe versucht, mich mit der Sprache vertraut zu machen, indem ich eine LinkedList implementiere.C++ Klassenmitglied Zugriff über Getter ergibt Müll, direkter Zugriff ok aber std :: cout stört?

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

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

ListElement enthält einen int Wert val und einen Zeiger auf den nächsten Listenelement und ein Konstruktor (nullptr wenn es keine nächste Element ist).

class MyLinkedList { 
public: 
    ListElement *head; 

    MyLinkedList() {head = nullptr;}; 

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

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

MyLinkedList enthält einen Zeiger auf das erste Element des head sowie benannte Liste als einige Methoden auf der Liste arbeiten. Bei einigen Methoden habe ich versucht, ihre Ursache zu finden. (Ich bin mir bewusst, dass ein Getter für eine öffentliche Klasse Mitglied macht keinen Sinn überhaupt, ursprünglich head privat war.) Doing so beobachtete ich das folgende Verhalten kann ich nicht erklären:

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

diesen Code Running (hinzufügen #include <iostream> und using namespace std; für ein Arbeitsbeispiel) druckt

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

so die erste direkte Zugang von head funktioniert wie erwartet, 1 als Wert des ersten Listenelement nachgebend, aber gibt den Getter mit Müll. Wenn head dann direkt wieder zugegriffen wird, liefert es auch Müll, die ich gemacht denken „Hm, scheint wie mit getHead() irgendwie verstümmelt das ListMember Objekt“, nur um zu entdecken, dass

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

druckt

head: 1 
head: -858993460 

ohne den Getter zu berühren. Also einfach auf l.head zugreifen, um es zu verunsichern? Kein

, als

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

Erträge (wie beabsichtigt) head: 1 zweimal. Also zwingt ich mit cout zwischendurch meine Objekte oder ihre Zeiger? Und was ist los mit getHead() als alles ist nur return head;?


Also ich bin hier ziemlich verloren und keine direkt Fragen finden konnten. (This Question hat einen vielversprechenden Titel, aber keine Zeiger). Kann ich Zeiger nicht korrekt verwenden? Oder wird hinter den Kulissen eine automatische Objektlöschung ausgeführt? Oder funktioniert magiC++ so?

+2

'head = & ListElement (i, head);' - so vergeben Sie kein neues 'ListElement'. Lies über "neu" (und versuche dann in den meisten Fällen, es zu vermeiden). – user2357112

+1

für ein gutes Maß, möchten Sie vielleicht auch über [Smartpointer] (http://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one) wissen) – jaggedSpire

+0

@jaggedSpire Richtig, mein Schlechter - zu spät, um direkt zu denken, denke ich: – jpw

Antwort

1

In C++ müssen Sie Ihren eigenen Speicher verwalten.Die Aussage

ListElement(i, head); 

erstellt eine Instanz Listelement lokal scoped die MyLinkedList :: append(). Sobald die Funktion beendet ist, existiert die Variable nicht mehr und der Zeiger zeigt nun auf ungültigen Speicher.

Der Grund, warum Ihre erste Druckanweisung Ihnen eine scheinbar korrekte Antwort gibt, ist ein bisschen ein Ablenkungsmanöver. In allen Fällen greifen Sie auf freien Speicher zu, der nicht definiert ist. Im ersten Fall hat der Speicher zufällig den Wert, den Sie zuvor festgelegt haben.

Sie müssen Ihren eigenen Speicher in append zuweisen und bereinigen, wenn Sie damit fertig sind. Sobald Sie "neu" gemeistert haben, achten Sie darauf, wie Sie durch eine Datenstruktur iterieren und jedes Element löschen. Mit Ihrer verknüpften gelisteten Implementierung sollte dies ziemlich trivial sein.

1

Änderung

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

zu

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

Der erste, wenn sie kompiliert, nimmt die Adresse eines temporären Objektstapel zugeordnet. Daher wird head nach seiner Zerstörung auf Müll zeigen.

2

In

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

ListElement(i, head) erstellt eine temporäre, namenlos ListElement und weist einen Zeiger darauf zu head. Dann, weil die ListElement selbst nichts zugewiesen wurde, wird die ListElementgoes out of scope zerstört. Dies lässt Kopf auf ungültigen Speicher zeigen.

Kopf geschrieben werden kann dynamische Speicher Ausmaß die Lebensdauer der ListElement

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

zu verwenden, aber jetzt hat jemand die Verantwortung dafür zu holen, dass die ListElement gelöscht, wenn nicht mehr erforderlich.

Zum Beispiel:

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

sorgfältige Verwendung von std::unique_ptr und std::move kann die Speicherverwaltung erhalten automatisieren und die Notwendigkeit delete beseitigen.