übergeben Was sind die Unterschiede zwischen einer Zeigervariable und einer Referenzvariablen in C++?




zeiger referenzieren (20)

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

Aber was sind die Unterschiede?

Zusammenfassung aus den Antworten und Links unten:

  1. Ein Zeiger kann beliebig oft neu zugewiesen werden, während eine Referenz nach dem Binden nicht erneut zugewiesen werden kann.
  2. Zeiger können nirgendwo zeigen ( NULL ), wohingegen eine Referenz immer auf ein Objekt verweist.
  3. Sie können die Adresse einer Referenz nicht wie bei Zeigern verwenden.
  4. Es gibt keine "Referenzarithmetik" (Sie können jedoch die Adresse eines Objekts, auf das durch eine Referenz verwiesen wird, verwenden und Zeigerarithmetik wie in &obj + 5 ).

Um ein Missverständnis zu klären:

Der C ++ - Standard achtet sehr darauf, zu vermeiden, wie ein Compiler Verweise implementieren darf, aber jeder C ++ - Compiler implementiert Verweise als Zeiger. Das heißt, eine Erklärung wie:

int &ri = i;

Wenn es nicht vollständig wegoptimiert ist, weist es dieselbe Menge an Speicher zu wie ein Zeiger und speichert die Adresse von i in diesen Speicher.

Ein Zeiger und eine Referenz verwenden also dieselbe Menge an Speicher.

Generell,

  • Verwenden Sie Referenzen in Funktionsparametern und Rückgabetypen, um nützliche und selbstdokumentierende Schnittstellen bereitzustellen.
  • Verwenden Sie Zeiger zur Implementierung von Algorithmen und Datenstrukturen.

Interessante Lektüre:

https://code.i-harness.com


Was ist eine C ++ - Referenz ( für C-Programmierer )

Eine Referenz kann als konstanter Zeiger (nicht zu verwechseln mit einem Zeiger auf einen konstanten Wert!) Mit automatischer Indirektion verstanden werden, dh der Compiler wendet den Operator * für Sie an.

Alle Referenzen müssen mit einem Wert ungleich Null initialisiert werden, da sonst die Kompilierung fehlschlägt. Es ist weder möglich, die Adresse einer Referenz abzurufen - der Adressoperator gibt stattdessen die Adresse des referenzierten Werts zurück, und es ist auch nicht möglich, Berechnungen mit Referenzen durchzuführen.

C-Programmierer mögen C ++ - Referenzen nicht mögen, da es nicht mehr offensichtlich ist, wenn eine Indirektion erfolgt oder wenn ein Argument als Wert oder als Zeiger übergeben wird, ohne die Funktionssignaturen zu betrachten.

C ++ - Programmierer mögen Zeiger nicht mögen, da sie als unsicher angesehen werden - obwohl Referenzen nicht wirklich sicherer sind als konstante Zeiger, außer in den trivialsten Fällen - sie die Bequemlichkeit der automatischen Indirektion haben und eine andere semantische Konnotation haben.

Betrachten Sie die folgende Aussage aus der C ++ - FAQ :

Auch wenn eine Referenz häufig mithilfe einer Adresse in der zugrunde liegenden Assemblersprache implementiert wird, denken Sie bitte nicht an eine Referenz als einen witzig aussehenden Zeiger auf ein Objekt. Eine Referenz ist das Objekt. Es ist weder ein Zeiger auf das Objekt noch eine Kopie des Objekts. Es ist das Objekt.

Aber wenn eine Referenz wirklich das Objekt wäre, wie könnte es dann hängende Referenzen geben? In nicht verwalteten Sprachen können Referenzen nicht "sicherer" als Zeiger sein - im Allgemeinen gibt es keine Möglichkeit, Alias-Werte über Bereichsgrenzen hinweg zuverlässig zu Alias-Werten zu verwenden!

Warum halte ich C ++ - Referenzen für nützlich?

Aus einem C-Hintergrund stammend, können C ++ - Verweise wie ein dummes Konzept aussehen, sollten jedoch nach Möglichkeit immer anstelle von Zeigern verwendet werden: Die automatische Indirektion ist praktisch, und Verweise sind besonders nützlich, wenn sie mit RAII - aber nicht aufgrund der wahrgenommenen Sicherheit Vorteil, aber eher, weil sie das Schreiben von idiomatischem Code weniger umständlich machen.

RAII ist eines der zentralen Konzepte von C ++, es interagiert jedoch nicht unerheblich mit der Kopiersemantik. Durch das Übergeben von Objekten als Referenz werden diese Probleme vermieden, da kein Kopieren erforderlich ist. Wenn Verweise nicht in der Sprache vorhanden wären, müssten stattdessen Zeiger verwendet werden, deren Verwendung umständlicher ist. Dies verstößt gegen das Prinzip des Sprachdesigns, wonach die Best-Practice-Lösung einfacher sein sollte als die Alternativen.


Unterschied zwischen Zeiger und Referenz

Ein Zeiger kann auf 0 und eine Referenz nicht initialisiert werden. Tatsächlich muss eine Referenz auch auf ein Objekt verweisen, aber ein Zeiger kann der Nullzeiger sein:

int* p = 0;

Aber wir können int& p = 0;und auch nichtint& p=5 ; .

Um dies richtig zu machen, müssen wir zuerst ein Objekt deklariert und definiert haben, dann können wir einen Verweis auf dieses Objekt erstellen, sodass die korrekte Implementierung des vorherigen Codes Folgendes ist:

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

Ein weiterer wichtiger Punkt ist, dass wir die Deklaration des Zeigers ohne Initialisierung vornehmen können. Im Fall einer Referenz, die immer auf eine Variable oder ein Objekt verweist, ist dies jedoch nicht möglich. Eine solche Verwendung eines Zeigers ist jedoch riskant, daher überprüfen wir im Allgemeinen, ob der Zeiger tatsächlich auf etwas zeigt oder nicht. Im Falle eines Verweises ist eine solche Prüfung nicht erforderlich, da wir bereits wissen, dass der Verweis auf ein Objekt während der Deklaration obligatorisch ist.

Ein weiterer Unterschied besteht darin, dass der Zeiger auf ein anderes Objekt zeigen kann, der Verweis jedoch immer auf dasselbe Objekt verweist. Nehmen wir das folgende Beispiel:

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

Ein weiterer Punkt: Wenn wir eine Vorlage wie eine STL-Vorlage haben, gibt eine solche Klassenvorlage immer einen Verweis und keinen Zeiger zurück, um das Lesen oder Zuweisen eines neuen Werts mit dem Operator [] zu erleichtern:

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="

Eigentlich ist eine Referenz nicht wirklich ein Zeiger.

Ein Compiler behält "Verweise" auf Variablen bei und ordnet einen Namen einer Speicheradresse zu. Das ist seine Aufgabe, einen beliebigen Variablennamen beim Kompilieren in eine Speicheradresse zu übersetzen.

Wenn Sie eine Referenz erstellen, teilen Sie dem Compiler nur mit, dass Sie der Zeigervariable einen anderen Namen zuweisen. Deshalb können Referenzen nicht "auf Null zeigen", da eine Variable nicht sein kann und auch nicht.

Zeiger sind Variablen; Sie enthalten die Adresse einer anderen Variablen oder können Null sein. Wichtig ist, dass ein Zeiger einen Wert hat, während eine Referenz nur eine Variable hat, auf die sie verweist.

Nun einige Erklärung für echten Code:

int a = 0;
int& b = a;

Hier erstellen Sie keine weitere Variable, die auf a ; Sie fügen einfach einen anderen Namen zum Speicherinhalt hinzu, der den Wert von a . Dieser Speicher hat jetzt zwei Namen, a und b , und kann mit beiden Namen angesprochen werden.

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

Beim Aufruf einer Funktion generiert der Compiler normalerweise Speicherplätze für die Argumente, in die kopiert werden soll. Die Funktionssignatur definiert die zu erstellenden Leerzeichen und gibt den Namen an, der für diese Leerzeichen verwendet werden soll. Durch das Deklarieren eines Parameters als Referenz wird der Compiler lediglich angewiesen, den Speicherbereich der Eingabevariablen zu verwenden, anstatt während des Methodenaufrufs einen neuen Speicherplatz zuzuweisen. Es mag seltsam erscheinen, wenn Sie sagen, dass Ihre Funktion eine im aufrufenden Bereich deklarierte Variable direkt bearbeitet. Beachten Sie jedoch, dass beim Ausführen von kompiliertem Code kein Bereich mehr vorhanden ist. Es gibt nur einfachen Speicher, und Ihr Funktionscode könnte alle Variablen beeinflussen.

Nun kann es Fälle geben, in denen Ihr Compiler die Referenz beim Kompilieren möglicherweise nicht kennt, z. B. wenn Sie eine externe Variable verwenden. Eine Referenz kann also als Zeiger im zugrunde liegenden Code implementiert sein oder nicht. In den Beispielen, die ich Ihnen gegeben habe, wird es höchstwahrscheinlich nicht mit einem Zeiger implementiert.


Eine Referenz ist neben einem syntaktischen Zucker ein const Zeiger ( kein Zeiger auf einen const ). Sie müssen festlegen, worauf es sich bezieht, wenn Sie die Referenzvariable deklarieren. Sie können sie später nicht mehr ändern.

Update: Jetzt, wo ich mehr darüber nachdenke, gibt es einen wichtigen Unterschied.

Das Ziel eines konstanten Zeigers kann ersetzt werden, indem seine Adresse verwendet und ein const cast verwendet wird.

Das Referenzziel einer Referenz kann in keiner Weise vor UB ersetzt werden.

Dies sollte es dem Compiler ermöglichen, eine weitere Optimierung für eine Referenz durchzuführen.


Referenzen sind Zeigern sehr ähnlich, aber sie sind speziell darauf ausgelegt, bei der Optimierung von Compilern hilfreich zu sein.

  • Referenzen sind so gestaltet, dass der Compiler wesentlich einfacher nachvollziehen kann, welche Referenzaliase welche Variablen haben. Zwei Hauptmerkmale sind sehr wichtig: keine "Referenzarithmetik" und keine Neuzuweisung von Referenzen. Dadurch kann der Compiler ermitteln, welche Verweise auf Alias ​​welche Variablen zur Kompilierzeit enthalten.
  • Verweise dürfen sich auf Variablen beziehen, die keine Speicheradressen haben, z. B. solche, die der Compiler für die Registrierung in Register wählt. Wenn Sie die Adresse einer lokalen Variablen verwenden, kann der Compiler diese nur schwer in ein Register eintragen.

Als Beispiel:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

Ein optimierender Compiler kann feststellen, dass wir auf eine ganze Reihe von [0] und [1] zugreifen. Es würde gerne den Algorithmus optimieren, um:

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

Für eine solche Optimierung muss nachgewiesen werden, dass Array [1] während des Aufrufs durch nichts geändert werden kann. Das ist ziemlich einfach. i ist nie kleiner als 2, daher kann sich array [i] niemals auf array [1] beziehen. maybeModify () erhält als Referenz a0 (Aliasing-Array [0]). Da es keine "Referenz" -Arithmetik gibt, muss der Compiler nur beweisen, dass vielleichtModify niemals die Adresse von x erhält, und es hat sich gezeigt, dass Array nichts ändert [1].

Es muss auch gezeigt werden, dass es keine Möglichkeiten gibt, mit denen ein zukünftiger Aufruf eine [0] lesen / schreiben kann, während in a0 eine temporäre Registerkopie davon vorhanden ist. Dies ist oftmals trivial zu beweisen, da in vielen Fällen offensichtlich ist, dass die Referenz niemals in einer permanenten Struktur wie einer Klasseninstanz gespeichert wird.

Machen Sie dasselbe mit Zeigern

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

Das Verhalten ist dasselbe; erst jetzt ist es viel schwieriger zu beweisen, dass maybeModify array [1] nie ändert, weil wir ihm bereits einen Zeiger gegeben haben; Die Katze ist aus der Tasche. Jetzt muss es den viel schwierigeren Beweis leisten: Eine statische Analyse von maybeModify, um zu beweisen, dass es niemals nach & x + 1 schreibt, muss auch beweisen, dass es niemals einen Zeiger speichert, der sich auf array [0] beziehen kann, was gerecht ist so knifflig.

Moderne Compiler werden immer besser in der statischen Analyse, aber es ist immer schön, ihnen zu helfen und Referenzen zu verwenden.

Natürlich können Compiler Verweise in Zeiger umwandeln, wenn dies nicht möglich ist.

EDIT: Fünf Jahre nach der Veröffentlichung dieser Antwort fand ich einen tatsächlichen technischen Unterschied, bei dem Referenzen anders sind als nur eine andere Sichtweise auf dasselbe Adressierungskonzept. Referenzen können die Lebensdauer von temporären Objekten auf eine Weise verändern, die Zeiger nicht können.

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

Normalerweise werden temporäre Objekte wie der durch den Aufruf von createF(5) am Ende des Ausdrucks zerstört.Indem refC ++ dieses Objekt an eine Referenz gebunden wird, verlängert C ++ jedoch die Lebensdauer dieses temporären Objekts, bis refes den Gültigkeitsbereich verlässt.


Sie haben den wichtigsten Teil vergessen:

Mitgliederzugriff mit Zeigern verwendet ->
Mitgliederzugang mit Verweisen .

foo.bar ist foo->bar in der gleichen Weise deutlich überlegen, wie vi Emacs eindeutig überlegen ist :-)


Außerdem kann eine Referenz, die ein Parameter für eine Funktion ist, die inliniert ist, anders als ein Zeiger behandelt werden.

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

Viele Compiler erzwingen beim Inlinieren der Zeigerversion eins tatsächlich ein Schreiben in den Speicher (wir nehmen die Adresse explizit). Sie lassen jedoch die Referenz in einem Register, das optimaler ist.

Für Funktionen, die nicht inline sind, erzeugen Zeiger und Referenz natürlich den gleichen Code. Es ist immer besser, Intrinsics nach Wert zu übergeben, als nach Referenz, wenn sie nicht geändert und von der Funktion zurückgegeben werden.


Auf die Gefahr hin, dass ich zur Verwirrung beitrage, möchte ich einige Eingaben machen. Ich bin sicher, es hängt hauptsächlich davon ab, wie der Compiler Referenzen implementiert. Im Fall von gcc besteht die Idee, dass eine Referenz nur auf eine Variable auf dem Stack zeigen kann ist eigentlich nicht richtig, zum Beispiel:

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

Was gibt das aus:

THIS IS A STRING
0xbb2070 : 0xbb2070

Wenn Sie feststellen, dass selbst die Speicheradressen genau gleich sind, zeigt die Referenz erfolgreich auf eine Variable auf dem Heap! Wenn Sie wirklich freaky werden wollen, funktioniert dies auch:

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

Was gibt das aus:

THIS IS A STRING

Daher ist ein Verweis ein Zeiger unter der Haube. Beide speichern nur eine Speicheradresse, auf die die Adresse zeigt, ist irrelevant. Was würde Ihrer Meinung nach passieren, wenn ich std :: cout << str_ref; NACH dem Aufruf von delete & str_ref? Natürlich kompiliert es gut, verursacht aber zur Laufzeit einen Segmentierungsfehler, da es nicht länger auf eine gültige Variable zeigt. Wir haben im Grunde eine gebrochene Referenz, die noch vorhanden ist (bis sie außerhalb des Gültigkeitsbereichs liegt), aber nutzlos ist.

Mit anderen Worten, eine Referenz ist nichts anderes als ein Zeiger, der die Zeigermechanik abstrahiert, wodurch die Verwendung sicherer und einfacher wird (keine versehentlichen Zeigermathematik, keine Vermischung von "." Und "->" usw.), vorausgesetzt, Sie sind davon überzeugt Versuchen Sie keinen Unsinn wie meine obigen Beispiele;)

Nun , unabhängig davon , wie ein Compiler behandelt Referenzen, wird es immer eine Art Zeiger unter der Haube hat, weil ein Verweis muss auf eine bestimmte Variable an einer bestimmten Speicheradresse bezieht sich für sie wie erwartet zu funktionieren, gibt es keine , dies zu umgehen (daher der Begriff "Referenz").

Die einzige wichtige Regel, die bei Verweisen beachtet werden muss, ist, dass sie zum Zeitpunkt der Deklaration definiert werden müssen (mit Ausnahme einer Referenz in einem Header. In diesem Fall muss sie im Konstruktor definiert werden, nachdem das Objekt darin enthalten ist) es ist zu spät, um es zu definieren).

Denken Sie daran, dass meine Beispiele oben nur Beispiele sind, die zeigen, was eine Referenz ist. Sie möchten niemals eine Referenz auf diese Weise verwenden! Für die richtige Verwendung einer Referenz gibt es hier bereits zahlreiche Antworten, die den Nagel auf den Kopf treffen


Eine weitere interessante Verwendung von Referenzen besteht darin, ein Standardargument eines benutzerdefinierten Typs anzugeben:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

Das Standardaroma verwendet den 'bind const'-Verweis auf einen temporären Aspekt von Verweisen.


Es gibt einen sehr wichtigen nichttechnischen Unterschied zwischen Zeigern und Verweisen: Ein Argument, das von einem Zeiger an eine Funktion übergeben wird, ist viel sichtbarer als ein Argument, das von einer nicht konstanten Referenz an eine Funktion übergeben wird. Zum Beispiel:

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

Zurück in C kann ein Aufruf, der so aussieht, fn(x)nur per Wert übergeben werden, er kann also definitiv nicht geändert werden x. Um ein Argument zu ändern, müssen Sie einen Zeiger übergeben fn(&x). Wenn also einem Argument kein Argument vorangestellt wurde &, wussten Sie, dass es nicht geändert werden würde. (Das Gegenteil, &bedeutet modifiziert, war nicht wahr, da manchmal große schreibgeschützte Strukturen per constZeiger übergeben werden mussten.)

Einige argumentieren, dass dies eine nützliche Funktion beim Lesen von Code ist. Zeigerparameter sollten immer für modifizierbare Parameter und nicht für Nichtverweise verwendet werden const, auch wenn die Funktion niemals a erwartet nullptr. Das heißt, diese Leute argumentieren, dass Funktionssignaturen wie fn3()oben nicht erlaubt sein sollten. Ein Beispiel dafür sind die C ++ - Stilrichtlinien von Google .


Es spielt keine Rolle, wie viel Speicherplatz belegt ist, da Sie tatsächlich keinen Nebeneffekt (ohne Ausführung von Code) des Speicherplatzes erkennen können, den er beansprucht.

Andererseits besteht ein Hauptunterschied zwischen Referenzen und Zeigern darin, dass Provisorien, die const-Referenzen zugewiesen sind, so lange leben, bis die const-Referenz ihren 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 drucken:

in scope
scope_test done!

Dies ist der Sprachmechanismus, mit dem ScopeGuard arbeiten kann.


Ich habe das Gefühl, dass es noch einen anderen Punkt gibt, der hier nicht behandelt wurde.

Verweise sind im Gegensatz zu den Zeigern syntaktisch gleichbedeutend mit dem Objekt, auf das sie sich beziehen, dh alle Operationen, die auf ein Objekt angewendet werden können, funktionieren für eine Referenz und mit derselben Syntax (die Ausnahme ist natürlich die Initialisierung).

Obwohl dies oberflächlich erscheinen mag, glaube ich, dass diese Eigenschaft für eine Reihe von C ++ - Funktionen von entscheidender Bedeutung ist, beispielsweise:

  • Vorlagen . Da Template - Parameter Ente typisierte sind syntaktische Eigenschaften eines Typs alles, was zählt, so oft die gleiche Vorlage kann sowohl verwendet werden , Tund T&.
    (oder std::reference_wrapper<T>die immer noch auf einen impliziten Cast angewiesen sind T&)
    Vorlagen, die beide abdecken T&und T&&noch üblicher sind.

  • Lvalues . Beachten Sie die Anweisung str[0] = 'X';Ohne Referenzen funktioniert sie nur für c-strings ( char* str). Durch die Rückgabe des Zeichens als Referenz können benutzerdefinierte Klassen dieselbe Notation haben.

  • Konstruktoren kopieren . Syntaktisch ist es sinnvoll, Objekte an Konstruktoren zu übergeben, und keine Zeiger auf Objekte. Es ist jedoch einfach nicht möglich, dass ein Kopierkonstruktor ein Objekt als Wert übernimmt. Dies würde zu einem rekursiven Aufruf des gleichen Kopierkonstruktors führen. Hier bleiben Referenzen als einzige Option.

  • Betreiber Überlastungen . Mit Referenzen ist es möglich, eine Indirektion in einen Operator-Aufruf einzuführen, beispielsweise operator+(const T& a, const T& b)unter Beibehaltung der gleichen Infix-Notation. Dies funktioniert auch bei regelmäßig überladenen Funktionen.

Diese Punkte befähigen einen erheblichen Teil von C ++ und der Standardbibliothek, so dass dies eine wichtige Eigenschaft von Referenzen ist.


Der Unterschied besteht darin, dass eine nicht konstante Zeigervariable (die nicht mit einem Zeiger auf eine Konstante zu verwechseln ist) zu einem bestimmten Zeitpunkt während der Programmausführung geändert werden kann. Dies erfordert die Verwendung der Zeiger-Semantik (&, *), während Referenzen bei der Initialisierung gesetzt werden können only (aus diesem Grund können Sie sie nur in der Konstruktor-Initialisierungsliste festlegen, jedoch nicht auf andere Weise) und die Semantik für den Zugriff auf normale Werte verwenden. Grundsätzlich wurden Referenzen eingeführt, um das Überladen von Operatoren zu ermöglichen, wie ich es in einem sehr alten Buch gelesen hatte. Wie in diesem Thread angegeben, kann der Zeiger auf 0 oder einen beliebigen Wert gesetzt werden. 0 (NULL, nullptr) bedeutet, dass der Zeiger mit nichts initialisiert wird. Es ist ein Fehler, den Nullzeiger zu dereferenzieren. Der Zeiger kann jedoch einen Wert enthalten, der nicht auf einen korrekten Speicherort verweist.Verweise wiederum versuchen einem Benutzer nicht zu erlauben, einen Verweis auf etwas zu initialisieren, auf das nicht verwiesen werden kann, da Sie ihm immer den richtigen Wert geben. Obwohl es viele Möglichkeiten gibt, die Referenzvariable an einem falschen Speicherort zu initialisieren, ist es besser, wenn Sie nicht so tief ins Detail gehen. Auf Maschinenebene arbeiten Zeiger und Referenz einheitlich - über Zeiger. Nehmen wir an, in wesentlichen Referenzen sei syntaktischer Zucker genannt. rvalue-Referenzen unterscheiden sich davon - sie sind natürlich Stack / Heap-Objekte.Obwohl es viele Möglichkeiten gibt, die Referenzvariable an einem falschen Speicherort zu initialisieren, ist es besser, wenn Sie nicht so tief ins Detail gehen. Auf Maschinenebene arbeiten Zeiger und Referenz einheitlich - über Zeiger. Nehmen wir an, in wesentlichen Referenzen sei syntaktischer Zucker genannt. rvalue-Referenzen unterscheiden sich davon - sie sind natürlich Stack / Heap-Objekte.Obwohl es viele Möglichkeiten gibt, die Referenzvariable an einem falschen Speicherort zu initialisieren, ist es besser, wenn Sie nicht so tief ins Detail gehen. Auf Maschinenebene arbeiten Zeiger und Referenz einheitlich - über Zeiger. Nehmen wir an, in wesentlichen Referenzen sei syntaktischer Zucker genannt. rvalue-Referenzen unterscheiden sich davon - sie sind natürlich Stack / Heap-Objekte.


Dies basiert auf dem tutorial . Was geschrieben wird, macht es klarer:

>>> 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 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.)

Da wir uns auf fast jedes Zeiger-Tutorial beziehen können, ist ein Zeiger ein Objekt, das von der Zeigerarithmetik unterstützt wird, wodurch der Zeiger einem Array ähnelt.

Schauen Sie sich die folgende Aussage an:

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

alias_Tomkann als alias of a variable(anders mit typedef, was ist alias of a type) verstanden werden Tom. Es ist auch in Ordnung zu vergessen, dass die Terminologie einer solchen Aussage darin besteht, eine Referenz von zu erstellen Tom.


Ein Verweis auf einen Zeiger ist in C ++ möglich, das Umkehren ist jedoch nicht möglich, wenn ein Zeiger auf einen Verweis nicht möglich ist. Ein Verweis auf einen Zeiger bietet eine sauberere Syntax zum Ändern des Zeigers. Schau dir dieses Beispiel an:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

Und betrachten Sie die C-Version des obigen Programms. In C müssen Sie Zeiger auf Zeiger verwenden (multiple Indirektion). Dies führt zu Verwirrung und das Programm kann kompliziert aussehen.

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

Besuchen Sie die folgenden Informationen, um weitere Informationen zum Verweis auf den Zeiger zu erhalten:

Wie gesagt, ein Zeiger auf einen Verweis ist nicht möglich. Versuchen Sie folgendes Programm:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}

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. 

Eine Referenz kann niemals sein NULL.


Es gibt einen grundlegenden Unterschied zwischen Zeigern und Referenzen, den ich nicht gesehen habe, der erwähnt wurde: Referenzen ermöglichen die Referenz-Semantik in Funktionsargumenten. Zeiger, auch wenn sie auf den ersten Blick nicht sichtbar sind, tun dies nicht: Sie stellen nur eine Wert-Semantik bereit. Dies wurde in diesem Artikel sehr gut beschrieben .

Grüße & rzej


Sowohl Verweise als auch Zeiger können verwendet werden, um lokale Variablen einer Funktion innerhalb einer anderen Funktion zu ändern. Beide können auch verwendet werden, um das Kopieren großer Objekte zu speichern, wenn sie als Argumente an Funktionen übergeben oder von Funktionen zurückgegeben werden, um die Effizienz zu steigern. Trotz der oben genannten Ähnlichkeiten gibt es folgende Unterschiede zwischen Verweisen und Zeigern.

Referenzen sind weniger mächtig als Zeiger

1) Sobald eine Referenz erstellt wurde, kann sie später nicht auf ein anderes Objekt verweist. es kann nicht erneut eingesetzt werden. Dies geschieht oft mit Zeigern.

2) Referenzen können nicht NULL sein. Zeiger werden oft zu NULL gemacht, um anzuzeigen, dass sie auf keine gültigen Dinge verweisen.

3) Eine Referenz muss bei der Deklaration initialisiert werden. Bei Zeigern gibt es keine solche Einschränkung

Aufgrund der oben genannten Einschränkungen können Verweise in C ++ nicht zum Implementieren von Datenstrukturen wie Linked List, Tree usw. verwendet werden. In Java haben Verweise keine obigen Einschränkungen und können zum Implementieren aller Datenstrukturen verwendet werden. Da die Referenzen in Java leistungsfähiger sind, benötigt Java keine Zeiger.

Referenzen sind sicherer und einfacher zu bedienen:

1) Sicherer: Da Referenzen initialisiert werden müssen, ist es unwahrscheinlich, dass wilde Referenzen wie wilde Zeiger existieren. Es ist immer noch möglich, Referenzen zu haben, die nicht auf einen gültigen Ort verweisen

2) Einfacher zu verwenden: Referenzen benötigen keinen Dereferenzierungsoperator, um auf den Wert zuzugreifen. Sie können wie normale Variablen verwendet werden. Der Operator "&" wird nur zum Zeitpunkt der Deklaration benötigt. Auf Mitglieder einer Objektreferenz kann auch mit Punktoperator ('.') Zugegriffen werden, im Gegensatz zu Zeigern, bei denen der Pfeiloperator (->) für den Zugriff auf Mitglieder erforderlich ist.

Zusammen mit den oben genannten Gründen gibt es nur wenige Stellen wie das Kopierkonstruktor-Argument, an dem der Zeiger nicht verwendet werden kann. Referenz muss verwendet werden, übergeben Sie das Argument im Kopierkonstruktor. Ebenso müssen Referenzen zum Überladen einiger Operatoren wie ++ verwendet werden .


Vielleicht helfen einige Metaphern; Im Kontext Ihres Desktop-Screenspaces -

  • Für eine Referenz müssen Sie ein aktuelles Fenster angeben.
  • Ein Zeiger erfordert die Position eines Platzes auf dem Bildschirm, der sicherstellt, dass er keine oder mehr Instanzen dieses Fenstertyps enthält.




c++-faq