Warum sollten zwei Werte, die auf Zeigern basieren, nicht außerhalb des Funktionsumfangs ausgetauscht werden?

8

Ich habe seit einigen Jahren nicht mehr in C ++ programmiert, also habe ich beschlossen, meine Erinnerungen mit Zeigern aufzufrischen.

Im klassischen Beispiel des Tauschens zwischen zwei Zahlen ist das Beispiel

%Vor%

Dies ermöglicht es uns, einen Funktionsaufruf wie swapPointersClassic(&foo, &bar); zu machen, und weil wir die Speicheradressen der beiden Variablen foo und bar übergeben, ruft die Funktion die Werte ab und führt den Austausch durch. Aber ich begann mich zu fragen, warum kann ich Folgendes nicht tun?

%Vor%

Das erscheint mir sinnvoller, weil wir nur genügend temporären Speicher für die Speicherung der Speicheradresse num1 erstellen müssen (anstelle eines vollständigen temporären Speichers für die Speicherung eines doppelten Werts * num1). Es scheint jedoch, dass der Funktionsumfang den Effekt des Zeigerwechsels begrenzt. Nachdem ich den Aufruf swapPointers(&foo, &bar); gemacht habe, kann ich sehen, dass innerhalb der Funktion swapPointers, foo & amp; Bar sind in der Tat ausgetauscht. Sobald wir die Funktion swapPointers verlassen, werden foo und bar nicht mehr vertauscht. Kann mir jemand helfen zu verstehen, warum das so ist? Dieses Verhalten erinnert mich an den typischen Wert-vor-Ort-Ansatz, aber wir gehen hier auf Hinweise zu. Das heißt also, wir können nur die Werte berühren, auf die diese Zeiger zeigen, nicht aber die Zeiger selbst.

    
Antony 08.11.2012, 17:52
quelle

8 Antworten

5

Tatsächlich passiert man den Zeiger nicht wirklich. Sie übergeben zwei Zeiger nach Wert. Das Übergeben von Zeigern an sich ist nicht genug - Sie müssen sie dereferenzieren. Der Akt der Dereferenzierung der (Kopien der) Zeiger macht die Magie passieren und es ist der eigentliche Ort, wo Scope Traversal erreicht wird.

Das Ändern der Argumente einer Funktion ist lokal für die Funktion, selbst wenn die Argumente selbst Zeiger sind. Sie müssen sie dereferenzieren , um auf den Speicher zuzugreifen, auf den sie zeigen (wiederum bedeutet "Zeiger übergeben" mehr als nur Zeiger übergeben - es gibt Zeiger und , die sie richtig verwenden) .

Beachten Sie auch Folgendes. Wenn Ihr zweiter Ansatz funktioniert und die beiden Zeiger ausgetauscht wurden, bedeutet dies, dass die -Adressen der Variablen ausgetauscht werden. Das ergibt keinen Sinn.

Übrigens haben wir in C ++ eine echte Call-by-Reference-Aufrufkonvention keine Notwendigkeit mit Zeigern zu verwirren:

%Vor%

(Es ist nur eine Frage der Implementierung, dass der Compiler dieses Verhalten höchstwahrscheinlich realisiert, indem er tatsächlich Zeiger verwendet, aber zumindest haben Sie keine Kopfschmerzen.)

    
user529758 08.11.2012, 17:58
quelle
5

Wenn Sie Zeiger, nicht ihre Werte, austauschen möchten, müssen Sie den Zeiger des Zeigers übergeben:

%Vor%

Sie können ein Beispiel in Ссылка

sehen

Sie können sogar mit Referenzen arbeiten:

%Vor%

Beispiel: Ссылка

Dies ist nützlich, wenn Sie mit sehr großen Objekten (z. B. Bilddaten) arbeiten und nicht viel Speicher, sondern nur Referenzen verschieben möchten.

    
Alessandro Pezzato 08.11.2012 17:55
quelle
2

Dies ist eine gute Frage, aber sobald Sie es herausgefunden haben, verwenden Sie std::swap oder < a href="http://en.cppreference.com/w/cpp/algorithm/iter_swap"> std::iter_swap anstatt eigene zu schreiben.

Wenn foo und bar Zeiger sind, würde der Aufruf von std::swap(foo, bar) die Adressen zwischen den beiden Zeigern austauschen. Der Aufruf von std::swap(*foo, *bar) oder std::iter_swap(foo, bar) würde die Zeiger dereferenzieren und die Objekte austauschen, auf die die Zeiger zeigen.

    
Blastfurnace 08.11.2012 18:30
quelle
0

Wenn Sie

anrufen %Vor%

Sie nehmen die Adresse von zwei Elementen auf dem Stapel . Diese Werte werden als Provisorien übergeben und nach Wert kopiert. In Ihrem Funktionskörper vertauschen Sie, wo diese Provisorien zeigen, bewegen sich aber nicht dorthin, wo foo und bar zeigen.

Am wichtigsten ist, dass foo und bar auf dem Stapel leben. Sie können nicht ändern, wo Variablen im Stapel in C ++ leben. Ein Wert wird einem Punkt auf dem Stapel zugeordnet und lebt dort. Sie können einen Zeiger auf den Zeiger auf diesen Wert erhalten (ähnlich wie bei der Übergabe nach Referenz), aber indem Sie einfach ändern, wo der temporäre Zeiger zeigt, ändern Sie nicht, wo sich das Objekt befindet, auf das verwiesen wird.

    
Doug T. 08.11.2012 17:56
quelle
0
___ qstnhdr ___ Warum sollten zwei Werte, die auf Zeigern basieren, nicht außerhalb des Funktionsumfangs ausgetauscht werden? ___ qstntxt ___

Ich habe seit einigen Jahren nicht mehr in C ++ programmiert, also habe ich beschlossen, meine Erinnerungen mit Zeigern aufzufrischen.

Im klassischen Beispiel des Tauschens zwischen zwei Zahlen ist das Beispiel

%Vor%

Dies ermöglicht es uns, einen Funktionsaufruf wie double *num1 zu machen, und weil wir die Speicheradressen der beiden Variablen foo und bar übergeben, ruft die Funktion die Werte ab und führt den Austausch durch. Aber ich begann mich zu fragen, warum kann ich Folgendes nicht tun?

%Vor%

Das erscheint mir sinnvoller, weil wir nur genügend temporären Speicher für die Speicherung der Speicheradresse num1 erstellen müssen (anstelle eines vollständigen temporären Speichers für die Speicherung eines doppelten Werts * num1). Es scheint jedoch, dass der Funktionsumfang den Effekt des Zeigerwechsels begrenzt. Nachdem ich den Aufruf %code% gemacht habe, kann ich sehen, dass innerhalb der Funktion swapPointers, foo & amp; Bar sind in der Tat ausgetauscht. Sobald wir die Funktion swapPointers verlassen, werden foo und bar nicht mehr vertauscht. Kann mir jemand helfen zu verstehen, warum das so ist? Dieses Verhalten erinnert mich an den typischen Wert-vor-Ort-Ansatz, aber wir gehen hier auf Hinweise zu. Das heißt also, wir können nur die Werte berühren, auf die diese Zeiger zeigen, nicht aber die Zeiger selbst.

    
___ answer13294881 ___

Wenn Sie Zeiger, nicht ihre Werte, austauschen möchten, müssen Sie den Zeiger des Zeigers übergeben:

%Vor%

Sie können ein Beispiel in Ссылка

sehen

Sie können sogar mit Referenzen arbeiten:

%Vor%

Beispiel: Ссылка

Dies ist nützlich, wenn Sie mit sehr großen Objekten (z. B. Bilddaten) arbeiten und nicht viel Speicher, sondern nur Referenzen verschieben möchten.

    
___ answer13294896 ___

Sie übergeben die Zeiger selbst nach Wert; %code% und %code% sind lokale Kopien von Zeigern, mit denen der Aufrufer die Funktion aufruft. Daher ist das Auslagern an der Callsite nicht sichtbar, wenn die Funktion beendet wird, da die Änderungen nur an Variablen vorgenommen wurden, die für die Funktion lokal sind.

Ändern Sie stattdessen die Funktion, um Verweise auf die Zeiger aufzunehmen, und Ihr Auslagerungscode funktioniert wie vorgesehen.

%Vor%

Nun sind Ihre Funktionsparameter Aliasnamen für die Zeiger, die der Aufrufer an die Funktion übergibt, und was immer Sie auch tun, beeinflusst auch die Zeiger im Kontext des Aufrufers.

    
___ answer13294913 ___

Wenn Sie

anrufen %Vor%

Sie nehmen die Adresse von zwei Elementen auf dem Stapel . Diese Werte werden als Provisorien übergeben und nach Wert kopiert. In Ihrem Funktionskörper vertauschen Sie, wo diese Provisorien zeigen, bewegen sich aber nicht dorthin, wo foo und bar zeigen.

Am wichtigsten ist, dass foo und bar auf dem Stapel leben. Sie können nicht ändern, wo Variablen im Stapel in C ++ leben. Ein Wert wird einem Punkt auf dem Stapel zugeordnet und lebt dort. Sie können einen Zeiger auf den Zeiger auf diesen Wert erhalten (ähnlich wie bei der Übergabe nach Referenz), aber indem Sie einfach ändern, wo der temporäre Zeiger zeigt, ändern Sie nicht, wo sich das Objekt befindet, auf das verwiesen wird.

    
___ antwort13294925 ___

Wenn Sie %code% an eine Funktion übergeben, übergeben Sie einen Zeiger als Wert. Dadurch wird im Funktionsumfang eine Variable erstellt, die die Adresse eines Double enthält. Wenn Sie dies zuweisen, ändern Sie den Wert des Zeigers im Funktionsumfang.

Genauso wie Sie einen Zeiger auf einen Doppelpunkt übergeben, ihn dereferenzieren und zuweisen müssen, um einen Doppelpunkt zu tauschen, müssen Sie einen Zeiger an einen Zeiger übergeben, ihn dereferenzieren und zuweisen, um einen Zeiger zu tauschen.

    
___ tag123c ___ C ++ ist eine universelle Programmiersprache. Es wurde ursprünglich als Erweiterung von C entworfen und behält eine ähnliche Syntax, ist aber jetzt eine komplett andere Sprache. Verwenden Sie dieses Tag für Fragen zu Code, der mit einem C ++ - Compiler kompiliert werden soll. ___ tag123pointer ___ Datentyp, der auf einen anderen gespeicherten Wert zeigt. Eine Zeigervariable enthält eine Speicheradresse einer anderen Entität (Variable oder Funktion oder andere Entität). Dieses Tag sollte für Fragen verwendet werden, bei denen Zeiger und keine Referenzen verwendet werden. Die gebräuchlichsten Programmiersprachen, die Zeiger verwenden, sind C-, C ++ -, Go- und Assemblersprachen. Verwenden Sie ein bestimmtes Sprach-Tag. Andere hilfreiche Tags sind Methode, Funktion, Struktur usw., die die Verwendung von Pointer beschreiben. ___ answer13295030 ___

Zeiger 101: Ein Zeiger ist ein Stück Papier mit der Adresse eines Hauses darauf.

Mit Schreiben & amp; bar, nehmen Sie das Haus namens Bar und schreiben seine Adresse auf ein anonymes Stück Papier.

Sie rufen dann eine Funktion mit diesem Zeiger als Argument auf. Was passiert, ist, dass die Funktion eine weitere Kopie des Blattes erstellt.

Ihre Swap-Funktion nimmt dann zwei solche lokale Kopien und tauscht sie aus, welche Adresse auf sie geschrieben ist.

Das tut also nichts außerhalb der Funktion. Klar?

Im Grunde genommen, selbst wenn es keine Kopie in der Funktion der Adresse gab, arbeiten Sie nur mit Altpapier mit Hausadressen auf ihnen. Keine Änderungen an einem solchen Papier können tatsächlich die in der Hausleiste oder im foo gespeicherten Daten verschieben.

    
___ answer13294935 ___

Tatsächlich passiert man den Zeiger nicht wirklich. Sie übergeben zwei Zeiger nach Wert. Das Übergeben von Zeigern an sich ist nicht genug - Sie müssen sie dereferenzieren. Der Akt der Dereferenzierung der (Kopien der) Zeiger macht die Magie passieren und es ist der eigentliche Ort, wo Scope Traversal erreicht wird.

Das Ändern der Argumente einer Funktion ist lokal für die Funktion, selbst wenn die Argumente selbst Zeiger sind. Sie müssen sie dereferenzieren , um auf den Speicher zuzugreifen, auf den sie zeigen (wiederum bedeutet "Zeiger übergeben" mehr als nur Zeiger übergeben - es gibt Zeiger und , die sie richtig verwenden) .

Beachten Sie auch Folgendes. Wenn Ihr zweiter Ansatz funktioniert und die beiden Zeiger ausgetauscht wurden, bedeutet dies, dass die -Adressen der Variablen ausgetauscht werden. Das ergibt keinen Sinn.

Übrigens haben wir in C ++ eine echte Call-by-Reference-Aufrufkonvention keine Notwendigkeit mit Zeigern zu verwirren:

%Vor%

(Es ist nur eine Frage der Implementierung, dass der Compiler dieses Verhalten höchstwahrscheinlich realisiert, indem er tatsächlich Zeiger verwendet, aber zumindest haben Sie keine Kopfschmerzen.)

    
___ tag123swap ___ Ändern der Position von zwei Elementen. ___ answer31434265 ___

Da die richtige Antwort auf diese Frage bereits veröffentlicht wurde, möchte ich einige Dinge klarstellen.

Das Austauschen von Werten aus zwei Variablen mithilfe von Referenzen ist im Allgemeinen eine schlechte Übung und sollte vermieden werden. Der Grund ist, dass Referenzen nicht neu zugewiesen werden sollten. Sehen Sie sich den folgenden Code an:

%Vor%

In den Zeilen 4 und 5 verstoßen Sie eindeutig gegen die allgemeine Regel "Verweise nie neu zuweisen".

Wenn Sie den Wert der Variablen wirklich ändern müssen, bevorzugen Sie immer "Übergabe nach Zeigern" und nicht "Nach Referenz". Der folgende Code macht Sinn und eine gute Praxis IMO.

%Vor%

Vereinbaren Sie, dass Verweise sauberer und einfacher zu verwenden sind und dass sie Informationen besser verbergen, wie Sie im ersten Beispiel gesehen haben. Referenzen können jedoch nicht neu zugewiesen werden. Wenn Sie zuerst auf ein Objekt und dann auf ein anderes Objekt zeigen müssen, müssen Sie einen Zeiger verwenden.

Faustregel:

  • Verwenden Sie KEINE Zeiger, wenn Referenzen funktionieren.
  • Versuchen Sie nicht, einen Verweis auf eine andere Variable neu zuzuweisen. Du kannst nicht.
___ answer13295434 ___

Dies ist eine gute Frage, aber sobald Sie es herausgefunden haben, verwenden Sie %code% oder < a href="http://en.cppreference.com/w/cpp/algorithm/iter_swap"> %code% anstatt eigene zu schreiben.

Wenn %code% und %code% Zeiger sind, würde der Aufruf von %code% die Adressen zwischen den beiden Zeigern austauschen. Der Aufruf von %code% oder %code% würde die Zeiger dereferenzieren und die Objekte austauschen, auf die die Zeiger zeigen.

    
___
Bill Carey 08.11.2012 17:57
quelle
0

Zeiger 101: Ein Zeiger ist ein Stück Papier mit der Adresse eines Hauses darauf.

Mit Schreiben & amp; bar, nehmen Sie das Haus namens Bar und schreiben seine Adresse auf ein anonymes Stück Papier.

Sie rufen dann eine Funktion mit diesem Zeiger als Argument auf. Was passiert, ist, dass die Funktion eine weitere Kopie des Blattes erstellt.

Ihre Swap-Funktion nimmt dann zwei solche lokale Kopien und tauscht sie aus, welche Adresse auf sie geschrieben ist.

Das tut also nichts außerhalb der Funktion. Klar?

Im Grunde genommen, selbst wenn es keine Kopie in der Funktion der Adresse gab, arbeiten Sie nur mit Altpapier mit Hausadressen auf ihnen. Keine Änderungen an einem solchen Papier können tatsächlich die in der Hausleiste oder im foo gespeicherten Daten verschieben.

    
Yakk 08.11.2012 18:04
quelle
0

Da die richtige Antwort auf diese Frage bereits veröffentlicht wurde, möchte ich einige Dinge klarstellen.

Das Austauschen von Werten aus zwei Variablen mithilfe von Referenzen ist im Allgemeinen eine schlechte Übung und sollte vermieden werden. Der Grund ist, dass Referenzen nicht neu zugewiesen werden sollten. Sehen Sie sich den folgenden Code an:

%Vor%

In den Zeilen 4 und 5 verstoßen Sie eindeutig gegen die allgemeine Regel "Verweise nie neu zuweisen".

Wenn Sie den Wert der Variablen wirklich ändern müssen, bevorzugen Sie immer "Übergabe nach Zeigern" und nicht "Nach Referenz". Der folgende Code macht Sinn und eine gute Praxis IMO.

%Vor%

Vereinbaren Sie, dass Verweise sauberer und einfacher zu verwenden sind und dass sie Informationen besser verbergen, wie Sie im ersten Beispiel gesehen haben. Referenzen können jedoch nicht neu zugewiesen werden. Wenn Sie zuerst auf ein Objekt und dann auf ein anderes Objekt zeigen müssen, müssen Sie einen Zeiger verwenden.

Faustregel:

  • Verwenden Sie KEINE Zeiger, wenn Referenzen funktionieren.
  • Versuchen Sie nicht, einen Verweis auf eine andere Variable neu zuzuweisen. Du kannst nicht.
Shashikant Mitkari 15.07.2015 15:10
quelle
0

Sie übergeben die Zeiger selbst nach Wert; num1 und num2 sind lokale Kopien von Zeigern, mit denen der Aufrufer die Funktion aufruft. Daher ist das Auslagern an der Callsite nicht sichtbar, wenn die Funktion beendet wird, da die Änderungen nur an Variablen vorgenommen wurden, die für die Funktion lokal sind.

Ändern Sie stattdessen die Funktion, um Verweise auf die Zeiger aufzunehmen, und Ihr Auslagerungscode funktioniert wie vorgesehen.

%Vor%

Nun sind Ihre Funktionsparameter Aliasnamen für die Zeiger, die der Aufrufer an die Funktion übergibt, und was immer Sie auch tun, beeinflusst auch die Zeiger im Kontext des Aufrufers.

    
Praetorian 08.11.2012 17:56
quelle

Tags und Links