Ich habe zuvor ausgiebig mit verknüpften Listen in Java gearbeitet, bin aber sehr neu in C ++. Ich habe diese Knotenklasse verwendet, die mir in einem Projekt gegeben wurde
class Node
{
public:
Node(int data);
int m_data;
Node *m_next;
};
aber ich hatte eine Frage, die nicht sehr gut beantwortet wurde. Warum ist es notwendig zu verwenden
Node *m_next;
um auf den nächsten Knoten in der Liste zu zeigen, anstatt
Node m_next;
Ich verstehe, dass es besser ist, die Zeigerversion zu verwenden; Ich werde nicht über Fakten streiten, aber ich weiß nicht, warum es besser ist. Ich erhielt eine nicht so klare Antwort darauf, wie der Zeiger für die Speicherzuweisung besser ist, und ich fragte mich, ob mir hier jemand helfen könnte, das besser zu verstehen.
c++
pointers
linked-list
m0meni
quelle
quelle
Node m_next
ist kein Verweis auf einen Knoten, sondern ein Speicher für den gesamten KnotenNode
.Antworten:
Es ist nicht nur besser, es ist der einzig mögliche Weg.
Wenn Sie ein
Node
Objekt in sich selbst speichern würden, welches wäresizeof(Node)
das? Es wäresizeof(int) + sizeof(Node)
, was gleich wäre , was gleichsizeof(int) + (sizeof(int) + sizeof(Node))
wäresizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node)))
usw. bis unendlich.Ein solches Objekt kann nicht existieren. Es ist unmöglich .
quelle
In Java
speichert einen Zeiger auf einen anderen Knoten. Sie haben keine Wahl. In C ++
bedeutet das Gleiche. Der Unterschied besteht darin, dass Sie in C ++ das Objekt tatsächlich speichern können, im Gegensatz zu einem Zeiger darauf. Deshalb muss man sagen, dass man einen Zeiger will. In C ++:
bedeutet, den Knoten genau hier zu speichern (und das kann für eine Liste eindeutig nicht funktionieren - Sie erhalten eine rekursiv definierte Struktur).
quelle
Node
eigene Konstruktor des Mitglieds aufgerufen, wodurch eine weitere neue Instanz instanziiert wird ... und Sie erhalten eine endlose Pseudorekursion. Es ist nicht wirklich so viel Größe Problem in ganz streng und wörtlichen Begriffe, wie es ein Performance - Problem ist.Node
, der tatsächlich im ersten enthalten istNode
. Sie erstellen also einen Zeiger, mit dem Java im Gegensatz zu Grundelementen im Wesentlichen mit Objekten umgeht. Wenn Sie eine Methode aufrufen oder eine Variable erstellen, speichert Java keine Kopie des Objekts oder sogar des Objekts selbst. Es speichert einen Verweis auf ein Objekt, bei dem es sich im Wesentlichen um einen Zeiger handelt, um den ein kleiner Kinderhandschuh gewickelt ist. Dies ist, was beide Antworten im Wesentlichen sagen.C ++ ist kein Java. Wenn du schreibst
In Java ist das dasselbe wie beim Schreiben
in C ++. In Java ist der Zeiger implizit, in C ++ explizit. Wenn du schreibst
In C ++ platzieren Sie eine Instanz von
Node
genau dort in dem Objekt, das Sie definieren. Es ist immer da und kann nicht weggelassen werden, es kann nicht zugeordnet werdennew
und es kann nicht entfernt werden. Dieser Effekt ist in Java nicht zu erreichen und unterscheidet sich grundlegend von dem, was Java mit derselben Syntax macht.quelle
Sie verwenden einen Zeiger, andernfalls Ihren Code:
… Würde nicht kompiliert, da der Compiler die Größe von nicht berechnen kann
Node
. Dies liegt daran, dass es von sich selbst abhängt. Dies bedeutet, dass der Compiler nicht entscheiden kann, wie viel Speicher er verbrauchen würde.quelle
k == sizeof(Node)
hält und Ihr Typ Daten hat, müsste er diesesizeof(Node) = k + sizeof(Data) = sizeof(Node) + sizeof(Data)
und dann auch haltensizeof(Node) > sizeof(Node)
.aleph_0
funktioniert. (Nur übermäßig pedantisch sein :-))sizeof
ein vorzeichenloser Integraltyp ist, also besteht die Hoffnung auf transfinite oder sogar reelle Größen. (noch pedantischer sein !: p)Node
es nicht einmal vor dem Ende dieses Snippets definiert, sodass Sie es nicht wirklich im Inneren verwenden können. Das implizite Vorwärtsdeklarieren von Zeigern auf eine noch nicht deklarierte Klasse ist ein kleiner Betrug, den die Sprache zulässt, um solche Strukturen zu ermöglichen, ohne dass Zeiger ständig explizit umgewandelt werden müssen.Letzteres (
Node m_next
) müsste den Knoten enthalten . Es würde nicht darauf hinweisen. Und dann würde es keine Verknüpfung von Elementen geben.quelle
Der Ansatz, den Sie beschreiben , ist kompatibel nicht nur mit C ++, sondern auch mit seiner (meist) Teilmenge Sprache C . Das Erlernen der Entwicklung einer verknüpften Liste im C-Stil ist eine gute Möglichkeit, sich mit einfachen Programmiertechniken (z. B. manueller Speicherverwaltung) vertraut zu machen, ist jedoch im Allgemeinen keine bewährte Methode für die moderne C ++ - Entwicklung.
Im Folgenden habe ich vier Varianten zum Verwalten einer Liste von Elementen in C ++ implementiert.
raw_pointer_demo
Verwendet den gleichen Ansatz wie Sie - manuelle Speicherverwaltung bei Verwendung von Rohzeigern erforderlich. Die Verwendung von C ++ ist hier nur für syntaktischen Zucker , und der verwendete Ansatz ist ansonsten mit der C-Sprache kompatibel.shared_pointer_demo
der Liste erfolgt die Verwaltung weiterhin manuell, die Speicherverwaltung erfolgt jedoch automatisch (verwendet keine Rohzeiger). Dies ist sehr ähnlich zu dem, was Sie wahrscheinlich mit Java erlebt haben.std_list_demo
verwendet den Standardbibliothekscontainerlist
. Dies zeigt, wie viel einfacher es wird, wenn Sie sich auf vorhandene Bibliotheken verlassen, anstatt Ihre eigenen zu rollen.std_vector_demo
verwendet den Standardbibliothekscontainervector
. Dies verwaltet den Listenspeicher in einer einzigen zusammenhängenden Speicherzuordnung. Mit anderen Worten, es gibt keine Zeiger auf einzelne Elemente. In bestimmten extremen Fällen kann dies erheblich ineffizient werden. In typischen Fällen ist dies jedoch die empfohlene Best Practice für die Listenverwaltung in C ++ .Bemerkenswert: Von all diesen
raw_pointer_demo
erfordert nur das tatsächlich, dass die Liste explizit zerstört wird, um ein "Verlieren" des Speichers zu vermeiden. Die anderen drei Methoden würden die Liste und ihren Inhalt automatisch zerstören, wenn der Container den Gültigkeitsbereich verlässt (am Ende der Funktion). Der Punkt ist: C ++ kann in dieser Hinsicht sehr "Java-ähnlich" sein - aber nur, wenn Sie Ihr Programm mit den verfügbaren High-Level-Tools entwickeln.quelle
Node_reference
obige Deklaration befasst sich mit einem der interessantesten Unterschiede auf Sprachebene zwischen Java und C ++. In Java würde beim Deklarieren eines Objekts vom TypNode
implizit ein Verweis auf ein separat zugewiesenes Objekt verwendet. In C ++ haben Sie die Wahl zwischen Referenzzuweisung (Zeiger) und direkter Zuordnung (Stapelzuweisung). Daher müssen Sie die Unterscheidung explizit behandeln. In den meisten Fällen verwenden Sie die direkte Zuordnung, jedoch nicht für Listenelemente.Überblick
Es gibt zwei Möglichkeiten, Objekte in C ++ zu referenzieren und zuzuweisen, während es in Java nur eine Möglichkeit gibt.
Um dies zu erklären, zeigen die folgenden Diagramme, wie Objekte im Speicher gespeichert werden.
1.1 C ++ - Elemente ohne Zeiger
Warnung : Die in diesem Beispiel verwendete C ++ - Syntax ähnelt der Syntax in Java. Die Speicherzuordnung ist jedoch unterschiedlich.
1.2 C ++ - Elemente mit Zeigern
Wenn Sie den Unterschied zwischen beiden Methoden überprüfen, werden Sie feststellen, dass bei der ersten Technik das Adresselement innerhalb des Kunden zugewiesen wird, während Sie bei der zweiten Methode jede Adresse explizit erstellen müssen.
Warnung: Java weist Objekte im Speicher wie bei dieser zweiten Technik zu, aber die Syntax ist wie bei der ersten Methode, was für Neulinge in "C ++" verwirrend sein kann.
Implementierung
Ihr Listenbeispiel könnte also dem folgenden Beispiel ähneln.
Zusammenfassung
Da eine verknüpfte Liste eine variable Anzahl von Elementen enthält, wird der Speicher nach Bedarf und nach Verfügbarkeit zugewiesen.
AKTUALISIEREN:
Ebenfalls erwähnenswert, wie @haccks in seinem Beitrag kommentierte.
Manchmal zeigen Referenzen oder Objektzeiger verschachtelte Elemente an (auch bekannt als "UML-Komposition").
Und manchmal weisen Referenzen oder Objektzeiger auf externe Elemente hin (auch bekannt als "UML-Aggregation").
Verschachtelte Elemente derselben Klasse können jedoch nicht mit der "No-Pointer" -Technik angewendet werden.
quelle
Nebenbei bemerkt, wenn das allererste Mitglied einer Klasse oder Struktur der nächste Zeiger ist (also keine virtuellen Funktionen oder andere Merkmale einer Klasse, die bedeuten würden, dass das nächste nicht das erste Mitglied einer Klasse oder Struktur ist), dann Sie kann eine "Basis" -Klasse oder -Struktur mit nur einem nächsten Zeiger verwenden und allgemeinen Code für grundlegende verknüpfte Listenoperationen wie Anhängen, Einfügen vor, Abrufen von vorne, .... verwenden. Dies liegt daran, dass C / C ++ garantiert, dass die Adresse des ersten Mitglieds einer Klasse oder Struktur mit der Adresse der Klasse oder Struktur übereinstimmt. Die Basisknotenklasse oder -struktur hätte nur einen nächsten Zeiger, der von den grundlegenden Funktionen der verknüpften Liste verwendet werden könnte, und dann würde die Typumwandlung nach Bedarf verwendet, um zwischen dem Basisknotentyp und den "abgeleiteten" Knotentypen zu konvertieren. Randnotiz - in C ++, wenn die Basisknotenklasse nur einen nächsten Zeiger hat,
quelle
Der Grund dafür ist, dass der
Node
Compiler beim Erstellen eines Objekts Speicher für dieses Objekt zuweisen muss und dafür die Größe des Objekts berechnet wird.Die Größe des Zeigers auf einen beliebigen Typ ist dem Compiler bekannt und daher kann mit einem selbstreferenziellen Zeiger die Größe des Objekts berechnet werden.
Wenn
Node m_node
stattdessen verwendet wird, hat der Compiler keine Ahnung von der Größe vonNode
und bleibt in einer unendlichen Rekursion der Berechnung steckensizeof(Node)
. Denken Sie immer daran: Eine Klasse kann kein Mitglied ihres eigenen Typs enthalten .quelle
Weil dies in C ++
entspricht dem in Java
Dabei erstellen beide ein neues Objekt
MyClass
mit dem Standardkonstruktor.quelle
Es gibt natürlich eine triviale Antwort.
Wenn sie nicht verknüpfen durch einen Zeiger einen Knoten zum nächsten, würden sie nicht werden Listen verknüpft .
Die Existenz von verknüpften Listen liegt daran, dass wir Objekte miteinander verketten wollen. Zum Beispiel: Wir haben bereits ein Objekt von irgendwoher. Wir möchten nun beispielsweise das eigentliche Objekt (keine Kopie) am Ende einer Warteschlange platzieren. Dies wird erreicht, indem ein Link vom letzten Element, das sich bereits in der Warteschlange befindet, zu dem Eintrag hinzugefügt wird, den wir hinzufügen. In maschineller Hinsicht bedeutet dies, ein Wort mit der Adresse des nächsten Elements zu füllen.
quelle