Ich denke, Punkt 2 sollte lauten: "Ein Zeiger darf NULL sein, eine Referenz jedoch nicht. Nur fehlerhafter Code kann eine NULL-Referenz erstellen und sein Verhalten ist undefiniert."
Mark Ransom
19
Zeiger sind nur ein anderer Objekttyp und können wie jedes Objekt in C ++ eine Variable sein. Referenzen hingegen sind niemals Objekte, sondern nur Variablen.
Kerrek SB
19
Dies wird ohne Warnungen kompiliert: int &x = *(int*)0;auf gcc. Die Referenz kann tatsächlich auf NULL verweisen.
Calmarius
20
Referenz ist ein variabler Alias
Khaled.K
20
Mir gefällt, wie der allererste Satz ein totaler Irrtum ist. Referenzen haben ihre eigene Semantik.
Leichtigkeitsrennen im Orbit
Antworten:
1708
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 nullptrwerden, 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 errorint&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 + 4zum 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):
constint&x =int(12);//legal C++int*y =&int(12);//illegal to dereference a temporary.
Dies macht die const&Verwendung in Argumentlisten usw. sicherer.
... aber die Dereferenzierung von NULL ist undefiniert. Sie können beispielsweise nicht testen, ob eine Referenz NULL ist (z. B. & ref == NULL).
Pat Notz
69
Nummer 2 ist nicht wahr. Eine Referenz ist nicht einfach "ein anderer Name für dieselbe Variable". Verweise können auf ähnliche Weise wie Zeiger an Funktionen übergeben, in Klassen usw. gespeichert werden. Sie existieren unabhängig von den Variablen, auf die sie zeigen.
Derek Park
31
Brian, der Stack ist nicht relevant. Referenzen und Zeiger müssen keinen Platz auf dem Stapel beanspruchen. Sie können beide auf dem Heap zugeordnet werden.
Derek Park
22
Brian, die Tatsache, dass eine Variable (in diesem Fall ein Zeiger oder eine Referenz) Speicherplatz benötigt, bedeutet nicht, dass sie Speicherplatz auf dem Stapel benötigt. Zeiger und Referenzen können nicht nur zeigen auf dem Heap, können sie tatsächlich sein zugeteilt auf dem Heap.
Derek Park
38
Ein weiterer wichtiger Unterschied: Referenzen können nicht in ein Array gestopft werden
Johannes Schaub - Litb
383
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 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!
Warum ich C ++ - Referenzen für nützlich halte
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.
@kriss: Nein, Sie können auch eine baumelnde Referenz erhalten, indem Sie eine automatische Variable als Referenz zurückgeben.
Ben Voigt
12
@kriss: Im allgemeinen Fall ist es für einen Compiler praktisch unmöglich, dies zu erkennen. Stellen Sie sich eine Mitgliedsfunktion vor, die einen Verweis auf eine Klassenmitgliedsvariable zurückgibt: Das ist sicher und sollte vom Compiler nicht verboten werden. Dann ruft ein Aufrufer, der eine automatische Instanz dieser Klasse hat, diese Mitgliedsfunktion auf und gibt die Referenz zurück. Presto: baumelnde Referenz. Und ja, es wird Ärger verursachen, @kriss: das ist mein Punkt. Viele Leute behaupten, dass ein Vorteil von Referenzen gegenüber Zeigern darin besteht, dass Referenzen immer gültig sind, aber es ist einfach nicht so.
Ben Voigt
4
@kriss: Nein, ein Verweis auf ein Objekt mit automatischer Speicherdauer unterscheidet sich stark von einem temporären Objekt. Wie auch immer, ich habe nur ein Gegenbeispiel zu Ihrer Aussage geliefert, dass Sie eine ungültige Referenz nur erhalten können, indem Sie einen ungültigen Zeiger dereferenzieren. Christoph ist richtig - Referenzen sind nicht sicherer als Zeiger. Ein Programm, das ausschließlich Referenzen verwendet, kann dennoch die Typensicherheit beeinträchtigen.
Ben Voigt
7
Referenzen sind keine Art Zeiger. Sie sind ein neuer Name für ein vorhandenes Objekt.
Catphive
18
@catphive: true, wenn Sie sich für die Sprachsemantik entscheiden, nicht true, wenn Sie sich die Implementierung tatsächlich ansehen. C ++ ist eine weitaus "magischere" Sprache als C, und wenn Sie die Magie aus Referenzen entfernen, erhalten Sie einen Zeiger
Christoph
191
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.
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.
Nun, s3_copy erstellt eine temporäre Datei und kopiert sie dann in s3_copy, während s3_reference die temporäre Datei direkt verwendet. Um wirklich pedantisch zu sein, müssen Sie sich die Rückgabewertoptimierung ansehen, bei der der Compiler im ersten Fall die Kopierkonstruktion entfernen darf.
Matt Price
6
@digitalSurgeon: Die Magie dort ist ziemlich mächtig. Die Objektlebensdauer wird durch die Tatsache der 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.
David Rodríguez - Dribeas
9
Update für C ++ 11: Der letzte Satz sollte lauten: "Sie können eine nicht konstante Wertreferenz nicht an eine temporäre Referenz binden ", da Sie eine nicht konstante Wertreferenz an eine temporäre Referenz binden können und dasselbe lebenslange Verhalten aufweist.
Oktalist
4
@AhmadMushtaq: Die Hauptverwendung hierfür sind abgeleitete Klassen . Wenn keine Vererbung erforderlich ist, können Sie auch eine Wertesemantik verwenden, die aufgrund der RVO / Move-Konstruktion billig oder kostenlos ist. Aber wenn Sie Animal x = fast ? getHare() : getTortoise()dann haben, xwird das klassische Schnittproblem konfrontiert, während Animal& x = ...es richtig funktioniert.
Arthur Tacca
128
Abgesehen von syntaktischem Zucker ist eine Referenz ein constZeiger ( 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.
Ich denke, das ist bei weitem die beste Antwort. Andere sprechen über Referenzen und Hinweise, als wären sie verschiedene Tiere, und legen dann dar, wie sie sich im Verhalten unterscheiden. Es macht die Dinge imho nicht einfacher. Ich habe Referenzen immer so verstanden, dass sie einen T* constanderen syntaktischen Zucker enthalten (was dazu führt, dass viele * und & aus Ihrem Code entfernt werden).
Carlo Wood
2
"Das Ziel eines Konstantenzeigers kann ersetzt werden, indem seine Adresse verwendet und ein Konstanten-Cast verwendet wird." Dies ist undefiniertes Verhalten. Weitere Informationen finden Sie unter stackoverflow.com/questions/25209838/… .
dgnuff
1
Der Versuch, entweder den Referenten einer Referenz oder den Wert eines const-Zeigers (oder eines beliebigen const-Skalars) zu ändern, ist ungleich. Was Sie tun können: Entfernen Sie eine Konstantenqualifikation, 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.
Neugieriger
1
Der Unterschied ist hier UB versus buchstäblich unmöglich. In C ++ gibt es keine Syntax, mit der Sie die Referenzpunkte ändern können.
Nicht unmöglich, schwieriger, Sie können einfach auf den Speicherbereich des Zeigers zugreifen, der diese Referenz modelliert, und ihren Inhalt ändern. Das kann man sicher machen.
Nicolas Bousquet
126
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.
classMyClass{...virtualvoidDoSomething(int,int,int,int,int);};voidFoo(constMyClass& 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 ifKlausel 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.
Der betreffende Code enthält undefiniertes Verhalten. Technisch gesehen können Sie mit einem Nullzeiger nichts anderes tun, als ihn zu setzen und zu vergleichen. Sobald Ihr Programm undefiniertes Verhalten aufruft, kann es alles tun, einschließlich des Anscheins, als würde es korrekt funktionieren, bis Sie dem großen Chef eine Demo geben.
KeithB
9
mark hat ein gültiges Argument. Das Argument, dass ein Zeiger NULL sein könnte und Sie dies überprüfen müssen, ist auch nicht real: Wenn Sie sagen, dass eine Funktion nicht NULL erfordert, muss der Aufrufer dies tun. Wenn der Anrufer dies nicht tut, ruft er undefiniertes Verhalten auf. Genau wie Mark mit der schlechten Referenz
Johannes Schaub - litb
13
Die Beschreibung ist falsch. Dieser Code erstellt möglicherweise eine Referenz, die NULL ist, oder auch nicht. Sein Verhalten ist undefiniert. Es könnte eine vollkommen gültige Referenz erstellen. Möglicherweise wird überhaupt keine Referenz erstellt.
David Schwartz
7
@ David Schwartz, wenn ich darüber sprechen würde, wie die Dinge gemäß dem Standard funktionieren müssten, wären Sie richtig. Aber das ist nicht das, worüber ich spreche - ich spreche über das tatsächlich beobachtete Verhalten mit einem sehr beliebten Compiler und die Extrapolation basierend auf meinem Wissen über typische Compiler und CPU-Architekturen auf das, was wahrscheinlich passieren wird. Wenn Sie glauben, dass Referenzen Zeigern überlegen sind, weil sie sicherer sind und nicht der Meinung sind, dass Referenzen schlecht sein können, werden Sie eines Tages genauso wie ich von einem einfachen Problem überrascht sein.
Mark Ransom
6
Das Dereferenzieren eines Nullzeigers ist falsch. Jedes Programm, das dies tut, selbst um eine Referenz zu initialisieren, ist falsch. Wenn Sie eine Referenz von einem Zeiger aus initialisieren, sollten Sie immer überprüfen, ob der Zeiger gültig ist. Selbst wenn dies erfolgreich ist, kann das zugrunde liegende Objekt jederzeit gelöscht werden, wobei der Verweis auf ein nicht vorhandenes Objekt verweist, oder? Was Sie sagen, ist gutes Zeug. Ich denke, das eigentliche Problem hier ist, dass die Referenz NICHT auf "Nullheit" überprüft werden muss, wenn Sie eine sehen und der Zeiger mindestens aktiviert werden sollte.
t0rakka
115
Sie haben den wichtigsten Teil vergessen:
Der Mitgliederzugriff mit Zeigern verwendet den ->
Mitgliederzugriff mit Verweisen.
foo.barist deutlich überlegen foo->barin der gleichen Art und Weise , dass vi ist klar zu überlegen Emacs :-)
@Orion Edwards> Mitgliederzugriff mit Zeigern verwendet - >> Mitgliederzugriff mit Referenzen verwendet. Dies ist nicht 100% wahr. Sie können einen Verweis auf einen Zeiger haben. In diesem Fall würden Sie mit -> struct Node {Node * next auf Mitglieder des nicht referenzierten Zeigers zugreifen. }; Knoten * zuerst; // p ist eine Referenz auf einen Zeiger void foo (Node * & p) {p-> next = first; } Node * bar = neuer Node; foo (Bar); - OP: Kennen Sie die Konzepte von rWerten und lWerten?
3
Smart Pointers haben beides. (Methoden für die Smart-Pointer-Klasse) und -> (Methoden für den zugrunde liegenden Typ).
JBRWilkinson
1
@ user6105 Orion Edwards Aussage ist tatsächlich 100% wahr. "Zugriff auf Mitglieder des Zeigers, auf den nicht verwiesen wird" Ein Zeiger hat keine Mitglieder. Das Objekt, auf das sich der Zeiger bezieht, hat Mitglieder, und der Zugriff auf diese Objekte ermöglicht ->genau wie der Zeiger selbst Verweise auf Zeiger.
Max Truxa
1
warum ist das .und ->hat etwas mit vi und emacs zu tun :)
artm
10
@artM - es war ein Witz und macht für nicht-englische Muttersprachler wahrscheinlich keinen Sinn. Entschuldigen Sie. Zu erklären, ob vi besser ist als Emacs, ist völlig subjektiv. Einige Leute denken, vi sei weit überlegen, andere denken genau das Gegenteil. Ebenso denke ich, dass das Verwenden .besser ist als das Verwenden ->, aber genau wie vi vs emacs ist es völlig subjektiv und man kann nichts beweisen
Orion Edwards
74
Referenzen sind Zeigern sehr ähnlich, wurden jedoch speziell entwickelt, um bei der Optimierung von Compilern hilfreich zu sein.
Referenzen sind so konzipiert, dass es für den Compiler wesentlich einfacher ist, zu verfolgen, welche Referenz Aliase welche Variablen sind. Zwei Hauptmerkmale sind sehr wichtig: keine "Referenzarithmetik" und keine Neuzuweisung von Referenzen. Auf diese Weise kann der Compiler herausfinden, welcher Verweis auf welche Variablen zum Zeitpunkt der Kompilierung verweist.
Referenzen dürfen sich auf Variablen beziehen, die keine Speicheradressen haben, wie sie der Compiler in Register einfügt. Wenn Sie die Adresse einer lokalen Variablen verwenden, ist es für den Compiler sehr schwierig, sie in ein Register aufzunehmen.
Als Beispiel:
void maybeModify(int& x);// may modify x in some wayvoid hurtTheCompilersOptimizer(short size,intarray[]){// 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,intarray[]){// 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.registerint a0 = a[0];registerint a1 = a[1];// access a[1] oncefor(int i =2; i <(int)size; i++){
maybeModify(a0);// Give maybeModify a reference to a registerarray[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 wayvoid hurtTheCompilersOptimizer(short size,intarray[]){// 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.
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 refverlängert C ++ jedoch die Lebensdauer dieses temporären Objekts, bis refder Gültigkeitsbereich überschritten wird.
Der Körper muss zwar sichtbar sein. Es ist jedoch wesentlich einfacher zu bestimmen, dass maybeModifynicht die Adresse von irgendetwas in Beziehung xsteht, als zu beweisen, dass eine Reihe von Zeigerarithmetik nicht auftritt.
Cort Ammon
Ich glaube, der Optimierer tut bereits, dass "eine Reihe von Zeigerarithmetiken nicht auftreten" aus einer Reihe anderer Gründe überprüft wird.
Ben Voigt
"Referenzen sind Zeigern sehr ähnlich" - semantisch in geeigneten Kontexten - aber in Bezug auf generierten Code, nur in einigen Implementierungen und nicht durch eine Definition / Anforderung. Ich weiß, dass Sie darauf hingewiesen haben, und ich bin mit keinem Ihrer Beiträge in praktischer Hinsicht nicht einverstanden, aber wir haben bereits zu viele Probleme mit Leuten, die zu viel in Kurzbeschreibungen wie "Referenzen sind wie / normalerweise als Zeiger implementiert" lesen. .
underscore_d
Ich habe das Gefühl, dass jemand fälschlicherweise einen Kommentar nach dem Vorbild von veraltet markiert hat void maybeModify(int& x) { 1[&x]++; }, über den in den anderen obigen Kommentaren gesprochen wird
Ben Voigt
68
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 averwiesen wird. Sie fügen dem Speicherinhalt, der den Wert von enthält, nur einen anderen Namen hinzu a. Dieser Speicher hat jetzt zwei Namen aund bund 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 ist eine Referenz auf den l-Wert, nicht unbedingt auf eine Variable. Aus diesem Grund ist es einem Zeiger viel näher als einem echten Alias (einem Konstrukt zur Kompilierungszeit). Beispiele für Ausdrücke, auf die verwiesen werden kann, sind * p oder sogar * p ++
5
Richtig, ich habe nur darauf hingewiesen, dass eine Referenz eine neue Variable möglicherweise nicht immer so auf den Stapel schiebt, wie es ein neuer Zeiger tut.
Vincent Robert
1
@ VincentRobert: Es verhält sich wie ein Zeiger ... Wenn die Funktion inline ist, werden sowohl Referenz als auch Zeiger weg optimiert. Bei einem Funktionsaufruf muss die Adresse des Objekts an die Funktion übergeben werden.
Ben Voigt
1
int * p = NULL; int & r = * p; Referenz zeigt auf NULL; if (r) {} -> boOm;)
sree
2
Dieser Fokus auf die Kompilierungsphase scheint nett zu sein, bis Sie sich daran erinnern, dass Referenzen zur Laufzeit weitergegeben werden können. Zu diesem Zeitpunkt wird das statische Aliasing aus dem Fenster entfernt. (Und dann werden Referenzen normalerweise als Zeiger implementiert, aber der Standard erfordert diese Methode nicht.)
Ein Gegenbeispiel finden Sie in der Antwort von Mark Ransom. Dies ist der am häufigsten behauptete Mythos über Referenzen, aber es ist ein Mythos. Die einzige Garantie, die Sie standardmäßig haben, ist, dass Sie sofort UB haben, wenn Sie eine NULL-Referenz haben. Aber das ist vergleichbar mit der Aussage "Dieses Auto ist sicher, es kann niemals von der Straße
abkommen
17
@cmaster: In einem gültigen Programm kann eine Referenz nicht null sein. Aber ein Zeiger kann. Dies ist kein Mythos, dies ist eine Tatsache.
user541686
8
@Mehrdad Ja, gültige Programme bleiben unterwegs. Es gibt jedoch keine Verkehrsbarriere, um zu erzwingen, dass Ihr Programm dies tatsächlich tut. In großen Teilen der Straße fehlen tatsächlich Markierungen. So ist es extrem einfach, nachts von der Straße zu kommen. Und es ist entscheidend für das Debuggen solcher Fehler, dass Sie wissen, dass dies passieren kann: Die Nullreferenz kann sich verbreiten, bevor sie Ihr Programm zum Absturz bringt, genau wie ein Nullzeiger. Und wenn doch, haben Sie 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.
cmaster
4
int * p = NULL; int & r = * p; Referenz zeigt auf NULL; if (r) {} -> boOm;) -
sree
10
@sree 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 .
CDOWIE
35
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.
"Eine Referenz bezieht sich immer auf ein Objekt" ist einfach völlig falsch
Ben Voigt
32
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.
Sie können die Adresse einer Referenz nicht übernehmen, dies bedeutet jedoch nicht, dass sie physisch keinen Speicherplatz beansprucht. Abgesehen von Optimierungen können sie dies mit Sicherheit.
Leichtigkeitsrennen im Orbit
2
Ungeachtet der Auswirkung ist "Eine Referenz auf dem Stapel nimmt überhaupt keinen Platz ein" offensichtlich falsch.
Leichtigkeitsrennen im Orbit
1
@Tomalak, na ja, das hängt auch vom Compiler ab. Aber ja, das zu sagen ist etwas verwirrend. Ich nehme an, es wäre weniger verwirrend, das einfach zu entfernen.
MSN
1
In einem bestimmten Fall kann es sein oder nicht. "Es ist nicht so" als kategorische Behauptung ist also falsch. Sag ich doch. :) [Ich kann mich nicht erinnern, was der Standard zu diesem Thema sagt; Die Regeln der Referenzmitglieder können eine allgemeine Regel für "Referenzen können Platz beanspruchen" enthalten, aber ich habe meine Kopie des Standards hier am Strand nicht dabei: D]
Leichtigkeitsrennen im Orbit
20
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:
intTom(0);int& alias_Tom =Tom;
alias_Tomkann 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.
Und wenn eine Klasse eine Referenzvariable hat, sollte sie entweder mit einem Nullptr oder einem gültigen Objekt in der Initialisierungsliste initialisiert werden.
Misgevolution
1
Der Wortlaut in dieser Antwort ist zu verwirrend, als dass er wirklich von Nutzen wäre. @Misgevolution, empfehlen Sie den Lesern ernsthaft, eine Referenz mit a zu initialisieren nullptr? Hast du tatsächlich einen anderen Teil dieses Threads gelesen oder ...?
underscore_d
1
Es tut mir leid, dass ich das gesagt habe. Zu diesem Zeitpunkt muss mir der Schlaf entzogen worden sein. 'Mit nullptr initialisieren' ist völlig falsch.
Misgevolution
19
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:
Dies ist nicht das, was der C ++ - Standard sagt, und es ist nicht erforderlich, dass der Compiler Referenzen auf die in Ihrer Antwort beschriebene Weise implementiert.
Jogojapan
@jogojapan: Jede Möglichkeit, die für einen C ++ - Compiler zum Implementieren einer Referenz gültig ist, ist auch eine gültige Methode zum Implementieren eines constZeigers. Diese Flexibilität beweist nicht, dass es einen Unterschied zwischen einer Referenz und einem Zeiger gibt.
Ben Voigt
2
@BenVoigt Es mag sein, dass jede gültige Implementierung von einer auch eine gültige Implementierung der anderen ist, aber das folgt nicht auf offensichtliche Weise aus den Definitionen dieser beiden Konzepte. Eine gute Antwort hätte von den Definitionen ausgegangen und gezeigt, warum die Behauptung, dass die beiden letztendlich gleich sind, wahr ist. Diese Antwort scheint eine Art Kommentar zu einigen der anderen Antworten zu sein.
Jogojapan
Eine Referenz ist ein anderer Name, der einem Objekt gegeben wird. Der Compiler darf jede Art von Implementierung haben, solange Sie den Unterschied nicht erkennen können, wird dies als "Als-ob" -Regel bezeichnet. Der wichtige Teil hier ist, dass Sie den Unterschied nicht erkennen können. Wenn Sie feststellen, dass ein Zeiger keinen Speicher hat, ist der Compiler fehlerhaft. Wenn Sie feststellen, dass eine Referenz keinen Speicher hat, ist der Compiler weiterhin konform.
SP2danny
18
Die direkte Antwort
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 .
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:
Objekttypen können cvQualifizierer der obersten Ebene haben . Referenzen können nicht.
Variablen von Objekttypen belegen Speicher gemäß der abstrakten Maschinensemantik . Die Referenz belegt keinen Speicherplatz (Einzelheiten finden Sie im Abschnitt über Missverständnisse weiter unten).
Spezielle Regeln für &&Parameter (als "Weiterleitungsreferenzen"), die auf dem Reduzieren von Referenzen während der Ableitung von Vorlagenparametern basieren, ermöglichen eine "perfekte Weiterleitung" von Parametern.
Referenzen haben spezielle Regeln für die Initialisierung. Die Lebensdauer der als Referenztyp deklarierten Variablen kann sich über die Erweiterung von normalen Objekten unterscheiden.
Übrigens std::initializer_listfolgen 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.)
Es ist nicht angegeben, ob eine Referenz gespeichert werden muss oder nicht.
Beachten Sie, dass dies semantische Eigenschaften sind.
Pragmatik
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.
Manchmal erfordern die Sprachregeln ausdrücklich die Verwendung bestimmter Typen. Wenn Sie diese Funktionen verwenden möchten, befolgen Sie die Regeln.
Kopierkonstruktoren erfordern bestimmte Typen von cv - &Referenztypen als 1. Parametertyp. (Und normalerweise sollte es constqualifiziert sein.)
Verschiebungskonstruktoren erfordern bestimmte Typen von cv - &&Referenztypen als 1. Parametertyp. (Und normalerweise sollte es keine Qualifikanten geben.)
Spezifische Überladungen von Operatoren erfordern Referenz- oder Nichtreferenztypen. Zum Beispiel:
operator=Als spezielle Elementfunktionen überladen, sind Referenztypen erforderlich, die dem ersten Parameter von Kopier- / Verschiebungskonstruktoren ähneln.
Postfix ++erfordert Dummy int.
...
Wenn Sie wissen, dass die Übergabe von Werten (dh die Verwendung von Nichtreferenztypen) ausreichend ist, verwenden Sie diese direkt, insbesondere wenn Sie eine Implementierung verwenden, die die von C ++ 17 vorgeschriebene Elision von Kopien unterstützt. ( Warnung : Es kann jedoch sehr kompliziert sein , ausführlich über die Notwendigkeit nachzudenken .)
Wenn Sie einige Handles mit Besitz betreiben möchten, verwenden Sie intelligente Zeiger wie unique_ptrund shared_ptr(oder sogar selbst mit Homebrew- Zeigern , wenn diese undurchsichtig sein sollen ) anstelle von rohen Zeigern.
Wenn Sie einige Iterationen über einen Bereich ausführen, verwenden Sie Iteratoren (oder einige Bereiche, die noch nicht von der Standardbibliothek bereitgestellt werden) anstelle von Rohzeigern, es sei denn, Sie sind davon überzeugt, dass Rohzeiger in bestimmten Bereichen besser abschneiden (z. B. für weniger Headerabhängigkeiten) Fälle.
Wenn Sie wissen, dass die Übergabe von Werten ausreichend ist und Sie eine explizite nullbare Semantik wünschen, verwenden Sie Wrapper-ähnliche std::optionalanstelle von rohen Zeigern.
Wenn Sie wissen, dass die Übergabe von Werten aus den oben genannten Gründen nicht ideal ist und Sie keine nullbare Semantik wünschen, verwenden Sie {lvalue, rvalue, forwarding} -Referenzen.
Selbst wenn Sie eine Semantik wie einen herkömmlichen Zeiger wünschen, gibt es oft etwas Passenderes, wie observer_ptrin Library Fundamental TS.
Die einzigen Ausnahmen können in der aktuellen Sprache nicht umgangen werden:
Wenn Sie oben intelligente Zeiger implementieren, müssen Sie sich möglicherweise mit Rohzeigern befassen.
Bestimmte Sprachinteroperationsroutinen erfordern Zeiger wie z 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.)
Funktionszeiger können aus Lambda-Ausdrücken ohne Captures konvertiert werden, Funktionsreferenzen nicht. In solchen Fällen müssen Sie Funktionszeiger in nicht generischem Code verwenden, auch wenn Sie absichtlich keine nullbaren Werte möchten.
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:
Sie müssen für C-APIs alten Stils sorgen.
Sie müssen die ABI-Anforderungen bestimmter C ++ - Implementierungen erfüllen.
Sie müssen zur Laufzeit mit verschiedenen Sprachimplementierungen (einschließlich verschiedener Assemblys, Sprachlaufzeit und FFI einiger Client-Sprachen auf hoher Ebene) zusammenarbeiten, basierend auf Annahmen bestimmter Implementierungen.
In einigen extremen Fällen müssen Sie die Effizienz der Übersetzung (Kompilierung und Verknüpfung) verbessern.
In einigen extremen Fällen müssen Sie das Aufblähen von Symbolen vermeiden.
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:
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);return0;}
Weitere Informationen zum Verweis auf Zeiger finden Sie im Folgenden:
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;
Sie können schreiben, &r + offsetwo rals Referenz deklariert wurde
MM
15
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 .
Referenzen und Zeiger sind beide Handles. Beide geben Ihnen die Semantik an, in der Ihr Objekt als Referenz übergeben wird, aber das Handle wird kopiert. Kein Unterschied. (Es gibt auch andere Möglichkeiten, Handles zu haben, wie zum Beispiel einen Schlüssel zum Nachschlagen in einem Wörterbuch)
Ben Voigt
Ich habe auch so gedacht. Lesen Sie jedoch den verlinkten Artikel, in dem beschrieben wird, warum dies nicht der Fall ist.
Andrzej
2
@Andrzj: Das ist nur eine sehr lange Version des einzelnen Satzes in meinem Kommentar: Das Handle wird kopiert.
Ben Voigt
Ich brauche weitere Erklärungen zu diesem Thema "Das Handle wird kopiert". Ich verstehe eine Grundidee, aber ich denke, dass sowohl die Referenz als auch der Zeiger physisch auf den Speicherort der Variablen zeigen. Ist es so, als würde ein Alias die Wertvariable speichern und aktualisieren, wenn sich der Wert der Variablen ändert oder etwas anderes? Ich bin ein Neuling, und bitte kennzeichnen Sie es nicht als dumme Frage.
Asim
1
@Andrzej Falsch. In beiden Fällen tritt eine Wertübergabe auf. Die Referenz wird als Wert übergeben, und der Zeiger wird als Wert übergeben. Anders zu sagen verwirrt Neulinge.
Miles Rout
14
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 heapdelete 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;// okvoid& 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.
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.
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.
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 Tund 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.
" implizite Besetzung " Eine Besetzung ist ein Syntaxkonstrukt, das in der Grammatik existiert. Eine Besetzung ist immer explizit
Neugieriger
9
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 constZeiger ü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 -
Für eine Referenz müssen Sie ein tatsächliches Fenster angeben.
Ein Zeiger erfordert die Position eines Platzes auf dem Bildschirm, von dem Sie sicherstellen, dass er null oder mehr Instanzen dieses Fenstertyps enthält.
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 "="
In diesem Fall wird die Referenz nur beim Lesen verwendet. Wir können diese const-Referenz auch mit "const_cast" nicht ändern, da "const_cast" nur Zeiger akzeptiert, die keine Referenz sind.
Sie machen eine Besetzung als Referenz, ohne eine Referenz zu gießen. Versuchen Sie dies; const int & i =; const_cast <int> (i); Ich versuche, die Konstanz der Referenz wegzuwerfen, um das Schreiben und Zuweisen eines neuen Werts zur Referenz zu ermöglichen, aber dies ist nicht möglich. bitte konzentrieren !!
dhokar.w
5
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 */
int &x = *(int*)0;
auf gcc. Die Referenz kann tatsächlich auf NULL verweisen.Antworten:
Ein Zeiger kann neu zugewiesen werden:
Eine Referenz kann und muss bei der Initialisierung nicht zugewiesen werden:
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.
Sie können Zeiger auf Zeiger auf Zeiger haben, die zusätzliche Indirektionsebenen bieten. Während Referenzen nur eine Indirektionsebene bieten.
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 angebennullptr
. Wenn Sie sich genug anstrengen, können Sie auch einen Verweis auf einen Zeiger haben, den dieser Verweis enthalten kannnullptr
.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):
Dies macht die
const&
Verwendung in Argumentlisten usw. sicherer.quelle
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 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 :
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!
Warum ich C ++ - Referenzen für nützlich halte
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.
quelle
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.
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.quelle
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ährendAnimal& x = ...
es richtig funktioniert.Abgesehen von syntaktischem Zucker ist eine Referenz ein
const
Zeiger ( kein Zeiger auf aconst
). 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.
quelle
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.
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.
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, dieif
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.
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 .
quelle
Sie haben den wichtigsten Teil vergessen:
Der Mitgliederzugriff mit Zeigern verwendet den
->
Mitgliederzugriff mit Verweisen
.
foo.bar
ist deutlich überlegenfoo->bar
in der gleichen Art und Weise , dass vi ist klar zu überlegen Emacs :-)quelle
->
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 beweisenReferenzen sind Zeigern sehr ähnlich, wurden jedoch speziell entwickelt, um bei der Optimierung von Compilern hilfreich zu sein.
Als Beispiel:
Ein optimierender Compiler kann erkennen, dass wir auf eine ganze Reihe von [0] und [1] zugreifen. Es würde gerne den Algorithmus optimieren, um:
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
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.
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 Referenzref
verlängert C ++ jedoch die Lebensdauer dieses temporären Objekts, bisref
der Gültigkeitsbereich überschritten wird.quelle
maybeModify
nicht die Adresse von irgendetwas in Beziehungx
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 wirdEigentlich 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:
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 hinzua
. Dieser Speicher hat jetzt zwei Namena
undb
und kann mit beiden Namen adressiert werden.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.
quelle
Eine Referenz kann niemals sein
NULL
.quelle
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:
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:
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.
quelle
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.
quelle
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.
quelle
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:
wird drucken:
Dies ist der Sprachmechanismus, mit dem ScopeGuard funktioniert.
quelle
Dies basiert auf dem Tutorial . Was geschrieben steht, macht es klarer:
Einfach um sich daran zu erinnern,
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:
alias_Tom
kann verstanden werden alsalias of a variable
(anders mittypedef
, was istalias of a type
)Tom
. Es ist auch in Ordnung, die Terminologie einer solchen Anweisung zu vergessen, um eine Referenz von zu erstellenTom
.quelle
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:
Es wird intern
quelle
const
Zeigers. Diese Flexibilität beweist nicht, dass es einen Unterschied zwischen einer Referenz und einem Zeiger gibt.Die direkte Antwort
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 :
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.Die Missverständnisse
Syntethischer Zucker
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+
.Lager
Die obige Aussage ist einfach falsch. Um solche Missverständnisse zu vermeiden, lesen Sie stattdessen die ISO C ++ - Regeln:
Aus [intro.object] / 1 :
Aus [dcl.ref] / 4 :
Beachten Sie, dass dies semantische Eigenschaften sind.
Pragmatik
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 esconst
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 Dummyint
.unique_ptr
undshared_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 Erweiterungvoid*
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:
Vorbehalte gegen Sprachneutralität
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. )
quelle
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:
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.
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:
quelle
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;
quelle
&r + offset
wor
als Referenz deklariert wurdeEs 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
quelle
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:
Welches gibt dies aus:
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:
Welches gibt dies aus:
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
quelle
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.
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.
quelle
Eine Referenz, die ein Parameter für eine Funktion ist, die inline ist, kann auch anders behandelt werden als ein Zeiger.
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.
quelle
Eine weitere interessante Verwendung von Referenzen besteht darin, ein Standardargument eines benutzerdefinierten Typs anzugeben:
Die Standardvariante verwendet den Aspekt "Konstante an einen temporären Bindebestand binden" von Referenzen.
quelle
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.
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
quelle
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 werdenT&
.(oder
std::reference_wrapper<T>
die immer noch auf einer impliziten Besetzung von basiertT&
)Vorlagen, die beide abdecken
T&
undT&&
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.
quelle
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:
Zurück in C kann ein Aufruf, der so aussieht,
fn(x)
nur als Wert übergeben werden, sodass er definitiv nicht geändert werden kannx
. Um ein Argument zu ändern, müssten Sie einen Zeiger übergebenfn(&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 perconst
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 erwartetnullptr
. Das heißt, diese Leute argumentieren, dass Funktionssignaturen wiefn3()
oben nicht erlaubt sein sollten. Die C ++ - Stilrichtlinien von Google sind ein Beispiel dafür.quelle
Vielleicht helfen einige Metaphern; Im Kontext Ihres Desktop-Bildschirmbereichs -
quelle
Unterschied zwischen Zeiger und Referenz
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:
Aber wir können nicht haben
int& p = 0;
und auchint& 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:
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:
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:
quelle
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.
quelle
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
quelle