Ich weiß, dass Referenzen syntaktischer Zucker sind, daher ist Code leichter zu lesen und zu schreiben.
Aber was sind die Unterschiede?
int &x = *(int*)0;
auf gcc. Die Referenz kann tatsächlich auf NULL verweisen.
Ich weiß, dass Referenzen syntaktischer Zucker sind, daher ist Code leichter zu lesen und zu schreiben.
Aber was sind die Unterschiede?
int &x = *(int*)0;
auf gcc. Die Referenz kann tatsächlich auf NULL verweisen.
Antworten:
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 zugewiesen werden:
int x = 5;
int y = 6;
int &r = x;
Ein Zeiger hat eine eigene Speicheradresse und -größe auf dem Stapel (4 Byte auf x86), während eine Referenz dieselbe Speicheradresse (mit der ursprünglichen Variablen) teilt, aber auch etwas Speicherplatz auf dem Stapel beansprucht. Da eine Referenz dieselbe Adresse wie die ursprüngliche Variable selbst hat, kann man sich eine Referenz sicher als einen anderen Namen für dieselbe Variable vorstellen. Hinweis: Auf was ein Zeiger zeigt, kann sich auf dem Stapel oder Heap befinden. Das Gleiche gilt für 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 einen eigenen Speicherplatz auf dem Stapel hat und die Adresse mit der Variablen übereinstimmt, auf die sie verweist. Mehr zu Stack vs Heap. Dies bedeutet, dass es eine echte Adresse einer Referenz gibt, die der Compiler Ihnen nicht mitteilt.
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2);
Sie können Zeiger auf Zeiger auf Zeiger haben, die zusätzliche Indirektionsebenen bieten. Während Referenzen nur eine Indirektionsebene bieten.
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);
Ein Zeiger kann direkt zugewiesen nullptr
werden, eine Referenz jedoch nicht. Wenn Sie sich genug anstrengen und wissen, wie, können Sie die Adresse einer Referenz angeben nullptr
. Wenn Sie sich genug anstrengen, können Sie auch einen Verweis auf einen Zeiger haben, den dieser Verweis enthalten kann 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
Zeiger können über ein Array iterieren. Sie können ++
zum nächsten Element wechseln, auf das ein Zeiger zeigt, und + 4
zum fünften Element. Dies ist unabhängig von der Größe des Objekts, auf das der Zeiger zeigt.
Ein Zeiger muss dereferenziert werden, *
um auf den Speicherort zuzugreifen, auf den er zeigt, während eine Referenz direkt verwendet werden kann. Ein Zeiger auf eine Klasse / Struktur verwendet ->
den Zugriff auf ihre Mitglieder, während eine Referenz a verwendet .
.
Referenzen können nicht in ein Array eingefügt werden, wohingegen Zeiger sein können (Erwähnt von Benutzer @litb)
Konstantenreferenzen können an Provisorien gebunden werden. Zeiger können nicht (nicht ohne Indirektion):
const int &x = int(12); //legal C++
int *y = &int(12); //illegal to dereference a temporary.
Dies macht die const&
Verwendung in Argumentlisten usw. sicherer.
Eine Referenz kann als konstanter Zeiger (nicht zu verwechseln mit einem Zeiger auf einen konstanten Wert!) Mit automatischer Indirektion betrachtet werden, dh der Compiler wendet den *
Operator für Sie an.
Alle Referenzen müssen mit einem Wert ungleich Null initialisiert werden. Andernfalls schlägt die Kompilierung fehl. Es ist weder möglich, die Adresse einer Referenz abzurufen - der Adressoperator gibt stattdessen die Adresse des referenzierten Werts zurück - noch ist es möglich, Referenzen zu arithmetisieren.
C-Programmierer mögen C ++ - Referenzen möglicherweise nicht, da dies nicht mehr offensichtlich ist, wenn eine Indirektion stattfindet oder wenn ein Argument als Wert oder Zeiger übergeben wird, ohne die Funktionssignaturen zu berücksichtigen.
C ++ - Programmierer mögen die Verwendung von Zeigern möglicherweise nicht, da sie als unsicher eingestuft werden - obwohl Referenzen nur in den trivialsten Fällen wirklich sicherer sind als konstante Zeiger -, fehlt ihnen die Bequemlichkeit der automatischen Indirektion und sie haben eine andere semantische Konnotation.
Beachten Sie die folgende Aussage aus den C ++ - FAQ :
Obwohl eine Referenz häufig unter Verwendung einer Adresse in der zugrunde liegenden Assemblersprache implementiert wird, stellen Sie sich eine Referenz bitte nicht als einen lustig aussehenden Zeiger auf ein Objekt vor. 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 baumelnde Referenzen geben? In nicht verwalteten Sprachen ist es unmöglich, dass Referenzen "sicherer" sind als Zeiger - es gibt im Allgemeinen keine Möglichkeit, Werte über Bereichsgrenzen hinweg zuverlässig zu aliasen!
Aus einem C-Hintergrund stammend, mögen C ++ - Referenzen wie ein etwas albernes Konzept aussehen, aber man sollte sie nach Möglichkeit anstelle von Zeigern verwenden: Die automatische Indirektion ist praktisch, 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 ++, interagiert jedoch nicht trivial mit der Kopiersemantik. Durch das Übergeben von Objekten als Referenz werden diese Probleme vermieden, da kein Kopieren erforderlich ist. Wenn in der Sprache keine Referenzen vorhanden wären, müssten Sie stattdessen Zeiger verwenden, deren Verwendung umständlicher ist, was gegen das Prinzip des Sprachdesigns verstößt, dass die Best-Practice-Lösung einfacher sein sollte als die Alternativen.
Wenn Sie wirklich pedantisch sein möchten, können Sie mit einer Referenz eines tun, das Sie mit einem Zeiger nicht tun können: Verlängern Sie die Lebensdauer eines temporären Objekts. Wenn Sie in C ++ eine konstante 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 eine Referenz auf ein temporäres Objekt, das jetzt die gleiche Lebensdauer wie die Referenz hat.
Wenn Sie dies ohne das versuchen const
, sollte es nicht kompiliert werden können. Sie können weder einen nicht konstanten Verweis an ein temporäres Objekt binden noch dessen Adresse übernehmen.
const &
Bindung verlängert, und nur wenn die Referenz den Gültigkeitsbereich verlässt, wird der Destruktor des tatsächlich referenzierten Typs (im Vergleich zum Referenztyp, der eine Basis sein könnte) aufgerufen. Da es sich um eine Referenz handelt, findet dazwischen kein Schneiden statt.
Animal x = fast ? getHare() : getTortoise()
dann haben, x
wird das klassische Schnittproblem konfrontiert, während Animal& x = ...
es richtig funktioniert.
Abgesehen von syntaktischem Zucker ist eine Referenz ein const
Zeiger ( kein Zeiger auf a const
). Sie müssen festlegen, worauf es sich bezieht, wenn Sie die Referenzvariable deklarieren, und können sie später nicht mehr ändern.
Update: Jetzt, wo ich noch etwas darüber nachdenke, gibt es einen wichtigen Unterschied.
Das Ziel eines Konstantenzeigers kann ersetzt werden, indem seine Adresse verwendet und eine Konstantenumwandlung verwendet wird.
Das Ziel einer Referenz kann in keiner Weise vor UB ersetzt werden.
Dies sollte es dem Compiler ermöglichen, eine Referenz weiter zu optimieren.
T* const
anderen syntaktischen Zucker enthalten (was dazu führt, dass viele * und & aus Ihrem Code entfernt werden).
int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
ist OK.
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, es ist viel schwieriger, mit einer Referenz umzugehen - aber wenn Sie es schaffen, werden Sie sich die Haare ausreißen, wenn Sie versuchen, sie zu finden. Referenzen sind in C ++ nicht von Natur aus sicher!
Technisch gesehen ist dies eine ungültige Referenz , keine Nullreferenz. C ++ unterstützt keine Nullreferenzen als Konzept, wie Sie es in anderen Sprachen finden könnten. Es gibt auch andere Arten ungültiger Referenzen. Jede ungültige Referenz löst das Gespenst eines undefinierten Verhaltens aus , genau wie die Verwendung eines ungültigen Zeigers.
Der eigentliche 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 bis zu einem Punkt weiter unten im Code aus. Das macht dieses Problem so heimtückisch. Wenn Sie einen NULL-Zeiger dereferenzieren, stürzen Sie meistens genau an dieser Stelle ab und es ist nicht viel Debugging erforderlich, um dies herauszufinden.
Mein Beispiel oben ist kurz und erfunden. 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 betonen, dass der einzige Weg, eine Nullreferenz zu erhalten, in fehlerhaftem Code besteht. Sobald Sie ihn haben, erhalten Sie undefiniertes Verhalten. Es ist niemals sinnvoll, nach einer Nullreferenz zu suchen. Sie können es beispielsweise versuchen, if(&bar==NULL)...
aber der Compiler optimiert möglicherweise die nicht mehr existierende Anweisung! 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 entfernen - dies ist die Essenz undefinierten Verhaltens.
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, um 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 weiteres Beispiel für die Gefahren der Dereferenzierung eines Nullzeigers finden Sie unter Aufdecken von undefiniertem Verhalten beim Versuch, Code von Raymond Chen auf eine andere Plattform zu portieren .
Sie haben den wichtigsten Teil vergessen:
Der Mitgliederzugriff mit Zeigern verwendet den ->
Mitgliederzugriff mit Verweisen.
foo.bar
ist deutlich überlegen foo->bar
in der gleichen Art und Weise , dass vi ist klar zu überlegen Emacs :-)
->
genau wie der Zeiger selbst Verweise auf Zeiger.
.
und ->
hat etwas mit vi und emacs zu tun :)
.
besser ist als das Verwenden ->
, aber genau wie vi vs emacs ist es völlig subjektiv und man kann nichts beweisen
Referenzen sind Zeigern sehr ähnlich, wurden jedoch speziell entwickelt, um bei der Optimierung von Compilern hilfreich zu sein.
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 erkennen, 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
}
Um eine solche Optimierung vorzunehmen, muss nachgewiesen werden, dass während des Aufrufs nichts das Array [1] ändern kann. Dies ist ziemlich einfach zu tun. i ist niemals kleiner als 2, daher kann sich Array [i] niemals auf Array [1] beziehen. Vielleicht erhält Modify () als Referenz a0 (Aliasing-Array [0]). Da es keine "Referenz" -Arithmetik gibt, muss der Compiler nur beweisen, dass vielleicht Modify nie die Adresse von x erhält, und er hat bewiesen, dass nichts das Array ändert [1].
Es muss auch beweisen, dass es keine Möglichkeiten gibt, wie ein zukünftiger Aufruf eine [0] lesen / schreiben kann, während wir eine temporäre Registerkopie davon in a0 haben. Dies ist oft 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 jetzt 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 das gleiche; erst jetzt ist es viel schwieriger zu beweisen, dass vielleicht Modify Array niemals ändert [1], weil wir ihm bereits 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 in & x + 1 schreibt. Es muss auch beweisen, dass es niemals einen Zeiger speichert, der auf Array [0] verweisen kann, was gerecht ist so knifflig.
Moderne Compiler werden bei der statischen Analyse immer besser, aber es ist immer schön, ihnen zu helfen und Referenzen zu verwenden.
Abgesehen von solchen cleveren Optimierungen werden Compiler Referenzen bei Bedarf in Zeiger verwandeln.
BEARBEITEN: Fünf Jahre nach der Veröffentlichung dieser Antwort stellte ich einen tatsächlichen technischen Unterschied fest, bei dem Referenzen anders sind als nur eine andere Sichtweise auf dasselbe Adressierungskonzept. Referenzen können die Lebensdauer temporärer Objekte so ändern, dass Zeiger dies 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 das durch den Aufruf an erstellte createF(5)
am Ende des Ausdrucks zerstört. Durch Binden dieses Objekts an eine Referenz ref
verlängert C ++ jedoch die Lebensdauer dieses temporären Objekts, bis ref
der Gültigkeitsbereich überschritten wird.
maybeModify
nicht die Adresse von irgendetwas in Beziehung x
steht, als zu beweisen, dass eine Reihe von Zeigerarithmetik nicht auftritt.
void maybeModify(int& x) { 1[&x]++; }
, über den in den anderen obigen Kommentaren gesprochen wird
Eigentlich ist eine Referenz nicht wirklich wie 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 Zeigervariablen einen anderen Namen zuweisen. Deshalb können Referenzen nicht "auf Null zeigen", weil eine Variable nicht sein kann und nicht sein kann.
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 eine Erklärung des echten Codes:
int a = 0;
int& b = a;
Hier erstellen Sie keine weitere Variable, auf die a
verwiesen wird. Sie fügen dem Speicherinhalt, der den Wert von enthält, nur einen anderen Namen hinzu a
. Dieser Speicher hat jetzt zwei Namen a
und b
und kann mit beiden Namen adressiert werden.
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
Beim Aufrufen einer Funktion generiert der Compiler normalerweise Speicherplätze für die zu kopierenden Argumente. Die Funktionssignatur definiert die Leerzeichen, die erstellt werden sollen, und gibt den Namen an, der für diese Leerzeichen verwendet werden soll. Wenn Sie einen Parameter als Referenz deklarieren, wird der Compiler lediglich angewiesen, den Speicherbereich der Eingabevariablen zu verwenden, anstatt während des Methodenaufrufs einen neuen Speicherplatz zuzuweisen. Es mag seltsam erscheinen zu sagen, dass Ihre Funktion eine im aufrufenden Bereich deklarierte Variable direkt manipuliert. Denken Sie jedoch daran, dass bei der Ausführung von kompiliertem Code kein Bereich mehr vorhanden ist. Es gibt nur einen flachen Speicher, und Ihr Funktionscode kann alle Variablen manipulieren.
In einigen Fällen kann es sein, dass Ihr Compiler die Referenz beim Kompilieren nicht kennt, z. B. bei Verwendung einer externen Variablen. Eine Referenz kann also als Zeiger im zugrunde liegenden Code implementiert sein oder nicht. Aber in den Beispielen, die ich Ihnen gegeben habe, wird es höchstwahrscheinlich nicht mit einem Zeiger implementiert.
Eine Referenz kann niemals sein NULL
.
void Foo::bar() { virtual_baz(); }
solchen Code . Wenn Sie nicht wissen, dass Referenzen null sein können, können Sie die Null nicht bis zu ihrem Ursprung zurückverfolgen.
int &r=*p;
ist undefiniertes Verhalten. An diesem Punkt Sie keinen haben „Referenz zeigt auf NULL,“ haben Sie ein Programm , das nicht mehr zu begründen überhaupt .
Während sowohl Referenzen als auch Zeiger verwendet werden, um indirekt auf einen anderen Wert zuzugreifen, gibt es zwei wichtige Unterschiede zwischen Referenzen und Zeigern. Das erste ist, dass sich eine Referenz immer auf ein Objekt bezieht: 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 dasselbe zugrunde liegende Objekt.
Betrachten Sie diese beiden Programmfragmente. Im ersten Schritt 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. Die Zuweisung ändert den Wert von pi und zeigt auf ein anderes Objekt. Betrachten Sie nun ein ähnliches Programm, das zwei Referenzen zuweist:
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival
Diese Zuordnung ändert ival, den Wert, auf den ri verweist, und nicht die Referenz selbst. Nach der Zuweisung beziehen sich die beiden Referenzen immer noch auf ihre ursprünglichen Objekte, und der Wert dieser Objekte ist jetzt ebenfalls der gleiche.
Es gibt einen semantischen Unterschied, der esoterisch erscheinen kann, wenn Sie nicht mit dem abstrakten oder sogar akademischen Studium von Computersprachen vertraut sind.
Auf höchster Ebene besteht die Idee von Referenzen darin, dass sie transparente "Aliase" sind. Ihr Computer verwendet möglicherweise eine Adresse, damit sie funktionieren, aber Sie sollten sich darüber keine Sorgen machen: Sie sollten sie als "nur einen anderen Namen" für ein vorhandenes Objekt betrachten, und die Syntax spiegelt dies wider. Sie sind strenger als Zeiger, sodass Ihr Compiler Sie zuverlässiger warnen kann, wenn Sie eine baumelnde Referenz erstellen möchten, als wenn Sie einen baumelnden Zeiger erstellen möchten.
Darüber hinaus gibt es natürlich einige praktische Unterschiede zwischen Zeigern und Referenzen. Die Syntax, um sie zu verwenden, ist offensichtlich unterschiedlich, und Sie können Referenzen nicht "neu platzieren", Verweise auf das Nichts haben oder Zeiger auf Verweise haben.
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, sodass 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.
Es spielt keine Rolle, wie viel Speicherplatz es einnimmt, da Sie keine Nebenwirkungen (ohne Ausführung von Code) des Speicherplatzes sehen können, den es beanspruchen würde.
Andererseits besteht ein Hauptunterschied zwischen Referenzen und Zeigern darin, dass temporäre Konstanten, die konstanten Referenzen zugewiesen sind, so lange leben, bis die konstante Referenz den 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 funktioniert.
Dies basiert auf dem Tutorial . Was geschrieben steht, 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 um sich 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 ähnlich wird.
Schauen Sie sich die folgende Aussage an:
int Tom(0);
int & alias_Tom = Tom;
alias_Tom
kann verstanden werden als alias of a variable
(anders mit typedef
, was ist alias of a type
) Tom
. Es ist auch in Ordnung, die Terminologie einer solchen Anweisung zu vergessen, um eine Referenz von zu erstellen Tom
.
nullptr
? Hast du tatsächlich einen anderen Teil dieses Threads gelesen oder ...?
Eine Referenz ist kein anderer Name für einen Speicher. Es ist ein unveränderlicher Zeiger, auf den bei Verwendung automatisch verwiesen wird. Im Grunde läuft es darauf hinaus:
int& j = i;
Es wird intern
int* const j = &i;
const
Zeigers. Diese Flexibilität beweist nicht, dass es einen Unterschied zwischen einer Referenz und einem Zeiger gibt.
Was ist eine Referenz in C ++? Eine bestimmte Instanz eines Typs, der kein Objekttyp ist .
Was ist ein Zeiger in C ++? Eine bestimmte Instanz eines Typs, der ein Objekttyp ist .
Aus der ISO C ++ - Definition des Objekttyps :
Ein Objekttyp ist ein (möglicherweise cv- qualifizierter) Typ, der kein Funktionstyp, kein Referenztyp und kein cv void ist.
Es kann wichtig sein zu wissen, dass der Objekttyp eine Kategorie der obersten Ebene des Typuniversums in C ++ ist. Referenz ist auch eine Kategorie der obersten Ebene. Aber Zeiger ist nicht.
Zeiger und Referenzen werden im Zusammenhang mit dem Verbindungstyp zusammen erwähnt . Dies liegt im Wesentlichen an der Art der Deklaratorsyntax, die von C geerbt (und erweitert) wurde und keine Referenzen enthält. (Außerdem gibt es seit C ++ 11 mehr als eine Art von Deklarator von Referenzen, während Zeiger immer noch "unitped" sind: &
+ &&
vs. *
) . (Ich werde immer noch argumentieren , dass die Syntax von Deklaratoren Abfällen des syntaktische Ausdruck viel , beide macht menschliche Benutzer und Implementierungen frustrierend. So sind sie alle nicht qualifiziert werden , built-inin einem neuen Sprachdesign. Dies ist jedoch ein völlig anderes Thema beim PL-Design.)
Andernfalls ist es unerheblich, dass Zeiger als bestimmte Arten von Typen mit Referenzen zusammen qualifiziert werden können. Sie haben neben der Syntaxähnlichkeit einfach zu wenige gemeinsame Eigenschaften, sodass sie in den meisten Fällen nicht zusammengesetzt werden müssen.
Beachten Sie, dass in den obigen Aussagen nur "Zeiger" und "Referenzen" als Typen erwähnt werden. Es gibt einige interessante Fragen zu ihren Instanzen (wie Variablen). Es gibt auch zu viele Missverständnisse.
Die Unterschiede der Kategorien der obersten Ebene können bereits viele konkrete Unterschiede aufzeigen, die nicht direkt mit Zeigern verbunden sind:
cv
Qualifizierer der obersten Ebene haben . Referenzen können nicht.Einige weitere Sonderregeln für Referenzen:
&&
Parameter (als "Weiterleitungsreferenzen"), die auf dem Reduzieren von Referenzen während der Ableitung von Vorlagenparametern basieren, ermöglichen eine "perfekte Weiterleitung" von Parametern.std::initializer_list
folgen einige andere Kontexte wie die Initialisierung einigen ähnlichen Regeln für die Verlängerung der Referenzlebensdauer. Es ist eine weitere Dose Würmer.Ich weiß, dass Referenzen syntaktischer Zucker sind, daher ist Code leichter zu lesen und zu schreiben.
Technisch ist das einfach falsch. Referenzen sind kein syntaktischer Zucker für andere Features in C ++, da sie ohne semantische Unterschiede nicht exakt durch andere Features ersetzt werden können.
( In ähnlicher Weise lambda-Ausdruck s sind nicht syntaktischer Zucker von irgendwelchen anderen Funktionen in C ++ , weil es nicht mit „unspezifiziert“ Eigenschaften wie genau simuliert werden kann , die Erklärung der Reihenfolge der erfassten Variablen , die wichtig sein können , da die Initialisierungsreihenfolge solcher Variablen sein kann von Bedeutung.)
C ++ hat nur wenige Arten von syntaktischen Zuckern in diesem strengen Sinne. Eine Instanz ist (von C geerbt) der eingebaute (nicht überladene) Operator []
, der genau definiert ist und dieselben semantischen Eigenschaften bestimmter Kombinationsformen gegenüber dem eingebauten Operator unär *
und binär aufweist+
.
Ein Zeiger und eine Referenz verwenden also beide dieselbe Speichermenge.
Die obige Aussage ist einfach falsch. Um solche Missverständnisse zu vermeiden, lesen Sie stattdessen die ISO C ++ - Regeln:
Aus [intro.object] / 1 :
... Ein Objekt befindet sich in seiner Bauzeit, während seiner gesamten Lebensdauer und in seiner Zerstörungszeit in einem Lagerbereich. ...
Aus [dcl.ref] / 4 :
Es ist nicht angegeben, ob eine Referenz gespeichert werden muss oder nicht.
Beachten Sie, dass dies semantische Eigenschaften sind.
Auch wenn Zeiger nicht qualifiziert genug sind, um mit Referenzen im Sinne des Sprachdesigns zusammengestellt zu werden, gibt es immer noch einige Argumente, die es fraglich machen, in einigen anderen Kontexten zwischen ihnen zu wählen, beispielsweise bei der Auswahl von Parametertypen.
Das ist aber nicht die ganze Geschichte. Ich meine, es gibt mehr Dinge als Zeiger und Referenzen, die Sie berücksichtigen müssen.
Wenn Sie sich nicht an solche überspezifischen Entscheidungen halten müssen, ist die Antwort in den meisten Fällen kurz: Sie müssen keine Zeiger verwenden, also nicht . Zeiger sind normalerweise schlecht genug, weil sie zu viele Dinge implizieren, die Sie nicht erwarten, und sich auf zu viele implizite Annahmen stützen, die die Wartbarkeit und (gleichmäßige) Portabilität des Codes untergraben. Das unnötige Verlassen auf Zeiger ist definitiv ein schlechter Stil und sollte im Sinne von modernem C ++ vermieden werden. Überdenken Sie Ihren Zweck und Sie werden schließlich feststellen, dass der Zeiger in den meisten Fällen das Merkmal der letzten Art ist.
&
Referenztypen als 1. Parametertyp. (Und normalerweise sollte es const
qualifiziert sein.)&&
Referenztypen als 1. Parametertyp. (Und normalerweise sollte es keine Qualifikanten geben.)operator=
Als spezielle Elementfunktionen überladen, sind Referenztypen erforderlich, die dem ersten Parameter von Kopier- / Verschiebungskonstruktoren ähneln.++
erfordert Dummy int
.unique_ptr
und shared_ptr
(oder sogar selbst mit Homebrew- Zeigern , wenn diese undurchsichtig sein sollen ) anstelle von rohen Zeigern.std::optional
anstelle von rohen Zeigern.observer_ptr
in Library Fundamental TS.Die einzigen Ausnahmen können in der aktuellen Sprache nicht umgangen werden:
operator new
. (Allerdings ist cv - void*
im Vergleich zu normalen Objektzeigern immer noch ganz anders und sicherer, da es unerwartete Zeigerarithmetik ausschließt, es sei denn, Sie verlassen sich auf eine nicht konforme Erweiterung void*
wie GNUs.)In der Praxis liegt die Antwort also auf der Hand: Vermeiden Sie im Zweifelsfall Hinweise . Sie müssen Zeiger nur verwenden, wenn es sehr explizite Gründe dafür gibt, dass nichts anderes angemessener ist. Mit Ausnahme einiger oben erwähnter Ausnahmefälle sind solche Auswahlmöglichkeiten fast immer nicht rein C ++ - spezifisch (aber wahrscheinlich sprachspezifisch). Solche Fälle können sein:
Wenn Sie die Frage über ein Google-Suchergebnis sehen (nicht spezifisch für C ++) , ist dies sehr wahrscheinlich der falsche Ort.
Referenzen in C ++ ist ziemlich „ungerade“, wie es im Wesentlichen nicht erster Klasse: sie werden als Objekte behandelt werden , oder die Funktionen bezeichnet werden , so dass sie keine Chance haben einige erstklassige Operationen wie sein linker Operand unterstützen die Mitgliedszugriffsoperator unabhängig vom Typ des referenzierten Objekts. Andere Sprachen können ähnliche Einschränkungen für ihre Referenzen haben oder nicht.
Referenzen in C ++ behalten wahrscheinlich nicht die Bedeutung in verschiedenen Sprachen bei. Beispielsweise implizieren Referenzen im Allgemeinen keine Nicht-Null-Eigenschaften für Werte wie in C ++, sodass solche Annahmen in einigen anderen Sprachen möglicherweise nicht funktionieren (und Sie finden Gegenbeispiele recht leicht, z. B. Java, C #, ...).
Es kann immer noch einige gemeinsame Eigenschaften von Referenzen in verschiedenen Programmiersprachen im Allgemeinen geben, aber lassen wir es für einige andere Fragen in SO.
(Eine Randnotiz: Die Frage kann früher von Bedeutung sein, als es sich um "C-ähnliche" Sprachen handelt, wie ALGOL 68 vs. PL / I. )
Ein Verweis auf einen Zeiger ist in C ++ möglich, aber das Gegenteil ist nicht möglich, was bedeutet, dass ein Zeiger auf einen Verweis nicht möglich ist. Ein Verweis auf einen Zeiger bietet eine übersichtlichere Syntax zum Ändern des Zeigers. Schauen Sie sich 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 (mehrfache Indirektion), was zu Verwirrung führt und das Programm möglicherweise kompliziert aussieht.
#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;
}
Weitere Informationen zum Verweis auf Zeiger finden Sie im Folgenden:
Wie gesagt, ein Zeiger auf eine Referenz ist nicht möglich. Versuchen Sie das folgende Programm:
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *ptr = &x;
int &*ptr1 = ptr;
}
Ich verwende Referenzen, es sei denn, ich benötige eine der folgenden:
Nullzeiger können als Sentinel-Wert verwendet werden, häufig eine kostengünstige Methode, um eine Überlastung der Funktionen oder die Verwendung eines Bools zu vermeiden.
Sie können mit einem Zeiger rechnen. Zum Beispiel,p += offset;
&r + offset
wo r
als Referenz deklariert wurde
Es gibt einen grundlegenden Unterschied zwischen Zeigern und Referenzen, den niemand erwähnt hat: Referenzen ermöglichen die Semantik der Referenzübergabe in Funktionsargumenten. Zeiger sind zwar zunächst nicht sichtbar, bieten jedoch nur eine Semantik für die Wertübergabe. Dies wurde in diesem Artikel sehr schön beschrieben .
Grüße & rzej
Ich bin mir sicher, dass dies hauptsächlich davon abhängt, wie der Compiler Referenzen implementiert, aber im Fall von gcc die Idee, dass eine Referenz nur auf eine Variable auf dem Stapel verweisen kann ist eigentlich nicht richtig, nehmen Sie 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;
}
Welches gibt dies aus:
THIS IS A STRING
0xbb2070 : 0xbb2070
Wenn Sie feststellen, dass sogar die Speicheradressen genau gleich sind, zeigt die Referenz erfolgreich auf eine Variable auf dem Heap! Wenn Sie wirklich ausgeflippt werden möchten, 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).*/
}
Welches gibt dies aus:
THIS IS A STRING
Daher ist eine Referenz 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 aufrufe? NACH dem Aufruf von delete & str_ref? Nun, offensichtlich wird es gut kompiliert, verursacht aber zur Laufzeit einen Segmentierungsfehler, da es nicht mehr auf eine gültige Variable zeigt. Wir haben im Wesentlichen eine fehlerhafte 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, bei dem die Zeigermechanik abstrahiert ist, was die Verwendung sicherer und einfacher macht (keine zufällige Zeigermathematik, kein Verwechseln von '.' Und '->' usw.), vorausgesetzt, Sie versuche keinen Unsinn wie meine obigen Beispiele;)
Nun , unabhängig davon , wie ein Compiler Griffe Referenzen, es wird immer eine Art Zeiger unter der Haube haben, weil ein Verweis muss auf eine bestimmte Variable an einer bestimmten Speicheradresse für sie Arbeit beziehen , wie erwartet, gibt es keine , dies zu umgehen (daher der Begriff "Referenz").
Die einzige wichtige Regel, die bei Referenzen 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, in dem sie enthalten ist, vorhanden ist konstruiert ist es zu spät, um es zu definieren).
Denken Sie daran, meine obigen Beispiele sind nur Beispiele, die zeigen, was eine Referenz ist. Sie würden niemals eine Referenz auf diese Weise verwenden wollen! Für die richtige Verwendung einer Referenz gibt es hier bereits viele Antworten, die den Nagel auf den Kopf treffen
Ein weiterer Unterschied besteht darin, dass Sie Zeiger auf einen ungültigen Typ haben können (und dies bedeutet Zeiger auf alles), 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 zufrieden bin. Ich würde es sehr bevorzugen, wenn es mit der Bedeutung Verweis auf alles mit einer Adresse und ansonsten das gleiche Verhalten für Verweise erlaubt wäre. Es würde ermöglichen, einige Äquivalente von C-Bibliotheksfunktionen wie memcpy unter Verwendung von Referenzen zu definieren.
Eine Referenz, die ein Parameter für eine Funktion ist, die inline ist, kann auch anders behandelt werden 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 erzwingen beim Inlinen der Zeigerversion tatsächlich ein Schreiben in den Speicher (wir nehmen die Adresse explizit). Sie belassen die Referenz jedoch in einem Register, das optimaler ist.
Für Funktionen, die nicht inline sind, generieren Zeiger und Referenz natürlich denselben Code, und es ist immer besser, Intrinsics als Wert als als Referenz zu übergeben, wenn sie nicht von der Funktion geändert und zurückgegeben werden.
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;
}
Die Standardvariante verwendet den Aspekt "Konstante an einen temporären Bindebestand binden" von Referenzen.
Dieses Programm kann helfen, die Antwort auf die Frage zu verstehen. Dies ist ein einfaches Programm aus 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();
}
Führen Sie das Programm aus und sehen Sie sich die Ausgabe an. Sie werden verstehen.
Nehmen Sie sich außerdem 10 Minuten Zeit und sehen Sie sich dieses Video an: https://www.youtube.com/watch?v=rlJrrGV0iOg
Ich habe das Gefühl, dass es noch einen weiteren Punkt gibt, der hier nicht behandelt wurde.
Im Gegensatz zu den Zeigern entsprechen Referenzen syntaktisch dem Objekt, auf das sie verweisen, dh jede Operation, die auf ein Objekt angewendet werden kann, funktioniert für eine Referenz und mit genau 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, zum Beispiel:
Vorlagen . Da Vorlagenparameter vom Typ Ente sind, sind nur die syntaktischen Eigenschaften eines Typs von Bedeutung. Daher kann häufig dieselbe Vorlage für beide T
und verwendet werden T&
.
(oder std::reference_wrapper<T>
die immer noch auf einer impliziten Besetzung von
basiert T&
)
Vorlagen, die beide abdecken T&
und T&&
noch häufiger sind.
LWerte . Betrachten Sie die Anweisung str[0] = 'X';
Ohne Referenzen würde sie nur für c-Strings ( char* str
) funktionieren . Durch die Rückgabe des Zeichens als Referenz können benutzerdefinierte Klassen dieselbe Notation haben.
Konstruktoren kopieren . Syntaktisch ist es sinnvoll, Objekte an Kopierkonstruktoren und keine Zeiger auf Objekte zu übergeben. Es gibt jedoch keine Möglichkeit für einen Kopierkonstruktor, ein Objekt nach Wert zu nehmen - dies würde zu einem rekursiven Aufruf desselben Kopierkonstruktors führen. Dies lässt Referenzen als einzige Option hier.
Überlastungen des Bedieners . Mit Referenzen ist es möglich, eine Indirektion in einen Operator-Aufruf einzuführen, beispielsweise operator+(const T& a, const T& b)
unter Beibehaltung derselben Infix-Notation. Dies funktioniert auch bei regulären überlasteten Funktionen.
Diese Punkte ermöglichen einen erheblichen Teil von C ++ und der Standardbibliothek, sodass dies eine wichtige Eigenschaft von Referenzen ist.
Es gibt einen sehr wichtigen nichttechnischen Unterschied zwischen Zeigern und Referenzen: 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 als Wert übergeben werden, sodass er definitiv nicht geändert werden kann x
. Um ein Argument zu ändern, müssten Sie einen Zeiger übergeben fn(&x)
. Wenn einem Argument also kein vorangestellt wurde, von dem &
Sie wussten, dass es nicht geändert wird. (Die Umkehrung, &
dh modifiziert, war nicht wahr, da Sie manchmal große schreibgeschützte Strukturen per const
Zeiger übergeben mussten.)
Einige argumentieren, dass dies eine so nützliche Funktion beim Lesen von Code ist, dass Zeigerparameter immer für modifizierbare Parameter anstelle von Nichtreferenzen verwendet werden sollten const
, selbst wenn die Funktion niemals a erwartet nullptr
. Das heißt, diese Leute argumentieren, dass Funktionssignaturen wie fn3()
oben nicht erlaubt sein sollten. Die C ++ - Stilrichtlinien von Google sind ein Beispiel dafür.
Vielleicht helfen einige Metaphern; Im Kontext Ihres Desktop-Bildschirmbereichs -
Ein Zeiger kann auf 0 initialisiert werden und eine Referenz nicht. Tatsächlich muss sich eine Referenz auch auf ein Objekt beziehen, aber ein Zeiger kann der Nullzeiger sein:
int* p = 0;
Aber wir können nicht haben int& p = 0;
und auch int& p=5 ;
.
Um es richtig zu machen, müssen wir zuerst ein Objekt deklariert und definiert haben, dann können wir auf dieses Objekt verweisen, damit die korrekte Implementierung des vorherigen Codes lautet:
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 Falle einer Referenz, die immer auf eine Variable oder ein Objekt verweisen muss, kann dies jedoch nicht erfolgen. Die Verwendung eines Zeigers ist jedoch riskant. Daher prüfen wir im Allgemeinen, ob der Zeiger tatsächlich auf etwas zeigt oder nicht. Im Falle einer Referenz ist eine solche Prüfung nicht erforderlich, da wir bereits wissen, dass die Bezugnahme auf ein Objekt während der Deklaration obligatorisch ist.
Ein weiterer Unterschied besteht darin, dass der Zeiger auf ein anderes Objekt zeigen kann, die Referenz 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 eine Referenz zurück, keinen Zeiger, um das Lesen oder Zuweisen eines neuen Werts mit dem Operator [] zu vereinfachen:
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 "="
const int& i = 0
.
Der Unterschied besteht darin, dass eine nicht konstante Zeigervariable (nicht zu verwechseln mit einem Zeiger auf eine Konstante) zu einem bestimmten Zeitpunkt während der Programmausführung geändert werden kann und die Verwendung von Zeigersemantikoperatoren (&, *) erfordert, während Referenzen bei der Initialisierung festgelegt werden können nur (deshalb können Sie sie nur in der Konstruktorinitialisiererliste festlegen, aber nicht irgendwie anders) und die Semantik für den Zugriff auf gewöhnliche Werte verwenden. Grundsätzlich wurden Referenzen eingeführt, um die Überlastung der Bediener zu unterstützen, wie ich 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. Tatsächlich kann der Zeiger jedoch einen Wert enthalten, der nicht auf einen korrekten Speicherort verweist. Referenzen wiederum versuchen, einem Benutzer nicht zu erlauben, eine Referenz auf etwas zu initialisieren, auf das nicht verwiesen werden kann, da Sie immer einen Wert vom richtigen Typ angeben. Obwohl es viele Möglichkeiten gibt, Referenzvariablen an einem falschen Speicherort zu initialisieren, ist es besser, wenn Sie nicht so tief in Details graben. Auf Maschinenebene arbeiten Zeiger und Referenz einheitlich - über Zeiger. Nehmen wir an, in wesentlichen Referenzen sind syntaktischer Zucker. rWertreferenzen unterscheiden sich davon - sie sind natürlich Stapel- / Heap-Objekte. Obwohl es viele Möglichkeiten gibt, Referenzvariablen an einem falschen Speicherort zu initialisieren, ist es besser, wenn Sie nicht so tief in Details graben. Auf Maschinenebene arbeiten Zeiger und Referenz einheitlich - über Zeiger. Nehmen wir an, in wesentlichen Referenzen sind syntaktischer Zucker. rWertreferenzen unterscheiden sich davon - sie sind natürlich Stapel- / Heap-Objekte. Obwohl es viele Möglichkeiten gibt, Referenzvariablen an einem falschen Speicherort zu initialisieren, ist es besser, wenn Sie nicht so tief in Details graben. Auf Maschinenebene arbeiten Zeiger und Referenz einheitlich - über Zeiger. Nehmen wir an, in wesentlichen Referenzen sind syntaktischer Zucker. rWertreferenzen unterscheiden sich davon - sie sind natürlich Stapel- / Heap-Objekte.
Mit einfachen Worten können wir sagen, dass eine Referenz ein alternativer Name für eine Variable ist, während ein Zeiger eine Variable ist, die die Adresse einer anderen Variablen enthält. z.B
int a = 20;
int &r = a;
r = 40; /* now the value of a is changed to 40 */
int b =20;
int *ptr;
ptr = &b; /*assigns address of b to ptr not the value */