Ich programmiere normalerweise in Python. Um die Leistung meiner Simulationen zu erhöhen, lerne ich C. Ich habe ein Problem zu verstehen, die Verwendung eines Zeigers eines Zeigers beim Implementieren der Append-Funktion zu einer verknüpften Liste. Dies ist ein Auszug des Codes aus meinem Buch (Verständnis Zeiger in C von Kanetkar).
%Vor%In diesem Code gebe ich den Doppelzeiger ** q an die Append-Funktion weiter. Ich bekomme, dass dies die Adresse der Adresse, d. H. Die Adresse von NULL in diesem Fall ist.
Ich verstehe einfach nicht warum man macht das so. Wäre es nicht zulässig, einen * Operator von allem in der Funktion append () zu entfernen und einfach die Adresse von NULL (d. H. P anstelle von & amp; p) an die Funktion append () zu übergeben?
Ich habe diese Frage gegoogelt. Die Antworten sind entweder zu schwer zu verstehen (da ich nur ein C-Anfänger bin) oder zu einfach. Ich bin dankbar für Hinweise, Kommentare oder Links, wo ich darüber nachlesen kann.
Wenn Sie Dinge an Funktionen in C übergeben, sei es seine Variablen oder Zeiger, ist es eine Kopie des Originals.
Schnelles Beispiel:
%Vor% So wird der erste Funktionsaufruf an change()
eine Kopie eines Zeigers an eine Adresse übergeben. Wenn wir in = "B"
machen, ändern wir nur die Kopie des Zeigers, den wir übergeben haben.
Im zweiten Funktionsaufruf, dem really_change()
, erhalten wir eine Kopie eines Pointer-to-a-pointer. Dieser Zeiger enthält die Adresse zu unserem ursprünglichen Zeiger und voila, wir können jetzt auf den ursprünglichen Zeiger verweisen und ändern, worauf der ursprüngliche Zeiger zeigen sollte.
Hoffentlich erklärt es etwas mehr:)
Zuerst ist es nicht "die Adresse der Adresse." Es ist die Adresse einer Zeigervariablen. Bsp .: Wenn Sie die Adresse einer int
Variable n
übergeben, die Null enthält, übergeben Sie nicht die Adresse Null; Sie übergeben die Adresse einer Variablen (in diesem Fall eine int
Variable, in Ihrem Fall eine Zeigervariable). Variablen haben Adressen im Speicher. Der Parameter ist in diesem Fall die Adresse einer Variablen, bei der es sich um eine Zeigervariable handelt, den Kopf Ihrer Liste.
Warum macht man das? Einfach. Alle Variablen in C (Arrays über Pointer-Decay erhalten nicht) werden mit value übergeben. Wenn Sie etwas nach Referenz (Adresse) ändern möchten, muss der zu übergebende "Wert" eine Adresse sein und der formale Parameter, der ihn empfängt, muss ein Zeigertyp sein. Kurz gesagt, Sie geben an, dass der "Wert" eine Speicheradresse und nicht nur ein einfacher Skaliererwert übergeben wird. Die Funktion verwendet dann (über einen formalen Zeigerparameter) Daten entsprechend zu speichern. Stellen Sie sich das so vor, als ob Sie "das, was ich wollte" in diese "Speicheradresse" schreiben.
Nehmen wir als kleines Beispiel an, Sie wollten eine Datei durchlaufen und jedes Zeichen darin in eine vorwärts verknüpfte Liste von Knoten einfügen. Sie würden nicht eine Append-Methode wie die verwenden, die Sie haben (siehe The Painter's Algorithm > für warum). Sehen Sie, ob Sie diesem Code folgen können, der einen Zeiger auf Zeiger, aber keinen Funktionsaufruf verwendet.
%Vor% Bleiben Sie eine Weile stehen und sehen Sie, ob Sie verstehen können, wie der Zeiger auf Zeiger next
verwendet wird, um immer den nächsten verknüpften Knoten zu füllen, der der Liste hinzugefügt wird, beginnend mit dem Kopfknoten.
Sie müssen es so machen, damit die Funktion den Speicher zuordnen kann. Vereinfachung des Codes:
%Vor% Dieser Code wurde so erstellt, dass die Unterfunktion funcA
den p
-Zeiger zuordnen kann. Betrachten Sie zuerst void* p
, als ob es int i
wäre. Was du tust, indem du p = NULL
machst, ist ähnlich wie int i = 0
. Übergeben Sie nun &i
, übergeben Sie nicht die Adresse von 0
, übergeben Sie die Adresse von i
. Das gleiche passiert mit &p
, wenn Sie die Adresse des Zeigers übergeben.
Nun möchten Sie in funcA die Zuweisung vornehmen, also verwenden Sie malloc
, aber wenn Sie q = malloc(...
tun würden und q wäre void* q
in der Hauptfunktion p wäre nicht zugewiesen worden. Warum? Denken Sie an funcB
, j hält die Adresse von i, wenn Sie ändern möchten, würden Sie dann *j = 1
tun, denn wenn Sie j = 1
tun würden, hätten Sie j auf eine andere Speicherregion anstelle von i zeigen lassen. Genauso ist es mit q für funcA. Stellen Sie es sich als <type of p>* q
vor es ist ein Zeiger auf den Typ von p, der void * ist, aber im Falle von funcB ist es ein int. Jetzt möchten Sie die Adresse ändern, auf die p zeigt, das bedeutet, dass Sie die Adresse, auf die q zeigt, nicht ändern möchten. Sie möchten die Zeigeadresse ändern, auf die q zeigt, aka *q
oder p
.
Wenn es noch nicht klar ist. Denk an Boxen. Ich habe ein kurzes Beispiel mit der Funktion der betroffenen Boxen gezeichnet. Jede Box hat einen Namen (innerhalb der Box), diese Box befindet sich im virtuellen Speicher des Prozesses an einer beliebigen Adresse, und jede Box enthält einen Wert. In dieser Ansicht befinden wir uns in dem Zustand, in dem der funcA(&p)
aufgerufen wurde und der malloc ausgeführt wird.
Hey, warum denkst du es auf diese Weise, denke, wenn jemand Struktur übergibt, um Funktion anzuhängen, in diesem Fall wird die gesamte Struktur struct node{int data; struct node *link; };
in deinem Fall auf Stapelrahmen von append function
kopiert, also besser Adresse übergeben des Strukturzeigers, um nur 4 Bytes auf den Stack zu kopieren.
Sie brauchen das if / else nicht; In beiden Fällen verknüpfen Sie den neuen Knoten mit einem Zeiger, der vor der Operation NULL war. Dies könnte der Wurzelknoten oder der nächste Knoten des letzten Knotens in der Kette sein. Beide sind Zeiger auf den Strukturknoten, und Sie benötigen für diese Zeiger einen -Zeiger , um ihnen zuzuordnen.
%Vor%Warum sollte jemand das tun? Grundsätzlich, weil es kürzer ist, verwendet es nur eine Schleife und keine zusätzlichen Bedingungen, verwendet keine zusätzlichen Variablen und es kann bewiesen werden, dass es korrekt ist. Es sollte natürlich ein Test für das Ergebnis von malloc hinzugefügt werden, der eine zusätzliche Bedingung benötigt.
Im Wesentlichen, als Jite & amp; die anderen sagten, ist richtig.
Wenn Sie eine Änderung in einer Datenstruktur in C anwenden möchten (Änderung durch eine andere Funktion), müssen Sie eine "Referenz" an diese Datenstruktur übergeben, damit die Änderung erhalten bleibt, nachdem die Funktion change () abgeschlossen wurde. Dies geschieht auch in Python. Sie übergeben Verweise auf Objekte, es sei denn, Sie erstellen explizit eine Kopie. In C müssen Sie angeben, was genau Sie tun möchten. Um es noch mehr zu vereinfachen, ist es entweder:
Geben Sie data_struct
einändern (data_struct) = & gt; Hier ist eine Kopie von meinem data_struct, machen Sie Ihre Änderung, aber es interessiert mich nicht in der Anruferfunktion über die Änderung, die Sie anwenden
oder
ändern (& amp; data_struct) = & gt; Hier ist die Adresse (eine "Referenz" zu) von meinem data_struct, wenden Sie Ihre Änderung an, die Anruferfunktion wird diese Änderung sehen, nachdem sie angewendet wird.
Je nachdem, was der ursprüngliche "Typ" ist, könnten Sie nun * oder ** haben. Denken Sie jedoch daran, dass es eine Grenze gibt für wie viele "Rückwege" Sie haben können, nicht sicher, ob es System oder Compiler ist, wenn jemand eine Antwort darauf hat, dass ich ein Nehmer bin. Ich hatte nie mehr als 3 Rückweisungen.
Ich denke, Grund ist wie folgt:
Strukturknoten * p; // Zeiger auf Knotenstruktur p = NULL;
Oberhalb des Schnipsels, wenn in den Hauptblock geschrieben, bedeutet der Wert des Zeigers p NULL, so dass er nicht auf irgendwas im Speicher zeigt. So übergeben wir die Adresse des Zeigers p, um einen neuen Knoten zu erzeugen, und weisen die Adresse des neuen Knotens dem Wert des Zeigers p zu.
* (& amp; p) == * q == temp;
Mit * q == temp; wir erreichen unser Ziel, dem Zeiger p, der ursprünglich nirgendwohin zeigte, einen Wert zuzuordnen.