[C++] Was sind die Unterschiede zwischen einer Zeigervariablen und einer Referenzvariablen in C ++?


Answers

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

Eine Referenz kann als konstanter Zeiger (nicht zu verwechseln mit einem Zeiger auf einen konstanten Wert!) Mit automatischer Indirektion verstanden werden, dh der Compiler wendet den Operator * für Sie an.

Alle Verweise müssen mit einem Wert ungleich Null initialisiert werden, andernfalls schlägt die Kompilierung fehl. Es ist auch nicht möglich, die Adresse eines Verweises zu erhalten - der Adressoperator gibt stattdessen die Adresse des referenzierten Wertes zurück - und es ist auch nicht möglich, Referenzen arithmetisch zu machen.

C-Programmierer mögen C ++ - Referenzen nicht mögen, da es nicht mehr offensichtlich ist, wenn eine Indirection stattfindet oder wenn ein Argument durch einen Wert oder durch einen Zeiger übergeben wird, ohne auf Funktionssignaturen zu schauen.

C ++ - Programmierer mögen es nicht, Zeiger zu verwenden, da sie als unsicher gelten - obwohl Verweise nicht wirklich sicherer sind als konstante Zeiger, außer in den einfachsten Fällen - nicht den Komfort einer automatischen Indirektion haben und eine andere semantische Bedeutung haben.

Betrachten Sie die folgende Aussage aus den C ++ - FAQ :

Obwohl eine Referenz häufig unter Verwendung einer Adresse in der zugrundeliegenden Assemblersprache implementiert wird, denken Sie bitte nicht an eine Referenz als einen lustig aussehenden Zeiger auf ein Objekt. Eine Referenz ist das Objekt. Es ist weder ein Zeiger auf das Objekt noch eine Kopie des Objekts. Es ist das Objekt.

Aber wenn eine Referenz wirklich das Objekt wäre, wie könnte es dangling 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 aliasieren!

Warum finde ich C ++ Referenzen nützlich?

Von einem C-Hintergrund her kommend, mögen C ++ - Verweise wie ein etwas albernes Konzept aussehen, aber man sollte sie immer noch anstelle von Zeigern verwenden, wo dies möglich ist: Automatische Indirektion ist praktisch, und Verweise werden besonders nützlich im Umgang mit RAII - aber nicht aus Sicherheitsgründen Vorteil, sondern weil sie das Schreiben von idiomatischem Code weniger unangenehm machen.

RAII ist eines der zentralen Konzepte von C ++, interagiert aber nicht-trivial mit der Semantik des Kopierens. Wenn Sie Objekte per Referenz übergeben, werden diese Probleme vermieden, da kein Kopieren erforderlich ist. Wenn Verweise in der Sprache nicht vorhanden wären, müssten Sie stattdessen Zeiger verwenden, die umständlicher zu verwenden sind, wodurch das Sprachdesign-Prinzip verletzt wird, dass die Best-Practice-Lösung einfacher als die Alternativen sein sollte.

Question

Ich weiß, Referenzen sind syntaktischer Zucker, so dass Code einfacher zu lesen und zu schreiben ist.

Aber was sind die Unterschiede?

Zusammenfassung aus Antworten und Links unten:

  1. Ein Zeiger kann beliebig oft neu zugewiesen werden, während eine Referenz nach dem Binden nicht erneut platziert werden kann.
  2. Zeiger können nirgendwo ( NULL ) zeigen, während Referenzen immer auf ein Objekt verweisen.
  3. Sie können die Adresse einer Referenz nicht wie mit Zeigern verwenden.
  4. Es gibt keine "Referenzarithmetik" (aber Sie können die Adresse eines Objekts nehmen, auf das eine Referenz zeigt und die Zeigerarithmetik wie in &obj + 5 ).

Um ein Missverständnis zu klären:

Der C ++ - Standard ist sehr vorsichtig, um nicht zu diktieren, wie ein Compiler Referenzen implementieren muss, aber jeder C ++ - Compiler implementiert Referenzen als Zeiger. Das heißt, eine Erklärung wie:

int &ri = i;

Wenn es nicht vollständig wegoptimiert ist, weist es die gleiche Speichermenge wie ein Zeiger zu und speichert die Adresse von i in diesem Speicher.

Ein Zeiger und eine Referenz belegen also dieselbe Speichermenge.

Generell,

  • Verwenden Sie Referenzen in Funktionsparametern und Rückgabetypen, um nützliche und selbstdokumentierende Schnittstellen zu definieren.
  • Verwenden Sie Zeiger, um Algorithmen und Datenstrukturen zu implementieren.

Interessantes lesen:




Both references and pointers can be used to change local variables of one function inside another function. Both of them can also be used to save copying of big objects when passed as arguments to functions or returned from functions, to get efficiency gain. Despite above similarities, there are following differences between references and pointers.

References are less powerful than pointers

1) Sobald eine Referenz erstellt wurde, kann sie später nicht mehr auf ein anderes Objekt verweisen; Es kann nicht erneut festgelegt werden. Dies wird oft mit Zeigern gemacht.

2) Referenzen können nicht NULL sein. Zeiger werden oft als NULL gesetzt, um anzuzeigen, dass sie nicht auf eine gültige Sache zeigen.

3) Eine Referenz muss bei der Deklaration initialisiert werden. Es gibt keine solche Beschränkung mit Zeigern

Due to the above limitations, references in C++ cannot be used for implementing data structures like Linked List, Tree, etc. In Java, references don't have above restrictions, and can be used to implement all data structures. References being more powerful in Java, is the main reason Java doesn't need pointers.

Referenzen sind sicherer und einfacher zu verwenden:

1) Sicherer: Da Referenzen initialisiert werden müssen, ist es unwahrscheinlich, dass Wild-Referenzen wie Wild-Pointer existieren. Es ist immer noch möglich, Referenzen zu haben, die sich nicht auf einen gültigen Standort beziehen

2) Einfacher zu verwenden: Verweise benötigen keinen Dereferenzierungsoperator, um auf den Wert zuzugreifen. Sie können wie normale Variablen verwendet werden. Der Operator '&' wird nur zum Zeitpunkt der Deklaration benötigt. Auf Elemente einer Objektreferenz kann auch mit dem Punktoperator ('.') Zugegriffen werden, im Gegensatz zu Zeigern, bei denen der Pfeiloperator (->) benötigt wird, um auf Mitglieder zuzugreifen.

Together with the above reasons, there are few places like copy constructor argument where pointer cannot be used. Reference must be used pass the argument in copy constructor. Similarly references must be used for overloading some operators like ++ .




Während Referenzen und Zeiger verwendet werden, um indirekt auf einen anderen Wert zuzugreifen, gibt es zwei wichtige Unterschiede zwischen Referenzen und Zeigern. Die erste besteht darin, dass eine Referenz immer auf ein Objekt verweist: Es ist ein Fehler, eine Referenz zu definieren, ohne sie zu initialisieren. Das Verhalten der Zuweisung ist der zweite wichtige Unterschied: Wenn Sie einer Referenz zuweisen, ändert sich das Objekt, an das die Referenz gebunden ist. Der Verweis auf ein anderes Objekt wird nicht erneut gebunden. Nach der Initialisierung verweist eine Referenz immer auf dasselbe zugrunde liegende Objekt.

Betrachten Sie diese zwei Programmfragmente. In der ersten weisen wir einen Zeiger einem anderen zu:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

Nach der Zuweisung bleibt das von pi adressierte Objekt unverändert. Die Zuweisung ändert den Wert von pi und weist auf ein anderes Objekt hin. Betrachten Sie nun ein ähnliches Programm, das zwei Referenzen zuweist:

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

This assignment changes ival, the value referenced by ri, and not the reference itself. After the assignment, the two references still refer to their original objects, and the value of those objects is now the same as well.




Another interesting use of references is to supply a default argument of a user-defined type:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

The default flavor uses the 'bind const reference to a temporary' aspect of references.




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 zu tun - aber wenn Sie es schaffen, werden Sie Ihre Haare reißen, um es zu finden. Referenzen sind in C ++ nicht von Natur aus sicher!

Technisch ist dies eine ungültige Referenz , keine Nullreferenz. C ++ unterstützt keine Null-Referenzen als Konzept, wie Sie es in anderen Sprachen finden. Es gibt auch andere Arten von ungültigen Referenzen. Jede ungültige Referenz wirft das Gespenst von undefiniertem Verhalten auf , genau wie ein ungültiger Zeiger dies tun würde.

Der eigentliche Fehler liegt in der Dereferenzierung des NULL-Zeigers vor der Zuweisung zu einer Referenz. Aber mir sind keine Compiler bekannt, die unter dieser Bedingung Fehler erzeugen - der Fehler breitet sich im Code weiter aus. Das macht dieses Problem so heimtückisch. Die meiste Zeit, wenn Sie einen NULL-Zeiger dereferenzieren, stürzen Sie direkt auf diesen Punkt und es braucht nicht viel Debugging, um es herauszufinden.

Mein Beispiel oben ist kurz und erfunden. Hier ist ein Beispiel aus der Praxis.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

Ich möchte wiederholen, dass der einzige Weg, um eine NULL-Referenz zu erhalten, durch missgebildeten Code ist, und sobald Sie es haben, erhalten Sie undefined Verhalten. Es macht nie Sinn, nach einer Nullreferenz zu suchen; zum Beispiel können Sie versuchen, if(&bar==NULL)... aber der Compiler könnte die Aussage aus der Existenz optimieren! Eine gültige Referenz kann niemals NULL sein, daher ist der Vergleich aus der Sicht des Compilers immer falsch, und es ist frei, die if Klausel als toten Code zu eliminieren - das ist die Essenz eines undefinierten Verhaltens.

Das Vermeiden von Dereferenzierung eines NULL-Zeigers, um eine Referenz zu erstellen, ist der richtige Weg, um Probleme zu vermeiden. Hier ist ein automatisierter Weg, dies zu erreichen.

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

Für einen älteren Blick auf dieses Problem von jemandem mit besseren Schreibfähigkeiten, siehe Null Referenzen von Jim Hyslop und Herb Sutter.

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




Another difference is that you can have pointers to a void type (and it means pointer to anything) but references to void are forbidden.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

I can't say I'm really happy with this particular difference. I would much prefer it would be allowed with the meaning reference to anything with an address and otherwise the same behavior for references. It would allow to define some equivalents of C library functions like memcpy using references.




Referenzen sind den Zeigern sehr ähnlich, aber sie sind speziell so gestaltet, dass sie zur Optimierung von Compilern hilfreich sind.

  • Referenzen sind so angelegt, dass es für den Compiler wesentlich einfacher ist, welche Referenzaliasnamen welche Variablen haben. Zwei Hauptmerkmale sind sehr wichtig: keine "Referenzarithmetik" und keine Neuzuordnung von Referenzen. Diese ermöglichen dem Compiler herauszufinden, welche Referenzen zur Kompilierungszeit welche Variablen aliasieren.
  • Verweise dürfen sich auf Variablen beziehen, die keine Speicheradressen haben, wie sie der Compiler in Register einträgt. Wenn Sie die Adresse einer lokalen Variablen nehmen, ist es sehr schwierig für den Compiler, sie in ein Register zu setzen.

Als Beispiel:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

Ein optimierender Compiler kann erkennen, dass wir ziemlich auf eine [0] und eine [1] zugreifen. Es würde gerne den Algorithmus optimieren, um:

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

Um eine solche Optimierung zu machen, muss bewiesen werden, dass nichts Array [1] während des Aufrufs ändern kann. Das ist ziemlich einfach zu machen. i ist nie kleiner als 2, daher kann sich array [i] niemals auf array [1] beziehen. maybeModify () erhält a0 als Referenz (Alias-Array [0]). Da es keine "Referenz" -Arithmetik gibt, muss der Compiler nur beweisen, dass vielleichtModify nie die Adresse von x bekommt, und es hat sich herausgestellt, dass nichts Array [1] ändert.

Es muss auch beweisen, dass es keine Möglichkeiten gibt, wie ein zukünftiger Aufruf a [0] lesen / schreiben könnte, während wir eine temporäre Registerkopie davon in a0 haben. Dies ist oft trivial, weil es in vielen Fällen offensichtlich ist, dass die Referenz niemals in einer permanenten Struktur wie einer Klasseninstanz gespeichert wird.

Jetzt mache dasselbe mit Zeigern

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

Das Verhalten ist das gleiche; nur jetzt ist es viel schwieriger zu beweisen, dass vielleicht Modify array [1] nicht ändert, 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 vielleichtModify, um zu beweisen, dass es niemals in & x + 1 schreibt. Es muss auch beweisen, dass es nie einen Zeiger abspeichert, der auf Array [0] verweisen kann, was gerecht ist so schwierig.

Moderne Compiler werden immer besser bei der statischen Analyse, aber es ist immer schön, ihnen zu helfen und Referenzen zu verwenden.

Abgesehen von solchen cleveren Optimierungen werden Compiler die Referenzen bei Bedarf tatsächlich in Zeiger umwandeln.




A reference is an alias for another variable whereas a pointer holds the memory address of a variable. References are generally used as function parameters so that the passed object is not the copy but the object itself.

    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. 



The difference is that non-constant pointer variable(not to be confused with a pointer to constant) may be changed at some time during program execution, requires pointer semantics to be used(&,*) operators, while references can be set upon initialization only(that's why you can set them in constructor initializer list only, but not somehow else) and use ordinary value accessing semantics. Basically references were introduced to allow support for operators overloading as I had read in some very old book. As somebody stated in this thread - pointer can be set to 0 or whatever value you want. 0(NULL, nullptr) means that the pointer is initialized with nothing. It is an error to dereference null pointer. But actually the pointer may contain a value that doesn't point to some correct memory location. References in their turn try not to allow a user to initialize a reference to something that cannot be referenced due to the fact that you always provide rvalue of correct type to it. Although there are a lot of ways to make reference variable be initialized to a wrong memory location - it is better for you not to dig this deep into details. On machine level both pointer and reference work uniformly - via pointers. Let's say in essential references are syntactic sugar. rvalue references are different to this - they are naturally stack/heap objects.




Abgesehen von syntaktischem Zucker ist eine Referenz ein const Zeiger ( kein Zeiger auf eine const ). Sie müssen festlegen, auf was Bezug genommen wird, wenn Sie die Referenzvariable deklarieren, und Sie können sie später nicht mehr ändern.

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

Das Ziel eines Const-Zeigers kann ersetzt werden, indem seine Adresse genommen und eine Const-Umwandlung verwendet wird.

Das Ziel einer Referenz kann in keiner Weise durch UB ersetzt werden.

Dies sollte dem Compiler erlauben, mehr Optimierung für eine Referenz durchzuführen.




It doesn't matter how much space it takes up since you can't actually see any side effect (without executing code) of whatever space it would take up.

On the other hand, one major difference between references and pointers is that temporaries assigned to const references live until the const reference goes out of scope.

Beispielsweise:

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!

This is the language mechanism that allows ScopeGuard to work.




I feel like there is yet another point that hasn't been covered here.

Unlike the pointers, references are syntactically equivalent to the object they refer to, ie any operation that can be applied to an object works for a reference, and with the exact same syntax (the exception is of course the initialization).

While this may appear superficial, I believe this property is crucial for a number of C++ features, for example:

  • Templates . Since template parameters are duck-typed, syntactic properties of a type is all that matters, so often the same template can be used with both T and T& .
    (or std::reference_wrapper<T> which still relies on an implicit cast to T& )
    Templates that cover both T& and T&& are even more common.

  • Lvalues . Consider the statement str[0] = 'X'; Without references it would only work for c-strings ( char* str ). Returning the character by reference allows user-defined classes to have the same notation.

  • Copy constructors . Syntactically it makes sense to pass objects to copy constructors, and not pointers to objects. But there is just no way for a copy constructor to take an object by value - it would result in a recursive call to the same copy constructor. This leaves references as the only option here.

  • Operator overloads . With references it is possible to introduce indirection to an operator call - say, operator+(const T& a, const T& b) while retaining the same infix notation. This also works for regular overloaded functions.

These points empower a considerable part of C++ and the standard library so this is quite a major property of references.




Ich habe eine Analogie für Referenzen und Zeiger, denke an Referenzen als einen anderen Namen für ein Objekt und Zeiger als die Adresse eines Objekts.

// receives an alias of an int, an address of an int and an int value
public void my_function(int& a,int* b,int c){
    int d = 1; // declares an integer named d
    int &e = d; // declares that e is an alias of d
    // using either d or e will yield the same result as d and e name the same object
    int *f = e; // invalid, you are trying to place an object in an address
    // imagine writting your name in an address field 
    int *g = f; // writes an address to an address
    g = &d; // &d means get me the address of the object named d you could also
    // use &e as it is an alias of d and write it on g, which is an address so it's ok
}



A reference to a pointer is possible in C++, but the reverse is not possible means a pointer to a reference isn't possible. A reference to a pointer provides a cleaner syntax to modify the pointer. Look at this example:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

And consider the C version of the above program. In C you have to use pointer to pointer (multiple indirection), and it leads to confusion and the program may look complicated.

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

Visit the following for more information about reference to pointer:

As I said, a pointer to a reference isn't possible. Try the following program:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}



At the risk of adding to confusion, I want to throw in some input, I'm sure it mostly depends on how the compiler implements references, but in the case of gcc the idea that a reference can only point to a variable on the stack is not actually correct, take this for example:

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

Which outputs this:

THIS IS A STRING
0xbb2070 : 0xbb2070

If you notice even the memory addresses are exactly the same, meaning the reference is successfully pointing to a variable on the heap! Now if you really want to get freaky, this also works:

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).*/
}

Which outputs this:

THIS IS A STRING

Therefore a reference IS a pointer under the hood, they both are just storing a memory address, where the address is pointing to is irrelevant, what do you think would happen if I called std::cout << str_ref; AFTER calling delete &str_ref? Well, obviously it compiles fine, but causes a segmentation fault at runtime because it's no longer pointing at a valid variable, we essentially have a broken reference that still exists (until it falls out of scope), but is useless.

In other words, a reference is nothing but a pointer that has the pointer mechanics abstracted away, making it safer and easier to use (no accidental pointer math, no mixing up '.' and '->', etc.), assuming you don't try any nonsense like my examples above ;)

Now regardless of how a compiler handles references, it will always have some kind of pointer under the hood, because a reference must refer to a specific variable at a specific memory address for it to work as expected, there is no getting around this (hence the term 'reference').

The only major rule that's important to remember with references is that they must be defined at the time of declaration (with the exception of a reference in a header, in that case it must be defined in the constructor, after the object it's contained in is constructed it's too late to define it).

Remember, my examples above are just that, examples demonstrating what a reference is, you would never want to use a reference in those ways! For proper usage of a reference there are plenty of answers on here already that hit the nail on the head




Links