Kapitel fünf - Pointer

Pointer - Variablen haben Adressen. Und auch Namen …

Der Pointer hat vielen C-Programmier-Anfängern Angst eingejagt, aber sobald Sie es verstanden haben wird er ein sehr nützliches Merkmal der Sprache. Pointer sind in der Realität eigentlich nicht so kompliziert, aber es ist leicht, sich verwirren zu lassen. Lasst uns Versuchen, sie zu verstehen …..

Erinnern wir uns die Deklaration von Variablen. Wir müssen eine Variable deklarieren, die dem Compiler mitteilt, um welchen Typ es sich handelt und wie sie heißt bevor wir sie verwenden können. Der Compiler weist einen Speicherblock zum Speichern der Variablen zu. Für jede von uns deklarierte Variable gibt es einen Speicherblock, der vom Compiler für diese Variable reserviert wird, und der Compiler merkt sich dass für jede Variable ein bestimmter Speicherblock verwendet wird.

[ * und & ] Es ist hilfreich, sich laut vorzusagen, was eine Codezeile tut - ein * ist worauf zeigt und ein & ist die Adresse von. Sobald wir diese beiden Ideen im Kopf haben, haben wir das Problem ziemlich gut verstanden!

Was ist ein Pointer?
Ein Pointer ist nur die Adresse eines Speicherblocks mit einer Variablen darin. Das ist alles dazu zu wissen gibt, wenn wir eine Variable und einen Pointer darauf deklarieren. Auf den Wert der Variable können wir auf zwei Arten in diesem Speicherblock zugreifen; entweder mit dem Variablennamen oder mit dem Pointer.

Dazu ein kleines Beispiel:

#include <stdio.h> void main (void) { int a; int *ptr_to_a; ptr_to_a = &a; a = 5; printf ("Der Wert von a ist %d\n", a); *ptr_to_a = 6; printf ("Der Wert von a ist %d\n", a); printf ("Der Wert von ptr_to_a ist %d\n", ptr_to_a); printf ("Er speichert den Wert %d\n", *ptr_to_a); printf ("Die Adresse von a ist %d\n", &a); }

Nach dem Erstellen und Compilieren (gcc -o point point.c) ergibt das die folgende Ausgabe an der Konsole:

./point
Der Wert von a ist 5
Der Wert von a ist 6
Der Wert von ptr_to_a ist -1090931256
Er speichert den Wert 6
Die Adresse von a ist -1090931256

Zeiger sind eine der Möglichkeiten, mit denen uns C ermöglicht (oder in einigen Fällen zwingt), darüber nachzudenken, was die tatsächliche Hardware Ihres Computers tut. Ein gutes Verständnis der Zeiger gibt uns ein gutes Verständnis dafür, wie der Compiler mit Speicher umgeht.

Gehen wir das Script nun Zeile für Zeile durch. Die Zeile int a; ist klar, damit deklarieren wir eine Variable. Sehen wir uns nun die nächste Zeile an:

int *ptr_to_a;

Es sieht so aus, als würde eine andere ganzzahlige Variable deklariert, nicht wahr? Aber schauen wir genauer hin; das Sternchen * am Anfang des Variablennamens gibt an, dass hier keine ganzzahlige Variable deklariert wird, sondern ein Zeiger auf eine Ganzzahlvariable.

Wir haben jetzt eine ganzzahlige Variable namens a und einen Zeiger auf eine ganzzahlige Variable namens ptr_to_a. Aber keiner von diesen hat tatsächlich einen Wert in sich: Sie sind beide nicht initialisiert. Er beruft sich auf den Pointer ptr_to_a, hat aber keine Ahnung, was (oder wo) a ist. Das wird in der nächsten Zeile erledigt.

ptr_to_a = &a;

Das ist Wichtig!
In C bedeutet das Symbol & vor einem Variablennamen Adresse der Variablen, und &a bedeutet die Adresse im Speicher der Variablen a. Und wie oben gesagt, ist ein Pointer die Adresse von einer Variablen.

Diese Zeile initialisiert also ptr_to_a als Adresse von a; ptr_to_a ist jetzt ein gültiger Zeiger auf die Variable a, sodass wir sie jetzt verwenden können.

Die nächsten beiden Zeilen sind bekannt. Wir setzen a auf 5 und überprüfen mit einem Ausdruck, dass es funktioniert hat. Versuchen wir jetzt das gleiche, aber mit dem Pointer.

*ptr_to_a = 6;

Schon wieder das Sternchen, aber etwas anders verwendet als zuvor. Wenn wir eine Variable deklarieren und ein Sternchen vor den Namen setzen, gibt das an, dass die Variable ein Ponter ist. Aber wenn der Pointer existiert und wir ein Sternchen vor den Namen setzen, bedeutet dies, die Variable, auf die dieser Zeiger zeigt. Dies wird als Dereferenzierung des Pointers bezeichnet. Diese Zeile weist den Compiler an, die Variable ptr_to_a auf 6. festzulegen, auf die der Zeiger zeigt.

Wir wissen, dass die Variable, auf die ptr_to_a zeigt, a ist. Wir haben das vor ein paar Zeilen eingerichtet, und so ist diese Zeile nur eine weitere Möglichkeit, a auf 6 zu setzen. Wenn wir den Wert von a jetzt drucken, sehen wir nun, dass er nun den Wert von sechs hat.

Die nächsten Zeilen helfen hoffentlich dabei, die Beziehung zwischen Zeiger, Variablen und Adressen zu erkennen.

printf ("The value of ptr_to_a is %d\n", ptr_to_a);

In dieser Zeile drucken wir den Wert von ptr_to_a. Nicht der Wert, auf den es zeigt, sondern der Wert des Zeigers selbst. Dies gibt eine große Zahl aus, da es sich um die Adresse im Speicher handelt, an der sich a befindet.

printf ("It stores the value %d\n", *ptr_to_a);

In dieser Zeile drucken wir den Wert, auf den ptr_to_a zeigt. Das Sternchen vor dem Namen beachten. Dies gibt den Wert von a aus.

printf ("The address of a is %d\n", &a);

Schließlich drucken wir in dieser Zeile die Adresse selbst. Das & Zeichen vor dem Namen beachten. Auch dies gibt eine große Zahl aus, die dem oben angegebenen Wert von ptr_to_a entspricht.

Bei der Arbeit mit Zeigern ist Folgendes zu beachten: wir können nicht nur einen Zeiger deklarieren, sondern auch die Variable deklarieren und zuordnen, auf die er zeigen soll. Wenn ein Zeiger erstellt wird, zeigt er auf eine zufällige Stelle im Speicher. Wenn hier versucht wird, etwas in diesen Speicherbereich zu schreiben, können alle Arten von Fehlern entstehen, bis hin zum Absturz des Rechners. Es muss immer sichergestellt werden, dass die Pointer auf etwas zeigen, bevor wir etwas damit tun.

Es lohnt sich, dies noch einmal zu betonen: Ein Zeiger ist kein Speicher, sondern nur eine Speicheradresse. Wenn wir etwas mit einem Zeiger tun möchten, müssen wir etwas deklarieren, auf das er zeigen soll, sowie den Zeiger selbst.

Leere Pointer und Umwandlungen
Wir können auch einen Pointer definieren, ohne anzugeben, auf welchen Variablentyp er zeigt. Dies ist ein void-Pointer, der als void * geschrieben ist. Wenn wir darüber nachdenken ist dies sinnvoll; Ein Pointer ist nur eine Adresse im Speicher, daher müssen wir nicht unbedingt wissen, was sich in diesem Speicher befindet. Um einen leeren Pointer zu verwenden, müssen wir ihn umwandeln. Sie teilen dem Compiler mit, als welche Art von Pointer er behandelt werden soll und welche Art von Variable sich an dieser Stelle im Speicher befindet.

Hier ein Beispiel:

#include <stdio.h> void main (void) { int intval = 255958283; void *vptr = &intval; printf ("The value at vptr as an int is %d\n", *((int *) vptr)); printf ("The value at vptr as a char is %d\n", *((char *) vptr)); }

Wir initialisieren den void-Pointer vptr so, dass er auf eine ganzzahlige Variable namens intval zeigt.
In der ersten printf-Anweisung fügen wir (int *) vor vptr ein, bevor wir es mit * dereferenzieren. Dadurch wird vptr in einen Ganzzahlzeiger umgewandelt, sodass der Wert von intval als Ganzzahl gedruckt wird. In der zweiten printf-Anweisung fügen wir (char *) vor vptr ein, bevor wir es dereferenzieren. Dadurch wird vptr in einen Zeichenzeiger umgewandelt. Es wird also der Wert des Zeichens gedruckt, aus dem das erste Byte von intval besteht.

Das ergibt folgende Ausgabe an der Konsole:

Der Wert auf vptr als int ist 255958283
Der Wert auf vptr als char ist 11

Wir können ++ und - - mit Pointern verwenden, müssen jedoch vorsichtig sein.(*a)++ erhöht den Wert, auf den a zeigt, aber *(a++) erhöht den Zeiger selbst und nicht den Wert, auf den er zeigt - dies verschiebt a, um unmittelbar nach a auf die Speicheradresse zu zeigen.

Wofür verwenden wir Zeiger?
Das ist wirklich alles, was es zu Pointern zu sagen gibt, außer der Frage, warum man sich die Mühe macht. Wir können bereits auf eine Variable mit ihrem Namen zugreifen. Warum brauchen wir also eine komplizierte alternative Methode, um zum Inhalt einer Variablen zu gelangen?

Es gibt verschiedene Möglichkeiten, wie Pointer nützlich sind, die wir in späteren Kapiteln genauer untersuchen werden. Aber einige der wichtigsten sind:

Funktionsaufrufe
Im nächsten Kapitel erfahren Sie, wie Sie C-Code in Funktionen aufteilen. Zeiger sind sehr nützlich, damit eine Funktion mehrere Werte zurückgeben kann.

Zeichenfolgenbehandlung
In C ist eine Zeichenfolge ein fortlaufender Speicherblock mit einem in jedem Byte gespeicherten Buchstaben. Zeiger ermöglichen effiziente Operationen an Zeichenfolgen.

Arrays
C ermöglicht Array-Variablen, Listen von Werten des gleichen Typs, die wie Zeichenfolgen in einem fortlaufenden Speicherblock gespeichert werden. Zeiger machen den Zugriff auf Arrays einfacher und effizienter.

Zurück zum Inhalt Vorige Seite Nächste Seite