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.

2228voto

Brian R. Bondy Punkte 325712
  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);

    Ein Verweis kann nicht neu gebunden werden, sondern muss bei der Initialisierung gebunden werden:

    int x = 5;
    int y = 6;
    int &q; // error
    int &r = x;
  2. Eine Zeigervariable hat ihre eigene Identität: eine eindeutige, sichtbare Speicheradresse, die mit dem unären & Operator und eine bestimmte Menge an Raum, die mit dem sizeof Betreiber. Die Anwendung dieser Operatoren auf einen Verweis gibt einen Wert zurück, der dem entspricht, an den der Verweis gebunden ist; die eigene Adresse und Größe des Verweises sind unsichtbar. Da die Referenz auf diese Weise die Identität der Originalvariablen annimmt, ist es praktisch, sich eine Referenz als einen anderen Namen für dieselbe Variable vorzustellen.

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

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    
    **pp = 2;
    pp = &q; // *pp is now q
    **pp = 4;
    
    assert(y == 4);
    assert(x == 2);
  4. Ein Zeiger kann zugewiesen werden nullptr während eine Referenz an ein bestehendes Objekt gebunden sein muss. Wenn Sie sich genug Mühe geben, können Sie eine Referenz an nullptr aber das ist undefiniert und wird sich nicht konsequent verhalten.

    /* the code below is undefined; your compiler may optimise it
     * differently, emit warnings, or outright refuse to compile it */
    
    int &r = *static_cast<int *>(nullptr);
    
    // prints "null" under GCC 10
    std::cout
        << (&r != nullptr
            ? "not null" : "null")
        << std::endl;
    
    bool f(int &r) { return &r != nullptr; }
    
    // prints "not null" under GCC 10
    std::cout
        << (f(*static_cast<int *>(nullptr))
            ? "not null" : "null")
        << std::endl;

    Sie können jedoch einen Verweis auf einen Zeiger haben, dessen Wert nullptr .

  5. Zeiger können über ein Array iterieren; Sie können mit ++ um zum nächsten Element zu gelangen, auf das ein Zeiger zeigt, und + 4 um zum 5. Element zu gelangen. Dabei spielt es keine Rolle, wie groß das Objekt ist, auf das der Zeiger zeigt.

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

  7. Referenzen können nicht in ein Array eingefügt werden, Zeiger hingegen schon (Erwähnt vom Benutzer @litb)

  8. Const-Referenzen können an Provisorien gebunden werden. Zeiger können das nicht (nicht ohne eine gewisse Umleitung):

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

    Dies macht const & bequemer in Argumentlisten usw. zu verwenden.

3 Stimmen

Referenz kann null sein. In manchen Fällen werden die Parameter für die Referenz wie folgt übergeben: function(*ptr); Wenn ptr NULL ist, ist es auch die Referenz.

34 Stimmen

...aber die Dereferenzierung von NULL ist undefiniert. Zum Beispiel kann man nicht testen, ob eine Referenz NULL ist (z.B. &ref == NULL).

1 Stimmen

IN VC++ stürzt *ptr ab, ziemlich sicher in gc++ wird es auch segfault

549voto

Christoph Punkte 157217

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

A Referenz kann man sich vorstellen als eine konstanter Zeiger (nicht zu verwechseln mit einem Zeiger auf einen konstanten Wert!) mit automatischer Umleitung, d.h. der Compiler wendet die * Betreiber für Sie.

Alle Verweise müssen mit einem Nicht-Null-Wert initialisiert werden, sonst schlägt die Kompilierung fehl. Es ist weder möglich, die Adresse eines Verweises zu erhalten - der Adressoperator gibt stattdessen die Adresse des referenzierten Wertes zurück - noch ist es möglich, mit Verweisen arithmetisch zu arbeiten.

C-Programmierer könnten C++-Referenzen nicht mögen, da es nicht mehr offensichtlich ist, wann eine Umleitung stattfindet oder ob ein Argument als Wert oder als Zeiger übergeben wird, ohne dass man sich die Funktionssignaturen ansehen muss.

C++-Programmierer mögen die Verwendung von Zeigern nicht, da sie als unsicher gelten - obwohl Referenzen außer in den trivialsten Fällen nicht wirklich sicherer sind als konstante Zeiger -, ihnen der Komfort der automatischen Umleitung fehlt und sie eine andere semantische Konnotation haben.

Betrachten Sie die folgende Aussage aus dem C++ FAQ :

Auch wenn eine Referenz oft durch eine Adresse in der zugrundeliegenden Assemblersprache implementiert wird, sollten no Betrachten Sie eine Referenz als lustig aussehenden Zeiger auf ein Objekt. Eine Referenz es das Objekt. Es ist weder ein Zeiger auf das Objekt, noch eine Kopie des Objekts. Es es die Objekt.

Aber wenn ein Verweis wirklich das Objekt war, wie konnte es dann zu "Dangling References" kommen? In nicht verwalteten Sprachen ist es unmöglich, dass Referenzen "sicherer" sind als Zeiger - es gibt im Allgemeinen einfach keine Möglichkeit, Werte über Bereichsgrenzen hinweg zuverlässig zu aliasieren!

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

Wenn man von einem C-Hintergrund kommt, mögen C++-Referenzen wie ein etwas albernes Konzept aussehen, aber man sollte sie trotzdem, wo immer möglich, anstelle von Zeigern verwenden: Automatische Umleitung es und Referenzen sind besonders nützlich, wenn es um RAII - aber nicht wegen eines vermeintlichen Sicherheitsvorteils, sondern eher, 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 der Kopiersemantik. Die Übergabe von Objekten per Referenz vermeidet diese Probleme, da kein Kopieren erforderlich ist. Gäbe es keine Referenzen in der Sprache, müsste man stattdessen Zeiger verwenden, die umständlicher zu handhaben sind, was gegen den Grundsatz des Sprachdesigns verstößt, dass die beste Lösung einfacher sein sollte als die Alternativen.

1 Stimmen

@Christoph: Nun, Referenzen können nur baumeln, wenn man sie irgendwo durch Dereferenzierung eines Zeigers erhalten hat.

23 Stimmen

@kriss: Nein, man kann auch eine baumelnde Referenz erhalten, indem man eine automatische Variable per Referenz zurückgibt.

2 Stimmen

@Ben Voight: OK, aber das ist wirklich ein Grund für Probleme. Einige Compiler geben sogar Warnungen aus, wenn Sie das tun.

232voto

Matt Price Punkte 41298

Wenn Sie wirklich pedantisch sein wollen, gibt es eine Sache, die Sie mit einer Referenz tun können, die Sie mit einem Zeiger nicht tun können: 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 das temporäre Objekt wird. Es ist eigentlich ein Verweis auf ein temporäres Objekt, das nun die gleiche Lebensdauer wie der Verweis hat.

Wenn Sie dies ohne die const sollte es sich nicht kompilieren lassen. Sie können keine nicht-konstante Referenz an ein temporäres Objekt binden, noch können Sie seine Adresse nehmen, was das betrifft.

8 Stimmen

Aber was ist der Anwendungsfall dafür?

26 Stimmen

Nun, s3_copy erstellt ein temporäres Verzeichnis und kopiert es dann in s3_copy, während s3_reference direkt das temporäre Verzeichnis verwendet. Um wirklich pedantisch zu sein, müssen Sie sich die Rückgabewert-Optimierung ansehen, die es dem Compiler erlaubt, den Kopieraufbau im ersten Fall zu umgehen.

7 Stimmen

@digitalSurgeon: Die Magie dort ist ziemlich mächtig. Die Lebensdauer des Objekts wird durch die Tatsache verlängert, dass die const & Bindung, und erst wenn der Verweis den Anwendungsbereich verlässt, wird der Destruktor der aktuell referenzierter Typ (im Vergleich zum Referenztyp, der eine Basis sein kann) aufgerufen wird. Da es sich um eine Referenz handelt, findet dazwischen kein Slicing statt.

177voto

Abgesehen vom syntaktischen Zucker ist eine Referenz ein const Zeiger ( no Zeiger auf eine const ). Sie müssen bei der Deklaration der Referenzvariablen festlegen, worauf sie sich bezieht, und können sie später nicht mehr ändern.

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

Das Ziel eines const-Zeigers kann ersetzt werden, indem man seine Adresse nimmt und einen const-Cast verwendet.

Das Ziel eines Verweises kann auf keine andere Weise als durch UB ersetzt werden.

Dies sollte es dem Compiler ermöglichen, mehr Optimierungen an einer Referenz vorzunehmen.

17 Stimmen

Ich denke, das ist bei weitem die beste Antwort. Andere reden über Verweise und Zeiger, als wären sie unterschiedliche Tiere und legen dann dar, wie sie sich im Verhalten unterscheiden. Das macht die Sache imho nicht einfacher. Ich habe Referenzen immer so verstanden, dass sie ein T* const mit anderem syntaktischen Zucker (der zufällig eine Menge * und & aus Ihrem Code entfernt).

10 Stimmen

"Das Ziel eines const-Zeigers kann ersetzt werden, indem man seine Adresse nimmt und einen const-Cast verwendet." Dies ist ein undefiniertes Verhalten. Siehe stackoverflow.com/questions/25209838/ für Details.

2 Stimmen

Der Versuch, entweder den Referenten eines Verweises oder den Wert eines const-Zeigers (oder eines const-Skalars) zu ändern, ist ebenso illegal. Was Sie tun können: Entfernen Sie eine Konst-Qualifikation, die durch implizite Konvertierung hinzugefügt wurde: int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci); ist OK.

139voto

Mark Ransom Punkte 283960

Entgegen der landläufigen 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, das ist mit einer Referenz viel schwieriger - aber wenn man es schafft, wird man sich die Haare raufen, um sie zu finden. Referenzen sind no inhärent sicher in C++!

Technisch gesehen ist dies eine ungültige Referenz und nicht eine Null-Referenz. C++ unterstützt das Konzept der Nullreferenz nicht, wie es in anderen Sprachen der Fall ist. Es gibt auch andere Arten von ungültigen Referenzen. Jede ungültiger Verweis wirft die Frage auf undefiniertes Verhalten genauso wie die Verwendung eines ungültigen Zeigers.

Der eigentliche Fehler liegt in der Dereferenzierung des NULL-Zeigers vor der Zuweisung zu einer Referenz. Mir ist jedoch kein Compiler bekannt, der unter dieser Bedingung Fehler erzeugt - der Fehler breitet sich an einer Stelle weiter im Code aus. Genau das macht dieses Problem so heimtückisch. Wenn man einen NULL-Zeiger dereferenziert, stürzt man meistens genau an dieser Stelle ab, und es ist nicht viel Debugging nötig, um das herauszufinden.

Mein obiges Beispiel ist kurz und ausgedacht. 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 wiederholen, dass die einzige Möglichkeit, eine Null-Referenz zu erhalten, durch fehlerhaften Code ist, und sobald Sie es haben, erhalten Sie undefiniertes Verhalten. Es niemals ist es sinnvoll, auf eine Null-Referenz zu prüfen; zum Beispiel können Sie versuchen if(&bar==NULL)... aber der Compiler könnte die Anweisung so optimieren, dass sie nicht mehr existiert! Eine gültige Referenz kann niemals NULL sein, so dass der Vergleich aus Sicht des Compilers immer falsch ist, und es steht ihm frei, die if Klausel als toten Code zu bezeichnen - das ist das Wesen von undefiniertem Verhalten.

Der richtige Weg, um Probleme zu vermeiden, besteht darin, die Dereferenzierung eines NULL-Zeigers zu vermeiden, um eine Referenz zu erzeugen. 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, der besser schreiben kann, finden Sie unter Null-Referenzen von Jim Hyslop und Herb Sutter.

Ein weiteres Beispiel für die Gefahren der Dereferenzierung eines Null-Zeigers finden Sie unter Undefiniertes Verhalten beim Versuch, Code auf eine andere Plattform zu portieren von Raymond Chen.

4 Stimmen

Als Erbsenzähler würde ich sagen, dass der Verweis nicht wirklich null ist - er verweist auf schlechten Speicher. Obwohl es ein gültiger Punkt ist, dass, nur weil es ein Verweis ist, bedeutet nicht, dass es auf etwas, das gültig ist bezieht

71 Stimmen

Der betreffende Code enthält undefiniertes Verhalten. Technisch gesehen können Sie mit einem Null-Zeiger nichts tun, außer ihn zu setzen und zu vergleichen. Sobald Ihr Programm ein undefiniertes Verhalten aufruft, kann es alles tun, auch den Anschein erwecken, dass es korrekt funktioniert, bis Sie dem großen Chef eine Demo vorführen.

1 Stimmen

Ich habe es nicht getestet, aber ich denke, der obige Code stürzt einfach in der zweiten Zeile ab, während er den NULL-Zeiger dereferenziert.

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