Zeiger vs. Referenz


256

Was wäre besser, wenn Sie einer Funktion die ursprüngliche Variable geben, mit der sie arbeiten soll:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

oder:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: Gibt es einen Grund, einen über den anderen zu wählen?


1
Referenzen sind natürlich wertvoll, aber ich komme aus C, wo überall Zeiger sind. Man muss zuerst mit Zeigern vertraut sein, um den Wert von Referenzen zu verstehen.
Jay D

Wie passt dies zu einem Ziel wie der referenziellen Transparenz aus der funktionalen Programmierung? Was ist, wenn Sie immer möchten, dass Funktionen neue Objekte zurückgeben und den Status niemals intern mutieren, insbesondere nicht von Variablen, die an die Funktion übergeben werden. Gibt es eine Möglichkeit, dieses Konzept noch mit Zeigern und Referenzen in einer Sprache wie C ++ zu verwenden? (Beachten Sie, ich gehe davon aus, dass jemand bereits das Ziel der referenziellen Transparenz hat. Ich bin nicht daran interessiert, darüber zu sprechen, ob es ein gutes Ziel ist oder nicht.)
ely

Bevorzugen Sie Referenzen. Benutzerzeiger, wenn Sie keine Wahl haben.
Ferruccio

Antworten:


285

Meine Faustregel lautet:

Verwenden Sie Zeiger, wenn Sie mit ihnen Zeigerarithmetik betreiben möchten (z. B. Inkrementieren der Zeigeradresse, um ein Array zu durchlaufen) oder wenn Sie jemals einen NULL-Zeiger übergeben müssen.

Verwenden Sie anderweitig Referenzen.


10
Hervorragender Punkt bezüglich eines Zeigers, der NULL ist. Wenn Sie einen Zeigerparameter haben, müssen Sie entweder explizit überprüfen, ob er nicht NULL ist, oder alle Verwendungen der Funktion durchsuchen, um sicherzustellen, dass er niemals NULL ist. Dieser Aufwand ist für Referenzen nicht erforderlich.
Richard Corden

26
Erklären Sie, was Sie unter Arithmetik verstehen. Ein neuer Benutzer versteht möglicherweise nicht, dass Sie anpassen möchten, auf was der Zeiger zeigt.
Martin York

7
Martin, mit Arithmetik meine ich, dass Sie einen Zeiger auf eine Struktur übergeben, aber wissen, dass es sich nicht um eine einfache Struktur handelt, sondern um ein Array davon. In diesem Fall können Sie es entweder mit [] indizieren oder mit ++ / - für den Zeiger rechnen. Das ist der Unterschied auf den Punkt gebracht.
Nils Pipenbrinck

2
Martin, das geht nur mit Zeigern direkt. Nicht mit Referenzen. Sicher können Sie einen Zeiger auf eine Referenz nehmen und das Gleiche in der Praxis tun, aber wenn Sie dies tun, enden Sie mit sehr schmutzigem Code.
Nils Pipenbrinck

1
Was ist mit Polymorphismus (zB Base* b = new Derived())? Dies scheint ein Fall zu sein, der ohne Zeiger nicht behandelt werden kann.
Chris Redford

72

Ich denke wirklich, dass Sie von der Einrichtung der folgenden Codierungsrichtlinien für Funktionsaufrufe profitieren werden:

  1. constSeien Sie wie an allen anderen Orten immer korrekt.

    • Hinweis: Dies bedeutet unter anderem, dass nur Out-Werte (siehe Punkt 3) und Werte, die von value (siehe Punkt 4) übergeben werden, den constBezeichner nicht haben können.
  2. Übergeben Sie einen Wert nur per Zeiger, wenn der Wert 0 / NULL im aktuellen Kontext eine gültige Eingabe ist.

    • Begründung 1: Als Anrufer sehen Sie, dass sich alles, was Sie übergeben , in einem brauchbaren Zustand befinden muss.

    • Rationale 2: Wie genannt , Sie wissen , dass das, was kommt ist in einem benutzbaren Zustand. Daher muss für diesen Wert keine NULL-Prüfung oder Fehlerbehandlung durchgeführt werden.

    • Begründung 3: Die Rationales 1 und 2 werden vom Compiler durchgesetzt . Fangen Sie Fehler immer beim Kompilieren ab, wenn Sie können.

  3. Wenn ein Funktionsargument ein Out-Wert ist, übergeben Sie ihn als Referenz.

    • Begründung: Wir wollen Punkt 2 nicht brechen ...
  4. Wählen Sie "Wert übergeben" anstelle von "Konstante übergeben " nur, wenn der Wert ein POD ( Plain old Datastructure ) oder klein genug (speichertechnisch) oder auf andere Weise billig genug (zeitlich) zum Kopieren ist.

    • Begründung: Vermeiden Sie unnötige Kopien.
    • Hinweis: Klein genug und billig genug sind keine absoluten Messgrößen.

Es fehlt die Richtlinie, wenn: ... "wann const &" verwendet werden soll ... ... Die Richtlinie 2 sollte für [in] -Werte geschrieben werden. Übergeben Sie den Zeiger nur, wenn NULL gültig ist. Andernfalls verwenden Sie die const-Referenz (oder für ". kleine "Objekte, Kopie) oder Referenz, wenn es sich um einen [out] -Wert handelt. Ich überwache diesen Beitrag, um möglicherweise einen +1 hinzuzufügen.
paercebal

Punkt 1 behandelt den von Ihnen beschriebenen Fall.
Johann Gerell

Es ist etwas schwierig, einen Out-Parameter als Referenz zu übergeben, wenn er nicht standardmäßig konstruierbar ist. Das ist in meinem Code ziemlich häufig - der ganze Grund, warum eine Funktion dieses Out-Objekt erstellt, ist, dass es nicht trivial ist.
MSalters

@MSalters: Wenn Sie den Speicher innerhalb der Funktion zuweisen möchten (was ich denke, was Sie meinen), warum nicht einfach einen Zeiger auf den zugewiesenen Speicher zurückgeben?
Kleist

@Kleist: Im Namen von @MSalters gibt es viele mögliche Gründe. Zum einen haben Sie möglicherweise bereits Speicher zum Füllen zugewiesen, z. B. eine Vorgröße std::vector<>.
Johann Gerell

24

Dies ist letztendlich subjektiv. Die bisherige Diskussion ist nützlich, aber ich glaube nicht, dass es eine richtige oder entscheidende Antwort darauf gibt. Viel hängt von den Stilrichtlinien und Ihren jeweiligen Bedürfnissen ab.

Während es mit einem Zeiger einige verschiedene Funktionen gibt (unabhängig davon, ob etwas NULL sein kann oder nicht), besteht der größte praktische Unterschied für einen Ausgabeparameter in der reinen Syntax. Der C ++ - Style Guide von Google ( https://google.github.io/styleguide/cppguide.html#Reference_Arguments ) schreibt beispielsweise nur Zeiger für Ausgabeparameter vor und lässt nur Verweise zu, die const sind. Die Argumentation ist Lesbarkeit: Etwas mit Wertsyntax sollte keine zeigersemantische Bedeutung haben. Ich behaupte nicht, dass dies notwendigerweise richtig oder falsch ist, aber ich denke, der Punkt hier ist, dass es um Stil geht, nicht um Korrektheit.


Was bedeutet es, dass Referenzen eine Wertsyntax haben, aber eine zeigersemantische Bedeutung haben?
Eric Andrew Lewis

Es sieht so aus, als würden Sie eine Kopie übergeben, da der Teil "Referenzübergabe" nur aus der Funktionsdefinition (Wertesyntax) hervorgeht. Sie kopieren jedoch nicht den übergebenen Wert, sondern übergeben im Wesentlichen einen Zeiger unter der Haube, der dies ermöglicht die Funktion zum Ändern Ihres Wertes.
Phant0m

Man sollte nicht vergessen, dass der Google C ++ - Styleguide sehr verabscheut ist.
Deduplikator

7

Sie sollten einen Zeiger übergeben, wenn Sie den Wert der Variablen ändern möchten. Obwohl die technische Übergabe einer Referenz oder eines Zeigers identisch ist, ist die Übergabe eines Zeigers in Ihrem Anwendungsfall besser lesbar, da dadurch die Tatsache "angekündigt" wird, dass der Wert von der Funktion geändert wird.


2
Wenn Sie die Richtlinien von Johann Gerell befolgen, kündigt eine nicht konstante Referenz auch eine veränderbare Variable an, sodass der Zeiger diesen Vorteil hier nicht hat.
Alexander Kondratskiy

4
@AlexanderKondratskiy: Sie verpassen den Punkt ... Sie können an der Aufrufstelle nicht sofort sehen, ob die aufgerufene Funktion einen Parameter als constoder ohne constReferenz akzeptiert , aber Sie können sehen, ob der Parameter ala &xvs. übergeben wurde x, und verwenden diese Konvension, um zu kodieren, ob der Parameter geändert werden kann. (Das heißt, es gibt Zeiten, in denen Sie einen constZeiger übergeben möchten , daher ist die Konvension nur ein Hinweis. Der Verdacht, dass etwas geändert werden könnte, wenn es nicht sein wird, ist weniger gefährlich als der Gedanke, dass es nicht sein wird, wenn es sein wird ....)
Tony Delroy

5

Wenn Sie einen Parameter haben, bei dem Sie möglicherweise das Fehlen eines Werts angeben müssen, ist es üblich, den Parameter zu einem Zeigerwert zu machen und NULL zu übergeben.

Eine bessere Lösung in den meisten Fällen (aus Sicherheitsgründen) ist die Verwendung von boost :: optional . Auf diese Weise können Sie optionale Werte als Referenz und auch als Rückgabewert übergeben.

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}


4

Zeiger

  • Ein Zeiger ist eine Variable, die eine Speicheradresse enthält.
  • Eine Zeigerdeklaration besteht aus einem Basistyp, einem * und dem Variablennamen.
  • Ein Zeiger kann auf eine beliebige Anzahl von Variablen in der Lebensdauer zeigen
  • Ein Zeiger, der derzeit nicht auf einen gültigen Speicherort zeigt, erhält den Wert null (was null ist).

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • Das & ist ein unärer Operator, der die Speicheradresse seines Operanden zurückgibt.

  • Der Dereferenzierungsoperator (*) wird verwendet, um auf den Wert zuzugreifen, der in der Variablen gespeichert ist, auf die der Zeiger zeigt.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

Referenz

  • Eine Referenz (&) ist wie ein Alias ​​für eine vorhandene Variable.

  • Eine Referenz (&) ist wie ein konstanter Zeiger, der automatisch dereferenziert wird.

  • Es wird normalerweise für Funktionsargumentlisten und Funktionsrückgabewerte verwendet.

  • Eine Referenz muss beim Erstellen initialisiert werden.

  • Sobald eine Referenz auf ein Objekt initialisiert wurde, kann sie nicht mehr geändert werden, um auf ein anderes Objekt zu verweisen.

  • Sie können keine NULL-Referenzen haben.

  • Eine const-Referenz kann sich auf eine const int beziehen. Dies erfolgt mit einer temporären Variablen mit dem Wert const

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein


3

Eine Referenz ist ein impliziter Zeiger. Grundsätzlich können Sie den Wert ändern, auf den die Referenz zeigt, aber Sie können die Referenz nicht ändern, um auf etwas anderes zu zeigen. Meine 2 Cent sind also: Wenn Sie nur den Wert eines Parameters ändern möchten, übergeben Sie ihn als Referenz, aber wenn Sie den Parameter ändern müssen, um auf ein anderes Objekt zu zeigen, übergeben Sie ihn mit einem Zeiger.


3

Betrachten Sie das Schlüsselwort out von C #. Der Compiler verlangt vom Aufrufer einer Methode, dass er das Schlüsselwort out auf alle out-Argumente anwendet, obwohl er bereits weiß, ob dies der Fall ist. Dies soll die Lesbarkeit verbessern. Obwohl ich bei modernen IDEs eher der Meinung bin, dass dies ein Job für die Hervorhebung von Syntax (oder Semantik) ist.


Tippfehler: semantisch, nicht symantisch; +1 Ich stimme der Möglichkeit des Hervorhebens statt des Schreibens (C #) oder & (im Fall von C keine Referenzen) zu
Peenut

2

Übergeben Sie die Konstantenreferenz, es sei denn, es gibt einen Grund, warum Sie den Inhalt, den Sie übergeben, ändern / behalten möchten.

Dies ist in den meisten Fällen die effizienteste Methode.

Stellen Sie sicher, dass Sie const für jeden Parameter verwenden, den Sie nicht ändern möchten, da dies Sie nicht nur davor schützt, etwas Dummes in der Funktion zu tun, sondern auch anderen Benutzern einen guten Hinweis darauf gibt, was die Funktion mit den übergebenen Werten macht. Dazu gehört das Erstellen einer Zeigerkonstante, wenn Sie nur ändern möchten, auf was ...


2

Zeiger:

  • Kann zugewiesen werden nullptr(oder NULL).
  • Auf der Aufrufsite müssen Sie verwenden, &wenn Ihr Typ selbst kein Zeiger ist, sodass Sie Ihr Objekt explizit ändern.
  • Zeiger können zurückprallen.

Verweise:

  • Kann nicht Null sein.
  • Einmal gebunden, kann sich nicht ändern.
  • Anrufer müssen nicht explizit verwenden &. Dies wird manchmal als schlecht angesehen, da Sie zur Implementierung der Funktion gehen müssen, um festzustellen, ob Ihr Parameter geändert wurde.

Ein kleiner Punkt für diejenigen, die nicht wissen: nullptr oder NULL ist einfach eine 0. stackoverflow.com/questions/462165/…
Serguei Fedorov

2
nullptr ist nicht dasselbe wie 0. Try int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Johan Lundberg

0

Eine Referenz ähnelt einem Zeiger, außer dass Sie kein Präfix ∗ verwenden müssen, um auf den Wert zuzugreifen, auf den sich die Referenz bezieht. Es kann auch nicht darauf verwiesen werden, dass nach seiner Initialisierung auf ein anderes Objekt verwiesen wird.

Referenzen sind besonders nützlich, um Funktionsargumente anzugeben.

Weitere Informationen finden Sie unter "Eine Tour durch C ++" von "Bjarne Stroustrup" (2014), Seiten 11-12

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.