language agnostic - Als Referenz übergeben oder als Wert übergeben?

Translate

Wenn Sie eine neue Programmiersprache lernen, ist eines der möglichen Hindernisse die Frage, ob die Sprache standardmäßig ist.Pass-by-Value oder Pass-by-Reference.

Hier ist meine Frage an Sie alle in Ihrer Lieblingssprache:Wieist es tatsächlich getan? Und was sind diemögliche Fallstricke?

Ihre Lieblingssprache kann natürlich alles sein, mit dem Sie jemals gespielt haben:Beliebt, obskur, esoterisch, Neu, alt...

This question and all comments follow the "Attribution Required."

Alle Antworten

Translate

Hier ist mein eigener Beitrag für dieJava-Programmiersprache.

zuerst etwas Code:

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

Das Aufrufen 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 übergibtnach Wertals Wert fürpiundallesund dieMyObj-Objektewerden nicht getauscht. Beachten Sie, dass "nach Wert" das isteinziger Wegin Java, um Parameter an eine Methode zu übergeben. (In einer Sprache wie c ++ kann der Entwickler beispielsweise einen Parameter als Referenz übergeben, indem er '&'nach dem Typ des Parameters)

jetzt dieschwieriger Teil, oder zumindest der Teil, der die meisten neuen Java-Entwickler verwirren wird: (entlehnt vonjavaworld)
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"

schwierigä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:dasObjektreferenzenwerden als Wert übergeben.

mehr von Tony Sintes:

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

figure 1
(Quelle:javaworld.com)

Fazitoder kurz gesagt:

  • Java übergibt es Parameternach Wert
  • "nach Wert"ist dereinziger Wegin Java, um einen Parameter an eine Methode zu übergeben
  • mitMethoden aus dem Objektals Parameter angegebenwird sich ändernDas Objekt als Referenz verweist auf die Originalobjekte. (wenn diese Methode selbst einige Werte ändert)

Nützliche Links:

Quelle
Translate

Hier ist ein weiterer Artikel für dieProgrammiersprache c #

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

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

Das Aufrufen dieser Swap-Version hat daher kein Ergebnis:

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

"output: 
x: foo
y: bar"

jedoch,im gegensatz zu javac #tutGeben Sie dem Entwickler die Möglichkeit, Parameter zu übergebendurch BezugnahmeDies erfolgt mithilfe des Schlüsselworts 'ref' vor dem Typ des Parameters:

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

dieser TauschwerdenÄndern Sie den Wert des referenzierten Parameters:

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

"output: 
x: bar
y: foo"

c # hat auch eineout Schlüsselwortund der Unterschied zwischen ref und out ist subtil.von msdn:

Der Aufrufer einer Methode, die eineout Parameterist nicht erforderlich, der Variablen zuzuweisen, die vor dem Aufruf als out-Parameter übergeben wurde; Der Angerufene ist jedocherforderlich, um den out-Parameter zuzuweisen, bevor Sie zurückkehren.

und

Im Gegensatzref Parametersindals ursprünglich zugewiesen betrachtetbeim Angerufenen. Als solches ist der Angerufenenicht erforderlich, um die ref zuzuweisenParameter vor der Verwendung. Ref-Parameter werden sowohl in eine Methode als auch aus einer Methode übergeben.

Eine kleine Falle ist, wie in Java, dasObjekte, die als Wert übergeben werden, können weiterhin mit ihren inneren Methoden geändert werden

Fazit:

  • c # übergibt seine Parameter standardmäßignach Wert
  • Bei Bedarf können aber auch Parameter übergeben werdendurch BezugnahmeVerwenden Sie das Schlüsselwort ref
  • innere Methoden aus einem Parameter, der als Wert übergeben wirdwird sich änderndas Objekt (wenn diese Methode selbst einige Werte ändert)

Nützliche Links:

Quelle
Translate

Pythonverwendet Pass-by-Value, aber da alle diese Werte Objektreferenzen sind, ähnelt der Nettoeffekt einer Pass-by-Reference. Python-Programmierer denken jedoch mehr darüber nach, ob es sich um einen Objekttyp handeltveränderlichoderunveränderlich. Veränderbare Objekte können direkt geändert werden (z. B. Wörterbücher, Listen, benutzerdefinierte Objekte), unveränderliche Objekte nicht (z. B. Ganzzahlen, Zeichenfolgen, Tupel).

Das folgende Beispiel zeigt eine Funktion, der zwei Argumente übergeben werden, eine unveränderliche Zeichenfolge und eine veränderbare Liste.

>>> 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 Liniea = "Red"erstellt lediglich einen lokalen Namen,a, für den Zeichenfolgenwert"Red"und hat keine Auswirkung auf das übergebene Argument (das jetzt als verborgen istamuss sich von da an auf den lokalen Namen beziehen). Die Zuweisung ist keine direkte Operation, unabhängig davon, ob das Argument veränderlich oder unveränderlich ist.

DasbDer Parameter ist eine Referenz auf ein veränderbares Listenobjekt.append()Die Methode führt eine direkte Erweiterung der Liste durch und greift dabei die neue auf"Blue"Zeichenfolgenwert.

(Da Zeichenfolgenobjekte unveränderlich sind, verfügen sie über keine Methoden, die direkte Änderungen unterstützen.)

Sobald die Funktion zurückkehrt, erfolgt die Neuzuweisung vonahat keine Wirkung gehabt, während die Verlängerung vonbzeigt deutlich die Aufrufsemantik im Pass-by-Reference-Stil.

Wie bereits erwähnt, auch wenn das Argument füraist ein veränderlicher Typ, die Neuzuweisung innerhalb der Funktion ist keine direkte Operation, und 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 Funktion geändert wird, verwenden Sie stattdessen den unveränderlichen Tupeltyp (gekennzeichnet durch die Klammern in der Literalform anstelle von eckigen Klammern), der die In-Place-Funktion nicht unterstützt.append()Methode:

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

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

Unter der Haube arbeitet Perl effektiv als Referenz. Variablen als Funktionsaufrufargumente werden referenziell übergeben, Konstanten als schreibgeschützte Werte und Ergebnisse von Ausdrücken als temporäre Werte. Die üblichen Redewendungen zum Erstellen von Argumentlisten durch Listenzuweisung aus@_, oder vonshiftneigen dazu, dies vor dem Benutzer zu verbergen, was den Anschein eines Pass-by-Value erweckt:

sub incr {
  my ( $x ) = @_;
  $x++;
}

my $value = 1;
incr($value);
say "Value is now $value";

Dies wird gedrucktValue is now 1weil das$x++hat die in der deklarierte lexikalische Variable inkrementiertincr()Funktion anstelle der übergebenen Variablen. Dieser Stil zum Übergeben von Werten wird normalerweise meistens gewünscht, da Funktionen, die ihre Argumente ändern, in Perl selten sind und der Stil vermieden werden sollte.

Wenn dieses Verhalten jedoch aus irgendeinem Grund speziell erwünscht ist, kann es erreicht werden, indem direkt auf Elemente des@_Array, da es sich um Aliase für Variablen handelt, die an die Funktion übergeben werden.

sub incr {
  $_[0]++;
}

my $value = 1;
incr($value);
say "Value is now $value";

Dieses Mal wird es gedrucktValue is now 2, weil das$_[0]++Ausdruck inkrementierte die tatsächliche$valueVariable. So funktioniert das unter der Haube@_ist kein reales Array wie die meisten anderen Arrays (wie sie von erhalten würdenmy @array), sondern seine Elemente werden direkt aus den Argumenten erstellt, die an einen Funktionsaufruf übergeben werden. Auf diese Weise können Sie eine Referenz-Passant-Semantik erstellen, wenn dies erforderlich wäre. Funktionsaufrufargumente, bei denen es sich um einfache Variablen handelt, werden unverändert in dieses Array eingefügt, und Konstanten oder Ergebnisse komplexerer Ausdrücke werden als schreibgeschützte temporäre Elemente eingefügt.

In der Praxis ist dies jedoch äußerst selten, da Perl Referenzwerte unterstützt. Das heißt, Werte, die sich auf andere Variablen beziehen. Normalerweise ist es viel klarer, eine Funktion zu konstruieren, die einen offensichtlichen Nebeneffekt auf eine Variable hat, indem ein Verweis auf diese Variable übergeben wird. Dies ist ein klarer Hinweis für den Leser auf der Call-Site, dass die Semantik der Referenzübergabe wirksam ist.

sub incr_ref {
  my ( $ref ) = @_;
  $$ref++;
}

my $value = 1;
incr(\$value);
say "Value is now $value";

Hier das\Der Operator liefert eine Referenz auf die gleiche Weise wie der&Adresse des Betreibers in C.

Quelle
Translate

Da ist eingute Erklärung hierfür .NET.

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

Quelle
Translate

Vergiss nicht, dass es auch gibtmit Namen übergeben, undWert-Ergebnis übergeben.

Das Übergeben von Wert-Ergebnis ähnelt dem Übergeben von Wert, mit dem zusätzlichen Aspekt, dass der Wert in der ursprünglichen Variablen festgelegt 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 eine Referenzübergabe einen Seitenfehler verursachen kann (Referenz).

Namensübergabe bedeutet, dass die Werte nur berechnet werden, wenn sie tatsächlich verwendet werden, und nicht zu Beginn des Vorgangs. Algol verwendete Pass-by-Name, aber ein interessanter Nebeneffekt ist, dass es sehr schwierig ist, ein Swap-Verfahren zu schreiben (Referenz). Außerdem wird der vom Namen übergebene Ausdruck bei jedem Zugriff neu ausgewertet, was ebenfalls Nebenwirkungen haben kann.

Quelle
Translate

nach Wert

  • ist langsamer als durch Referenz, da das System den Parameter kopieren muss
  • wird nur zur Eingabe verwendet

durch Bezugnahme

  • schneller, da nur ein Zeiger übergeben wird
  • wird für die Eingabe verwendetundAusgabe
  • kann in Verbindung mit globalen Variablen sehr gefährlich sein
Quelle
Hobart Lee
Translate

Was auch immer Sie als Pass-by-Value oder Pass-by-Reference sagen, muss sprachübergreifend konsistent sein. Die gebräuchlichste und konsistenteste Definition, die in verschiedenen Sprachen verwendet wird, ist, dass Sie mit Pass-by-Reference eine Variable "normal" an eine Funktion übergeben können (dh ohne explizite Adressierung oder ähnliches), und die Funktion kann dieszuweisen(den Inhalt des Parameters innerhalb der Funktion nicht mutieren) und hat den gleichen Effekt wie die Zuweisung zur Variablen im aufrufenden Bereich.

Aus dieser Sicht sind die Sprachen wie folgt gruppiert: Jede Gruppe hat die gleiche Semantik. Wenn Sie der Meinung sind, dass zwei Sprachen nicht in dieselbe Gruppe eingeordnet werden sollten, fordere ich Sie auf, ein Beispiel zu finden, das sie voneinander unterscheidet.

Die überwiegende Mehrheit der Sprachen einschließlichC, Java, Python, Rubin, JavaScript, Planen, OCaml, Standard ML, Go, Ziel c, Smalltalkusw. sind alleNur Wertübergabe. Das Übergeben eines Zeigerwerts (in einigen Sprachen als "Referenz" bezeichnet) zählt nicht als Referenzübergabe. Wir sind nur besorgt über das übergebene Ding, den Zeiger, nicht über das Ding, auf das gezeigt wird.

Sprachen wieC ++, C#, PHPsind standardmäßig Pass-by-Value-Werte wie die oben genannten Sprachen, aber Funktionen können Parameter mithilfe von explizit als Pass-by-Reference deklarieren&oderref.

Perlwird immer als Referenz übergeben; In der Praxis kopieren die Benutzer die Werte jedoch fast immer, nachdem sie sie erhalten haben, und verwenden sie so als Wertübergabe.

Quelle
Kerr Lee
Translate

ÜberJWährend es nur AFAIK gibt, die als Wert übergeben werden, gibt es eine Form der Referenzübergabe, die das Verschieben vieler Daten ermöglicht. Sie übergeben einfach etwas, das als Gebietsschema bekannt ist, an ein Verb (oder eine Funktion). Dies kann eine Instanz einer Klasse 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 der aufgerufenen Funktion irgendwie kennen müssen. Diese Technik kann jedoch viele Daten schmerzlos verschieben. Das ist der Grund, warum ich es, obwohl es technisch nicht als Referenz dient, "so ziemlich das" nenne.

Quelle
Translate

PHP wird auch als Wert übergeben.

<?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:

a b

In PHP4 wurden Objekte jedoch wie behandeltPrimitive. 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"
Quelle
Nat Lee
Translate

Standardmäßig verwendet ANSI / ISO C beides - dies hängt davon ab, wie Sie Ihre Funktion und ihre Parameter deklarieren.

Wenn Sie Ihre Funktionsparameter als Zeiger deklarieren, wird die Funktion als Referenz übergeben, und wenn Sie Ihre Funktionsparameter als Nichtzeigervariablen deklarieren, wird die Funktion 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 auf Probleme stoßen, wenn Sie eine Funktion erstellen, die einen Zeiger auf eine nicht statische Variable zurückgibt, die innerhalb dieser Funktion erstellt wurde. Der zurückgegebene Wert des folgenden Codes wäre undefiniert. Es kann nicht festgestellt werden, ob der der in der Funktion erstellten temporären Variablen zugewiesene Speicherplatz überschrieben wurde oder nicht.

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

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

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