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?
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?
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;
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);
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);
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
.
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.
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 .
.
Referenzen können nicht in ein Array eingefügt werden, Zeiger hingegen schon (Erwähnt vom Benutzer @litb)
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.
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.
...aber die Dereferenzierung von NULL ist undefiniert. Zum Beispiel kann man nicht testen, ob eine Referenz NULL ist (z.B. &ref == NULL).
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!
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.
@Christoph: Nun, Referenzen können nur baumeln, wenn man sie irgendwo durch Dereferenzierung eines Zeigers erhalten hat.
@kriss: Nein, man kann auch eine baumelnde Referenz erhalten, indem man eine automatische Variable per Referenz zurückgibt.
@Ben Voight: OK, aber das ist wirklich ein Grund für Probleme. Einige Compiler geben sogar Warnungen aus, wenn Sie das tun.
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.
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.
@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.
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.
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).
"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.
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.
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.
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
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.
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 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.
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.
31 Stimmen
Dies kompiliert ohne Warnungen:
int &x = *(int*)0;
auf gcc. Referenz kann tatsächlich auf NULL zeigen.7 Stimmen
Ich bin mir nicht sicher, ob ich zustimme, dass "Referenzen syntaktischer Zucker" sind. Wie würden Sie Kopierkonstruktoren in Ihrer Sprache entwerfen, wenn Sie keine Referenzen hätten?
1 Stimmen
Wahrscheinlich könnte man die Adresse eines Verweises nehmen, wie es bei der Implementierung des Zuweisungsoperators üblich ist, d. h.
T& T::operator=(const T& rhs) { if (this == &rhs) return *this; ... }
.2 Stimmen
Auch hilfreich lien
9 Stimmen
Ich fühle mich nicht qualifiziert, den Teil "Klärung eines Missverständnisses" dieser Frage zu bearbeiten, aber ich denke, es sollte beachtet werden, dass, wenn
int i
ein ganzes Leben lang in einem Register lebt, ist der Verweis wahrscheinlich kein Zeiger - er kann einfach ein Alias für das gleiche Register sein.29 Stimmen
Referenz ist ein Variablen-Alias
4 Stimmen
Referenzen sind nicht nur syntaktischer Zucker: Sie befinden sich IMMER in einem definierten Zustand. Dies wird durch den Compiler erzwungen. Zeiger haben keine solche Garantie.
26 Stimmen
Mir gefällt, dass schon der erste Satz ein völliger Irrtum ist. Referenzen haben ihre eigene Semantik.
9 Stimmen
@Calmarius Nein, das ist nicht richtig. Das ist ein Verweis auf das, was sich an dieser Speicherstelle befindet. Der Verweis selbst ist gültig. Der Zugriff auf das, was sich dort befindet, ist jedoch undefiniert.
17 Stimmen
@Calmarius natürlich, um überhaupt dorthin zu gelangen, haben Sie einen Null-Zeiger dereferenziert. der selbst undefiniert ist. . .
1 Stimmen
Diese Frage zeigt eine Zeigerarithmetik, die mit einer Referenz ("
&obj + 5
"). Verstößt dies nicht gegen die Aliasing-Annahmen, bei denen die aufrufende Seite davon ausgeht, dass eine aufgerufene Funktion keine solche Arithmetik durchführt. Das bedeutet, dass die Referenzarithmetik UB ist?1 Stimmen
Ein Zeiger speichert grundsätzlich die Adresse seines Zeigers.
5 Stimmen
Int *p = NULL; int &r=*p; Referenz zeigt auf NULL; if(r){} -> boOm ;)
1 Stimmen
@QiangXu Nein, das nimmt die Adresse des Ziels der Referenz. Lies es noch einmal: Eine Referenz ist ein Alias auf ein anderes Objekt. (Abgesehen von der Erstellungsphase) ist die Semantik völlig identisch mit der, wenn Sie den ursprünglichen Namen des Ziels verwenden.
1 Stimmen
Ich würde nicht sagen, dass Referenzen als Zeiger in C++ implementiert sind, es sind zwei verschiedene Abstraktionen für Adresse eines Objekts. Denken Sie daran, wie die Hardware funktioniert: In der Hardware gibt es keine "Zeiger", sondern Register, Speicher und Anweisungen, die mit den Registern und dem Speicher arbeiten.
2 Stimmen
Ich stelle mir gerne vor, dass Variablen Menschen auf einer Party sind, dann eine Zeiger ist jemand, der normalerweise mit dem Finger auf jemand anderen zeigt, und wer das ist, kann geändert werden, und in einigen Fällen verlässt entweder der andere die Partei und kann nicht mehr auf ihn gezeigt werden (außerhalb des Geltungsbereichs) oder er wird ermordet (zerstört, es ist ein schlecht Party!), bei anderen Gelegenheiten kann der Zeiger Person auf eine neue Person (Wert geändert) oder den Boden (auf 0,
nullptr
usw.). Im Vergleich dazu ist eine Referenz variabel ist ein neues Namensschild, das sich eine Person selbst anlegen kann (und das auch ein Zeiger verwenden kann!)2 Stimmen
a pointer and a reference both occupy the same amount of memory
- Das ist falsch. Ein Zeiger auf eine virtuelle Funktion kann mehr Speicherplatz belegen als eine Referenz.4 Stimmen
Bin ich der Einzige, der der Meinung ist, dass Zeiger leichter zu lesen sind und den Code besser verdeutlichen als Referenzen?
1 Stimmen
Ein sehr wichtiger Punkt: Der Programmierer muss den Speicher von Zeigern verwalten, aber nicht den von Referenzen.
2 Stimmen
Ein Zeiger kann null sein, d.h. einen Wert annehmen, der eindeutig auf kein gültiges Objekt oder keine gültige Funktion zeigt. Andererseits kann eine Null-Referenz in einem wohldefinierten Programm nicht existieren, da die einzige Möglichkeit, eine solche Referenz zu erzeugen, darin bestünde, sie an das Objekt oder die Funktion zu binden, das/die durch Dereferenzierung eines Null-Zeigers erhalten wurde, was zu undefiniertem Verhalten führt.
0 Stimmen
fbb-git.github.io/cppannotations/cppannotations/html/ Könnte auch von Interesse sein und möglicherweise auch fbb-git.github.io/cppannotations/cppannotations/html/ - aus The C++ Annotations, das für C-Programmierer nützlich ist - was auch immer es wert sein mag, ist vielleicht für einige von Interesse. Ich persönlich hatte mehrere sehr schlechte Reaktionen auf C++ - das Aussehen ist für mich in höchstem Maße unästhetisch, und das ist nur ein Problem von vielen anderen ... also werde ich nicht in der Lage sein, mehr als die Links hinzuzufügen, aber vielleicht sind sie für einige nützlich oder interessant.
2 Stimmen
Ist keine der Antworten eine Lösung für Ihre Frage? Wenn nicht, bin ich sehr neugierig zu erfahren, warum.