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





rückgabewert als (25)


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.

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:




Während sowohl Referenzen als auch Zeiger für den indirekten Zugriff auf einen anderen Wert verwendet werden, gibt es zwei wichtige Unterschiede zwischen Referenzen und Zeigern. Der erste ist, dass eine Referenz immer auf ein Objekt verweist: Es ist ein Fehler, eine Referenz zu definieren, ohne sie zu initialisieren. Das Zuweisungsverhalten ist der zweite wichtige Unterschied: Durch das Zuweisen zu einer Referenz wird das Objekt geändert, an das die Referenz gebunden ist. Der Verweis auf ein anderes Objekt wird nicht erneut gebunden. Nach der Initialisierung bezieht sich eine Referenz immer auf das gleiche 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 von pi adressierte Objekt unverändert. Durch die Zuweisung wird der Wert von pi geändert, sodass er auf ein anderes Objekt verweist. Betrachten Sie nun ein ähnliches Programm, das zwei Referenzen zuweist:

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

Diese Zuweisung ändert sich in ival, den von ri referenzierten Wert, und nicht die Referenz selbst. Nach der Zuweisung beziehen sich die beiden Verweise immer noch auf ihre ursprünglichen Objekte, und der Wert dieser Objekte ist jetzt ebenfalls gleich.




  1. Ein Zeiger kann neu zugewiesen werden:

    int x = 5;
    int y = 6;
    int *p;
    p =  &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);
    

    Eine Referenz kann und muss bei der Initialisierung nicht vergeben werden:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Ein Zeiger hat eine eigene Speicheradresse und -größe auf dem Stapel (4 Bytes bei x86), während eine Referenz die gleiche Speicheradresse (mit der ursprünglichen Variablen) hat, aber auch etwas Platz auf dem Stapel beansprucht. Da eine Referenz dieselbe Adresse wie die ursprüngliche Variable selbst hat, kann eine Referenz als anderer Name für dieselbe Variable betrachtet werden. Anmerkung: Was ein Zeiger darauf zeigt, kann auf dem Stapel oder Heap liegen. Dito eine Referenz. Mein Anspruch in dieser Anweisung ist nicht, dass ein Zeiger auf den Stapel zeigen muss. Ein Zeiger ist nur eine Variable, die eine Speicheradresse enthält. Diese Variable befindet sich auf dem Stapel. Da eine Referenz auf dem Stack einen eigenen Speicherplatz hat und die Adresse der Variablen entspricht, auf die sie verweist. Mehr über Stack vs Heap . Dies impliziert, dass es eine reale Adresse einer Referenz gibt, die der Compiler Ihnen nicht mitteilen wird.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. Sie können Zeiger auf Zeiger auf Zeiger haben, die zusätzliche Indirektionsniveaus bieten. Referenzen bieten jedoch nur eine Ebene der Indirektion.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
    
  4. Zeiger kann direkt mit nullptr zugewiesen werden, Verweise jedoch nicht. Wenn Sie sich stark genug bemühen und wissen, wie, können Sie die Adresse eines Referenz- nullptr . Wenn Sie sich bemühen, können Sie einen Verweis auf einen Zeiger haben, der dann nullptr enthalten nullptr .

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *p;  <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
    
  5. Zeiger können über ein Array iterieren. Mit ++ Sie zum nächsten Element wechseln, auf das ein Zeiger zeigt. Mit + 4 Sie zum fünften Element. Dies ist unabhängig von der Größe des Objekts, auf das der Zeiger zeigt.

  6. Ein Zeiger muss mit * dereferenziert werden, um auf den Speicherplatz zuzugreifen, auf den er verweist, während ein Verweis direkt verwendet werden kann. Ein Zeiger auf eine Klasse / Struktur verwendet -> um auf seine Mitglieder zuzugreifen, während ein Verweis ein . .

  7. Ein Zeiger ist eine Variable, die eine Speicheradresse enthält. Unabhängig davon, wie eine Referenz implementiert wird, hat eine Referenz dieselbe Speicheradresse wie das referenzierte Element.

  8. Referenzen können nicht in ein Array eingefügt werden, wohingegen Zeiger (von Benutzer @litb erwähnt) sein können.

  9. Const-Referenzen können an Provisorien gebunden werden. Zeiger können nicht (nicht ohne Umleitung):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.
    

    Dies macht const& sicherer für die Verwendung in Argumentlisten und so weiter.




Eine Referenz kann niemals sein NULL.




Wenn Sie wirklich pedantisch sein möchten, können Sie mit einem Verweis, den Sie nicht mit einem Zeiger machen können, eines tun: die Lebensdauer eines temporären Objekts verlängern. Wenn Sie in C ++ eine const-Referenz an ein temporäres Objekt binden, wird die Lebensdauer dieses Objekts zur Lebensdauer der Referenz.

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

In diesem Beispiel kopiert s3_copy das temporäre Objekt, das ein Ergebnis der Verkettung ist. Während s3_reference im Wesentlichen zum temporären Objekt wird. Es ist wirklich ein Verweis auf ein temporäres Objekt, das jetzt dieselbe Lebensdauer wie der Verweis hat.

Wenn Sie dies ohne const versuchen, sollte es nicht kompiliert werden. Sie können einen Nicht-Konstanten-Verweis nicht an ein temporäres Objekt binden und auch nicht dessen Adresse übernehmen.




Eine Referenz ist kein anderer Name, der einem Speicher zugewiesen wird. Es ist ein unveränderlicher Zeiger, der bei Verwendung automatisch abgemeldet wird. Im Grunde läuft es darauf hinaus:

int& j = i;

Es wird intern

int* const j = &i;



Im Gegensatz zur allgemeinen Meinung ist es möglich, eine Referenz zu haben, die NULL ist.

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

Zugegeben, es ist viel schwieriger, mit einer Referenz zu arbeiten - aber wenn Sie es schaffen, reißen Sie sich die Haare aus und versuchen, sie zu finden. Referenzen sind in C ++ nicht unbedingt sicher!

Technisch ist dies eine ungültige Referenz , keine Nullreferenz. C ++ unterstützt keine Nullreferenzen als Konzept, wie Sie es vielleicht in anderen Sprachen finden. Es gibt auch andere Arten ungültiger Verweise. Jede ungültige Referenz wirft das Spektrum undefinierten Verhaltens auf , genau wie bei einem ungültigen Zeiger.

Der tatsächliche Fehler liegt in der Dereferenzierung des NULL-Zeigers vor der Zuweisung zu einer Referenz. Mir sind jedoch keine Compiler bekannt, die unter dieser Bedingung Fehler erzeugen - der Fehler breitet sich im Code weiter aus. Das macht dieses Problem so heimtückisch. Wenn Sie einen NULL-Zeiger dereferenzieren, stürzen Sie meistens an dieser Stelle ab, und es ist nicht viel Debugging erforderlich, um dies herauszufinden.

Mein Beispiel oben ist kurz und verständlich. Hier ist ein realistischeres Beispiel.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

Ich möchte noch einmal darauf hinweisen, dass der einzige Weg, eine Nullreferenz zu erhalten, durch missgebildeten Code besteht, und wenn Sie ihn haben, erhalten Sie undefiniertes Verhalten. Es ist nie sinnvoll, nach einer Nullreferenz zu suchen. Sie können beispielsweise if(&bar==NULL)... versuchen if(&bar==NULL)... aber der Compiler optimiert möglicherweise die Anweisung nicht mehr! Eine gültige Referenz kann niemals NULL sein, daher ist der Vergleich aus Sicht des Compilers immer falsch, und es ist frei, die if Klausel als toten Code zu löschen. Dies ist der Kern von undefiniertem Verhalten.

Der richtige Weg, um Probleme zu vermeiden, besteht darin, zu vermeiden, dass ein NULL-Zeiger dereferenziert wird, um eine Referenz zu erstellen. Hier ist ein automatisierter Weg, dies zu erreichen.

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

Einen älteren Blick auf dieses Problem von jemandem mit besseren Schreibfähigkeiten finden Sie unter Null-Referenzen von Jim Hyslop und Herb Sutter.

Ein anderes Beispiel für die Gefahren der Dereferenzierung eines Nullzeigers finden Sie unter Offenlegen von undefiniertem Verhalten beim Versuch, Code von Raymond Chen auf eine andere Plattform zu portieren .




Ein weiterer Unterschied ist, dass Sie Zeiger auf einen ungültigen Typ haben können (und zwar Zeiger auf irgendetwas), aber Verweise auf ungültig sind verboten.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

Ich kann nicht sagen, dass ich mit diesem besonderen Unterschied wirklich glücklich bin. Ich würde es sehr vorziehen, wenn die Bedeutung der Referenz auf alles mit einer Adresse und sonst das gleiche Verhalten für Referenzen erlaubt wäre. Damit könnten einige Äquivalente von C-Bibliotheksfunktionen wie memcpy unter Verwendung von Referenzen definiert werden.




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.




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 gibt einen semantischen Unterschied, der esoterisch erscheinen kann, wenn Sie nicht mit dem Erlernen von Computersprachen auf abstrakte oder gar akademische Weise vertraut sind.

Auf höchster Ebene besteht die Idee der Referenzen darin, dass es sich um transparente "Aliase" handelt. Ihr Computer verwendet möglicherweise eine Adresse, um sie zum Laufen zu bringen, aber Sie sollten sich keine Sorgen darüber machen: Sie sollten sie als "nur einen anderen Namen" für ein vorhandenes Objekt betrachten. Sie sind strenger als Zeiger, so dass Ihr Compiler Sie zuverlässiger warnen kann, wenn Sie eine baumelnde Referenz erstellen, als wenn Sie einen baumelnden Zeiger erstellen.

Darüber hinaus gibt es natürlich einige praktische Unterschiede zwischen Zeigern und Referenzen. Die zu verwendende Syntax ist offensichtlich unterschiedlich, und Sie können Verweise nicht "neu setzen", Verweise auf nichts oder Zeiger auf Verweise.




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 verwende Referenzen, es sei denn, ich brauche eine dieser

  • Nullzeiger können als Sentinel-Wert verwendet werden. Dies ist häufig eine kostengünstige Möglichkeit, Funktionsüberlastung oder die Verwendung eines Bool zu vermeiden.

  • Sie können Arithmetik mit einem Zeiger ausführen. Zum Beispiel,p += offset;




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




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. 



Ich entscheide immer nach this Regel aus den C ++ Core Guidelines:

T * gegenüber T vorziehen, wenn "kein Argument" eine gültige Option ist




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;
}



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




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.



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 "="



Dieses Programm kann helfen, die Antwort auf die Frage zu verstehen. Dies ist ein einfaches Programm mit einer Referenz "j" und einem Zeiger "ptr", der auf die Variable "x" zeigt.

#include<iostream>

using namespace std;

int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"

cout << "x=" << x << endl;

cout << "&x=" << &x << endl;

cout << "j=" << j << endl;

cout << "&j=" << &j << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr=" << ptr << endl;

cout << "&ptr=" << &ptr << endl;
    getch();
}

Starten Sie das Programm und sehen Sie sich die Ausgabe an. Sie werden es verstehen.

Nehmen Sie sich auch 10 Minuten Zeit und schauen Sie sich dieses Video an: https://www.youtube.com/watch?v=rlJrrGV0iOg




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.




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.




Microsoft bietet ein #pragma an, um die richtige Bibliothek zu Verknüpfungszeit zu verweisen;

#pragma comment(lib, "libname.lib")

Neben dem Bibliothekspfad einschließlich des Verzeichnisses der Bibliothek sollte dies der vollständige Name der Bibliothek sein.





c++ pointers reference c++-faq