Was ist der Unterschied zwischen Referenzübergabe und Wertübergabe?

Antworten:

1079

In erster Linie ist die in der CS-Theorie definierte Unterscheidung zwischen "Pass by Value vs. Pass by Reference" veraltet, da die ursprünglich als "Pass by Reference" definierte Technik seitdem in Ungnade gefallen ist und derzeit nur noch selten verwendet wird. 1

Neuere Sprachen 2 verwenden in der Regel ein anderes (aber ähnliches) Technikpaar, um die gleichen Effekte zu erzielen (siehe unten), was die Hauptursache für Verwirrung ist.

Eine sekundäre Quelle der Verwirrung ist die Tatsache, dass "Referenz" in "Referenzübergabe" eine engere Bedeutung hat als der allgemeine Begriff "Referenz" (weil der Ausdruck älter ist als er).


Die authentische Definition lautet nun:

  • Wenn ein Parameter als Referenz übergeben wird , verwenden der Aufrufer und der Angerufene dieselbe Variable für den Parameter. Wenn der Angerufene die Parametervariable ändert, ist der Effekt für die Variable des Anrufers sichtbar.

  • Wenn ein Parameter als Wert übergeben wird , haben der Anrufer und der Angerufene zwei unabhängige Variablen mit demselben Wert. Wenn der Angerufene die Parametervariable ändert, ist der Effekt für den Anrufer nicht sichtbar.

In dieser Definition sind folgende Punkte zu beachten:

  • "Variable" bedeutet hier die (lokale oder globale) Variable des Aufrufers selbst. Wenn ich also eine lokale Variable als Referenz übergebe und ihr zuweise, ändere ich die Variable des Aufrufers selbst, nicht z. B. was auch immer sie zeigt, wenn es sich um einen Zeiger handelt .

    • Dies wird jetzt als schlechte Praxis angesehen (als implizite Abhängigkeit). Daher sind praktisch alle neueren Sprachen ausschließlich oder fast ausschließlich pass-by-value. Pass-by-Reference wird jetzt hauptsächlich in Form von "output / inout-Argumenten" in Sprachen verwendet, in denen eine Funktion nicht mehr als einen Wert zurückgeben kann.
  • Die Bedeutung von "Referenz" in "Referenz übergeben" . Der Unterschied zum allgemeinen Begriff "Referenz" besteht darin, dass diese "Referenz" vorübergehend und implizit ist. Was der Angerufene im Grunde bekommt, ist eine "Variable", die irgendwie "die gleiche" wie die ursprüngliche ist. Wie genau dieser Effekt erzielt wird, ist irrelevant (z. B. kann die Sprache auch einige Implementierungsdetails offenlegen - Adressen, Zeiger, Dereferenzierung - dies ist alles irrelevant; wenn der Nettoeffekt dies ist, wird er als Referenz übergeben).


In modernen Sprachen sind Variablen in der Regel "Referenztypen" (ein anderes Konzept, das später als "Referenzübergabe" erfunden und von ihm inspiriert wurde), dh die tatsächlichen Objektdaten werden separat irgendwo gespeichert (normalerweise auf dem Heap) Nur "Verweise" darauf werden jemals in Variablen gespeichert und als Parameter übergeben. 3

Das Übergeben einer solchen Referenz fällt unter den Wert " Übergeben", da der Wert einer Variablen technisch gesehen die Referenz selbst und nicht das referenzierte Objekt ist. Der Nettoeffekt auf das Programm kann jedoch der gleiche sein wie Pass-by-Value oder Pass-by-Reference:

  • Wenn eine Referenz nur aus der Variablen eines Aufrufers entnommen und als Argument übergeben wird, hat dies den gleichen Effekt wie die Referenzübergabe: Wenn das referenzierte Objekt im Angerufenen mutiert ist , sieht der Aufrufer die Änderung.
    • Wenn jedoch eine Variable, die diese Referenz enthält, neu zugewiesen wird, zeigt sie nicht mehr auf dieses Objekt, sodass alle weiteren Operationen an dieser Variablen stattdessen Auswirkungen auf das haben, auf das sie jetzt zeigt.
  • Um den gleichen Effekt wie beim Übergeben von Werten zu erzielen, wird irgendwann eine Kopie des Objekts erstellt. Zu den Optionen gehören:
    • Der Anrufer kann einfach vor dem Anruf eine private Kopie erstellen und dem Angerufenen stattdessen einen Verweis darauf geben.
    • In einigen Sprachen sind einige Objekttypen "unveränderlich": Jede Operation, die den Wert zu ändern scheint, erzeugt tatsächlich ein völlig neues Objekt, ohne das ursprüngliche zu beeinflussen. Das Übergeben eines Objekts eines solchen Typs als Argument hat also immer den Effekt der Wertübergabe: Eine Kopie für den Angerufenen wird automatisch erstellt, wenn eine Änderung erforderlich ist, und das Objekt des Aufrufers wird niemals beeinflusst.
      • In funktionalen Sprachen sind alle Objekte unveränderlich.

Wie Sie vielleicht sehen, ist dieses Technikpaar fast dasselbe wie das in der Definition, nur mit einer Indirektionsebene: Ersetzen Sie einfach "Variable" durch "referenziertes Objekt".

Es gibt keinen vereinbarten Namen für sie, was zu verzerrten Erklärungen wie "Call by Value, bei dem der Wert eine Referenz ist" führt. 1975 schlug Barbara Liskov den Begriff " Call-by-Object-Sharing " (oder manchmal auch nur "Call-by-Sharing") vor, obwohl er sich nie ganz durchsetzte. Darüber hinaus zeichnet keiner dieser Sätze eine Parallele zum ursprünglichen Paar. Kein Wunder, dass die alten Begriffe in Abwesenheit von etwas Besserem wiederverwendet wurden, was zu Verwirrung führte. 4


HINWEIS : Diese Antwort lautete lange Zeit:

Angenommen, ich möchte eine Webseite mit Ihnen teilen. Wenn ich Ihnen die URL sage, übergebe ich als Referenz. Sie können diese URL verwenden, um dieselbe Webseite anzuzeigen, die ich sehen kann. Wenn diese Seite geändert wird, sehen wir beide die Änderungen. Wenn Sie die URL löschen, zerstören Sie lediglich Ihren Verweis auf diese Seite - Sie löschen nicht die eigentliche Seite.

Wenn ich die Seite ausdrucke und Ihnen den Ausdruck gebe, übergebe ich den Wert. Ihre Seite ist eine nicht verbundene Kopie des Originals. Sie werden keine nachfolgenden Änderungen sehen und alle Änderungen, die Sie vornehmen (z. B. Kritzeleien auf Ihrem Ausdruck), werden nicht auf der Originalseite angezeigt. Wenn Sie den Ausdruck zerstören, haben Sie tatsächlich Ihre Kopie des Objekts zerstört - die ursprüngliche Webseite bleibt jedoch erhalten.

Dies ist größtenteils richtig, mit Ausnahme der engeren Bedeutung von "Referenz" - es ist sowohl temporär als auch implizit (es muss nicht, aber explizit und / oder persistent zu sein, sind zusätzliche Merkmale, die nicht Teil der Referenz-Semantik sind , wie oben erklärt). Eine nähere Analogie wäre, Ihnen eine Kopie eines Dokuments zu geben, anstatt Sie einzuladen, am Original zu arbeiten.


1 Wenn Sie nicht in Fortran oder Visual Basic programmieren, ist dies nicht das Standardverhalten, und in den meisten modernen Sprachen ist ein echter Call-by-Reference nicht einmal möglich.

2 Eine ganze Reihe älterer unterstützt dies ebenfalls

3 In mehreren modernen Sprachen sind alle Typen Referenztypen. Dieser Ansatz wurde 1975 von der Sprache CLU entwickelt und seitdem von vielen anderen Sprachen übernommen, darunter Python und Ruby. Viele weitere Sprachen verwenden einen hybriden Ansatz, bei dem einige Typen "Werttypen" und andere "Referenztypen" sind - darunter C #, Java und JavaScript.

4 Es ist nichts Schlimmes daran, einen passenden alten Begriff per se zu recyceln , aber man muss irgendwie klar machen, welche Bedeutung jedes Mal verwendet wird. Nicht zu tun ist genau das, was immer wieder Verwirrung stiftet.

ivan_pozdeev
quelle
Ich persönlich würde die Begriffe "neu" oder "indirekt" Pass-by-Value / Pass-by-Reference für die neuen Techniken verwenden.
ivan_pozdeev
Die von Ihnen angegebene „authentische“ Definition ist nicht die Definition, die in fast jedem Einführungskurs in die Programmierung angegeben ist. Google, was als Referenz übergeben wird, und Sie erhalten diese Antwort nicht. Die authentische Definition, die Sie angeben, ist ein Missbrauch der Wortreferenz. Wenn Sie dieser Definition folgen, verwenden Sie einen Alias, keine Referenz: Sie haben zwei Variablen, die tatsächlich dieselbe Variable sind, dh einen Alias ​​und keine Referenz. Ihre authentische Definition führt ohne Grund zu Massenverwirrung. Sagen Sie einfach "Referenz übergeben" bedeutet "Adresse übergeben". Es macht Sinn und würde diese sinnlose Verwirrung vermeiden.
YungGun
@YungGun 1) Bitte geben Sie einen Link zu einer "Definition in fast jedem Einführungskurs in die Programmierung" an. Beachten Sie auch, dass dies in der heutigen Realität klar sein soll, nicht in der Realität vor ein oder drei Jahrzehnten, als ein CS-Kurs geschrieben wurde. 2) "Adresse" kann nicht in der Definition verwendet werden, da es absichtlich von möglichen Implementierungen abstrahiert. ZB haben einige Sprachen (Fortran) keine Zeiger; Sie unterscheiden sich auch darin, ob sie dem Benutzer die Rohadresse zur Verfügung stellen (VB nicht). Es muss auch keine Rohspeicheradresse sein, alles, was eine Verknüpfung mit der Variablen ermöglichen würde, würde funktionieren.
ivan_pozdeev
@ivan_podeev kein Link sorry. Ich sage "fast jeden Einführungskurs", weil ich persönlich aufs College gegangen bin und auch Bootcamps programmiert habe, die mir das beigebracht haben. Diese Kurse waren modern (vor weniger als 5 Jahren). Eine "Rohadresse" ist ein Synonym für einen "Zeiger" ... Sie mögen technisch korrekt sein (laut einem von Kirschen gepflückten Link), aber die Sprache, die Sie verwenden, ist für die meisten Programmierer unpraktisch und verwirrend. Wenn Sie meine vollständigen Gedanken dazu haben möchten, habe ich einen Blog-Beitrag mit 3500 Wörtern geschrieben: medium.com/@isaaccway228/…
YungGun
@YungGun "zu lange, nicht gelesen". Ein Blick zeigt genau die in der Antwort beschriebenen Verwirrungen. Referenzübergabe ist eine abstrakte Technik, die für die Implementierung nicht relevant ist. Es spielt keine Rolle, was genau unter der Haube passiert, es ist wichtig, wie sich das auf das Programm auswirkt.
ivan_pozdeev
150

Auf diese Weise können Argumente an Funktionen übergeben werden. Als Referenz übergeben bedeutet, dass der Parameter der aufgerufenen Funktionen mit dem übergebenen Argument des Aufrufers identisch ist (nicht der Wert, sondern die Identität - die Variable selbst). Wert übergeben bedeutet, dass der Parameter der aufgerufenen Funktionen eine Kopie des übergebenen Arguments des Aufrufers ist. Der Wert ist der gleiche, aber die Identität - die Variable - ist unterschiedlich. Änderungen an einem Parameter, die von der aufgerufenen Funktion ausgeführt werden, ändern in einem Fall das übergebene Argument und im anderen Fall nur den Wert des Parameters in der aufgerufenen Funktion (die nur eine Kopie ist). In kurzer Zeit:

  • Java unterstützt nur die Übergabe von Werten. Kopiert immer Argumente, obwohl beim Kopieren eines Verweises auf ein Objekt der Parameter in der aufgerufenen Funktion auf dasselbe Objekt verweist und Änderungen an diesem Objekt im Aufrufer angezeigt werden. Da dies verwirrend sein kann, hier ist das, was Jon Skeet dazu zu sagen hat.
  • C # unterstützt die Übergabe nach Wert und die Übergabe nach Referenz (Schlüsselwort, refdas beim Aufrufer verwendet wird und die aufgerufene Funktion). Jon Skeet hat auch eine schöne Erklärung dafür hier .
  • C ++ unterstützt die Übergabe nach Wert und die Übergabe nach Referenz (Referenzparametertyp, der bei der aufgerufenen Funktion verwendet wird). Eine Erklärung hierzu finden Sie unten.

Codes

Da meine Sprache C ++ ist, werde ich das hier verwenden

// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
    p = NULL;
}

// passes an integer
void call_by_value(int p) { // :2
    p = 42;
}

// passes an integer by reference
void call_by_reference(int & p) { // :3
    p = 42;
}

// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
    *p = 10; // changes what p points to ("what p references" in java)
    // only changes the value of the parameter, but *not* of 
    // the argument passed by the caller. thus it's pass-by-value:
    p = NULL;
}

int main() {
    int value = 10;
    int * pointer = &value;

    call_by_value(pointer); // :1
    assert(pointer == &value); // pointer was copied

    call_by_value(value); // :2
    assert(value == 10); // value was copied

    call_by_reference(value); // :3
    assert(value == 42); // value was passed by reference

    call_by_value_special(pointer); // :4
    // pointer was copied but what pointer references was changed.
    assert(value == 10 && pointer == &value);
}

Und ein Beispiel in Java wird nicht schaden:

class Example {
    int value = 0;

    // similar to :4 case in the c++ example
    static void accept_reference(Example e) { // :1
        e.value++; // will change the referenced object
        e = null; // will only change the parameter
    }

    // similar to the :2 case in the c++ example
    static void accept_primitive(int v) { // :2
        v++; // will only change the parameter
    }        

    public static void main(String... args) {
        int value = 0;
        Example ref = new Example(); // reference

        // note what we pass is the reference, not the object. we can't 
        // pass objects. The reference is copied (pass-by-value).
        accept_reference(ref); // :1
        assert ref != null && ref.value == 1;

        // the primitive int variable is copied
        accept_primitive(value); // :2
        assert value == 0;
    }
}

Wikipedia

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference

Dieser Typ nagelt es so ziemlich:

http://javadude.com/articles/passbyvalue.htm

Johannes Schaub - litb
quelle
9
Warum die Ablehnung? Wenn etwas nicht stimmt oder zu Missverständnissen führt, hinterlassen Sie bitte einen Kommentar.
Johannes Schaub - litb
1
War nicht meine Stimme, aber in einem kleinen Punkt (Sie wissen, die Art, die in den Präsidentendebatten aufgegriffen wurde) würde ich sagen, dass es eher eine "Taktik" als eine "Strategie" ist.
Harpo
28
+1 für die Vollständigkeit. Mach dir keine Sorgen über Abstimmungen - die Leute tun es aus seltsamen Gründen. In einer Meinungsfrage zu Taschenrechnern wurde jeder von einem Typen abgelehnt, der nicht glaubte, dass Programmierer Taschenrechner verwenden sollten! Wie auch immer, ich fand deine Antwort sehr gut.
Mark Brittingham
1
Die Links zu Skeets Erklärungen sind unterbrochen.
Geldorientierter Programmierer
Die Links zu Skeets Erklärungen sind immer noch unterbrochen.
Rokit
85

Viele Antworten hier (und insbesondere die am besten bewertete Antwort) sind sachlich falsch, da sie falsch verstehen, was "Call by Reference" wirklich bedeutet. Hier ist mein Versuch, die Sache klar zu stellen.

TL; DR

In einfachsten Worten:

  • call by value bedeutet, dass Sie Werte als Funktionsargumente übergeben
  • Call by Reference bedeutet, dass Sie Variablen als Funktionsargumente übergeben

In metaphorischen Begriffen:

  • Call by Value ist der Ort, an dem ich etwas auf ein Stück Papier schreibe und es Ihnen übergebe . Vielleicht ist es eine URL, vielleicht ist es eine vollständige Kopie von Krieg und Frieden. Egal was es ist, es ist auf einem Stück Papier, das ich dir gegeben habe, und jetzt ist es effektiv dein Stück Papier . Sie können jetzt auf dieses Stück Papier kritzeln oder mit diesem Stück Papier etwas anderes finden und damit herumspielen, was auch immer.
  • Als Referenz wird angerufen, wenn ich Ihnen mein Notizbuch gebe, in dem etwas aufgeschrieben ist . Sie können in mein Notizbuch kritzeln (vielleicht möchte ich, dass Sie es tun, vielleicht auch nicht), und danach behalte ich mein Notizbuch mit den Kritzeleien, die Sie dort abgelegt haben. Wenn das, was Sie oder ich dort geschrieben haben, Informationen darüber enthält, wie Sie etwas anderes finden können, können Sie oder ich dorthin gehen und mit diesen Informationen herumspielen.

Was "Call by Value" und "Call by Reference" nicht bedeuten

Beachten Sie, dass diese beiden Konzepte völlig unabhängig und orthogonal vom Konzept der Referenztypen (die in Java alle Typen sind, die Untertypen sind Object, und in C # alle classTypen) oder vom Konzept der Zeigertypen wie in C (die semantisch äquivalent sind) sind zu Javas "Referenztypen", einfach mit unterschiedlicher Syntax).

Der Begriff des Referenztyps entspricht einer URL: Er ist sowohl selbst eine Information als auch eine Referenz (ein Zeiger , wenn Sie so wollen) auf andere Informationen. Sie können viele Kopien einer URL an verschiedenen Stellen haben, und sie ändern nicht, auf welche Website sie alle verlinken. Wenn die Website aktualisiert wird, führt jede URL-Kopie weiterhin zu den aktualisierten Informationen. Umgekehrt wirkt sich das Ändern der URL an einer beliebigen Stelle nicht auf eine andere schriftliche Kopie der URL aus.

Beachten Sie, dass C ++ hat einen Begriff von „Referenzen“ (zB int&) , das ist nicht wie Java und C # 's ‚Referenz - Typen‘, sondern ist wie ‚call by reference‘. Die "Referenztypen" von Java und C # sowie alle Typen in Python entsprechen denen, die C und C ++ als "Zeigertypen" bezeichnen (z int*. B. ).


OK, hier ist die längere und formellere Erklärung.

Terminologie

Zunächst möchte ich einige wichtige Begriffe hervorheben, um meine Antwort zu verdeutlichen und sicherzustellen, dass wir uns bei der Verwendung von Wörtern alle auf dieselben Ideen beziehen. (In der Praxis glaube ich, dass die überwiegende Mehrheit der Verwirrung über Themen wie diese darauf zurückzuführen ist, dass Wörter so verwendet werden, dass die beabsichtigte Bedeutung nicht vollständig kommuniziert wird.)

Hier ist zunächst ein Beispiel in einer C-ähnlichen Sprache für eine Funktionsdeklaration:

void foo(int param) {  // line 1
  param += 1;
}

Und hier ist ein Beispiel für den Aufruf dieser Funktion:

void bar() {
  int arg = 1;  // line 2
  foo(arg);     // line 3
}

Anhand dieses Beispiels möchte ich einige wichtige Begriffe definieren:

  • fooist eine in Zeile 1 deklarierte Funktion (Java besteht darauf, alle Funktionsmethoden zu erstellen, aber das Konzept ist das gleiche ohne Verlust der Allgemeinheit; C und C ++ unterscheiden zwischen Deklaration und Definition, auf die ich hier nicht näher eingehen werde).
  • paramist ein formaler Parameter von foo, der ebenfalls in Zeile 1 deklariert ist
  • argist eine Variable , insbesondere eine lokale Variable der Funktion bar, die in Zeile 2 deklariert und initialisiert wird
  • argist auch ein Argument für einen bestimmten Aufruf von fooin Zeile 3

Hier sind zwei sehr wichtige Konzepte zu unterscheiden. Der erste ist Wert gegen Variable :

  • Ein Wert ist das Ergebnis der Auswertung eines Ausdrucks in der Sprache. In der barobigen Funktion int arg = 1;hat der Ausdruck beispielsweise nach der Zeile argden Wert 1 .
  • Eine Variable ist ein Container für Werte . Eine Variable kann veränderlich (dies ist die Standardeinstellung in den meisten C-ähnlichen Sprachen), schreibgeschützt (z. B. mit Java finaloder C # deklariert readonly) oder tief unveränderlich (z. B. mit C ++ const) sein.

Das andere wichtige Konzeptpaar, das unterschieden werden muss, ist Parameter gegenüber Argument :

  • Ein Parameter (auch als formaler Parameter bezeichnet ) ist eine Variable, die der Aufrufer beim Aufruf einer Funktion angeben muss.
  • Ein Argument ist ein Wert , der vom Aufrufer einer Funktion angegeben wird, um einen bestimmten formalen Parameter dieser Funktion zu erfüllen

Call by value

Beim Aufruf nach Wert sind die formalen Parameter der Funktion Variablen, die für den Funktionsaufruf neu erstellt und mit den Werten ihrer Argumente initialisiert werden .

Dies funktioniert genauso wie alle anderen Arten von Variablen mit Werten initialisiert werden. Zum Beispiel:

int arg = 1;
int another_variable = arg;

Hier argund another_variablesind völlig unabhängige Variablen - ihre Werte können sich unabhängig voneinander ändern. An dem Punkt, an dem another_variabledeklariert wird, wird es jedoch so initialisiert, dass es denselben Wert enthält, der arggilt - nämlich 1.

Da es sich um unabhängige Variablen handelt, wirken sich Änderungen another_variablenicht auf Folgendes aus arg:

int arg = 1;
int another_variable = arg;
another_variable = 2;

assert arg == 1; // true
assert another_variable == 2; // true

Dies ist genau das gleiche wie die Beziehung zwischen argund paramin unserem obigen Beispiel, die ich hier aus Symmetriegründen wiederholen werde:

void foo(int param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

Es ist genau so, als hätten wir den Code so geschrieben:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here

Das heißt, das definierende Merkmal dessen, was Call by Value bedeutet, ist, dass der Angerufene ( fooin diesem Fall) Werte als Argumente empfängt , aber eine eigene separate hat für diese Werte Variablen von den Variablen des Aufrufers ( barin diesem Fall) hat.

Zurück zu meiner Metapher oben: Wenn ich barund du sind foo, wenn ich dich anrufe , gebe ich dir ein Stück Papier mit einem darauf geschriebenen Wert . Sie nennen das Stück Papier param. Dieser Wert ist eine Kopie des Werts, den ich in mein Notizbuch (meine lokalen Variablen) in einer von mir aufgerufenen Variablen geschrieben habe arg.

(Nebenbei: Je nach Hardware und Betriebssystem gibt es verschiedene Aufrufkonventionen, wie Sie eine Funktion von einer anderen aufrufen. Die Aufrufkonvention ist so, als würden wir entscheiden, ob ich den Wert auf ein Stück meines Papiers schreibe und ihn Ihnen dann übergebe oder wenn Sie ein Stück Papier haben, auf das ich es schreibe, oder wenn ich es vor uns beiden an die Wand schreibe. Dies ist ebenfalls ein interessantes Thema, das jedoch weit über den Rahmen dieser bereits langen Antwort hinausgeht.)

Rufen Sie als Referenz an

Beim Aufruf per Referenz sind die formalen Parameter der Funktion einfach neue Namen für dieselben Variablen, die der Aufrufer als Argumente angibt.

Zurück zu unserem obigen Beispiel: Es entspricht:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here

Da parames sich nur um einen anderen Namen handelt arg- das heißt, es handelt sich um dieselbe Variable , in der sich Änderungen paramwiderspiegelnarg . Dies ist die grundlegende Art und Weise, in der sich Call by Reference von Call by Value unterscheidet.

Sehr wenige Sprachen unterstützen Call-by-Reference, aber C ++ kann dies folgendermaßen tun:

void foo(int& param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

In diesem Fall parammuss nicht nur den gleichen Wert wie arges tatsächlich ist arg (nur unter einem anderen Namen) und so barkann beobachten , dassarg erhöht wurde.

Beachten Sie, dass dies ist nicht wie jeder von Java, JavaScript, C, Objective-C, Python, oder fast jede andere populäre Sprache heute funktioniert. Dies bedeutet, dass diese Sprachen nicht sind als Referenz aufgerufen werden, sondern als Wert.

Nachtrag: Aufruf durch Objektfreigabe

Wenn das, was Sie haben, ein Aufruf nach Wert ist, der tatsächliche Wert jedoch ein Referenztyp oder ein Zeigertyp ist , ist der "Wert" selbst nicht sehr interessant (z. B. in C ist es nur eine Ganzzahl einer plattformspezifischen Größe) - was ist Interessant ist, worauf dieser Wert hinweist .

Wenn das, worauf dieser Referenztyp ( dh der Zeiger) zeigt, veränderbar ist ein interessanter Effekt möglich: Sie können den Wert, auf den gezeigt wird, ändern, und der Aufrufer kann Änderungen des Werts beobachten, auf den gezeigt wird, obwohl der Anrufer dies nicht beobachten kann wechselt zum Zeiger selbst.

Um die Analogie der URL noch einmal auszuleihen, ist die Tatsache, dass ich Ihnen eine Kopie der URL zu einer Website gegeben habe, nicht besonders interessant, wenn es uns beiden um die Website geht, nicht um die URL. Die Tatsache, dass Sie über Ihre Kopie der URL kritzeln, wirkt sich nicht auf meine Kopie der URL aus. Dies ist uns egal (und in Sprachen wie Java und Python kann dies die "URL" oder der Referenztypwert überhaupt nicht modifiziert werden, nur das, worauf es hinweist, kann).

Als Barbara Liskov die CLU-Programmiersprache (die diese Semantik hatte) erfand, stellte sie fest, dass die vorhandenen Begriffe "Call by Value" und "Call by Reference" für die Beschreibung der Semantik dieser neuen Sprache nicht besonders nützlich waren. Also erfand sie einen neuen Begriff: Call by Object Sharing .

Wenn ich über Sprachen spreche, die technisch nach Wert aufgerufen werden, bei denen jedoch häufig verwendete Referenz- oder Zeigertypen verwendet werden (dh fast jede moderne imperative, objektorientierte oder Multi-Paradigma-Programmiersprache), finde ich dies viel weniger verwirrend Vermeiden Sie es einfach, über Call by Value oder Call by Reference zu sprechen . Halten Sie sich an die gemeinsame Nutzung von Objekten (oder rufen Sie einfach nach Objekten an ), und niemand wird verwirrt sein. :-)

Daniel Pryden
quelle
Besser erklärt: Hier sind zwei sehr wichtige Konzepte zu unterscheiden. The first is value versus variable. The other important pair of concepts to distinguish is parameter versus argument:
SK Venkat
2
Hervorragende Antwort. Ich denke, ich würde hinzufügen, dass kein neuer Speicher als Referenz erstellt werden muss. Der Parametername verweist auf den ursprünglichen Speicher (Speicher). Danke
drlolly
1
Beste Antwort IMO
Rafael Eyng
59

Bevor Sie die beiden Begriffe verstehen , MÜSSEN Sie Folgendes verstehen. Jedes Objekt hat zwei Dinge, die es unterscheiden können.

  • Dessen Wert.
  • Seine Adresse.

Also wenn du sagst employee.name = "John"

Ich weiß, dass es zwei Dinge gibt name. Sein Wert, der ist, "John"und auch seine Position im Speicher, die eine hexadezimale Zahl ist, könnten wie folgt aussehen : 0x7fd5d258dd00.

Abhängig von der Architektur der Sprache oder dem Typ (Klasse, Struktur usw.) Ihres Objekts würden Sie entweder "John"oder übertragen0x7fd5d258dd00

Das Übergeben "John"wird als Übergeben nach Wert bezeichnet. Das Übergeben 0x7fd5d258dd00wird als Übergeben als Referenz bezeichnet. Jeder, der auf diesen Speicherort zeigt, hat Zugriff auf den Wert von "John".

Um mehr darüber zu erfahren , empfehle ich Ihnen, sich über die Dereferenzierung eines Zeigers zu informieren und darüber, warum Sie struct (Werttyp) anstelle von class (Referenztyp) wählen.

Honig
quelle
3
Das ist, wonach ich gesucht habe, eigentlich sollte man nach dem Konzept suchen, nicht nur nach der Erklärung, Daumen hoch, Bruder.
Haisum Usman
Java wird immer als Wert übergeben. Das Übergeben von Verweisen auf Objekte in Java wird als Wertübergabe betrachtet. Dies widerspricht Ihrer Aussage "Das Übergeben von 0x7fd5d258dd00 wird als Übergeben als Referenz bezeichnet."
Chetan Raina
53

Hier ist ein Beispiel:

#include <iostream>

void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }

int main()
{
    int x = 0;
    by_val(x); std::cout << x << std::endl;  // prints 0
    by_ref(x); std::cout << x << std::endl;  // prints 2

    int y = 0;
    by_ref(y); std::cout << y << std::endl;  // prints 2
    by_val(y); std::cout << y << std::endl;  // prints 2
}
pyon
quelle
1
Ich denke, es gibt ein Problem, da in der letzten Zeile 0 statt 2 gedruckt werden sollte. Bitte sagen Sie mir, wenn mir etwas fehlt.
Taimoor Changaiz
@TaimoorChangaiz; Welche "letzte Zeile"? Übrigens, wenn Sie IRC verwenden können, kommen Sie bitte zu ## Programmierung auf Freenode. Es wäre viel einfacher, die Dinge dort zu erklären. Mein Nick dort ist "pyon".
Pyon
1
@ EduardoLeón by_val (y); std :: cout << y << std :: endl; // druckt 2
Taimoor Changaiz
5
@TaimoorChangaiz: Warum sollte es nicht 2 drucken? ywurde in der vorhergehenden Zeile bereits auf 2 gesetzt. Warum sollte es auf 0 zurückgehen?
Pyon
@ EduardoLeón mein schlechtes. ja, du hast recht. Vielen Dank für die Korrektur
Taimoor Changaiz
28

Der einfachste Weg, dies zu erreichen, ist eine Excel-Datei. Nehmen wir zum Beispiel an, Sie haben zwei Zahlen, 5 und 2 in den Zellen A1 und B1, und Sie möchten ihre Summe in einer dritten Zelle finden, sagen wir A2. Sie können dies auf zwei Arten tun.

  • Entweder indem Sie ihre Werte an Zelle A2 übergeben, indem Sie = 5 + 2 in diese Zelle eingeben . In diesem Fall bleibt die Summe in A2 gleich, wenn sich die Werte der Zellen A1 oder B1 ändern.

  • Oder indem Sie die "Referenzen" der Zellen A1 und B1 an Zelle A2 übergeben, indem Sie = A1 + B1 eingeben . In diesem Fall ändert sich auch die Summe in A2, wenn sich die Werte der Zellen A1 oder B1 ändern.

Als Skourtan
quelle
Dies ist das einfachste und beste Beispiel unter allen anderen Antworten.
Amit Ray
18

Wenn Sie an ref vorbeikommen, übergeben Sie grundsätzlich einen Zeiger auf die Variable. Als Wert übergeben Sie übergeben eine Kopie der Variablen. In der Grundverwendung bedeutet dies normalerweise, dass Änderungen an der Variablen, die als Referenz übergeben werden, als aufrufende Methode angesehen werden und als Wert übergeben werden, den sie nicht verwenden.

Craig
quelle
12

Übergeben nach Wert sendet eine KOPIE der Daten, die in der von Ihnen angegebenen Variablen gespeichert sind. Übergeben als Referenz sendet eine direkte Verknüpfung zur Variablen selbst. Wenn Sie also eine Variable als Referenz übergeben und dann die Variable innerhalb des Blocks ändern, in den Sie sie übergeben haben, wird die ursprüngliche Variable geändert. Wenn Sie einfach den Wert übergeben, kann die ursprüngliche Variable von dem Block, in den Sie sie übergeben haben, nicht geändert werden. Sie erhalten jedoch eine Kopie dessen, was zum Zeitpunkt des Aufrufs enthalten war.

MetaGuru
quelle
7

Wert übergeben - Die Funktion kopiert die Variable und arbeitet mit einer Kopie (sodass an der ursprünglichen Variablen nichts geändert wird).

Referenzübergabe - Die Funktion verwendet die ursprüngliche Variable. Wenn Sie die Variable in der anderen Funktion ändern, ändert sich auch die ursprüngliche Variable.

Beispiel (kopieren und verwenden / versuchen Sie dies selbst und sehen Sie):

#include <iostream>

using namespace std;

void funct1(int a){ //pass-by-value
    a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
    a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}

int main()
{
    int a = 5;

    funct1(a);
    cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
    funct2(a);
    cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7

    return 0;
}

Halte es einfach, guck mal. Textwände können eine schlechte Angewohnheit sein.

user326964
quelle
Dies ist sehr hilfreich, um zu verstehen, ob der Parameterwert geändert wurde oder nicht, danke!
Kevin Zhao
5

Ein Hauptunterschied zwischen ihnen besteht darin, dass Variablen vom Werttyp Werte speichern. Wenn Sie also eine Variable vom Typ Wert in einem Methodenaufruf angeben, wird eine Kopie des Werts dieser Variablen an die Methode übergeben. Variablen vom Referenztyp speichern Verweise auf Objekte. Wenn Sie also eine Variable vom Referenztyp als Argument angeben, wird der Methode eine Kopie der tatsächlichen Referenz übergeben, die auf das Objekt verweist. Obwohl die Referenz selbst als Wert übergeben wird, kann die Methode die empfangene Referenz weiterhin verwenden, um mit dem ursprünglichen Objekt zu interagieren und es möglicherweise zu ändern. In ähnlicher Weise gibt die Methode beim Zurückgeben von Informationen von einer Methode über eine return-Anweisung eine Kopie des in einer Variablen vom Wertetyp gespeicherten Werts oder eine Kopie der in einer Variablen vom Referenztyp gespeicherten Referenz zurück. Wenn eine Referenz zurückgegeben wird, kann die aufrufende Methode diese Referenz verwenden, um mit dem referenzierten Objekt zu interagieren. Damit,

Um in c # eine Variable als Referenz zu übergeben, damit die aufgerufene Methode die Variablen ändern kann, stellt C # die Schlüsselwörter ref und out bereit. Durch Anwenden des Schlüsselworts ref auf eine Parameterdeklaration können Sie eine Variable als Referenz an eine Methode übergeben. Die aufgerufene Methode kann die ursprüngliche Variable im Aufrufer ändern. Das Schlüsselwort ref wird für Variablen verwendet, die bereits in der aufrufenden Methode initialisiert wurden. Wenn ein Methodenaufruf eine nicht initialisierte Variable als Argument enthält, generiert der Compiler normalerweise einen Fehler. Wenn Sie einem Parameter mit dem Schlüsselwort out vorangehen, wird ein Ausgabeparameter erstellt. Dies zeigt dem Compiler an, dass das Argument als Referenz an die aufgerufene Methode übergeben wird und dass die aufgerufene Methode der ursprünglichen Variablen im Aufrufer einen Wert zuweist. Wenn die Methode dem Ausgabeparameter nicht in jedem möglichen Ausführungspfad einen Wert zuweist, generiert der Compiler einen Fehler. Dies verhindert auch, dass der Compiler eine Fehlermeldung für eine nicht initialisierte Variable generiert, die als Argument an eine Methode übergeben wird. Eine Methode kann über eine return-Anweisung nur einen Wert an ihren Aufrufer zurückgeben, aber durch Angabe mehrerer Ausgabeparameter (ref und / oder out) viele Werte zurückgeben.

siehe c # Diskussion und Beispiele hier Linktext

Tina Endresen
quelle
3

Beispiele:

class Dog 
{ 
public:
    barkAt( const std::string& pOtherDog ); // const reference
    barkAt( std::string pOtherDog ); // value
};

const &ist in der Regel am besten. Sie müssen keine Bau- und Zerstörungsstrafe zahlen. Wenn die Referenz nicht const ist, schlägt Ihre Schnittstelle vor, die übergebenen Daten zu ändern.

Deduplikator
quelle
2

Kurz gesagt, Übergeben als Wert ist WAS es ist und als Referenz übergeben ist WO es ist.

Wenn Ihr Wert VAR1 = "Happy Guy!" Ist, wird nur "Happy Guy!" Angezeigt. Wenn VAR1 zu "Happy Gal!" Wechselt, wissen Sie das nicht. Wenn es als Referenz übergeben wird und sich VAR1 ändert, werden Sie.

Monster
quelle
2

Wenn Sie den Wert der ursprünglichen Variablen nach der Übergabe an eine Funktion nicht ändern möchten, sollte die Funktion mit einem " Übergabewert " erstellt werden Parameter " erstellt werden.

Dann hat die Funktion NUR den Wert, aber nicht die Adresse der übergebenen Variablen. Ohne die Adresse der Variablen kann der Code innerhalb der Funktion den Variablenwert von außerhalb der Funktion nicht ändern.

Wenn Sie der Funktion jedoch die Möglichkeit geben möchten, den Wert der Variablen von außen gesehen zu ändern , müssen Sie die Referenzübergabe verwenden . Da sowohl der Wert als auch die Adresse (Referenz) übergeben werden und innerhalb der Funktion verfügbar sind.

Stanley
quelle
1

Wert übergeben bedeutet, wie ein Wert unter Verwendung von Argumenten an eine Funktion übergeben wird. Bei der Übergabe des Wertes kopieren wir die Daten, die in der von uns angegebenen Variablen gespeichert sind, und sie sind langsamer als die Übergabe als Referenz, da die Daten kopiert werden. Wenn wir Änderungen an den kopierten Daten vornehmen, sind die Originaldaten nicht betroffen. nd in pass by refernce oder pass by address senden wir einen direkten Link zur Variablen selbst. oder Zeiger auf eine Variable übergeben. Es ist schneller, da weniger Zeit verbraucht wird

Abhinisha Thakur
quelle
0

Hier ist ein Beispiel, das die Unterschiede zwischen Wertübergabe - Zeigerwert - Referenz zeigt :

void swap_by_value(int a, int b){
    int temp;

    temp = a;
    a = b;
    b = temp;
}   
void swap_by_pointer(int *a, int *b){
    int temp;

    temp = *a;
    *a = *b;
    *b = temp;
}    
void swap_by_reference(int &a, int &b){
    int temp;

    temp = a;
    a = b;
    b = temp;
}

int main(void){
    int arg1 = 1, arg2 = 2;

    swap_by_value(arg1, arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 1 2

    swap_by_pointer(&arg1, &arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 2 1

    arg1 = 1;                               //reset values
    arg2 = 2;
    swap_by_reference(arg1, arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 2 1
}

Die Methode "Referenzübergabe" weist eine wichtige Einschränkung auf . Wenn ein Parameter als Referenz übergeben deklariert wird (daher steht vor dem & -Zeichen), muss sein entsprechender tatsächlicher Parameter eine Variable sein .

Ein tatsächlicher Parameter, der sich auf den formalen Parameter "übergeben durch Wert" bezieht, kann im Allgemeinen ein Ausdruck sein , sodass nicht nur eine Variable, sondern auch ein Literal oder sogar das Ergebnis eines Funktionsaufrufs verwendet werden darf.

Die Funktion kann keinen Wert in etwas anderes als eine Variable setzen. Es kann einem Literal keinen neuen Wert zuweisen oder einen Ausdruck zwingen, sein Ergebnis zu ändern.

PS: Sie können auch die Antwort von Dylan Beattie im aktuellen Thread überprüfen, der sie in einfachen Worten erklärt.

BugShotGG
quelle
Sie geben an, "wenn ein Parameter [als Referenz] deklariert ist, muss sein entsprechender tatsächlicher Parameter eine Variable sein", aber das ist im Allgemeinen nicht wahr. Wenn eine Referenz an eine temporäre Referenz gebunden ist (z. B. den Rückgabewert einer Funktion), wird ihre Lebensdauer verlängert, um mit der Referenz übereinzustimmen. Siehe hier für Details.
Chris Hunt