Forum: Compiler & IDEs Unterschied zwischen Zeiger und Referenz


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Hallo Leute,

mir ist der Unterschied zwischen einem Zeiger und einer Referenz nicht 
so ganz klar.

Ein Beispiel: (siehe hier: 
http://www.highprogrammer.com/alan/rants/mutable.html )
1
class Person {
2
public:
3
    virtual bool has_pulse() const { return true; } 
4
    void set_name() { /* ... */ }
5
};
6
7
class Robot : public Person {
8
public:
9
    virtual bool has_pulse() const { return false; }
10
    void set_name() { /* ... */ }
11
};
12
13
/*
14
Because Person is const, take_pulse cannot call set_name().
15
Because Person is a reference, we can pass in a Robot robot
16
and get the correct answer (false).
17
*/
18
bool take_pulse( const Person & X ) {
19
    return X.has_pulse();
20
}

Warum wird bei der Funktion
1
bool take_pulse( const Person & X )
als Argument
1
const Person & X
und nicht
1
const Person * X
 oder
1
const Person const * X
uebergeben?

Wo ist da jetzt genau der Unterschied, und wann nimmt man welche 
Variante?

Gruesse

von Torsten Robitzki (Gast)


Lesenswert?

Technisch sind beide gleich. Wenn ich anzeigen möchte, dass eine 
Referenz optional ist. Also entweder auf ein gültiges Objekt zeigt, oder 
aber auf nichts zeigt, dann verwendet man einen Zeiger ansonsten eine 
Referenz. Eine Referenz referenziert immer ein gültiges Objekt (wenn 
nicht, hat man undefiniertes Verhalten; also ein Fehler in der SW). Das 
ist auch der Grund, warum ich eine Referenz immer initialisieren muss, 
einen Zeiger aber nicht. Eine Referenz kann ich später auch nicht mehr 
ändern. Sprich die Identität des Objekt, auf das ich referenziere bleibt 
immer die selbe.

Es gibt sicher noch ein paar Spezialfälle, wo es sinnvoller ist, einen 
Zeiger zu verwenden, der dann eben nicht 0 sein darf. Z.B. wenn man 
einer Funktion einen Puffer übergeben möchte. Dann verwendet man in der 
Regel einen Zeiger auf das erste Element eines Arrays und das sollte man 
auch weiter so beibehalten, weil es einfach idiomatisch ist.

mfg Torsten

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Ok, vielen dank fuer die Antwort. :)

von Sebastian V. (sebi_s)


Lesenswert?

Einige Sachen funktionieren aber nur mit References. Zum Beispiel kann 
man mit Pointern nur Arrays übergeben indem man auf das erste Element 
zeigt. Dabei geht die Größe des Arrays verloren (array to pointer 
decay). Mit References kann man tatsächlich eine Reference auf ein Array 
haben (wahlweise mit der Größe als Template Parameter). Ein Unterschied 
zwischen Pointer und Reference ist natürlich auch, dass man bei 
References kein & (Address Operator) vor die Parameter zu schreiben 
braucht. Das ist spätestens bei Operator Overloading praktisch. Ich 
glaube niemand wollte Code wie
1
BigInt a, b;
2
BigInt* c = &a + &b;
schreiben. Ansonsten kann ich dem Beitrag von Torsten nur zustimmen.

von mem (Gast)


Lesenswert?

Wie sieht dass denn im Speicher aus?
Wieviel Speicher belegt ein pointer und wieviel eine Referenz?

von Ralf G. (ralg)


Lesenswert?

mem schrieb:
> Wieviel Speicher belegt ein pointer und wieviel eine Referenz?

Torsten Robitzki schrieb:
> Technisch sind beide gleich.

von Rolf M. (rmagnus)


Lesenswert?

mem schrieb:
> Wie sieht dass denn im Speicher aus?
> Wieviel Speicher belegt ein pointer und wieviel eine Referenz?

Offiziell (also laut ISO-Norm) ist eine Referenz selbst kein Objekt, 
sondern nur ein anderer Name für ein bereits bestehendes Objekt und 
belegt daher keinen eigenen Speicher. Deshalb kann man mit sizeof nicht 
die Größe einer Referenz ermitteln und auch nicht eine mit dem operator 
new dynamisch erzeugen. Auch die Adresse einer Referenz läßt sich nicht 
ermitteln. Um eine Referenz zu implementieren, wird aber je nach 
Anwendungsfall ein gewisser zusätzlicher Platz im Speicher benötigt.
Unter der Haube werden Referenzen dann typischerweise genau wie Zeiger 
implementiert. Das ist dann aber ein Implementierungsdetail des 
Compilers.

: Bearbeitet durch User
von mem (Gast)


Lesenswert?

Aha, gut das ich gefragt habe. ;-)

von Klaus W. (mfgkw)


Lesenswert?

Rolf Magnus schrieb:
> Offiziell (also laut ISO-Norm) ist eine Referenz selbst kein Objekt,
> sondern nur ein anderer Name für ein bereits bestehendes Objekt und
> belegt daher keinen eigenen Speicher. Deshalb kann man mit sizeof nicht
> die Größe einer Referenz ermitteln und auch nicht eine mit dem operator
> new dynamisch erzeugen.

Zur Klarstellung:
Man kann sehr wohl den Adressoperator auf eine Referenz anwenden und 
bekommt die Adresse des referenzierten Objekts, analog mit sizeof.

Was du wohl meinst, ist daß man an eventuelle Hilfsdaten (heimlicher 
Zeiger o.ä.) nicht herankommt.

von mem (Gast)


Lesenswert?

Was erhalte ich vom Adressoperator bei einem pointer und was bei einer 
Referenz?

von Karl H. (kbuchegg)


Lesenswert?

mem schrieb:
> Was erhalte ich vom Adressoperator bei einem pointer und was bei einer
> Referenz?
1
int * pI;
2
3
 sizeof( pI )  ->  die Größe der Pointervariablen
4
 sizeof( *pI ) ->  die Größe dessen, worauf der Pointer zeigt. In
5
                   diesem Fall die Größe eines int
6
7
8
int i;
9
int& j = i;
10
11
  sizeof(j)    -> ist dasselbe wie sizeof(i). Die Größe eines int


Nachdem eine Referenz eingerichtet wurde, gibt es keinen UNterschied 
mehr, ob du die Referenz benutzt oder direkt das originale Objekt. Eine 
Referenz ist nur ein anderer Name für ein anderes Objekt. Du kannst 
deine Frau ja auch 'Helga' oder 'Schatzi' nennen. Aber egal welchen 
Namen du benutzt, es ist immer dieselbe Frau. So auch hier: es ist 
Speicher reserviert worden, der so groß ist, dass ein int da 
hineinpasst. Aber egal ob du den Variablennamen i oder den 
Variablennamen j benutzt, es ist immer genau derselbe Speicherbereich 
damit gemeint.

VOn daher bin ich mit der Aussage, dass Referenzen und Pointer technisch 
identisch sind eher unglücklich. Denn das sind sie nicht. Die Semantik 
von Referenzen wird oft mit einem Pointer im Hintergrund realisiert, 
aber das deckt nicht alle Fälle ab und ist für das Verständnis von 
Referenzen nicht hilfreich. Es ist ein Implementierungsdetail, denn 
irgendwie müssen Referenzen ja tatsächlich realisiert werden und das 
geht in manchen Fällen nicht anders. Aber es ist nur die halbe Wahrheit. 
Im obigen Beispiel wird der Compiler zb für die realisierung von j eben 
keinen Pointer benutzen, sondern sich in seinen Tabellen eintragen, dass 
eigentlich i gemeint ist, wann immer du j benutzt (bzw. das j denselben 
Speicherbereich kennzeichnet wie i). Eben ein anderer Name für dasselbe 
Objekt (= denselben Speicherbereich)

: Bearbeitet durch User
von mem (Gast)


Lesenswert?

sizeof ist zwar nicht der Adressoperator, aber du hast die Lunte 
gerochen. ;-)

Eine Referenz ist nur ein anderer Name. Und ein pointer ein eigener 
Datentyp. Für mich sind das zwei völlig verschiedene Dinge, auch wenn 
sie nach aussen gleich erscheinen.

Ein array ist ja auch kein pointer. Aber das ist eine gaaannnzzz andere 
Sache. ;-)

Frohes Fest!

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Karl Heinz schrieb:
> VOn daher bin ich mit der Aussage, dass Referenzen und Pointer technisch
> identisch sind eher unglücklich.

Inhaltlich aber ist das nicht ganz falsch, wenn so etwas an eine 
Funktion übergeben wird. Aber auch nicht komplett synonym ...
1
void blafusel(int& x)
2
{
3
  x = 4;
4
}
5
6
void machwas(void)
7
{
8
  int i = 0;
9
10
  blafusel(i);
11
12
  printf("%d\n", i);
13
}

Hier zeigt sich auch etwas, was ich als Nachteil, oder zumindes sehr 
gewöhnungsbedürftige Eigenschaft von Referenzen ansehe.

In C (das keine Referenzen kennt) kann man bei alleiniger Betrachtung 
der Funktion "machwas" klar sehen, daß "i" als 0 ausgegeben werden wird, 
da der Funktionsaufruf von "blafusel" die Variable nicht verändern 
kann.

In C genügt es dafür, den Aufruf einer Funktion zu sehen, ohne sich 
die Funktion selbst oder deren Prototyp anzusehen.

In C++ (mit Referenzen) genügt das nicht; obiges Beispiel zeigt das.

Somit ist C++-Code definitiv anders (und mit mehr Vorsicht) zu lesen als 
C-Code, weil etwas, was in C ein Objekt nicht verändern kann, in C++ das 
sehr wohl kann.

(Ob es das letztlich tut, ist wieder eine ganz andere Angelegenheit)

Das ist aber in Pascal-artigen Sprachen auch nicht anders, dort lässt 
sich bei alleiniger Betrachtung des Aufrufs einer Funktion (oder 
Prozedur) nicht erkennen, ob dort "call by value" oder "call by 
reference" durchgeführt wird. Um das zu erkennen, muss man die 
Funktions/Prozedurdefinition ansehen.

Klar, wenn man eigenen Code schreibt, ist das kein relevantes Problem, 
aber wenn man fremden Code durchliest, dann ist so etwas nicht ganz 
bedeutungslos.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Rufus Τ. Firefly schrieb:
> In C (das keine Referenzen kennt) kann man bei alleiniger
> Betrachtung der Funktion "machwas" klar sehen, daß "i" als 0
> ausgegeben werden wird, da der Funktionsaufruf von "blafusel" die
> Variable nicht verändern kann.

Es sei denn, "blafusel" ist ein Makro oder i ist en Array. Das tolle an 
C ist, daß es bei allen Regeln Ausnahmen gibt.
Generell ist es keine gute Idee, Funktionen aufzurufen, ohne zu wissen, 
was sie mit den Parametern machen, egal ob in C oder C++. Da gibt es 
noch viel mehr Stolperfallen, als eine den übergebnen Wert ändernde 
Funktion.
Es ist halt nur anfangs ungewohnt, daß das in C++ mit Referenzen möglich 
ist.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Rolf Magnus schrieb:
> Es sei denn, "blafusel" ist ein Makro oder i ist en Array.

Welchen Typ i hat, sieht man in diesem Fall, da es hier in der Nähe des 
Aufrufs von "blafusel" definiert wird.

"blafusel" sollte kein Makro sein, sofern der Programmierer sich an 
die Konventionen gehalten hat -- Makronamen werden in VERSALIEN 
geschrieben.

Allerdings ist das bei fremden Quelltexten keine Annahme, von der man 
unbedingt ausgehen kann.

Insofern hast Du natürlich recht.

von Hans (Gast)


Lesenswert?

In manchen Coderichtlinien (u.a. Google C++ Style Guide) wird deshalb 
empfohlen, Parameter an Funktionen nur per Value, per Const-Referenz 
oder per (Non-Const-)Zeiger zu übergeben.

Wenn man sich daran hält, ist beim Aufruf auf einen Blick ersichtlich, 
was als Ein- und Ausgabeparameter gedacht ist.

Ich nutzte außerdem Zeiger statt Referenzen, wenn sich die Funktion die 
Adresse über den Funktionsaufruf hinweg merkt. Pass-by-Const-Reference 
ist dann einfach eine effizientere Form von Pass-by-Value, die sich aber 
semantisch und syntaktisch gleich verhält.

von Willi Wampe (Gast)


Lesenswert?

> Pass-by-Const-Reference ist dann einfach eine effizientere Form von
> Pass-by-Value

Aber auch nur bei größeren Objekten ("größer" ist hier natürlich relativ 
und von der Umgebung abhängig). Ich habe schon "const long&" u.ä. als 
Parameter gesehen ...

> die sich aber semantisch und syntaktisch gleich verhält.

void hmmm(int x)
{
  x = 42;
}

void hmmm(const int& x)
{
  const_cast<int&>(x) = 42;
}

von Rolf M. (rmagnus)


Lesenswert?

Willi Wampe schrieb:
>> die sich aber semantisch und syntaktisch gleich verhält.
>
> void hmmm(int x)
> {
>   x = 42;
> }
>
> void hmmm(const int& x)
> {
>   const_cast<int&>(x) = 42;
> }

Und was willst du damit sagen, außer daß const_cast immer im 
Zusammenhang mit einem Programmierfehler auftaucht?

von Torsten Robitzki (Gast)


Lesenswert?

Hans schrieb:
> In manchen Coderichtlinien (u.a. Google C++ Style Guide) wird
> deshalb
> empfohlen, Parameter an Funktionen nur per Value, per Const-Referenz
> oder per (Non-Const-)Zeiger zu übergeben.
>
> Wenn man sich daran hält, ist beim Aufruf auf einen Blick ersichtlich,
> was als Ein- und Ausgabeparameter gedacht ist.

In komplexeren Beispielen ist dies aber meist schon sehr schnell nicht 
mehr anwendbar:
1
struct result {...};
2
3
void add_bar(result*);
4
void add_foo(result*);
5
6
void fill_in_root_cause(result* r)
7
{
8
  assert(r);
9
10
  add_bar(r);
11
  add_foo(r);
12
}
13
14
int main()
15
{
16
  result r;
17
  ...
18
  fill_in_root_cause(&r);
19
}

hier ist jetzt nur noch an einer von drei Stellen sichtbar, dass der 
Parameter geändert wird. Und das Beispiel ist jetzt auch nicht wirklich 
besonders konstruiert. Wenn Du z.B. so etwas wie eine Senke für ein 
Protokoll hast, dann wird diese Senke sicher über mehrere Ebenen weiter 
gereicht. Zusätzlich handelt man sich die Möglichkeit ein, dass der 
übergebene Parameter 0 sein kann.

Die einzige, wirklich funktionierende Vorgehensweise, ist meiner Meinung 
nach vernünftige Namen für Variablen und Funktionen zu verwenden.

mfg Torsten

von N.R. (Gast)


Lesenswert?

N'abend,

browse gerade durch die Threads und kann mich nicht zurück halten...

1. Cool bei Referenzen ist die Funktionsübergabe von fixed sized arrays 
(compile time checked) also etwas wie "int (&array)[100]". Das geht 
nicht in C. ...

2. Schön bei Referenzen als Parameter ist der implizite Ausschlus von 
NULL Pointern (wenn man nicht gerade castet)...

3. Const References als Funktionsparameter sind ok (im Sinn von Punkt 
2)...

4. Non-Const References als Funktionsparameter oder Rückgabewerte sind 
verwirrend, wenn man nur die Caller-Side sieht: Soll heißen, man sieht 
nicht ob das ein Value oder eine "Reference" ist und weiß somit nicht ob 
bei Modifikationen das Urpsungsobjekt oder eine Kopie verändert wird... 
(ich habe eine Zeitlang statt dessen Pointer benutzt, ist aber auch 
keine optimale Lösung)...

5. Eine Reference als Object Variable ist interessant... (don't do it 
unless you know what you do).

Grüße

btw: Ich mag Karl Heinz' Beiträge!

von Yalu X. (yalu) (Moderator)


Lesenswert?

Sebastian V. O. schrieb:
> Einige Sachen funktionieren aber nur mit References. Zum Beispiel kann
> man mit Pointern nur Arrays übergeben indem man auf das erste Element
> zeigt. Dabei geht die Größe des Arrays verloren (array to pointer
> decay). Mit References kann man tatsächlich eine Reference auf ein Array
> haben

N.R. schrieb:
> 1. Cool bei Referenzen ist die Funktionsübergabe von fixed sized arrays
> (compile time checked) also etwas wie "int (&array)[100]". Das geht
> nicht in C. ...

Man kann doch in C auch einen Zeiger auf ein Array übergeben (nicht nur
einen auf dessen erstes Element). Man muss diesen Zeiger halt an den
Stellen, wo man das Array benutzen möchte, mit dem *-Operator
dereferenzieren. Das ist aber immer so, wenn man C++Referenzen durch
Zeiger nachbildet.

Eine Referenz in C++ ist im Wesentlichen ein selbstdereferenzierender
Zeiger mit ein paar durch diese automatische Dereferenzierung bedingten
Einschränkungen (z.B. keine Zuweisung möglich, deswegen Initialisierung
immer erforderlich). Man kann praktisch alles, was mit Referenzen
möglich ist, mit gewöhnlichen Zeigern nachbilden, allerdings entsteht
dabei etwas mehr Schreibarbeit, da man häufiger die *- und &-Operatoren
bemühen muss.

von Sebastian V. (sebi_s)


Lesenswert?

Yalu X. schrieb:
> Man kann doch in C auch einen Zeiger auf ein Array übergeben (nicht nur
> einen auf dessen erstes Element).

Stimmt, da hast du recht.

Mir ist gerade aber noch eine andere Eigenheit von References in C++ 
eingefallen. Wenn man const References als Parameter von Funktionen hat, 
dann verhält es sich ziemlich genau wie wenn ich direkt den Wert 
übergeben hätte. Insbesondere ist auch sowas erlaubt:
1
void foo(const MyClass& x) { ... }
2
...
3
foo(MyClass());
Also einen temporären Wert übergeben. Der C++ Standard garantiert hier, 
dass der Parameter bis nach dem Funktionsaufruf erhalten bleiben. Dies 
gilt aber nur für const References! Bei einer normalen Reference wird 
das temporäre Objekt in der Regel vorm Funktionsaufruf wieder zerstört. 
Visual Studio hat hier aber wieder seine Eigenheiten und eine nicht 
standardisierte Erweiterung die auch nicht const References erlaubt.

: Bearbeitet durch User
von Nase (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Makronamen werden in VERSALIEN
> geschrieben.
Die intention ist schön, wird aber schon in der Standard-C-Bibliothek 
nicht eingehalten...

N.R. schrieb:
> Cool bei Referenzen ist die Funktionsübergabe von fixed sized arrays
Cool ist auch, dass man in c++ einen vector verwenden kann. Der hat eine 
Größeninformation und braucht kaum mehr Speicher.

Sebastian V. O. schrieb:
> Bei einer normalen Reference wird
> das temporäre Objekt in der Regel vorm Funktionsaufruf wieder zerstört.
An eine normale Reference darfst du mit einem gescheiten Compiler 
garkeine temporären Werte übergeben. Da sollte wenigstens eine Warnung 
rausspringen.

Eine Referenz ist ungefähr so viel Zeiger, wie ein Hardlink eine 
symbolische Verknüpfung ist...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.