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


Answers

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

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

Alle Referenzen müssen mit einem Nicht-Nullwert initialisiert werden oder die Kompilierung wird fehlschlagen. Es ist weder möglich, die Adresse eines Verweises zu erhalten - der Adressbetreiber gibt stattdessen die Adresse des referenzierten Wertes zurück - noch ist es möglich, Arithmetik auf Referenzen zu machen.

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

C ++ Programmierer mögen es nicht, Zeiger zu verwenden, da sie als unsicher gelten - obwohl Referenzen nicht wirklich sicherer sind als Konstante Zeiger, außer in den trivialsten Fällen - fehlt die Bequemlichkeit der automatischen Indirektion und trägt eine andere semantische Konnotation.

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

Obwohl eine Referenz oft mit einer Adresse in der zugrunde liegenden Assembler-Sprache implementiert wird, denke ich nicht an einen Hinweis als einen lustigen Blickzeiger auf ein Objekt. Eine Referenz ist das Objekt. Es ist kein Zeiger auf das Objekt, noch eine Kopie des Objekts. Es ist das Objekt.

Aber wenn eine Referenz wirklich das Objekt war, wie könnte es baumelnde Referenzen geben? In nicht verwalteten Sprachen ist es unmöglich, dass Referenzen irgendwelche "sicherer" als Zeiger sind - es gibt im Allgemeinen einfach nicht einen Weg, um zuverlässig Alias-Werte über Geltungsbereich Grenzen!

Warum betrachte ich C ++ - Referenzen nützlich

Von einem C-Hintergrund aus zu kommen, können C ++ - Referenzen wie ein etwas dummes Konzept aussehen, aber man sollte sie dennoch anstelle von Zeigern verwenden, wo es möglich ist: Automatische Indirektion ist bequem, und Referenzen werden besonders nützlich, wenn es um RAII geht - aber nicht wegen der wahrgenommenen Sicherheit Vorteil, sondern weil sie das Schreiben von idiomatischem Code weniger umständlich machen.

RAII ist eines der zentralen Konzepte von C ++, aber es interagiert nicht-trivial mit Kopier-Semantik. Das Übergeben von Objekten durch Verweis vermeidet diese Probleme, da kein Kopieren beteiligt ist. Wenn Referenzen nicht in der Sprache vorhanden waren, müssten Sie stattdessen Zeiger verwenden, die umständlicher zu bedienen sind und damit das Sprachdesignprinzip verletzen, dass die Best-Practice-Lösung einfacher sein sollte als die Alternativen.

Question

Ich weiß, Referenzen sind syntaktischen Zucker, so Code ist einfacher zu lesen und zu schreiben.

Aber was sind die Unterschiede?

Zusammenfassung aus Antworten und Links unten:

  1. Ein Zeiger kann beliebig oft neu zugeordnet werden, während eine Referenz nach der Bindung nicht wieder gesetzt werden kann.
  2. Zeiger können nirgendwo ( NULL ) zeigen, während Referenz immer auf ein Objekt verweist.
  3. Sie können nicht die Adresse einer Referenz wie Sie können mit Zeiger.
  4. Es gibt keine "Referenz-Arithmetik" (aber Sie können die Adresse eines Objekts mit einer Referenz und Zeiger-Arithmetik darauf als in &obj + 5 ).

Um ein Missverständnis zu klären:

Der C ++ - Standard ist sehr vorsichtig, um zu vermeiden, dass ein Compiler Referenzen implementieren muss, aber jeder C ++ - Compiler implementiert Referenzen als Zeiger. Das heißt, eine Deklaration wie:

int &ri = i;

Wenn es nicht vollständig weg optimiert ist , ordnet die gleiche Menge an Speicher wie ein Zeiger, und platziert die Adresse von i in diesen Speicher.

Also, ein Zeiger und eine Referenz belegen beide die gleiche Menge an Speicher.

Generell,

  • Verwenden Sie Referenzen in Funktionsparametern und Rückgabetypen, um nützliche und selbstdokumentierende Schnittstellen zu definieren.
  • Verwenden Sie Zeiger, um Algorithmen und Datenstrukturen zu implementieren.

Interessante lese:




Sie haben den wichtigsten Teil vergessen:

Mitgliedszugriff mit Zeigern verwendet ->
Mitgliedszugriff mit Referenzen verwendet .

foo.bar ist deutlich besser als foo->bar in der gleichen Weise, dass vi deutlich überlegen ist Emacs :-)




Abgesehen von syntaktischen Zucker ist eine Referenz ein const-Zeiger (nicht Zeiger auf eine const-Ding, ein const-Zeiger). Sie müssen festlegen, worauf es sich bezieht, wenn Sie die Referenzvariable deklarieren, und Sie können es später nicht ändern.




Referenzen sind sehr ähnlich zu Zeigern, aber sie sind speziell gefertigt, um hilfreich zu sein, um Compiler zu optimieren.

  • Referenzen sind so konzipiert, dass es für den Compiler wesentlich einfacher ist, zu verfolgen, welche Referenzaliasnamen welche Variablen sind. Zwei Hauptmerkmale sind sehr wichtig: keine "Referenzarithmetik" und keine Neuzuweisung von Referenzen. Diese erlauben dem Compiler, herauszufinden, welche Referenzen alias welche Variablen zum Zeitpunkt der Kompilierung sind.
  • Referenzen können sich auf Variablen beziehen, die keine Speicheradressen haben, wie z. B. die, die der Compiler in Register setzt. Wenn Sie die Adresse einer lokalen Variablen nehmen, ist es sehr schwer für den Compiler, es in ein Register zu setzen.

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 Optimierungs-Compiler kann erkennen, dass wir auf eine [0] und eine [1] ganz ein Bündel zugreifen. Es würde gerne den Algorithmus optimieren:

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
}

Um eine solche Optimierung zu machen, muss es beweisen, dass nichts während des Anrufs das Array [1] ändern kann. Das ist ziemlich einfach zu tun Ich bin nie weniger als 2, so dass Array [i] sich nie auf Array [1] beziehen kann. MaybeModify () erhält a0 als Referenz (aliasing array [0]). Weil es keine "Referenz" Arithmetik gibt, muss der Compiler nur beweisen, dass mayModify nie die Adresse von x bekommt, und es hat bewiesen, dass nichts Array verändert [1].

Es muss auch beweisen, dass es keine Möglichkeiten gibt, wie ein zukünftiger Anruf ein [0] lesen / schreiben könnte, während wir eine temporäre Registerkopie davon in a0 haben. Dies ist oft trivial zu beweisen, denn in vielen Fällen ist es offensichtlich, dass die Referenz niemals in einer permanenten Struktur wie eine Klasseninstanz gespeichert wird.

Jetzt mach das gleiche 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 das gleiche; Erst jetzt ist es viel schwieriger zu beweisen, dass mayModify nicht immer Array ändern [1], weil wir es schon einen Zeiger gegeben haben; Die Katze ist aus der Tasche. Jetzt muss es den viel schwierigeren Beweis machen: eine statische Analyse von maybeModify, um zu beweisen, dass es niemals auf & x + 1 schreibt. Es muss auch beweisen, dass es niemals einen Zeiger abnimmt, der sich auf Array [0] beziehen kann, was gerade ist So knifflig

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

Natürlich, ohne solche cleveren Optimierungen zu beenden, werden Compiler in der Tat Referenzen in Zeiger, wenn nötig.




Ein Hinweis kann niemals NULL .




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

    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. 



Es spielt keine Rolle, wie viel Platz es in Anspruch nimmt, da man eigentlich keine Nebenwirkung sehen (ohne die Ausführung von Code) von welchem ​​Raum es bis nehmen würde.

Auf der anderen Seite ist ein großer Unterschied zwischen Referenzen und Zeiger, die auf const Referenzen zugewiesen Provisorien leben, bis die konstante Referenz den Gültigkeitsbereich verlässt.

Beispielsweise:

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

...

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

druckt:

in scope
scope_test done!

Dies ist die Sprache Mechanismus, der Scopeguard erlaubt zu arbeiten.




Es gibt einen fundamentalen Unterschied zwischen Zeigern und Referenzen , die ich nicht , dass jemand sah erwähnt hatte: Referenzen pass-by-reference Semantik in Funktionsargumenten ermöglichen. Pointers, obwohl es nicht auf den ersten ist nicht: sie bieten nur Pass-by-value - Semantik. Dies wurde sehr schön beschrieben in diesem Artikel .

Grüße, & rzej




Ich benutze Referenzen, wenn ich eine dieser Voraussetzungen:

  • Null-Zeiger kann als Sentinel-Wert verwendet werden, oft eine billige Art und Weise Funktion Überlastung oder die Verwendung eines Bool zu vermeiden.

  • Sie können auf einem Pointer-Arithmetik tun. Beispielsweise,p += offset;




Dieses Programm könnte in Begreifen die Antwort der Frage helfen. Dies ist ein einfaches Programm einer Referenz „j“ und ein Zeiger „ptr“ zeigt auf Variable „x“.

#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();
}

Führen Sie das Programm und haben einen Blick auf die Ausgabe und Sie werden verstehen.

Außerdem ersparen 10 Minuten und schauen Sie sich dieses Video: https://www.youtube.com/watch?v=rlJrrGV0iOg




Beide Referenzen und Zeiger können in einer anderen Funktion lokale Variablen einer Funktion zu ändern, verwendet werden. Beide können auch das Kopieren von großen Objekten verwendet werden, zu speichern, wenn sie als Argumente an Funktionen übergeben oder von Funktionen zurückgegeben werden, um Effizienzgewinn zu erhalten. Trotz über Ähnlichkeiten gibt es folgende Unterschiede zwischen Referenzen und Zeigern.

Referenzen sind weniger leistungsfähig als Zeiger

1) Nachdem eine Referenz erstellt wird, ist es nicht später gemacht werden kann ein anderes Objekt zu referenzieren; es kann nicht neu eingesetzt werden. Dies ist oft mit Zeigern getan.

2) Referenzen können nicht NULL sein. Zeiger werden oft NULL gemacht, um anzuzeigen, dass sie nicht auf eine gültige Sache zeigen.

3) Eine Referenz muss initialisiert werden, wenn erklärt. Es gibt keine solche Beschränkung mit Zeigern

Aufgrund der oben genannten Einschränkungen, Referenzen in C ++ können nicht für die Implementierung Datenstrukturen wie Linked List, Baum, usw. In Java, Referenzen, nicht über Einschränkungen verwendet werden und können verwendet werden, alle Datenstrukturen zu implementieren. Referenzen in Java leistungsstärker zu sein, ist der Hauptgrund, Java keine Zeiger benötigt.

Referenzen sind sicherer und einfacher zu bedienen:

1) Sicherer: Da Referenzen müssen initialisiert werden, wild Referenzen wie wilde Pointer sind unwahrscheinlich, existieren. Es ist immer noch möglich, Referenzen zu haben, die auf eine gültige Position beziehen sich nicht

2) Einfacher zu verwenden: Referenzen müssen nicht Dereferenzierungsoperator den Wert zuzugreifen. Sie können wie normale Variablen verwendet werden. ‚&‘ Operator wird nur zum Zeitpunkt der Erklärung benötigt. Auch können die Mitglieder einer Objektreferenz mit Punkt-Operator zugegriffen werden ( ‚‘), im Gegensatz zu Zeigern wo Pfeil-Operator (->), um Zugang Mitglieder erforderlich ist.

Zusammen mit den oben genannten Gründen gibt es nur wenige Orte wie Copykonstruktor Argument wo Zeiger nicht verwendet werden können. Referenz verwendet werden muss , das Argument in Kopie Konstruktor übergeben. Ebenso müssen Referenzen für Überlastung einige Operatoren wie ++ verwendet werden .




Auch ist eine Referenz, die ein Parameter ist eine Funktion, die inlined gehandhabt werden kann, anders als ein Zeiger.

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, wenn der Zeiger Version inlining wird eine Kraft tatsächlich ein Schreiben in Speicher (wir die Adresse explizit nehmen). Allerdings werden sie den Verweis in einem Register lassen, die mehr optimal ist.

Natürlich, für Funktionen, die die Zeiger und Referenz nicht inlined werden generieren den gleichen Code und es ist immer besser intrinsics als durch Bezugnahme von Wert zu übergeben, wenn sie durch die Funktion nicht geändert werden und zurückgegeben.




Eine weitere interessante Anwendung von Referenzen ist ein Standardargument eines benutzerdefinierten Typs zu liefern:

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

Der Standardaroma verwendet die ‚bind const Referenz auf einen temporären‘ Aspekt der Referenzen.




Differenz zwischen Zeiger und Referenz

Ein Zeiger kann auf 0 gesetzt und eine Bezug nicht initialisiert werden. In der Tat muss ein Verweis auch auf ein Objekt beziehen, sondern ein Zeiger kann der Null-Zeiger sein:

int* p = 0;

Aber wir können nicht haben int& p = 0;und auchint& p=5 ; .

In der Tat, es richtig zu tun, müssen wir bei der ersten deklariert und definiert ein Objekt haben, dann können wir eine Referenz auf das Objekt machen, so dass die korrekte Umsetzung des vorherigen Code wird sein:

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

Ein weiterer wichtiger Punkt ist, dass ist, dass wir die Deklaration des Zeigers ohne Initialisierung machen kann jedoch nicht so etwas kann bei Bezugnahme erfolgen, die eine Referenz immer auf veränderliche oder Objekt vornehmen müssen. Jedoch eine solche Verwendung eines Zeigers riskant ist so allgemein überprüfen wir, ob der Zeiger tatsächlich auf etwas oder nicht zeigt. Im Fall einer Referenz keine solche Überprüfung ist notwendig, weil wir bereits wissen, dass bei der Deklaration auf ein Objekt verweist zwingend vorgeschrieben ist.

Ein weiterer Unterschied besteht darin, dass Zeiger auf ein anderes Objekt verweisen können jedoch Bezug immer auf das gleiche Objekt verweisen, lassen Sie sich dieses Beispiel nehmen:

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 ein STL-Vorlage eine solche Art einer Klasse-Vorlage haben immer einen Verweis zurückgeben, keinen Zeiger, eine einfache Lesung zu machen oder neue Wert zuweisen mit dem Operator []:

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



Ein Bezugspunkt an den Ort , wo das Objekt ist jetzt . In Anwendungen , die verwalteten Speicher verwenden kann dies nicht sein , wenn Sie es das letzte Mal sah.

So zum Beispiel .NET CLR verwenden Sie könnten ein Objekt erstellen (Referenz) auf kurzfristig Haufen, und wenn Sie die nächste aussehen es im physischen Speicher auf den langfristigen Heap verschoben wird. Sie werden sehen, dies nicht passieren, da es vom Garbage Collector auf einem separaten Thread, und werden in der Regel stattfinden zwischen einer Anwendung Taktzyklus und den nächsten getan hat. Sie können diese Adresse nicht bekommen, weil es nicht als gültig für den Einsatz bei jeder zukünftigen Zeit guarenteed wird, und wie an anderer Stelle darauf hingewiesen, es für eine ganze Zahl oder Zeiger nicht einmal in einem Register gehalten existieren.