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

Inhaltsverzeichnis
Vorwort
Vorwort des Gutachters
1 Einstieg in C
2 Das erste Programm
3 Grundlagen
4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()«
5 Basisdatentypen
6 Operatoren
7 Typumwandlung
8 Kontrollstrukturen
9 Funktionen
10 Präprozessor-Direktiven
11 Arrays
12 Zeiger (Pointer)
13 Kommandozeilenargumente
14 Dynamische Speicherverwaltung
15 Strukturen
16 Ein-/Ausgabe-Funktionen
17 Attribute von Dateien und das Arbeiten mit Verzeichnissen (nicht ANSI C)
18 Arbeiten mit variabel langen Argumentlisten – <stdarg.h>
19 Zeitroutinen
20 Weitere Headerdateien und ihre Funktionen (ANSI C)
21 Dynamische Datenstrukturen
22 Algorithmen
23 CGI mit C
24 MySQL und C
25 Netzwerkprogrammierung und Cross–Plattform-Entwicklung
26 Paralleles Rechnen
27 Sicheres Programmieren
28 Wie geht’s jetzt weiter?
A Operatoren
B Die C-Standard-Bibliothek
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
C von A bis Z von Jürgen Wolf
Das umfassende Handbuch
Buch: C von A bis Z

C von A bis Z
3., aktualisierte und erweiterte Auflage, geb., mit CD und Referenzkarte
1.190 S., 39,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1411-7
Pfeil 12 Zeiger (Pointer)
Pfeil 12.1 Zeiger deklarieren
Pfeil 12.2 Zeiger initialisieren
Pfeil 12.2.1 Speichergröße von Zeigern
Pfeil 12.3 Zeigerarithmetik
Pfeil 12.4 Zeiger, die auf andere Zeiger verweisen
Pfeil 12.4.1 Subtraktion zweier Zeiger
Pfeil 12.5 Typensicherung bei der Dereferenzierung
Pfeil 12.6 Zeiger als Funktionsparameter (call–by–reference)
Pfeil 12.6.1 Zeiger als Rückgabewert
Pfeil 12.7 Array und Zeiger
Pfeil 12.8 Zeiger auf Strings
Pfeil 12.8.1 Zeiger auf konstante Objekte (Read-only-Zeiger)
Pfeil 12.9 Zeiger auf Zeiger und Stringtabellen
Pfeil 12.9.1 Stringtabellen
Pfeil 12.10 Zeiger auf Funktionen
Pfeil 12.11 void-Zeiger
Pfeil 12.12 Äquivalenz zwischen Zeigern und Arrays
Pfeil 12.13 Der »restrict«-Zeiger


Rheinwerk Computing - Zum Seitenanfang

12.9 Zeiger auf Zeiger und Stringtabellen Zur nächsten ÜberschriftZur vorigen Überschrift

»Zeiger auf Zeiger« sind ein recht schwieriges Thema, aber es zu verstehen, lohnt sich. Die Syntax von Zeigern auf Zeiger sieht so aus:

datentyp **bezeichner;

Was heißt jetzt »Zeiger auf Zeiger« genau? Sie haben einen Zeiger, der auf einen Zeiger zeigt, der 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 ist die dynamische Erzeugung von mehrdimensionalen Arrays wie beispielsweise Matrizenberechnungen. Aber darauf gehe ich in Kapitel 14, »Dynamische Speicherverwaltung«, ein.

Sehen wir uns zuerst ein Beispiel zu diesem komplexen Thema an:

/* 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 14 wird dieses Thema nochmals aufgegriffen, und es wird Ihnen dort einiges sinnvoller erscheinen.


Rheinwerk Computing - Zum Seitenanfang

12.9.1 Stringtabellen topZur vorigen Überschrift

Um es jetzt noch komplizierter zu machen, will ich gleich noch die Stringtabellen hinzunehmen, die 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, dass *ptr dieselbe Anfangsadresse wie ptr[0] repräsentiert. 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 sogenannte 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 12.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 12.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 der Ü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, das 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 22.

Das Wichtigste – wie schon mehrmals erwähnt wurde – 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. Genauer 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 brauchen Sie Kenntnisse in 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 rührt daher, dass ja nur Adressen auf einem String benutzt werden.

Und anstatt

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:

  • Mit Stringtabellen wird das Programm übersichtlicher.
  • Es wird Speicherplatz gespart.
  • Die Verwaltung von Stringtabellen ist einfacher und effizienter.
  • 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, das 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 auseinanderzusetzen, müssen Sie sich noch ein wenig gedulden, bis Sie zur (ich wiederhole mich) dynamischen Speicherverwaltung gelangen (Kapitel 14).



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
  
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: C von A bis Z

 C von A bis Z
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: C/C++






 C/C++


Zum Rheinwerk-Shop: Einstieg in C






 Einstieg in C


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
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.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern