Pass auf Referenz oder Wert?

Wenn Sie eine neue Programmiersprache lernen, ist eine der möglichen Straßensperren die Frage, ob die Sprache standardmäßig pass-by-value oder pass-by-reference ist .

Also hier ist meine Frage an Sie alle, in Ihrer Lieblingssprache, wie wird es eigentlich gemacht? Und was sind die möglichen Fallstricke ?

Deine Lieblingssprache kann natürlich alles sein, mit dem du je gespielt hast: beliebt , obskur , esoterisch , neu , alt …

Hier ist mein eigener Beitrag für die Programmiersprache Java .

Zuerst ein bisschen Code:

public void swap(int x, int y) { int tmp = x; x = y; y = tmp; } 

Der Aufruf dieser Methode führt zu folgendem Ergebnis:

 int pi = 3; int everything = 42; swap(pi, everything); System.out.println("pi: " + pi); System.out.println("everything: " + everything); "Output: pi: 3 everything: 42" 

selbst die Verwendung von “echten” Objekten zeigt ein ähnliches Ergebnis:

 public class MyObj { private String msg; private int number; //getters and setters public String getMsg() { return this.msg; } public void setMsg(String msg) { this.msg = msg; } public int getNumber() { return this.number; } public void setNumber(int number) { this.number = number; } //constructor public MyObj(String msg, int number) { setMsg(msg); setNumber(number); } } public static void swap(MyObj x, MyObj y) { MyObj tmp = x; x = y; y = tmp; } public static void main(String args[]) { MyObj x = new MyObj("Hello world", 1); MyObj y = new MyObj("Goodbye Cruel World", -1); swap(x, y); System.out.println(x.getMsg() + " -- "+ x.getNumber()); System.out.println(y.getMsg() + " -- "+ y.getNumber()); } "Output: Hello world -- 1 Goodbye Cruel World -- -1" 

Somit ist klar, dass Java seine Parameter nach Wert übergibt, da der Wert für pi und alles und die MyObj-Objekte nicht vertauscht sind. Beachten Sie, dass “by value” die einzige Möglichkeit in Java ist, Parameter an eine Methode zu übergeben. (Zum Beispiel erlaubt eine Sprache wie C ++ dem Entwickler, einen Parameter als Referenz zu übergeben, indem er nach dem Parametertyp ‘ & ‘ verwendet)

jetzt der schwierige Teil , oder zumindest der Teil, der die meisten der neuen Java-Entwickler verwirren wird: (entlehnt von javaworld )
Ursprünglicher Autor: Tony Sintes

 public void tricky(Point arg1, Point arg2) { arg1.x = 100; arg1.y = 100; Point temp = arg1; arg1 = arg2; arg2 = temp; } public static void main(String [] args) { Point pnt1 = new Point(0,0); Point pnt2 = new Point(0,0); System.out.println("X: " + pnt1.x + " Y: " +pnt1.y); System.out.println("X: " + pnt2.x + " Y: " +pnt2.y); System.out.println(" "); tricky(pnt1,pnt2); System.out.println("X: " + pnt1.x + " Y:" + pnt1.y); System.out.println("X: " + pnt2.x + " Y: " +pnt2.y); } "Output X: 0 Y: 0 X: 0 Y: 0 X: 100 Y: 100 X: 0 Y: 0" 

tricky ändert erfolgreich den Wert von pnt1! Dies würde bedeuten, dass Objekte als Referenz übergeben werden, dies ist nicht der Fall! Eine korrekte Aussage wäre: Die Objektreferenzen werden nach Wert übergeben.

mehr von Tony Sintes:

Die Methode ändert erfolgreich den Wert von pnt1, obwohl sie nach Wert übergeben wird. Ein Tausch von pnt1 und pnt2 schlägt jedoch fehl! Dies ist die Hauptursache für Verwirrung. In der Methode main () sind pnt1 und pnt2 nichts anderes als Objektreferenzen. Wenn Sie pnt1 und pnt2 an die tricky () -Methode übergeben, übergibt Java die Verweise genau wie alle anderen Parameter nach Wert. Dies bedeutet, dass die an die Methode übergebenen Referenzen tatsächlich Kopien der ursprünglichen Referenzen sind. Abbildung 1 zeigt zwei Referenzen, die auf dasselbe Objekt zeigen, nachdem Java ein Objekt an eine Methode übergeben hat.

Abbildung 1 http://sofde.miximages.com/language-agnostic/03-qa-0512-pass2b.gif

Fazit oder kurz gesagt:

  • Java übergibt Parameter nach Wert
  • “by value” ist die einzige Möglichkeit in Java, einen Parameter an eine Methode zu übergeben
  • Die Verwendung von Methoden aus dem als Parameter angegebenen Objekt ändert das Objekt so, dass die Referenzen auf die ursprünglichen Objekte verweisen. (wenn diese Methode selbst einige Werte ändert)

Nützliche Links:

Hier ist ein weiterer Artikel für die Programmiersprache c #

c # übergibt seine Argumente nach Wert (standardmäßig)

 private void swap(string a, string b) { string tmp = a; a = b; b = tmp; } 

Der Aufruf dieser Version von swap hat somit kein Ergebnis:

 string x = "foo"; string y = "bar"; swap(x, y); "output: x: foo y: bar" 

Im Gegensatz zu Java gibt c # dem Entwickler jedoch die Möglichkeit, Parameter als Referenz zu übergeben , indem das Schlüsselwort ‘ref’ vor dem Typ des Parameters verwendet wird:

 private void swap(ref string a, ref string b) { string tmp = a; a = b; b = tmp; } 

Dieser Austausch ändert den Wert des referenzierten Parameters:

 string x = "foo"; string y = "bar"; swap(x, y); "output: x: bar y: foo" 

c # hat auch ein out-Schlüsselwort , und der Unterschied zwischen ref und out ist ein subtiler. von msdn:

Der Aufrufer einer Methode, die einen out-Parameter akzeptiert, muss nicht der Variablen zugewiesen werden, die vor dem Aufruf als out-Parameter übergeben wurde. Der Angerufene muss jedoch vor der Rückgabe dem Ausgangsparameter zugewiesen werden.

und

Im Gegensatz dazu werden ref-Parameter als anfänglich vom Angerufenen zugewiesen betrachtet. Daher muss der Angerufene den ref- Parameter vor der Verwendung nicht zuordnen . Ref-Parameter werden sowohl innerhalb als auch außerhalb einer Methode übergeben.

Ein kleiner Fallstrick ist, wie in Java, dass Objekte, die nach Wert weitergegeben werden, immer noch mit ihren inneren Methoden verändert werden können

Fazit:

  • c # übergibt seine Parameter standardmäßig nach Wert
  • Bei Bedarf können die Parameter auch mit dem Schlüsselwort ref übergeben werden
  • innere Methoden aus einem Parameter, der als Wert übergeben wird, verändern das Objekt (wenn diese Methode selbst einige Werte ändert)

Nützliche Links:

Python verwendet Pass-by-Value, aber da alle diese Werte Objektreferenzen sind, ist der Nettoeffekt etwas wie Pass-by-Reference. Python-Programmierer denken jedoch mehr darüber nach, ob ein Objekttyp änderbar oder unveränderbar ist . Veränderbare Objekte können vor Ort geändert werden (z. B. Wörterbücher, Listen, benutzerdefinierte Objekte), während unveränderliche Objekte nicht in der Lage sind (z. B. Integer, Strings, Tupel).

Das folgende Beispiel zeigt eine function, die zwei Argumente, eine unveränderliche Zeichenfolge und eine änderbare Liste übergeben wird.

 >>> def do_something(a, b): ... a = "Red" ... b.append("Blue") ... >>> a = "Yellow" >>> b = ["Black", "Burgundy"] >>> do_something(a, b) >>> print a, b Yellow ['Black', 'Burgundy', 'Blue'] 

Die Zeile a = "Red" erzeugt lediglich einen lokalen Namen, a , für den String-Wert "Red" und hat keine Auswirkung auf das übergebene Argument (welches nun ausgeblendet ist, da von da an auf den lokalen Namen verwiesen werden muss) . Die Zuweisung ist keine In-Place-Operation, unabhängig davon, ob das Argument veränderlich oder unveränderlich ist.

Der b Parameter ist ein Verweis auf ein veränderbares .append() , und die Methode .append() führt eine .append() Erweiterung der Liste durch, indem sie den neuen .append() "Blue" anheftet.

(Da String-Objekte unveränderbar sind, verfügen sie nicht über Methoden, die direkte Änderungen unterstützen.)

Sobald die function zurückkehrt, hatte die Neuzuweisung von a keine Auswirkung, während die Erweiterung von b deutlich die semantische Aufrufsyntax zeigt.

Wie bereits erwähnt, ist die Neuzuweisung innerhalb der function keine in-place-Operation, selbst wenn das Argument für a ein veränderbarer Typ ist. Daher würde sich der Wert des übergebenen Arguments nicht ändern:

 >>> a = ["Purple", "Violet"] >>> do_something(a, b) >>> print a, b ['Purple', 'Violet'] ['Black', 'Burgundy', 'Blue', 'Blue'] 

Wenn Sie nicht möchten, dass Ihre Liste durch die aufgerufene function geändert wird, verwenden Sie stattdessen den unveränderlichen Tupel-Typ (durch die Klammern in der .append() anstelle von eckigen Klammern gekennzeichnet), der das In-Place- .append() nicht unterstützt. Methode:

 >>> a = "Yellow" >>> b = ("Black", "Burgundy") >>> do_something(a, b) Traceback (most recent call last): File "", line 1, in  File "", line 3, in do_something AttributeError: 'tuple' object has no attribute 'append' 

Da ich noch keine Perl-Antwort gesehen habe, dachte ich, ich würde eine schreiben.

Unter der Haube arbeitet Perl effektiv als Pass-by-Reference. Variablen als functionsaufrufargumente werden referenziell übergeben, Konstanten werden als schreibgeschützte Werte übergeben, und Ergebnisse von Ausdrücken werden als Provisorien übergeben. Die üblichen Idiome, um Argumentlisten nach @_ von @_ oder durch shift zu @_ , neigen dazu, dies vor dem Benutzer zu verbergen, was das Aussehen von Pass-by-Value ergibt:

 sub incr { my ( $x ) = @_; $x++; } my $value = 1; incr($value); say "Value is now $value"; 

Dies wird gedruckt Value is now 1 da $x++ die lexikalische Variable erhöht hat, die in der function incr() deklariert ist, und nicht die übergebene Variable. Diese Vorübergehende-Wert-Art ist normalerweise die meiste Zeit als function erwünscht Das Ändern ihrer Argumente ist in Perl selten, und der Stil sollte vermieden werden.

Wenn dieses Verhalten jedoch aus bestimmten Gründen speziell gewünscht wird, kann dies durch direktes @_ von Elementen des Array @_ , da sie Aliasnamen für Variablen sind, die an die function übergeben werden.

 sub incr { $_[0]++; } my $value = 1; incr($value); say "Value is now $value"; 

Dieses Mal wird der Ausdruck ” Value is now 2 , da der Ausdruck $_[0]++ die tatsächliche $value Variable inkrementiert. Die Art und Weise, wie dies funktioniert, ist, dass unter der Haube @_ kein reelles Array ist wie die meisten anderen Arrays (wie von my @array ), sondern dass seine Elemente direkt aus den Argumenten gebildet werden, die an einen functionsaufruf übergeben werden. Auf diese Weise können Sie eine Semantik für die Weitergabe nach Referenz erstellen, falls dies erforderlich sein sollte. functionsaufrufargumente, die einfache Variablen sind, werden unverändert in dieses Array eingefügt, und Konstanten oder Ergebnisse komplexerer Ausdrücke werden als schreibgeschützte Provisorien eingefügt.

Es ist jedoch äußerst selten, dies in der Praxis zu tun, da Perl Referenzwerte unterstützt; das sind Werte, die sich auf andere Variablen beziehen. Normalerweise ist es viel klarer, eine function zu konstruieren, die eine offensichtliche Nebenwirkung auf eine Variable hat, indem eine Referenz auf diese Variable übergeben wird. Dies ist ein klarer Hinweis für den Leser auf der Callsite, dass eine Semantik mit pass-by-reference-function vorliegt.

 sub incr_ref { my ( $ref ) = @_; $$ref++; } my $value = 1; incr(\$value); say "Value is now $value"; 

Hier liefert der \ Operator eine Referenz auf die gleiche Weise wie der & Adresse-Operator in C.

Es gibt eine gute Erklärung für .NET.

Viele Leute sind überrascht, dass Referenzobjekte tatsächlich nach Wert übergeben werden (sowohl in C # als auch in Java). Es ist eine Kopie einer Stapeladresse. Dadurch wird verhindert, dass eine Methode ändert, auf was das Objekt tatsächlich zeigt, und dennoch eine Methode die Werte des Objekts ändern kann. In C # ist es möglich, eine Referenz als Referenz zu übergeben, was bedeutet, dass Sie ändern können, wohin ein tatsächliches Objekt zeigt.

Vergiss nicht, dass es auch einen Vornamen gibt und an einem Wert-Ergebnis vorbei geht .

Pass-by-value-result ist ähnlich wie pass-by-value, mit dem zusätzlichen Aspekt, dass der Wert in der ursprünglichen Variable gesetzt wird, die als Parameter übergeben wurde. Es kann bis zu einem gewissen Grad Interferenzen mit globalen Variablen vermeiden. Es ist anscheinend besser im partitionierten Speicher, wo ein Durchlauf durch Verweis einen Seitenerrors verursachen könnte ( Referenz ).

Mit Name übergeben bedeutet, dass die Werte nur berechnet werden, wenn sie tatsächlich verwendet werden, und nicht erst zu Beginn der Prozedur. Algol verwendet Pass-by-Name, aber ein interessanter Nebeneffekt ist, dass es sehr schwierig ist, ein Swap-Verfahren ( Reference ) zu schreiben. Außerdem wird der mit dem Namen übergebene Ausdruck bei jedem Zugriff neu ausgewertet, was auch Nebenwirkungen haben kann.

nach Wert

  • ist langsamer als durch Referenz, da das System den Parameter kopieren muss
  • nur für die Eingabe verwendet

durch Bezugnahme

  • schneller, da nur ein pointers übergeben wird
  • verwendet für Eingabe und Ausgabe
  • kann sehr gefährlich sein, wenn es in Verbindung mit globalen Variablen verwendet wird

Was immer Sie als Vorabüberweisung oder Vorabüberweisung angeben, muss in allen Sprachen konsistent sein. Die gebräuchlichste und konsistenteste Definition, die in allen Sprachen verwendet wird, besteht darin, dass Sie bei pass-by-reference eine Variable “normal” an eine function übergeben können (dh ohne explizite Adresse oder ähnliches), und die function zuweisen kann (nicht mutieren) der Inhalt von) der Parameter innerhalb der function und hat denselben Effekt wie die Zuweisung an die Variable im aufrufenden Bereich.

Aus dieser Sicht sind die Sprachen wie folgt gruppiert: Jede Gruppe hat die gleiche Semantik. Wenn Sie denken, dass zwei Sprachen nicht in derselben Gruppe zusammengefasst werden sollten, fordere ich Sie heraus, ein Beispiel zu entwickeln, das sie auszeichnet.

Die überwiegende Mehrheit der Sprachen einschließlich C , Java , Python , Ruby , JavaScript , Schema , OCaml , Standard ML , Go , Objective-C , Smalltalk usw. sind alle nur pass-by-value . Das Übergeben eines pointerswerts (einige Sprachen nennen es eine “Referenz”) zählt nicht als Verweis durch Verweis; wir sind nur besorgt über das Ding, das passiert ist, den pointers, nicht das Ding, auf das gezeigt wird.

Sprachen wie C ++ , C # , PHP sind standardmäßig pass-by-value wie die oben genannten Sprachen, aber functionen können Parameter explizit als pass-by-reference deklarieren, indem Sie & oder ref .

Perl ist immer pass-by-reference; In der Praxis kopieren die Leute jedoch fast immer die Werte, nachdem sie sie erhalten haben, und verwenden sie so in einer “Pass-by-Value” -Weise.

In Bezug auf J gibt es, während nur AFAIK nach Wert geht, eine Form der Referenzübergabe, die es ermöglicht, viele Daten zu bewegen. Sie übergeben einfach etwas, das als Locale bekannt ist, an ein Verb (oder eine function). Dies kann eine Instanz einer class oder nur ein generischer Container sein.

 spaceused=: [: 7!:5 < exectime =: 6!:2 big_chunk_of_data =. i. 1000 1000 100 passbyvalue =: 3 : 0 $ y '' ) locale =. cocreate'' big_chunk_of_data__locale =. big_chunk_of_data passbyreference =: 3 : 0 l =. y $ big_chunk_of_data__l '' ) exectime 'passbyvalue big_chunk_of_data' 0.00205586720663967 exectime 'passbyreference locale' 8.57957102144893e_6 

Der offensichtliche Nachteil ist, dass Sie den Namen Ihrer Variablen in irgendeiner Weise in der aufgerufenen function kennen müssen. Aber diese Technik kann viele Daten schmerzlos bewegen. Deshalb nenne ich das, obwohl es technisch nicht durch Verweis geht, "so ziemlich das".

PHP ist auch nach Wert.

 < ?php class Holder { private $value; public function __construct($value) { $this->value = $value; } public function getValue() { return $this->value; } } function swap($x, $y) { $tmp = $x; $x = $y; $y = $tmp; } $a = new Holder('a'); $b = new Holder('b'); swap($a, $b); echo $a->getValue() . ", " . $b->getValue() . "\n"; 

Ausgänge:

 ab 

In PHP4 wurden Objekte jedoch wie Primitive behandelt. Was bedeutet:

 < ?php $myData = new Holder('this should be replaced'); function replaceWithGreeting($holder) { $myData->setValue('hello'); } replaceWithGreeting($myData); echo $myData->getValue(); // Prints out "this should be replaced" 

Standardmäßig verwendet ANSI / ISO C beides – es hängt davon ab, wie Sie Ihre function und ihre Parameter deklarieren.

Wenn Sie Ihre functionsparameter als pointers deklarieren, wird die function pass-by-reference sein, und wenn Sie Ihre functionsparameter als Nicht-pointers-Variablen deklarieren, wird die function als Wert übergeben.

 void swap(int *x, int *y); //< Declared as pass-by-reference. void swap(int x, int y); //< Declared as pass-by-value (and probably doesn't do anything useful.) 

Sie können Probleme auftreten, wenn Sie eine function erstellen, die einen pointers auf eine nicht statische Variable zurückgibt, die innerhalb dieser function erstellt wurde. Der zurückgegebene Wert des folgenden Codes wäre undefiniert - es gibt keine Möglichkeit zu wissen, ob der Speicherplatz, der der in der function erstellten temporären Variablen zugewiesen wurde, überschrieben wurde oder nicht.

 float *FtoC(float temp) { float c; c = (temp-32)*9/5; return &c; } 

Sie könnten jedoch einen Verweis auf eine statische Variable oder einen pointers, der in der Parameterliste übergeben wurde, zurückgeben.

 float *FtoC(float *temp) { *temp = (*temp-32)*9/5; return temp; }