Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

 << zurück
C von A bis Z von Jürgen Wolf
Das umfassende Handbuch für Linux, Unix und Windows
– 2., aktualisierte und erweiterte Auflage 2006
Buch: C von A bis Z

C von A bis Z
1.116 S., mit CD, Referenzkarte, 39,90 Euro
Galileo Computing
ISBN 3-89842-643-2
gp Kapitel 14 Zeiger (Pointer)
  gp 14.1 Zeiger deklarieren
  gp 14.2 Zeiger initialisieren
    gp 14.2.1 Speichergröße von Zeigern
  gp 14.3 Zeigerarithmetik
  gp 14.4 Zeiger, die auf andere Zeiger verweisen
    gp 14.4.1 Subtraktion zweier Zeiger
  gp 14.5 Typensicherung bei der Dereferenzierung
  gp 14.6 Zeiger als Funktionsparameter (call–by–reference)
    gp 14.6.1 Zeiger als Rückgabewert
  gp 14.7 Array und Zeiger
  gp 14.8 Zeiger auf Strings
    gp 14.8.1 Zeiger auf konstante Objekte (Read-only-Zeiger)
  gp 14.9 Zeiger auf Zeiger und Stringtabellen
    gp 14.9.1 Stringtabellen
  gp 14.10 Zeiger auf Funktionen
  gp 14.11 void-Zeiger
  gp 14.12 Äquivalenz zwischen Zeigern und Arrays


Galileo Computing - Zum Seitenanfang

14.9 Zeiger auf Zeiger und Stringtabellen  downtop

Zeiger auf Zeiger ist ein recht schwieriges Thema, aber es zu verstehen, lohnt sich. Hierzu die Syntax von Zeigern auf Zeiger:

datentyp **bezeichner;

Was heißt jetzt »Zeiger auf Zeiger« genau? Sie haben einen Zeiger, der auf einen Zeiger zeigt, welcher auf eine Variable zeigt, und auf diese Variable zurückgreifen kann. Im Fachjargon wird dabei von einer mehrfachen Indirektion gesprochen. Theoretisch ist es auch möglich, Zeiger auf Zeiger auf Zeiger usw. zu verwenden. In der Praxis machen allerdings solche mehrfachen Indirektionen kaum noch Sinn. Meistens verwenden Sie Zeiger auf Zeiger, also zwei Dimensionen.

Das Haupteinsatzgebiet von Zeigern auf Zeiger liegt in der dynamischen Erzeugung von mehrdimensionalen Arrays wie beispielsweise Matrizenberechnungen. Aber darauf wird in Kapitel 16, Dynamische Speicherverwaltung, eingegangen.

Zuerst ein Beispiel zu diesem komplexen Thema:

/* ptrptr1.c */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
   int wert = 10;
   /* ptr ist ein Zeiger auf int wert */
   int *ptr=&wert;
   /* ptr_ptr ist ein Zeiger auf den Zeiger int *ptr */
   int **ptr_ptr =& ptr;
   printf("*ptr      : %d\n",*ptr);
   printf("**ptr_ptr : %d\n", **ptr_ptr);
   /* Verändert den Wert, auf den int *ptr zeigt */
   **ptr_ptr = 100;
   printf("*ptr      : %d\n",*ptr);
   printf("**ptr_ptr : %d\n", **ptr_ptr);
   /* Verändert nochmals den Wert */
   *ptr = 200;
   printf("*ptr      : %d\n",*ptr);
   printf("**ptr_ptr : %d\n", **ptr_ptr);
   return EXIT_SUCCESS;
}

Wichtig in diesem Beispiel ist, dass Sie bei der Veränderung der Variablen den doppelten Indirektionsoperator (**) einsetzen, genauso wie bei der Deklaration des Zeigers auf einen Zeiger. Hätten Sie nämlich anstatt

**ptr_ptr = 100;

Folgendes geschrieben:

*ptr_ptr = 100;

würde der Zeiger ptr_ptr auf die Speicheradresse 100 verweisen. Und dies ist zumeist irgendwo im Nirwana des Speichers. Wie gesagt, in Kapitel 16 wird dieses Thema nochmals aufgegriffen, und es wird Ihnen dort einiges sinnvoller erscheinen.


Galileo Computing - Zum Seitenanfang

14.9.1 Stringtabellen  toptop

Um es jetzt noch komplizierter zu machen, will ich gleich noch die Stringtabellen hinzunehmen, welche den Zeigern auf Zeiger nicht unähnlich sind (aber nicht dasselbe sind!).

Ein Beispiel: Folgende Stringkonstanten sollen nach Alphabet sortiert werden (ohne Verwendung der Headerdatei <string.h>):

"Zeppelin", "Auto", "Amerika", "Programmieren"

Sie wissen ja noch (sollten Sie eigentlich): *ptr repräsentiert ja dieselbe Anfangsadresse wie ptr[0]. Und Gleiches gilt jetzt auch für:

**ptrptr und *ptrptr[0]

Damit haben Sie ein Array von Zeigern. Und so würde dies im Beispiel aussehen:

char *sort[] = {
   "Zeppelin", "Auto", "Amerika", "Programmieren"
};

Hier haben Sie eine so genannte Stringtabelle. Wie kann jetzt auf die einzelnen Strings einer Stringtabelle zugegriffen werden? Dazu ein kleines Beispiel:

/* ptrptr2.c */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
   char *sort[] = {
      "Zeppelin", "Auto", "Amerika", "Programmieren"
    };
   printf("%s\n", sort[1]);                 /* Auto       */
   printf("%s ", (sort[2]+2));              /* erika      */
   printf("%s %s\n", (sort[0]+6), sort[2]); /* in Amerika */
   printf("%.5s\n", (sort[3]+5–2));         /* gramm      */
   return EXIT_SUCCESS;
}

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.18   Verwendung einer Stringtabelle

Der Zugriff auf die Stringtabelle erfolgt ähnlich wie bei den mehrdimensionalen Arrays. Die erste Ausgabe

printf("%s\n", sort[1]);

gibt das Wort »Auto« auf dem Bildschirm aus. Daher kann davon ausgegangen werden, dass bei

sort[0] = "Zeppelin"
sort[1] = "Auto"
sort[2] = "Amerika"
sort[3] = "Programmieren"

mithilfe des Indizierungsoperators auf die Anfangsadressen der einzelnen Zeichenketten verwiesen wird. Mit der zweiten Anweisung

printf("%s ", (sort[2] + 2) );

wird der Name »erika« ausgegeben. Das lässt sich so erklären: sort[2] repräsentiert die Anfangsadresse von »Amerika« also »A«. Danach kommt +2 hinter dem Feldindex hinzu. Der Zeiger, der ohne +2 weiterhin auch auf den Anfang von »Amerika« gezeigt hätte, zeigt jetzt auf den dritten Buchstaben des Wortes, also auf »e«. Oder genauer: auf die Adresse von »e«. Mit dem Formatzeichen %s wird anschließend veranlasst, dass der String von dieser Adresse an auf dem Bildschirm ausgegeben wird. Genauso verläuft dies bei der nächsten Ausgabe. Die Schreibweise

printf("%s\n", (sort[3] + 5 – 2) );

dient nur der Demonstration, dass es so auch geht. Natürlich lässt sich das leichter lesen mit:

printf("%s\n", (sort[3] + 3) );

Das Programm soll nochmals anhand von Adressen demonstriert werden:

/* ptrptr3.c */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
   char *sort[] = {
      "Zeppelin", "Auto", "Amerika", "Programmieren"
    };
   printf("%p = %c\n", **sort, **sort);
   printf("%p = %c\n", *sort[0], *sort[0]);
   printf("%p = %c\n", *(sort[0]+0), *(sort[0]+0));
   printf("%p = %s\n", sort[0], sort[0]);
   printf("%p = %s\n", *sort, *sort);
   printf("%p = %s\n", (sort[0]+1), (sort[0]+1));
   printf("%p = %s\n", (sort[0]+2), (sort[0]+2));
   printf("*sort = %p, **sort = %p\n", *sort, **sort);
   return EXIT_SUCCESS;
}

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.19   Ausgabe der Adressen einer Stringtabelle

Bei den ersten drei Ausgaben

printf("%p = %c\n", **sort, **sort);
printf("%p = %c\n", *sort[0], *sort[0]);
printf("%p = %c\n", *(sort[0]+0), *(sort[0]+0));

wurden immer die (Anfangs-)Adressen und Inhalte verwendet, auf die der zweite Zeiger zeigt. Was die Ausgabe auch bestätigt. Anschließend wird nur die Adresse des ersten Zeigers benutzt:

printf("%p = %s\n", sort[0], sort[0]);
printf("%p = %s\n", *sort, *sort);

Der Inhalt ist bei Benutzung von einem Zeiger natürlich derselbe wie bei der Benutzung von zwei Zeigern. Aber bei Übersetzung des Programms haben beide Zeiger eine andere Adresse. Die Ausgabe von

printf("*sort = %p, **sort = %p\n", *sort, **sort);

bestätigt alles dies erneut. Jeder einzelne Zeiger benötigt also seinen Speicherplatz und somit auch eine eigene Adresse. Ich versuche, es noch einmal anders zu erklären:

*(* (Variable + x) +y)

Hiermit wird auf das y-te Zeichen im x-ten String gezeigt. Bei dem Programm sieht dies so aus:

*(* (sort + 1) +2)

oder auch – wie schon bekannt – so:

*( (sort[1]) +2)

Hiermit würde auf das 3-te Zeichen im 2-ten String verwiesen, was hierbei dem Zeichen »t« vom String »Auto« entspricht.

Jetzt soll diese Stringtabelle nach Alphabet sortiert werden. Dabei wird nicht die ganze Textzeile verlagert und unnötig hin- und herkopiert, sondern es müssen lediglich die Zeiger in die richtige Reihenfolge gebracht werden:

/* ptrptr4.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
   char *sort[] = {
      "Zeppelin", "Auto", "Amerika", "Programmieren"
    };
   int i,j;
   char *temp;
   for(i = 0; i < 4; i++) {
      for(j = i + 1; j < 4; j++) {
        if( (strcmp(sort[i],sort[j]) > 0) ) {
           temp=sort[i];
           sort[i]=sort[j];
           sort[j]=temp;
        }
      }
   }
   for(i = 0; i < 4; i++)
      printf("%s\n", sort[i]);
   return EXIT_SUCCESS;
}

Bei diesem Sortieralgorithmus handelt es sich um »Selektion Sort«. Die folgenden Zeilen sortieren die Felder mit Zeigern:

   for(i = 0; i < 4; i++) {
      for(j = i + 1; j < 4; j++) {
        if( (strcmp(sort[i],sort[j]) > 0) ) {
           temp=sort[i];
           sort[i]=sort[j];
           sort[j]=temp;
        }
      }
   }

Zuerst wird das erste Element in der Stringtabelle mit allen anderen verglichen. So wird das kleinste Element gefunden, welches an den Anfang gestellt wird. Danach wird das zweite Element mit allen vor ihm liegenden verglichen. Dies geht so weiter bis zum letzten Element in der Stringtabelle. Mehr zu den Algorithmen finden Sie in Kapitel 24.

Das Wichtigste, wie schon mehrmals erwähnt, ist, dass Zeiger für Adressen da sind und sonst nichts. Beispielsweise bedeutet

char *text[500];

nichts anderes als ein char-Array mit 500 char-Zeigern. Genau gesagt, kann jeder dieser 500 Zeiger z.B. auf einen String (char-Array) zeigen. Beweis gefällig? Bitte sehr:

/* ptrptr5.c */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
   char *text[500];
   char str1[] = "Text1";
   char str2[] = "Text2";
   char str3[] = "Text3";
   text[0] = str1;
   text[1] = str2;
   text[2] = str3;
   printf("%s %s %s\n", text[0], text[1], text[2]);
   return EXIT_SUCCESS;
}

In diesem Beispiel wurde den ersten drei Zeigern jeweils die Anfangsadresse einer Stringkonstante übergeben. Mit einfachen Arrays war dies nicht ausführbar. Natürlich ist es jetzt noch nicht möglich, die Anfangsadresse eines zur Laufzeit erstellten Textes so zuzuweisen. Dazu bedarf es Kenntnissen der dynamischen Speicherverwaltung.

Als es darum ging, Strings zu sortieren, konnte mithilfe der Zeiger auf die Anfangsadresse der Strings wesentlich effektiver (schneller) sortiert werden, als wenn dies mit dem ganzen String gemacht würde. Dies daher, da ja nur Adressen auf einem String benutzt werden.

Und statt

char *sort1 = "Zeppelin";
char *sort2 = "Auto" ;
char *sort3 = "Amerika";
char *sort4 = "Programmieren";

zu schreiben, ist doch diese Schreibweise

char *sort[] = {
   "Zeppelin", "Auto", "Amerika", "Programmieren"
 };

viel effektiver und kürzer. Hier sind es vier Zeiger auf ein char-Array, die auf die Anfangsadresse eines jeden einzelnen Wortes zeigen.

Folgende Vorteile ergeben sich für den Programmierer, wenn er Stringtabellen verwendet:

gp  Mit Stringtabellen wird das Programm übersichtlicher.
gp  Es wird Speicherplatz gespart.
gp  Die Verwaltung von Stringtabellen ist einfacher und effizienter.
gp  Sollte ein Programm in mehreren Sprachen geschrieben werden, kann dies leichter lokalisiert werden.

Hier ein Beispiel, wie Sie Stringtabellen effektiv einsetzen können:

/* ptrptr6.c */
#include <stdio.h>
#include <stdlib.h>
#define ASK    0
#define WORDS  1
#define START  2
#define ENGLISH 1
#ifdef GERMAN
const char *language[] = {
   "Du sprichst Deutsch?", "Einige Worte: ",
   "Feuer", "Erde", "Wasser", "Luft", "Leben", NULL
 };
#elif ENGLISH
const char *language[] = {
   "Do you speak english?", "Some words: ",
   "Fire", "earth", "water", "air", "life", NULL
 };
#else /* FRENCH */
const char *language[] = {
   "Tu parle francais?", "quelques mots: ",
   "Le feu", "La terre", "de l'eau", "de l'air", "La vie", NULL
 };
#endif
int main(void) {
   int i;
   printf("%s\n", language[ASK]);
   printf("%s\n",language[WORDS]);
   for(i = START; language[i] != NULL; i++)
      printf("\t%s,\n", language[i]);
   return EXIT_SUCCESS;
}

Hierbei handelt es sich um ein einfaches Listing, welches mit bedingter Kompilierung ein Programm in entsprechender Sprache übersetzt. In diesem Beispiel wurde mit

#define ENGLISH 1

die Sprache auf Englisch eingestellt. Bei Ausgabe des Programms wird dies auch bestätigt. Müssen Sie jetzt eine Version für Ihren spanischen Kollegen schreiben, müssen Sie nur nach der Stringtabelle suchen und entsprechende Einträge übersetzen und hinzufügen. So entsteht ohne allzu großen Aufwand ein internationales Programm:

#ifdef GERMAN
const char *language[] = {
   "Du sprichst Deutsch?", "Einige Worte: ",
   "Feuer", "Erde", "Wasser", "Luft", "Leben", NULL
 };
#elif ENGLISH
const char *language[] = {
   "Do you speak english?", "Some words: ",
   "Fire", "earth", "water", "air", "life", NULL
 };
#elif FRENCH
const char *language[] = {
   "Tu parle francais?", "quelques mots: ",
   "Le feu", "La terre", "de l'eau", "de l'air", "La vie", NULL
 };
#else /* ESPANOL */
const char *language[] = {
   "Habla Usted espanol", "algunas palabras: ",
   "Fuego", "tierra", "agua", "aire", "vida", NULL
 };
#endif

Mit Stringtabellen lassen sich auch komfortabel Fehlermeldungen auf dem Bildschirm ausgeben:

char *fehlermeldung[] = {
   "Mangel an Speicherplatz",
   "Speicherbereichsüberschreitung",
   "Wertbereichsüberschreitung",
   "Die Syntax scheint falsch",
   "Zugriff verweigert – keine Rechte",
   "Zugriff verweigert – falsches Passwort",
   "Unbekannter Fehler trat auf"
 };

Zugegriffen wird auf die einzelnen Fehlermeldungen mit dem Feldindex von Nr.[0]-[6]. »Zugriff verweigert – keine Rechte« beispielsweise ist somit fehlermeldung[4].

Nach diesem Abschnitt über Zeiger auf Zeiger und den Stringtabellen kommen sicherlich jetzt die einen oder anderen Fragen auf. Vor allem wurden die Beispiele immer nur mit konstanten Werten gegeben. Um sich also wirklich effektiv und sinnvoll mit dem Thema auseinander zu setzen, müssen Sie sich noch ein wenig gedulden, bis Sie zur (ich wiederhole mich) dynamischen Speicherverwaltung gelangen (Kapitel 16).

 << zurück
  
  Zum Katalog
Zum Katalog: C von A bis Z
C von A bis Z
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Shell-Programmierung






 Shell-Programmierung


Zum Katalog: Linux-UNIX-Programmierung






 Linux-UNIX-Programmierung


Zum Katalog: C/C++






 C/C++


Zum Katalog: UML 2.0






 UML 2.0


Zum Katalog: Reguläre Ausdrücke






 Reguläre Ausdrücke


Zum Katalog: Linux






 Linux


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de