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




rückgabewert als (25)

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:


Answers

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;

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.


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.


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.


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

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


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 .


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


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.


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.


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.


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


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

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 .


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 kann niemals sein NULL.


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.


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.


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


  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.


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.


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.


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


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.

A wrapper around GNU ld that doesn't support linker scripts

Some .so files are actually GNU ld linker scripts , eg libtbb.so file is an ASCII text file with this contents:

INPUT (libtbb.so.2)

Some more complex builds may not support this. For example, if you include -v in the compiler options, you can see that the mainwin gcc wrapper mwdip discards linker script command files in the verbose output list of libraries to link in. A simple work around is to replace the linker script input command file with a copy of the file instead (or a symlink), eg

cp libtbb.so.2 libtbb.so

Or you could replace the -l argument with the full path of the .so, eg instead of -ltbb do /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2





c++ pointers reference c++-faq