8. Zeiger
  Zeiger gehören eigentlich zu den elementaren Datentypen.
  Die bisher vorgestellten Datentypen ermöglichen es, unabhängig von Speicheradressen
  zu arbeiten. Es wurde nur mit dem Namen der Variablen gearbeitet und nur das Programm selber
  wusste, welche Speicheradressen sich hinter den Variablennamen verbergen.
  Zu einer Variablen gehören folgende Dinge:
   der Name (kann, muss aber nicht!),
   die Speicheradresse, an der der Wert der Variablen gespeichert ist,
   der Inhalt der Variable, also der Wert an der Speicheradresse und
   der Datentyp, um den Inhalt zu interpretieren und um die Anzahl der Speicherzellen
  zu bestimmen.
  Beispiel:
  
| Name | Adresse (hex.) | Inhalt | Datentyp | Interpretation | 
| Zahl1 | 00003D24h | 00000000 00001011 | short | 11 | 
| 00003D25h | ||||
| Zahl2 | 00003D26h | 00000000 00011110 | short | 30 | 
| 00003D27h | ||||
| Zeichen | 00003D28h | 01000010 | char | 'B' | 
  Die Adressen in diesem Beispiel sind frei erfunden; jede Ähnlichkeit mit vorhandenen
  Speicheradressen ist rein zufällig! :-)
  Jede Speicheradresse kann 1 Byte = 8 Bits aufnehmen. Die beiden Zahlen
  im Beispiel sind short-Zahlen (16 Bit-Zahlen) und belegen daher 2
  Speicheradressen. Dagegen hat die char-Variable nur 8 Bits und
  belegt damit auch nur eine Speicheradresse.
  
8.1. Zeiger und Adressen
  In C/C++ wird an vielen Stellen mit Zeigern (im engl. Pointer) gearbeitet, um
  unabhängig von Variablennamen auf die Inhalte der Variablen zugreifen zu können.
  Ein Zeiger "zeigt" auf eine Variable, d.h. ein Zeiger ist eine Zahlenvariable und beinhaltet
  als Wert die Adresse der Variablen, auf die sie zeigt. Dadurch benötigen Zeiger immer den
  gleichen Speicherplatz (z.B. 32 Bit unter einem 32Bit-Betriebssystem).
  Bei der Deklaration/Definition von Zeigern wird der Datentyp angegeben, auf den der Zeiger zeigen
  soll. Vor den Variablennamen von Zeigern wird ein Sternchen (*) gesetzt.
  Beispiel:
  int *ip;
  Diese Zeile bewirkt, dass ein Zeiger mit dem Namen ip deklariert und definiert
  wird. Dieser Zeiger zeigt jetzt grundsätzlich auf Variablen vom Typ int.
  Diese Datentypangabe ist wichtig, da mit ihr nicht nur auf die angegebene Speicheradresse, sondern
  auch noch auf die nächsten 3 zugegriffen wird (weil eine int-Zahl
  32 Bits = 4 Bytes = 4 Speicheradressen beinhaltet). Im nächsten Beispiel wird mit
  einem Zeiger auf eine int-Variable gezeigt.
  Beispiel:
  
  int i;     /* Deklaration der int-Zahl i */
  int *ip;   /* Deklaration des Zeigers ip */
  ip = &i;   /* Speicheradresse von i in ip speichern */
  i = 99;
  *ip = 100; /* weist i die Zahl 100 zu, da ip auf i zeigt */
  
  Der unäre Adressoperator & liefert die
  Speicheradresse eines Objekts bzw. einer Variablen. Es wird in der Literatur manchmal auch
  fälschlicherweise von einer Referenz auf das Objekt bzw. vom Referenzieren gesprochen.
  Unter dem Begriff Referenz wird jedoch in C++ etwas anderes verstanden!
  Im Beispiel wird also die Speicheradresse der Variable i dem Zeiger
  ip zugewiesen. Danach wird der Variable i der Wert
  99 zugewiesen. In der letzten Zeile wird dem Inhalt der Variablen, auf die der Zeiger
  ip zeigt, der Wert 100 zugewiesen. Da der Zeiger auf die Variable
  i zeigt, hat i anschließend den Wert 100. Dies
  wird durch den unären Variablenoperator * erreicht.
  Es wird auch fälschlicherweise vom Dereferenzieren (siehe oben) gesprochen. Im Speicher
  sieht es nach dem Ablauf des Programms folgendermaßen aus (die Speicheradressen sind
  wieder rein zufällig gewählt):
  
| Name | Adresse (hex.) | Inhalt | Datentyp | 
| i | 00004711h | 100 | int | 
| 00004712h | |||
| 00004713h | |||
| 00004714h | |||
| ip | 00004715h | 00004711h | int* | 
| 00004716h | |||
| 00004717h | |||
| 00004718h | 
  Hier noch einmal eine Tabelle mit verschiedenen Ausdrücken und die im Beispiel
  resultierenden Werte:
  
| Ausdruck | Ergebnis | 
| i | 100 | 
| &i | 00004711h | 
| ip | 00004711h | 
| *ip | 100 | 
| &ip | 00004715h | 
  Der Variablenoperator liefert aber nicht nur den Zugriff auf den Wert der Variable, auf die
  er zeigt, sondern er liefert die komplette Variable (daher auch der Begriff Variablenoperator)!
  D.h. es kann auch wieder auf die Adresse der Variablen zugegriffen werden, wie das folgende
  Beispiel zeigt.
  Beispiel:
   kap08_01.c
  kap08_01.c
  
  01 #include <stdio.h>
  02 
  03 int main()
  04 {
  05    int i = 5;
  06    int *ip = &i;
  07 
  08    printf("    ip = %p\n", ip);     /* Liefert  */
  09    printf("  &*ip = %p\n", &*ip);   /* immer    */
  10    printf("  *&ip = %p\n", *&ip);   /* die       */
  11    printf("&*&*ip = %p\n", &*&*ip); /* gleiche  */
  12    printf("*&*&ip = %p\n", *&*&ip); /* Adresse! */
  13 
  14    return 0;
  15 }
  
  Dabei ist zu sehen, dass sich Adressoperator und Variablenoperator aufheben, egal ob erst der
  Adressoperator und dann der Variablenoperator steht oder umgekehrt; in allen Fällen wird
  die gleiche Adresse - nämlich die Speicheradresse der Variablen i
  - ausgegeben.
  
8.2. Der Zeigerwert NULL
  Es gibt einen speziellen Zeigerwert, nämlich den Wert NULL
  (definiert in der Headerdatei stdio.h). Dieser Zeigerwert zeigt nicht
  irgendwo hin, sondern definitiv auf "nichts". Die meisten Compiler definieren den
  NULL-Zeiger als einen Zeiger, der auf die Speicheradresse 00000000h
  zeigt, also auf die allererste Speicheradresse im Arbeitsspeicher. Dieser Zeigerwert kann
  abgefragt werden, z.B.
  if (ip == NULL) ....
  Genauso wie die "normalen" Variablen haben Zeiger nach der Definition einen unbekannten
  Wert, d.h. sie "zeigen" irgendwo hin. Um dies zu vermeiden, sollten Zeiger immer mit dem
  Zeigerwert NULL initialisiert werden.
  
  
8.3. Typprüfung
  In C wird - im Gegensatz zu C++ und anderen Programmiersprachen - der Typ eines Zeigers
  nicht geprüft. Es ist also möglich, einen
  int-Zeiger plötzlich auf ein char zeigen zu
  lassen.
  Wird mal ein Zeiger benötigt, bei dem noch nicht feststeht, auf welchen Datentypen
  er zeigen soll, muss ein Zeiger auf void verwendet werden.
  Beispiel:
  
  int *ip = NULL;  /* Zeiger auf int */
  char *cp = NULL; /* Zeiger auf char */
  void *vp;        /* Zeiger auf void */
  vp = ip;  /* korrekt */
  vp = cp;  /* korrekt */
  /* umgekehrt: auch korrekt (aber nur in C!) */
  ip = vp;  /* nur in C korrekt */
  cp = vp;  /* nur in C korrekt */
  
  In C++ können die umgekehrten Zuweisungen nur mit einer expliziten Typumwandlung
  durchgeführt werden. Dies sollte aber nicht ohne wichtigen Grund gemacht werden, weil
  damit die Typkontrolle des C++-Compilers umgangen wird und somit eine weitere Fehlerquelle
  gegeben ist.
  Beispiel:
  int *ip = NULL;  /* Zeiger auf int */
  void *vp = NULL; /* Zeiger auf void */
  ip = (int *) vp; /* jetzt auch in C++ korrekt! */
  
  
8.4. Zeiger-Arithmetik
  Mit Zeiger-Arithmetik wird die Addition und Subtraktion von Zeigern mit
  ganzen Zahlen bezeichnet. Auch lassen sich zwei Zeiger subtrahieren - vorausgesetzt, sie
  zeigen auf den gleichen Datentypen.
  Beispiel:
  
  int i1 = 5, i2 = 7;
  int *ip1 = &i1, *ip2 = &i2;
  ip2 = ip1 + 1;  /* erlaubt */
  ip1 = ip2 - 1;  /* erlaubt */
  ip2 = 1 + ip1;  /* erlaubt */
  ip1 = 1 - ip2;  /* Fehler! */
  i1 = ip1 + ip2; /* Fehler! */
  i2 = ip2 - ip1; /* erlaubt */
  
  Alle anderen Operationen wie Multiplizieren usw. dürfen nicht auf Zeiger angewendet
  werden.
  Dabei wird bei der Zeiger-Arithmetik nicht in Bytes sondern in Anzahl von Elementen des
  Datentyps, auf den der Zeiger zeigt, gerechnet. Wird also zu einem
  int-Zeiger eine 1 addiert, wird nicht auf die nächste Speicheradresse,
  sondern auf den nächsten int gezeigt.
  Beispiel:
  
  int i = 100;
  int *ip1 = &i;
  int *ip2 = ip1 + 1;
  
  In diesem Beispiel zeigt der Zeiger ip2 nach der Initialisierung
  (ip1 + 1; also 00004711h + 1) nicht auf die Adresse 00004712h sondern
  ein Element (also ein int) weiter auf die Adresse 00004715h.
  
| Name | Adresse (hex.) | Inhalt | Datentyp | 
| i | 00004711h | 100 | int | 
| 00004712h | |||
| 00004713h | |||
| 00004714h | |||
| ip1 | 00004715h | 00004711h | int* | 
| 00004716h | |||
| 00004717h | |||
| 00004718h | |||
| ip2 = ip1 + 1 | 00004719h | 00004715h | int* | 
| 0000471ah | |||
| 0000471bh | |||
| 0000471ch | 
  Entsprechend liefert die Differenz von zwei Zeigern nicht die Anzahl der dazwischen liegenden
  Bytes, sondern die Anzahl der dazwischen liegenden Datenelementen.
  Bei einem void-Zeiger steht der Datentyp, auf den der Zeiger zeigt, nicht
  fest. Es wird hier in Bytes gerechnet. Wenn also im obigen Beispiel die Zeiger ip1
  und ip2 jeweils ein Zeiger auf void wäre, würde
  ip2 auf die Adresse 00004712h zeigen.
  
  void *ip1 = &i;
  void *ip2 = ip1 + 1; /* zeigt so auf 00004712h! */
  
  Hinweis:
  Bei der Zeigerarithmetik muss darauf geachtet werden, dass der resultierende Zeiger auf eine
  Adresse zeigt, die innerhalb des Programmsegments oder innerhalb eines reservierten
  Speicherbereichs liegt. Ansonsten kommt es schnell zu einem Speicherzugriffsfehler!
  
  
8.5. Zeiger und Arrays
  Zeiger und Arrays haben vieles gemeinsam. Tatsächlich setzen die meisten Compiler alle
  Array-Befehle in Zeiger-Befehle um. Der Name eines Arrays (also der Variablenname) ohne eckigen
  Klammern und Indexangaben gibt die Startadresse des Arrays zurück, d.h. die Adresse des
  ersten Elements. Er ist damit wie ein Zeiger einsetzbar. Im Gegensatz zu einem richtigen
  Zeiger kann ihm allerdings keine andere Adresse zugeordnet werden; er ist kein
  L-Wert (siehe Kapitel Variablen Abschnitt L-Werte und R-Werte).
  Beispiel:
  
  int *IntZeiger = NULL; /* Zeiger auf int */
  int IntArray[5];       /* Array von int */
  IntZeiger = IntArray;  /* Zeiger auf Array-Startadresse */
  IntArray[0] = 5;       /* ist identisch mit folgender Zeile */
  *IntZeiger = 5;
  
  Es kann aber nicht nur auf die Startadresse des Arrays mit Zeigern zugegriffen werden,
  sondern auch auf jedes andere Element des Arrays. Dazu wird die sogenannte
  Zeiger-Arithmetik verwendet. Der Begriff Zeiger-Arithmetik bedeutet
  das Addieren und Subtrahieren von ganzen Zahlen zu bzw. von einem Zeiger unter
  Berücksichtigung des Speicherbedarfs des Datentyps, auf den der Zeiger zeigt. Zum
  besseren Verständnis wird das obige Beispiel erweitert.
  Beispiel:
  
  int *IntZeiger = NULL; /* Zeiger auf int */
  int IntArray[5];       /* Array von int */
  IntZeiger = IntArray;  /* Zeiger auf Array-Startadresse */
  IntArray[0] = 5;       /* ist identisch mit folgender Zeile */
  *IntZeiger = 5;
  IntArray[1] = 4;       /* ist identisch mit folgender Zeile */
  *(IntZeiger + 1) = 4;
  
  In der letzen Zeile wird zu dem Wert des Zeigers noch eine 1 dazuaddiert. Damit zeigt
  dieser Zeiger nicht auf die nächste Speicheradresse, sondern auf das nächste
  Element im Array (alle Elemente eines Arrays - auch mehrdimensionale - liegen direkt
  hintereinander im Speicher). Im Beispiel liegt diese Adresse nicht eine sondern 4
  Speicheradressen weiter, da der Datentyp int einen Speicherbedarf von
  4 Byte hat. Das bedeutet, dass bei der Zeiger-Arithmetik der Speicherbedarf des
  Datentyps, auf den der Zeiger zeigt, bei der Addition und Subtraktion berücksichtigt
  wird.
  
| Array | Zeiger | Adresse (hex.) | Inhalt | 
| IntArray[0] | *IntZeiger --> | 00004711h | 5 | 
| 00004712h | |||
| 00004713h | |||
| 00004714h | |||
| IntArray[1] | *(IntZeiger + 1) --> | 00004715h | 4 | 
| 00004716h | |||
| 00004717h | |||
| 00004718h | 
  Mit Hilfe des Inkrement-Operators (++) kann diese letzte Zeile des
  obigen Beispiels auch folgendermaßen geschrieben werden:
   *(++IntZeiger) = 4;
  Allerdings zeigt jetzt der Zeiger nicht mehr auf die Startadresse des Arrays, da das
  Inkrementieren den Zeiger selbst verändert hat. Im Beispiel zeigt der Zeiger nun nicht
  auf die Adresse 00004712h sondern auf die Adresse 00004715h.
  
8.6. Zeiger und Zeichenketten
  Da Zeichenketten ein Spezialfall von Arrays sind, gilt der ganze vorige Abschnitt auch
  für Zeichenketten. Hier sollen noch einige Beispiele zur Verwendung von Zeigern und
  Zeichenketten zur Verdeutlichung vorgestellt werden.
  Beispiel:
  /* Textlänge ermitteln: */
  char Text[] = "Dies ist ein Textarray!";
  char *TextZeiger = Text;
  int TextLaenge = 0;
  while (*TextZeiger++)
     TextLaenge++;
  printf("Textlänge: %i",TextLaenge);
  
  In der Bedingung der while-Schleife wird das erste Zeichen in der
  Zeichenkette (dahin zeigt der Textzeiger) geprüft. Ist dieses wahr (also ungleich Null
  und damit ungleich dem Nullzeichen für Textende), wird die Schleife ausgeführt.
  Zuvor wird noch der Zeiger auf das nächste Zeichen gesetzt (also der Zeiger
  inkrementiert). In der Schleife wird die Variable TextLaenge um eins
  erhöht. Nun wird in der Bedingung das zweite Zeichen geprüft usw. Ist das
  Textende erreicht, ist das Zeichen, auf das der Zeiger zeigt, das Nullzeichen, das den
  ASCII-Wert Null hat. Null bedeutet aber auch falsch, womit die
  while-Schleife nun abgebrochen wird. In der Variablen
  TextLaenge steht damit die Anzahl der Zeichen in der
  Zeichenkette.
  Beispiel:
  /* Text kopieren: */
  char Text1[] = "Dieser Text soll kopiert werden!";
  char Text2[35],*TextZeiger1 = Text1,*TextZeiger2 = Text2;
  while (*TextZeiger2++ = *TextZeiger1++)
     ;
  printf("kopierter Text: %s",Text2);
  
  In dieser Schleife wird eine Zeichenkette kopiert. Als Bedingung wird die Zuweisung des
  ersten Zeichens der Originalzeichenkette Text1 nach Text2 durchgeführt.
  Anschließend werden beide Zeiger ums eins erhöht; sie zeigen damit auf das
  nächste Zeichen. Das Ergebnis der Zuweisung ist das kopierte Zeichen selber und wird
  jetzt als Bedingung geprüft. Ist es wahr (also ungleich Null und damit ungleich dem
  Nullzeichen), wird die Schleife durchgeführt (Leeranweisung). Dann wird das
  nächste Zeichen kopiert usw. Ist das Textende erreicht, ist das Zeichen, auf das der
  Zeiger zeigt, das Nullzeichen. Auch dieses wird kopiert. Dann werden die Zeiger ums eins
  erhöht und das kopierte Zeichen geprüft. Das Nullzeichen hat den ASCII-Wert Null
  und dadurch ist die Bedingung falsch; die Schleife wird abgebrochen. Das Beispiel kopiert
  also den vollständigen Text einschließlich des Nullzeichens; die Zeiger zeigen
  anschließend auf das erste Zeichen nach dem Nullzeichen!
  
8.7. Zeiger und Strukturen
  Mit Zeigern kann auf alle Objekte gezeigt werden, so auch auf Strukturen
  (struct). Dabei verändert sich die Schreibweise, wenn mit Zeigern
  auf Felder einer Struktur zugegriffen wird. Anstelle des Punktes zwischen Strukturnamen und
  Feld wird nun ein Pfeil - bestehend aus Minuszeichen und Größer-als-Zeichen
  (->) - verwendet. Das folgende Beispiel verdeutlicht dieses.
  Beispiel:
  
  struct Buch
  {
     char Titel[100];
     char Autor[100];
     char ISBN[20];
     char Standort[10];
     float Preis;
  } Buecher[50];     /* 50mal struct Buch */
  struct Buch *BuchZeiger;
  BuchZeiger = Buecher;       /* zeigt auf's 1. Array-Element */
  Buecher[0].Preis = 9.99;    /* ist identisch mit */
  (*BuchZeiger).Preis = 9.99; /* ist identisch mit */
  BuchZeiger->Preis = 9.99;
  
  Hier gilt es, ganz genau hinzusehen und einen Teilausdruck nach dem anderen auszuwerten, um
  zu verstehen, was tatsächlich passiert. Ansonsten ändert sich beim Umgang mit den
  Strukturen gar nichts.
  
8.8. Unveränderbare Zeiger
  Ein Zeiger kann unveränderbar sein (d.h. er kann auf keine andere Adresse zeigen) oder
  auf eine unveränderbare Variable zeigen - oder beides; je nachdem, an welcher Stelle das
  Schlüsselwort const verwendet wird.
  Beispiel:
  
  int       i1 = 5;
  int const i2 = 3;
  // veränderbarer Zeiger auf veränderbare Variable:
  int       *       ip1 = &i1;
  // veränderbarer Zeiger auf unveränderbare Variable:
  int const *       ip2 = &i2;
  // unveränderbarer Zeiger auf veränderbare Variable:
  int       * const ip3 = &i1;
  // unveränderbarer Zeiger auf unveränderbare Variable:
  int const * const ip4 = &i2;
  *(ip1++); // erlaubt!
  (*ip1)++; // erlaubt!
  *(ip2++); // erlaubt!
  (*ip2)++; // Fehler - unveränderbare Variable!
  *(ip3++); // Fehler - unveränderbarer Zeiger!
  (*ip3)++; // erlaubt!
  *(ip4++); // Fehler - unveränderbarer Zeiger!
  (*ip4)++; // Fehler - unveränderbare Variable!
  
  Dabei muss eine Variable, auf die ein Zeiger auf unveränderbare Variable (z.B.
  int const *) zeigt, nicht unbedingt unveränderbar sein. Dies mag
  vielleicht unsinnig erscheinen, kann aber durchaus sinnvoll sein.
  Beispiel:
  
  int i = 5;     // veränderbare Variable
  int *ip1 = &i; // Zeiger auf veränderbare Variable
  int const * ip2 = &i; // Zeiger auf
                        // unveränderbare Variable
  i = 7;     // ok
  *ip1 = 9;  // ok
  *ip2 = 11; // Fehler, da ip2 auf
             // unveränderbare Variable zeigt!
  
  Obwohl alle drei Zuweisungen auf die gleiche (veränderbare) Speicheradresse zugreifen, hat
  der Zeiger ip2 keine Veränderungsberechtigung. Man sagt auch: Der
  Zeiger ip1 bietet eine veränderbare Ansicht (im engl: non-constant
  view) und der Zeiger ip2 eine unveränderbare Ansicht (im engl.
  constant view).
  Anders herum funktioniert es übrigens nicht: Ein Zeiger auf eine veränderbare
  Variable kann nicht auf eine unveränderbare Variable zeigen.
  Beispiel:
  
  int const i = 5;
  int * ip = &i; // Fehler, da i unveränderbar ist!
  
  
8.9. Zeiger auf Zeiger
  Ein Zeiger kann auch auf einen anderen Zeiger zeigen.
  Beispiel:
   kap08_02.c
  kap08_02.c
  
  01 #include <stdio.h>
  02 
  03 int main()
  04 {
  05    int Wert = 1234;
  06    int *Zeiger_auf_Wert = &Wert;
  07    int **Zeiger_auf_Zeiger = &Zeiger_auf_Wert;
  08 
  09    printf("  Wert              = %i\n", Wert);
  10    printf(" *Zeiger_auf_Wert   = %i\n", *Zeiger_auf_Wert);
  11    printf("**Zeiger_auf_Zeiger = %i\n", **Zeiger_auf_Zeiger);
  12 
  13    return 0;
  14 }
  
  Ein Zeiger auf einen Zeiger wird durch einen doppelten Variablenoperator
  (**) geschaffen. Diesem Zeiger wird die Adresse eines anderen Zeigers
  zugewiesen, dem wiederum die Adresse einer Variablen zugewiesen wurde. Das angegebene
  Beispiel gibt folgendes aus:
  
    Wert              = 1234
   *Zeiger_auf_Wert   = 1234
  **Zeiger_auf_Zeiger = 1234
  Dies kann beliebig fortgesetzt werden: Es wäre also möglich, einen Zeiger auf
  Zeiger auf Zeiger auf Zeiger usw. zu erzeugen, aber es wird keine sinnvolle Anwendung
  dafür geben.
  
  Voriges Kapitel: 7. Strukturierte Datentypen
  
Nächstes Kapitel: 9. Funktionen

