3929 Stimmen

Was sind die Unterschiede zwischen einer Zeigervariablen und einer Referenzvariablen in C++?

Ich weiß, dass Referenzen syntaktischer Zucker sind, damit der Code leichter zu lesen und zu schreiben ist.

Aber was ist der Unterschied zwischen einer Zeigervariablen und einer Referenzvariablen?

1 Stimmen

Ein lokaler Verweis (d.h. ein Verweis, der nicht in einer Struktur oder Klasse enthalten ist) weist nicht unbedingt Speicherplatz zu. Das erkennen Sie an dem Unterschied zwischen sizeof(int &) und sizeof(struct { int &x; }).

136 Stimmen

Ich denke, Punkt 2 sollte lauten: "Ein Zeiger darf NULL sein, eine Referenz jedoch nicht. Nur fehlerhafter Code kann eine NULL-Referenz erzeugen und ihr Verhalten ist undefiniert."

32 Stimmen

Zeiger sind nur ein weiterer Objekttyp, und wie jedes Objekt in C++ können sie eine Variable sein. Referenzen hingegen sind niemals Objekte, nur Variablen.

36voto

Kunal Vyas Punkte 1373

Obwohl sowohl Referenzen als auch Zeiger verwendet werden, um indirekt auf einen anderen Wert zuzugreifen, gibt es zwei wichtige Unterschiede zwischen Referenzen und Zeigern. Der erste ist, dass ein Verweis immer auf ein Objekt verweist: Es ist ein Fehler, eine Referenz zu definieren, ohne sie zu initialisieren. Der zweite wichtige Unterschied ist das Verhalten bei der Zuweisung: Die Zuweisung an eine Referenz ändert das Objekt, an das die Referenz gebunden ist; sie bindet die Referenz nicht an ein anderes Objekt neu. Einmal initialisiert, bezieht sich eine Referenz immer auf dasselbe zugrunde liegende Objekt.

Betrachten Sie diese beiden Programmfragmente. Im ersten weisen wir einen Zeiger einem anderen zu:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

Nach der Zuweisung, ival, bleibt das durch pi adressierte Objekt unverändert. Die Zuweisung ändert den Wert von pi, so dass er auf ein anderes Objekt zeigt. Betrachten wir nun ein ähnliches Programm, das zwei Referenzen zuweist:

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

Diese Zuweisung ändert ival, den von ri referenzierten Wert, und nicht die Referenz selbst. Nach der Zuweisung verweisen die beiden Referenzen immer noch auf ihre ursprünglichen Objekte, und der Wert dieser Objekte ist nun ebenfalls derselbe.

0 Stimmen

"eine Referenz bezieht sich immer auf ein Objekt" ist einfach völlig falsch

30voto

fatma.ekici Punkte 2657

Eine Referenz ist ein Alias für eine andere Variable, während ein Zeiger die Speicheradresse einer Variablen enthält. Referenzen werden im Allgemeinen als Funktionsparameter verwendet, so dass das übergebene Objekt nicht die Kopie, sondern das Objekt selbst ist.

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use.

27voto

FrankHB Punkte 1919

Die direkte Antwort

Was ist eine Referenz in C++? Eine bestimmte Instanz eines Typs, die kein Objekttyp ist .

Was ist ein Zeiger in C++? Eine bestimmte Instanz eines Typs, die ist ein Objekttyp .

De die ISO-C++-Definition des Objekttyps :

Eine Objekt Typ ist ein (möglicherweise cv -qualifizierter) Typ, der weder ein Funktionstyp noch ein Referenztyp ist, und nicht cv nichtig.

Es ist vielleicht wichtig zu wissen, dass der Objekttyp eine Top-Level-Kategorie des Typuniversums in C++ ist. Referenz ist ebenfalls eine Kategorie der obersten Ebene. Aber Zeiger ist nicht.

Zeiger und Verweise werden zusammen erwähnt im Rahmen von Verbindungstyp . Dies liegt im Wesentlichen an der von C geerbten (und erweiterten) Syntax des Deklarators, der keine Referenzen kennt. (Außerdem gibt es seit C++ 11 mehr als eine Art von Deklarator für Referenzen, während Zeiger immer noch "unityped" sind: & + && vs. * .) Daher ist es in diesem Zusammenhang durchaus sinnvoll, eine Sprache zu entwerfen, die eine "Erweiterung" im Stil von C darstellt. (Ich werde immer noch argumentieren, dass die Syntax der Deklaratoren die syntaktische Ausdruckskraft verschwendet viel macht sowohl menschliche Benutzer als auch Implementierungen frustriert. Daher sind nicht alle von ihnen qualifiziert zu sein eingebaut in einem neuen Sprachdesign. Dies ist jedoch ein völlig anderes Thema über PL-Design).

Ansonsten ist es unbedeutend, dass Zeiger als eine bestimmte Art von Typen mit Verweisen zusammen qualifiziert werden können. Sie haben einfach zu wenig gemeinsame Eigenschaften außer der Ähnlichkeit der Syntax, so dass es in den meisten Fällen keinen Grund gibt, sie zusammenzufassen.

Beachten Sie, dass in den obigen Ausführungen nur "Zeiger" und "Referenzen" als Typen genannt werden. Es gibt einige interessierte Fragen über ihre Instanzen (wie Variablen). Es gibt auch zu viele Missverständnisse.

Aus den Unterschieden der Oberkategorien lassen sich bereits viele konkrete Unterschiede ableiten, die nicht direkt mit Zeigern zusammenhängen:

  • Objekttypen können eine Top-Level cv Qualifikanten. Referenzen können nicht.
  • Die Variablen der Objekttypen belegen Speicherplatz gemäß die abstrakte Maschine Semantik. Verweise müssen keinen Speicherplatz belegen (siehe den Abschnitt über Missverständnisse weiter unten).
  • ...

Ein paar weitere Sonderregeln für Referenzen:

  • Zusammengesetzte Deklaratoren sind bei Verweisen restriktiver.
  • Referenzen können zusammenbrechen .
    • Besondere Regeln für && Parameter (als "Weiterleitungsreferenzen") auf der Grundlage der Referenzkollabierung während der Ableitung der Vorlagenparameter ermöglichen "perfekte Weiterleitung" der Parameter.
  • Für Referenzen gelten besondere Regeln bei der Initialisierung. Die Lebensdauer von Variablen, die als Referenztyp deklariert sind, kann sich von gewöhnlichen Objekten durch Erweiterung unterscheiden.
    • BTW, ein paar andere Zusammenhänge wie die Initialisierung mit std::initializer_list folgt einigen ähnlichen Regeln der Verlängerung der Referenzlebensdauer. Das ist ein weiteres Wespennest.
  • ...

Die Missverständnisse

Syntaktischer Zucker

Ich weiß, dass Referenzen syntaktischer Zucker sind, damit der Code leichter zu lesen und zu schreiben ist.

Technisch gesehen ist dies schlichtweg falsch. Referenzen sind kein syntaktischer Zucker anderer Funktionen in C++, denn sie können nicht genau durch andere Funktionen ohne semantische Unterschiede ersetzt werden.

(Ähnlich, lambda-Ausdruck s sind no syntaktischer Zucker aller anderen Funktionen in C++, weil sie nicht genau mit "unspezifizierten" Eigenschaften wie die Reihenfolge der Deklaration der erfassten Variablen was wichtig sein kann, weil die Reihenfolge der Initialisierung solcher Variablen von Bedeutung sein kann).

C++ hat nur wenige Arten von syntaktischen Zuckern in diesem strengen Sinne. Ein Beispiel ist der (von C geerbte) eingebaute (nicht überladene) Operator [] die ist genau so definiert, dass es dieselben semantischen Eigenschaften spezifischer Kombinationsformen über den eingebauten Operator unary * und binär + .

Lagerung

Ein Zeiger und ein Verweis verwenden also beide die gleiche Menge an Speicher.

Die obige Aussage ist schlichtweg falsch. Um solche Missverständnisse zu vermeiden, sollten Sie sich stattdessen die ISO-C++-Regeln ansehen:

De [intro.object]/1 :

... Ein Objekt belegt einen Speicherbereich in seiner Entstehungszeit, während seiner gesamten Lebensdauer und in der Zeit seiner Zerstörung. ...

De [dcl.ref]/4 :

Es ist nicht spezifiziert, ob eine Referenz gespeichert werden muss oder nicht.

Beachten Sie, dass es sich um semantisch Eigenschaften.

Pragmatik

Selbst wenn Zeiger nicht qualifiziert genug sind, um mit Referenzen im Sinne des Sprachdesigns zusammengelegt zu werden, gibt es immer noch einige Argumente, die es fragwürdig machen, in einigen anderen Zusammenhängen eine Wahl zwischen ihnen zu treffen, z.B. wenn es um die Wahl von Parametertypen geht.

Aber das ist nicht die ganze Geschichte. Ich meine, es gibt mehr Dinge als Zeiger vs. Referenzen, die Sie berücksichtigen müssen.

Wenn Sie sich nicht an solch übermäßig spezifische Auswahlmöglichkeiten halten müssen, ist die Antwort in den meisten Fällen kurz: Sie brauchen keine Zeiger zu verwenden, also brauchen Sie auch keine . Zeiger sind in der Regel schlecht genug, weil sie zu viele Dinge implizieren, die man nicht erwartet, und sich auf zu viele implizite Annahmen stützen, die die Wartbarkeit und (sogar) Portabilität des Codes untergraben. Unnötigerweise auf Zeiger zu setzen, ist definitiv ein schlechter Stil und sollte im Sinne von modernem C++ vermieden werden. Überdenken Sie Ihr Ziel und Sie werden feststellen, dass Zeiger ist das Merkmal der letzten Art in den meisten Fällen.

  • Manchmal schreiben die Sprachregeln ausdrücklich vor, dass bestimmte Typen zu verwenden sind. Wenn Sie diese Funktionen nutzen wollen, halten Sie sich an die Regeln.
    • Kopierkonstruktoren erfordern bestimmte Typen von cv - & Referenztyp als 1. Parametertyp. (Und normalerweise sollte es sein const qualifiziert).
    • Move-Konstruktoren erfordern bestimmte Typen von cv - && Referenztyp als 1. Parametertyp. (Und normalerweise sollte es keine Qualifier geben.)
    • Bestimmte Überladungen von Operatoren erfordern Referenz- oder Nicht-Referenztypen. Zum Beispiel:
      • Überlastet operator= als spezielle Mitgliedsfunktionen erfordern Referenztypen ähnlich dem 1. Parameter von Kopier-/Verschiebekonstruktoren.
      • Postfix ++ erfordert Dummy int .
      • ...
  • Wenn Sie wissen, dass Pass-by-Value (d. h. die Verwendung von Nicht-Referenztypen) ausreichend ist, verwenden Sie es direkt, insbesondere wenn Sie eine Implementierung verwenden, die die in C++17 vorgeschriebene Copy Elision unterstützt. ( Warnung : Um jedoch Ausführlich Grund für die Notwendigkeit sein kann sehr kompliziert .)
  • Wenn Sie einige Handles mit Besitzrechten betreiben wollen, verwenden Sie intelligente Zeiger wie unique_ptr y shared_ptr (oder sogar mit selbstgebauten, wenn Sie diese benötigen, um undurchsichtig ), statt roher Zeiger.
  • Wenn Sie einige Iterationen über einen Bereich durchführen, verwenden Sie Iteratoren (oder einige Bereiche, die von der Standardbibliothek noch nicht zur Verfügung gestellt werden), anstatt rohe Zeiger, es sei denn, Sie sind überzeugt, dass rohe Zeiger in sehr speziellen Fällen besser sind (z.B. für weniger Header-Abhängigkeiten).
  • Wenn Sie wissen, dass pass-by-value ausreichend ist und Sie eine explizite nullable Semantik wollen, verwenden Sie Wrapper wie std::optional anstelle von rohen Zeigern.
  • Wenn Sie wissen, dass pass-by-value aus den oben genannten Gründen nicht ideal ist und Sie keine nullable Semantik wollen, verwenden Sie {lvalue, rvalue, forwarding}-references.
  • Selbst wenn Sie Semantik wie traditionelle Zeiger wollen, gibt es oft etwas passenderes, wie observer_ptr in der Bibliothek Fundamental TS.

Die einzigen Ausnahmen können in der derzeitigen Sprache nicht umgangen werden:

  • Bei der obigen Implementierung von intelligenten Zeigern müssen Sie möglicherweise mit rohen Zeigern arbeiten.
  • Bestimmte Routinen der Sprachinteraktion erfordern Zeiger, wie operator new . (Allerdings, cv - void* ist immer noch ganz anders und sicherer als gewöhnliche Objektzeiger, weil es unerwartete Zeigerarithmetik ausschließt, es sei denn, Sie verlassen sich auf eine nicht konforme Erweiterung von void* wie die von GNU).
  • Funktionszeiger können aus Lambda-Ausdrücken ohne Captures konvertiert werden, Funktionsreferenzen hingegen nicht. In solchen Fällen müssen Sie Funktionszeiger in nicht-generischem Code verwenden, auch wenn Sie absichtlich keine nullbaren Werte wollen.

In der Praxis ist die Antwort also so offensichtlich: im Zweifelsfall Zeiger vermeiden . Sie müssen Zeiger nur dann verwenden, wenn es ganz eindeutige Gründe dafür gibt, dass nichts anderes besser geeignet ist. Abgesehen von einigen oben erwähnten Ausnahmefällen sind solche Entscheidungen fast immer nicht rein C++-spezifisch (aber wahrscheinlich sprachimplementierungsspezifisch). Solche Fälle können sein:

  • Sie müssen die APIs im alten Stil (C) verwenden.
  • Sie müssen die ABI-Anforderungen bestimmter C++-Implementierungen erfüllen.
  • Sie müssen zur Laufzeit mit verschiedenen Sprachimplementierungen (einschließlich verschiedener Assemblies, Sprachlaufzeit und FFI einiger High-Level-Client-Sprachen) auf der Grundlage von Annahmen über bestimmte Implementierungen interagieren.
  • In einigen extremen Fällen müssen Sie die Effizienz der Übersetzung (Kompilierung und Verknüpfung) verbessern.
  • In einigen extremen Fällen müssen Sie die Aufblähung von Symbolen vermeiden.

Vorbehalte gegen die Sprachneutralität

Wenn Sie sich die Frage über einige Google-Suchergebnisse (nicht spezifisch für C++) ist dies höchstwahrscheinlich der falsche Ort.

Referenzen in C++ sind ziemlich "seltsam", da sie im Wesentlichen nicht erstklassig sind: sie werden als die Objekte oder die Funktionen behandelt, auf die verwiesen wird daher haben sie keine Chance, einige erstklassige Operationen zu unterstützen, wie z.B. den linken Operanden von der Betreiber des Mitgliederzugangs unabhängig vom Typ des referenzierten Objekts. Andere Sprachen können ähnliche Beschränkungen für ihre Referenzen haben oder auch nicht.

Referenzen in C++ behalten wahrscheinlich nicht die Bedeutung in verschiedenen Sprachen. Zum Beispiel implizieren Referenzen im Allgemeinen keine Nulleigenschaften für Werte wie in C++, so dass solche Annahmen in einigen anderen Sprachen nicht funktionieren (und Sie werden ziemlich leicht Gegenbeispiele finden, z. B. Java, C#, ...).

Es kann noch einige gemeinsame Eigenschaften zwischen Referenzen in verschiedenen Programmiersprachen im Allgemeinen geben, aber lassen wir das für andere Fragen in SO.

(Eine Randbemerkung: Die Frage kann schon bedeutend sein, bevor irgendwelche "C-ähnlichen" Sprachen beteiligt sind, wie ALGOL 68 vs. PL/I .)

23voto

Life Punkte 419

Dies basiert auf dem Lehrgang . Was geschrieben steht, macht es deutlicher:

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

Einfach, um sich daran zu erinnern,

>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)

Was mehr ist, wie wir fast jeder Zeiger Tutorial beziehen können, ist ein Zeiger ein Objekt, das durch Zeiger Arithmetik unterstützt wird, die Zeiger ähnlich wie ein Array macht.

Sehen Sie sich die folgende Aussage an,

int Tom(0);
int & alias_Tom = Tom;

alias_Tom kann verstanden werden als eine alias of a variable (anders als bei typedef das ist alias of a type ) Tom . Es ist auch OK zu vergessen, dass die Terminologie einer solchen Aussage darin besteht, einen Verweis auf Tom .

1 Stimmen

Und wenn eine Klasse eine Referenzvariable hat, sollte sie entweder mit einem nullptr oder einem gültigen Objekt in der Initialisierungsliste initialisiert werden.

1 Stimmen

Die Formulierung in dieser Antwort ist zu verwirrend, als dass sie wirklich von Nutzen sein könnte. Außerdem, @Misgevolution, empfehlen Sie den Lesern ernsthaft, eine Referenz mit einer nullptr ? Haben Sie eigentlich irgendeinen anderen Teil dieses Threads gelesen, oder...?

1 Stimmen

Mein Fehler, entschuldigen Sie die dumme Bemerkung, die ich gemacht habe. Ich muss um diese Zeit schon unter Schlafentzug gestanden haben. 'Mit nullptr initialisieren' ist völlig falsch.

23voto

MSN Punkte 51308

Es spielt keine Rolle, wie viel Speicherplatz er beansprucht, da man (ohne Code auszuführen) nicht sehen kann, welchen Nebeneffekt der beanspruchte Speicherplatz hat.

Ein wesentlicher Unterschied zwischen Referenzen und Zeigern besteht jedoch darin, dass temporäre Elemente, die const-Referenzen zugewiesen sind, so lange leben, bis die const-Referenz den Gültigkeitsbereich verlässt.

Zum Beispiel:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

wird gedruckt:

in scope
scope_test done!

Dies ist der Sprachmechanismus, der die Arbeit von ScopeGuard ermöglicht.

1 Stimmen

Die Adresse eines Verweises kann nicht übernommen werden, aber das bedeutet nicht, dass sie keinen Platz beanspruchen. Wenn man von Optimierungen absieht, können sie das sehr wohl.

3 Stimmen

Ungeachtet der Auswirkungen ist die Aussage "Eine Referenz auf dem Stapel nimmt überhaupt keinen Platz ein" offensichtlich falsch.

1 Stimmen

@Tomalak, nun, das hängt auch vom Compiler ab. Aber ja, das zu sagen ist ein bisschen verwirrend. Ich vermute, es wäre weniger verwirrend, das einfach zu entfernen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X