# Wozu sind Zeiger und Referenzen (C++)



## the_undertaker (16. Mai 2007)

Hallo,

ich lese in den Büchern über C++ immer soooooooooooooooooooo lange Kapitel über Zeiger und Referenzen, aber habe keine Ahnung, wozu die eigentlich gut sind und wann man die wirklich *braucht*. Könnte mir das bitte jemand erklären (vielleicht auch an einem Beispiel)?


----------



## CSANecromancer (16. Mai 2007)

Ich kann es mal versuchen.

Wenn du irgendwelche Daten im Speicher anlegst, dann liegen die da. Klingt komisch, ist aber so.  Ein Zeiger ist nichts anderes als ein Zahlenwert, der zeigt, *wo* im Speicher deine Daten liegen.
Als theoretisches Beispiel nehmen wir mal einen Datensatz aus einer Kundendatei. Da drin sind Anrede, Name, Vorname, Straße, PLZ, Wohnort, Telefonnummer, Email und noch ein paar andere Sachen gespeichert. Insgesamt ist der Datensatz ca. 500 Byte (Zeichen und Zahlen) lang. Und genau soviel Platz belegt der Datensatz im Speicher. Logisch.
Mit einem einzelnen Datensatz ist das auch gar nicht tragisch. Was aber, wenn du jetzt aus der gesamten Kundendatei mit 5000 Kunden genau einen gaaanz speziellen Datensatz finden willst? Insgesamt belegen jetzt die zu durchsuchenden Daten 5000 * 500 = 2.500.000 Bytes. Das ist schon etwas mehr. Sogar deutlich mehr. (Bevor du meinst, daß 2,5 Mio Bytes heutzutage kein Problem darstellen - in der Grafikprogrammierung bist du ruckzuck im Gigabyte-Bereich, wenn du nicht aufpasst).

Hmmm...

Du könntest z.B. einfach die ganzen 2,5 Mio Bytes durchgrasen und schauen, wo sich der gewünschte Datensatz befindet. 

Aber!

Dein Programm weiß, daß jeder Datensatz 500 Byte groß ist. Jetzt kannst du z.B. einen *Zeiger* auf einen Datensatz erzeugen. Dieser ist nur ein paar popelige Byte groß. Denn er enthält lediglich die Speicheradresse, an der sich der Datensatz befindet.

Du kannst also sagen: Zeiger = 1;
Damit zeigt der Zeiger auf das 1. Byte im Speicher. Und dann liest du einfach die Bytes ab dieser Adresse ein, bis zu entscheiden kannst, ob das der gesuchte Datensatz ist, oder nicht. Sobald du weisst, daß er es nicht ist, kannst du sagen: Zeiger += 500 (wenn er noch auf der 1 stand). Und schon zeigt der Zeiger auf den nächsten Datensatz und du musst wieder nur den Namen oder Ort oder wonach auch immer du suchst einlesen und vergleichen.

Statt also alle 2,5 Mio Bytes Zeichen für Zeichen durchzuackern, kannst du mittels eines Zeigers gezielt von Datensatz zu Datensatz springen.
Jetzt sind Zeiger noch so gebastelt, daß sie "wissen", wieviel Bytes an Daten sich an der Stelle befinden müssen, wo sie hinzeigen.
Beispiele:

char* wwi = 1; 
Damit wird an der Speicherstelle 1 ein einziges Zeichen, nämlich ein char gelesen.

int* wwi2 = 12; 
Damit werden (bei den meisten Systemen) an der Speicherstelle 12, 13, 14 und 15 insgesamt 4 Bytes gelesen, die dann den Zahlenwert des Integers darstellen.

Kundendatensatz* Zeiger = 501; 
Hiermit werden von 501-1001 die Daten eines Kundendatensatzes (wie oben beschrieben) eingelesen.

*Ein Zeiger ist eine Speicheradresse, die auf einen Speicherbereich zeigt, an dem sich die Daten eines bestimmten Datentyps befinden sollen.*


Eine Referenz hängt eng damit zu sammen.
Wenn du ein Unterprogramm aufrufst und ihm dabei Parameter übergibst, dann hast du zwei Möglichkeiten dies zu tun:
1. Es wird im Speicher eine Kopie des zu übergebenden Werts erzeugt und damit innerhalb der Funktion gearbeitet. Dadurch kann der originale Wert nicht versehentlich verändert werden, allerdings kann das Kopieren des Wertes einen enormen Speicher- und Zeitaufwand bedeuten.
2. Du übergibt einen Zeiger auf den Wert. Dann kann die Funktion tatsächlich die Originaldaten des Parameters verändern, aber das enorme Kopieren der Daten entfällt.

Auch dazu ein Beispiel. Du hast ein Bild Bitmap, 500MB groß.
Und du hast eine Unterfunktion Grayscale, welche dir das Bild in Graustufen umwandeln soll. Betrachten wir uns die beiden genannten Möglichkeiten:

1. Bitmap wird temporär im Speicher kopiert. Das bedeutet, daß 500MB an Daten herumgeschaufelt werden. Diese Kopie wird dann an Grayscale übergeben. Sobald Grayscale mit der Arbeit fertig ist, müssen die veränderten 500MB über die originalen 500MB drüber geschrieben werden und die veränderten 500MB müssen danach wieder aus dem Speicher entfernt werden. Ein ungeheurer Aufwand.

2. Du übergibst Grayscale einen *Zeiger*, der genau auf die Stelle zeigt, an der das 500MB Bitmap beginnt. Es werden ein paar popelige Byte an Grayscale übergeben und verwaltet, was schneller geschieht, als du schauen kannst. Die Veränderungen werden direkt am Bild im Speicher vorgenommen. Es sind auch keine Aufräumarbeiten notwendig. *Aber!* Wenn Grayscale irgendeinen Fehler macht, dann hast du deine Originaldaten verloren, was bei Methode 1 nicht zwingend der Fall sein muß.

Methode 2 nennt man Parameterübergabe als *Referenz*.


Verbesserungen und Korrekturen dieses Grobabrisses sind von Leuten mit mehr Ahnung explizit erwünscht. ;-)


----------



## limago (16. Mai 2007)

Hi,

die Begriffe Zeiger und Refernenz sind sehr ähnlich und werden deshalb oft verwechselt. Eine Sprachen wie C/C++ unterschiden da auch nicht sehr. Ein Zeiger ist eine doofe Adresse. Er weiss nicht WAS sich dort befindet. Deshalb gibt es auch sowas wie Zeiger arithmetik. ( z,B. Zeiger = Zeiger + 1Byte). Trotzdem muss man in C Zeiger typisieren. Das heisst bei der Declaration sagen wie er sich bei Zeigeroperationen verhalten soll. Beispiel

Du definiertst einen Zeiger auf int. Dann könnte man ja einen pseudotyp "ptr" oder sowas benutzen. Um eine Adresse zu speichern würde das reichen

prt * intptr; z.B.
in C entspräche dies der Declaration

void * intptr;

allerdings würde intptr ++, dann nicht wissen, wieviele Bytes es weiterspringen  muss. Damit solche Arithmetik möglich ist, braucht der Compiler die Information, dass er auf einen int zeigt.

deswegen wird die Variable so deklariert:

int * intptr;

mit einem simplen Cast würde sich das Verhalten aber ändern: double * neuerzeiger = (double *) intptr; neuerzeiger ++; und schon springt er nicht mehr sizeof(int) sondern sizeof(double) Bytes weiter.

Eine Referenz dagegen weiss worauf sie zeigt. Es gibt daher bei Referenzen keine Arithmetik. Es ist ein Zeiger, der zusätzlich über Metainformation verfügt. Deshalb würde ein Ausdruck wie Referenz = Referenz + 1Byte keinen Sinn machen, denn die Metainformation würde nicht mehr passen. Ein Cast hier funktioniert nur unter definierten Bedingungen, sonst fliegt eine CastException. 

Wie gesagt in C ist das nicht ganz so klar getrennt wie in anderen Sprachen.

aber hier sind Zeiger ganz gut erklärt:

http://www.highscore.de/cpp/einfuehrung/zeiger.html


----------



## jsendrow (16. Mai 2007)

limago hat gesagt.:


> Eine Referenz dagegen weiss worauf sie zeigt. Es gibt daher bei Referenzen keine Arithmetik. Es ist ein Zeiger, der zusätzlich über Metainformation verfügt. Deshalb würde ein Ausdruck wie Referenz = Referenz + 1Byte keinen Sinn machen, denn die Metainformation würde nicht mehr passen. Ein Cast hier funktioniert nur unter definierten Bedingungen, sonst fliegt eine CastException.



Gut erklärt insgesamt, nur an der Stelle mit den Referenzen ist es imho. falsch dargestellt.

Eine Referenz zeigt auf gar nix, denn anders als ein Zeiger hat eine Referenz keinen eigenen Speicher. Eine Referenz ist nichts weiter als ein Namensalias für eine bereits existierende Variable, oder anders gesagt, es ist ein alternativer Name für die _gleiche_ variakle die zuvor unter definiert wurde.  Sich vorzustellen das eine Referenz eine Art Zeiger ist, auch wenn sie sich ähnlich verhält, ist schlicht falsch.


----------



## RedWing (16. Mai 2007)

Hallo,


			
				limago hat gesagt.:
			
		

> Hi,
> 
> die Begriffe Zeiger und Refernenz sind sehr ähnlich und werden deshalb oft verwechselt. Eine Sprachen wie C/C++ unterschiden da auch nicht sehr.
> --schnipp--
> Wie gesagt in C ist das nicht ganz so klar getrennt wie in anderen Sprachen.


in C gibt es keine Referenzen!

//edit noch ein bisschen Verschlimmbessern  :


			
				CSANecromancer hat gesagt.:
			
		

> 2. Du übergibst Grayscale einen Zeiger, der genau auf die Stelle zeigt, an der das 500MB Bitmap beginnt. Es werden ein paar popelige Byte an Grayscale übergeben und verwaltet, was schneller geschieht, als du schauen kannst. Die Veränderungen werden direkt am Bild im Speicher vorgenommen. Es sind auch keine Aufräumarbeiten notwendig. Aber! Wenn Grayscale irgendeinen Fehler macht, dann hast du deine Originaldaten verloren, was bei Methode 1 nicht zwingend der Fall sein muß.
> 
> Methode 2 nennt man Parameterübergabe als Referenz.


Entweder übergibst du einer Methode einen Zeiger dann ist das Call by value, oder du übergibst ihr eine Referenz dann ist es Call by reference, wobei das letztere wie schon erwähnt nur in C++ funktioniert. Die von dir angesprochen Mischform gibt es so nicht.

Gruß,
RedWing


----------



## CSANecromancer (16. Mai 2007)

@Redwing: Wenn du das so sagst, dann wird das so auch stimmen, da du garantiert dicker in C++ drin bist als ich.  Danke für den Hinweis.


----------



## limago (16. Mai 2007)

jsendrow hat gesagt.:


> Gut erklärt insgesamt, nur an der Stelle mit den Referenzen ist es imho. falsch dargestellt.
> 
> Eine Referenz zeigt auf gar nix, denn anders als ein Zeiger hat eine Referenz keinen eigenen Speicher. Eine Referenz ist nichts weiter als ein Namensalias für eine bereits existierende Variable, oder anders gesagt, es ist ein alternativer Name für die _gleiche_ variakle die zuvor unter definiert wurde.  Sich vorzustellen das eine Referenz eine Art Zeiger ist, auch wenn sie sich ähnlich verhält, ist schlicht falsch.



Ich habe mich vielleicht nicht ganz klar ausgedrückt. Aber in Java oder in Perl beispielsweise gibt es keine Zeiger, sondern nur Referenzen. Und dort ist es genauso. Zeigerarithmetik gibt es deswegen dort nicht. 

Wenn Du Dich auf Parameterüber by Reference oder ähnliche Mechanismen beziehst, gebe ich Dir Recht. 

Ich habe auch mal das Stichwort unter Wikipedia nachgeschlagen

Dort ist die Definition so:

http://de.wikipedia.org/wiki/Referenz_(Programmierung)

Gruß


----------



## jsendrow (16. Mai 2007)

C++ Referenzen sind etwas anderes als Referenzen in Java oder C#, das ist richtig, weil letzte Sprachen keine Unterscheidung nach Zeiger und Referenz machen. 

Um den arglosen User ganz zu verwirren werden java referenzen übrigends by value übergeben, und nicht wie man erwarten könnte by referenz, weil die java referenzen eigendlich vom Konzept her Zeiger sind... nur ohne das Zeiger-Symbol.


----------



## limago (16. Mai 2007)

jsendrow hat gesagt.:


> C++ Referenzen sind etwas anderes als Referenzen in Java oder C#, das ist richtig, weil letzte Sprachen keine Unterscheidung nach Zeiger und Referenz machen.
> 
> Um den arglosen User ganz zu verwirren werden java referenzen übrigends by value übergeben, und nicht wie man erwarten könnte by referenz, weil die java referenzen eigendlich vom Konzept her Zeiger sind... nur ohne das Zeiger-Symbol.



Aber wenn Du eine Referenz by Value übergibst, referenziert die dabei erzeugte Kopie ja das gleiche Objekt.  !


----------

