Warum verwenden C ++ und Java beide den Begriff „Referenz“, aber nicht im gleichen Sinne?

26

In C ++ ermöglicht ein Referenzargument für eine Funktion der Funktion, die Referenz auf etwas anderes zu verweisen:

int replacement = 23;

void changeNumberReference(int& reference) {
    reference = replacement;
}

int main() {
    int i = 1;
    std::cout << "i=" << i << "\n"; // i = 1;
    changeNumberReference(i);
    std::cout << "i=" << i << "\n"; // i = 23;
}

Analog löst ein konstantes Referenzargument für eine Funktion einen Fehler bei der Kompilierung aus, wenn wir versuchen, die Referenz zu ändern:

void changeNumberReference(const int& reference) {
    reference = replacement; // compile-time error: assignment of read-only reference 'reference'
}

In Java heißt es in den Dokumenten, dass Funktionsargumente nicht-primitiver Typen Referenzen sind. Beispiel aus den offiziellen Dokumenten:

public void moveCircle(Circle circle, int deltaX, int deltaY) {
    // code to move origin of circle to x+deltaX, y+deltaY
    circle.setX(circle.getX() + deltaX);
    circle.setY(circle.getY() + deltaY);

    // code to assign a new reference to circle
    circle = new Circle(0, 0);
}

Dann wird circle eine Referenz auf ein neues Circle-Objekt mit x = y = 0 zugewiesen. Diese Neuzuweisung hat jedoch keine Permanenz, da die Referenz als Wert übergeben wurde und sich nicht ändern kann.

Für mich sieht das überhaupt nicht nach C ++ Referenzen aus. Es ähnelt keinen regulären C ++ - Referenzen, weil Sie nicht festlegen können, dass es auf etwas anderes verweist, und es ähnelt keinen C ++ - const-Referenzen, weil in Java der Code, der die Referenz ändern würde (aber wirklich nicht), keine Kompilierung auslöst -Zeitfehler.

Dies ähnelt eher dem Verhalten von C ++ - Zeigern. Sie können es verwenden, um die Werte der angezeigten Objekte zu ändern, aber Sie können den Wert des Zeigers selbst in einer Funktion nicht ändern. Ebenso wie bei C ++ - Zeigern (aber nicht bei C ++ - Referenzen) können Sie in Java "null" als Wert für ein solches Argument übergeben.

Meine Frage lautet also: Warum verwendet Java den Begriff "Referenz"? Ist zu verstehen, dass sie nicht C ++ Referenzen ähneln? Oder ähneln sie tatsächlich C ++ - Referenzen und mir fehlt etwas?

Shivan Drache
quelle
10
Beachten Sie, dass Sie in Ihrem ersten C ++ - Beispiel nicht den Verweis "auf etwas anderes verweisen". Stattdessen ändern Sie den Wert der Variablen, auf die der Referenzpunkt verweist. Dies ähnelt konzeptionell der Mutation des Objektstatus, auf den mit Java-Referenz verwiesen wird.
Xion
@Xion yep, jetzt, da ich Ihren Kommentar gelesen habe, verstehe ich, was Sie meinen und stimme zu 98% von dem, was Sie sagen :) Das Problem in Java ist, dass Sie Zugriff auf jeden Zustand des Objekts haben müssen, um konzeptionell Um das zu erreichen, was C ++ einfach tut, können Sie die Referenz auf den Wert einer anderen Referenz setzen.
Shivan Dragon

Antworten:

48

Warum? Denn obwohl eine einheitliche Terminologie im Allgemeinen für den gesamten Beruf gut ist, respektieren Sprachdesigner nicht immer den Sprachgebrauch anderer Sprachdesigner, insbesondere wenn diese anderen Sprachen als Konkurrenten wahrgenommen werden.

Aber in Wirklichkeit war keine der beiden Verweise eine sehr gute Wahl. "Referenzen" in C ++ sind einfach ein Sprachkonstrukt, um Aliase (alternative Namen für genau dieselbe Entität) explizit einzuführen . Die Dinge wären viel klarer gewesen, hätten sie das neue Feature einfach als "Aliase" bezeichnet. Zu dieser Zeit bestand die große Schwierigkeit darin, jedem den Unterschied zwischen Zeigern (für die eine Dereferenzierung erforderlich ist) und Referenzen (für die dies nicht der Fall ist) verständlich zu machen. Daher war es wichtig, dass sie etwas anderes als "Zeiger" hießen und nicht so sehr speziell, welchen Begriff zu verwenden.

Java hat keine Zeiger und ist stolz darauf. Daher war es keine Option, "Zeiger" als Begriff zu verwenden. Die "Referenzen", die es gibt, verhalten sich jedoch ziemlich ähnlich wie die C ++ - Zeiger, wenn Sie sie weitergeben. Der große Unterschied besteht darin, dass Sie die fieseren Operationen auf niedriger Ebene (Casting, Hinzufügen ...) nicht ausführen können Sie führen jedoch zu genau der gleichen Semantik, wenn Sie Handles an Entitäten übergeben, die identisch sind, im Gegensatz zu Entitäten, die zufällig nur gleich sind. Leider enthält der Begriff "Zeiger" so viele negative Assoziationen auf niedriger Ebene, dass es unwahrscheinlich ist, dass er jemals von der Java-Community akzeptiert wird.

Das Ergebnis ist, dass beide Sprachen den gleichen vagen Begriff für zwei ziemlich unterschiedliche Dinge verwenden, von denen beide von einem spezifischeren Namen profitieren könnten, von denen jedoch wahrscheinlich keiner bald ersetzt wird. Auch natürliche Sprache kann manchmal frustrierend sein!

Kilian Foth
quelle
7
Vielen Dank, C ++ - Referenzen als "Aliase" (für denselben Wert / dieselbe Instanz) und Java-Referenzen als "Zeiger ohne die lästigeren Operationen auf niedriger Ebene (Casting, Hinzufügen ...)" zu betrachten, macht die Dinge für mich wirklich klar.
Shivan Dragon
14
Java hat keine Zeiger und ist stolz darauf. Daher war es keine Option, "Zeiger" als Begriff zu verwenden. Was ist mit NullPointerExceptionoder die Tatsache, dass die JLS den Begriff "Zeiger" verwendet?
Tobias Brandt
7
@TobiasBrandt: NullPointerExceptionist kein Zeiger, aber es ist eine Ausnahme, die darauf hinweist, dass auf einer niedrigeren Ebene ein NULL-Zeiger übergeben / dereferenziert wurde. Das Verwenden von Pointerals Teil einer Ausnahme, wenn überhaupt, fügt dem bösen Image, das sie haben, etwas hinzu. Das JLS muss Zeiger erwähnen, da Javas VM hauptsächlich in einer Sprache geschrieben wurde, die sie verwendet. Sie können die Sprachspezifikationen nicht genau beschreiben, ohne zu beschreiben, wie die Interna funktionieren
Elias Van Ootegem
3
@TobiasBrandt: Java verwendet überall Zeiger. Auf Heap-Objekte wird durch etwas verwiesen, das für alle praktischen Zwecke ein Zeiger ist. Es ist nur so, dass Java dem Programmierer keine Operationen auf Zeigertypen zur Verfügung stellt.
John Bode
2
Ich bevorzuge den Begriff "Objekt-ID" für Java / .NET-Referenzen. Auf der Bitebene können solche Dinge typischerweise als etwas implementiert werden, das einem Zeiger ähnelt, aber nichts erfordert, dass dies der Fall ist. Wichtig ist, dass eine Objekt-ID alle Informationen enthält, die das Framework / vm zur Identifizierung eines Objekts benötigt.
Superkatze
9

Eine Referenz ist eine Sache, die sich auf eine andere Sache bezieht. Verschiedene Sprachen haben dem Wort „Referenz“ eine spezifischere Bedeutung zugeschrieben, normalerweise „etwas wie ein Zeiger ohne all die schlechten Aspekte“. C ++ schreibt genau wie Java oder Perl eine bestimmte Bedeutung zu.

In C ++ sind Referenzen eher Aliase (die über einen Zeiger implementiert werden können). Dies ermöglicht das Übergeben von Referenz- oder Aus-Argumenten .

In Java sind Referenzen Zeiger, mit der Ausnahme, dass dies kein einheitliches Konzept der Sprache ist: Alle Objekte sind Referenzen, primitive Typen wie Zahlen nicht. Sie möchten nicht "Zeiger" sagen, weil es in Java keine Zeigerarithmetik und keine reifizierten Zeiger gibt. Sie möchten jedoch klarstellen, dass Objectdas Objekt beim Übergeben eines als Argument nicht kopiert wird . Dies ist auch keine Referenzübergabe , sondern eher eine Weitergabe durch Teilen .

amon
quelle
6

Es geht größtenteils auf Algol 68 und teilweise auf eine Reaktion gegen die Art und Weise zurück, wie C Zeiger definiert.

Algol 68 definierte ein Konzept, das als Referenz bezeichnet wurde. Es war so ziemlich dasselbe wie (für ein Beispiel) ein Zeiger in Pascal. Es war eine Zelle, die entweder NIL oder die Adresse einer anderen Zelle eines bestimmten Typs enthielt. Sie können eine Referenz zuweisen, sodass sich eine Referenz nach der Neuzuweisung jeweils auf eine Zelle und auf eine andere Zelle beziehen kann. Es tat nicht jedoch Unterstützung etwas analog zu C oder C ++ Zeigerarithmetik.

Zumindest, wie Algol 68 es definierte, waren Referenzen jedoch ein ziemlich sauberes Konzept, dessen Verwendung in der Praxis ziemlich umständlich war. Die meisten Variablendefinitionen enthielten tatsächlich Verweise, so dass sie eine Kurzschreibweise definierten, um zu verhindern, dass sie völlig außer Kontrolle geraten, aber mehr als eine triviale Verwendung könnte ohnehin ziemlich schnell ungeschickt werden.

Beispielsweise wurde eine Deklaration wie INT j := 7;wirklich vom Compiler als Deklaration wie behandelt REF INT j = NEW LOC INT := 7. Also, was Sie in Algol 68 deklariert haben, war normalerweise eine Referenz, die dann initialisiert wurde, um auf etwas zu verweisen, das auf dem Heap zugewiesen wurde, und die (optional) initialisiert wurde, um einen bestimmten Wert zu enthalten. Im Gegensatz zu Java wurde jedoch zumindest versucht, eine vernünftige Syntax beizubehalten, anstatt ständig Dinge wie foo bar = new foo();"Unsere Zeiger sind keine Zeiger" zu haben oder Fibs zu erzählen.

Pascal und die meisten seiner Nachkommen (sowohl direkt als auch spirituell) benannten das Algol 68-Referenzkonzept in "Zeiger" um, behielten aber das Konzept selbst im Wesentlichen bei: Ein Zeiger war eine Variable, die entweder niloder die Adresse von etwas, das Sie zugewiesen haben, enthielt auf dem Heap (dh, zumindest wie ursprünglich von Jensen und Wirth definiert, gab es keinen "Address-of" -Operator, sodass ein Zeiger nicht auf eine normal definierte Variable verweisen konnte). Obwohl es sich um "Zeiger" handelt, wurde keine Zeigerarithmetik unterstützt.

C und C ++ haben dem einige Wendungen hinzugefügt. Erstens, sie tun haben einen Adressoperator, so ein Zeiger nicht nur etwas zugewiesen auf dem Heap verweisen, sondern auf jede Variable, unabhängig davon , wie sie zugeordnet ist. Zweitens definieren sie Arithmetik auf Zeigern - und definieren Array-Indizes im Grunde genommen nur als Kurzschreibweise für Zeigerarithmetik, sodass Zeigerarithmetik in den meisten C- und C ++-Umgebungen allgegenwärtig (neben unvermeidbar) ist.

Als Java erfunden wurde, dachte Sun anscheinend, "Java hat keine Zeiger" sei eine einfachere und sauberere Marketingbotschaft als: "Fast alles in Java ist ein Zeiger, aber diese Zeiger sind meistens wie Pascals Zeiger anstelle von Cs." Da sie entschieden hatten, dass "Zeiger" kein akzeptabler Begriff war, brauchten sie etwas anderes und gruben stattdessen "Verweis" aus, obwohl sich ihre Verweise subtil (und in einigen Fällen nicht so subtil) von Algol 68-Verweisen unterschieden.

Obwohl es etwas anders herauskam, war C ++ mit ungefähr dem gleichen Problem konfrontiert: Das Wort "Zeiger" war bereits bekannt und verstanden, so dass sie ein anderes Wort für diese andere Sache brauchten, die sie hinzufügten, die sich auf etwas anderes bezog, aber ansonsten recht war ein bisschen anders als das, was die Leute unter "Zeiger" verstanden haben. Obwohl es sich auch deutlich von einer Algol 68-Referenz unterscheidet, wurde der Begriff "Referenz" ebenfalls wiederverwendet.

Jerry Sarg
quelle
Was die Bezugnahme in Algol 68 besonders schmerzhaft macht, ist die automatische Dereferenzierung. Das ist praktisch, solange es auf eine Ebene beschränkt ist. Aber die Tatsache, dass es mehrmals in Kombination mit dem syntaktischen Zucker geschehen konnte, der einige der Hinweise auf die ahnungslosen Augen verbarg, machte es verwirrend.
Programmierer
0

Der Begriff 'Referenztyp' ist ein generischer Begriff. Er kann im Gegensatz zu 'Werttyp' sowohl einen Zeiger als auch eine Referenz (in C ++) ausdrücken. Javas Referenzen sind wirklich Zeiger ohne syntaktischen Aufwand ->einer *Dereferenzierung. Wenn Sie sich die Implementierung von JVM in C ++ ansehen, sind sie wirklich bloße Zeiger. C # hat auch eine ähnliche Vorstellung von Referenzen wie Java, aber es hat auch Zeiger und 'ref'-Qualifizierer für Funktionsparameter, die es ermöglichen, Werttypen als Referenz zu übergeben und das Kopieren wie &in C ++ zu vermeiden .

Hertz
quelle
Inwiefern unterscheidet sich diese Antwort von früheren Antworten hier?
Mücke