10. Zeiger-Datentyp
Ziele dieser Einheit:
- Einführung dynamischer Datentypen und deren
Anwendung (in erster Näherung, als Vorbereitung auf verkettete Listen und Dateiverarbeitung).
- Einblick in C-spezifischen Umgang mit Zeiger-Datentypen
(„Zeiger-Arithmetik“).
10.1 Sinn des Zeiger-Datentyps, speziell in C
- Realisierung dynamischer Datenstrukturen, bei denen die Größe (Speicherbedarf) erst zur Laufzeit bekannt wird (Listen-,
Dateiverarbeitung).
- Effizienzsteigerung durch hardwarenahe Programmierung, z.B.
Rechnen mit Absolut-/Relativ-Adressen, wahlfreien Direktzugriff auf Daten etc. durch
Zeiger-Arithmetik in C: Operatoren + (Addition) und – (Subtraktion) speziell für Zeiger definiert.
Parameterübergabe zwischen aufrufendem Programm (Hauptprogramm) und aufgerufenem Unterprogramm (Funktion).
Beispiel: scanf(“ %c“, &zeichen)
- ABER: Nicht bzw. falsch initialisierte Zeiger können zu schwer erkenn- baren Fehlern und Systemabstürzen führen!
10.2 Definition von Zeigern
Zeiger: pInt ist ein Integer-Zeiger (Inhalt: die Hexadezimal-
Adresse af00 – die Speicheradresse (&zhl) der Variablen zhl).
Gezeigte: Inhalt des Speichers, auf den pInt zeigt, ist eine ganze Zahl (Integer-Variable, hier 20).
Definition eines Zeiger-Datentyps
Bezeichner
Datentyp *
- Beispiel: int *pInt; /* Zeiger */
int zhl = 20;
pInt = &zhl;
- Zeiger-Variablen werden mit dem * Operator deklariert.
0xaf00 pInt
20 zhl
af00
- int* i,j; entspricht int *i; int j;
- Adressoperator & liefert die Speicheradresse einer Variablen.
Beispiel: int idx;
int *pIdx; /* Definition eines Zeigers */
pIdx = &idx; /* pIdx zeigt jetzt auf die Variable idx */
- Dereferenzierungsoperator : * (Inhaltsoperator)
Achtung: der *-Operator wird auch zur Definition eines Zeigers benutzt.
Beispiel: int idx, zahl;
int *pIdx = &idx;
zahl = *pIdx; /* *pIdx liefert den Inhalt von idx */
Bemerkungen
Beispiel (1):
Gezeigte (Bezugs-)Variable:
• Wert: 10
• Adresse: 0xa004
• Typ: int
• Name: idx (symb. Adr.)
• Definition: int idx;
• Wert: 0xa004
• Adresse: 0xa00b
• Typ: Zeiger auf int
• Name: pIdx (symb. Adr.)
• Definition: int *pIdx;
Zeiger-Variable 10
0xa004 0xa004
(idx)
0xa00b
(*pIdx)
Arbeitsspeicher:
...
Beispiel (2):
- Inhalt von idx: Wert 10
- Inhalt von &idx: Adresse 0xa004
- &idx liefert 0xa004
- &pIdx liefert 0xa00b
- *pIdx liefert 10 int idx;
0xa004
0xa00b
Arbeitsspeicher:
10 idx = 10;
0xa004
pIdx = &idx;
int *pIdx;
Beispiele:
*pInt2 = *pInt1;
10
... ... 10
pInt1 *pInt1 pInt2 *pInt2
10
*pInt1 ...
pInt1
30 ...
pInt2 *pInt2
*pInt1 = 10; *pInt2 = 30;
pInt2 = pInt1;
10
*pInt1(*pInt2)
... ... 10
pInt1 pInt2
int *pInt1 = &...
int *pInt2 = &...
*pInt1 = 5;
5
... ... 10
pInt1 *pInt1(*pInt2) pInt2
#include <stdio.h>
void main() {
short zahl = 10;
short *pShort; /* Pointer/Zeiger auf short-Variable */
pShort = &zahl; /* pShort zeigt auf zahl */
printf("Wert %d \n", *pShort);
/* Ausgabe : Wert 10 */
}
Beispiel (3):
- Feldbezeichner werden bei Bedarf automatisch in Zeiger auf die erste Komponente umgewandelt.
Beispiel: *(vektor+idx) /* Feldbezeicher: vektor */
10.3 Felder und Zeiger
for (idx=0; idx<5; idx++) {
vektor [idx] = 0;
}
for (idx=0; idx<5; idx++) {
*(vektor+idx) = 0;
}
int vektor[5]; /* Feldbezeichner: vektor */
int idx;
- Enger Zusammenhang zwischen Feldern (Arrays) und Zeigern.
- Feldverarbeitung mittels Zeiger und spezieller Arithmetik darauf.
- Beispiel: Es gelten folgende Vereinbarungen:
- Folgende Code-Fragmente bewirken dann das gleiche (Initialisierung):
int vektor [10];
int *pInt = vektor;
int idx = ..;
Dann gilt:
*(pInt+idx) entspricht vektor[idx] (Inhalt einer Komponente)
pInt+idx entspricht &vektor[idx] (Adresse einer Komponente) -Beispiel: Folgende Vereinbarungen mögen gelten:
vektor:
pInt:
*(pInt+1)
*(pInt+2)
pInt+9
Inhalt Inhalt
Adresse Adresse
Beispiel:
..
10.4 Felder von Zeigern
- Folgendes Programmfragment veranschaulicht das:
int zhl = 7;
int *pVektor[5]; /* Feld mit 5 Zeigern auf int */
...
pVektor[3] = &zhl;
...
printf("%d \n", *pVektor[3]);
7
pVektor:
- Feldelemente können aus Zeigern bestehen.
Beispiel:
- Bei der Addition/Subtraktion von ganzen Zahlen auf einen Zeiger wird automatisch die Größe des Datentyps berücksichtigt.
Beispiel:
Inhalt von pInt 0xa004
pInt + 1 0xa004+4 = 0xa008
10.5 Zeiger-Arithmetik
int vektor[5];
int *pInt = vektor, idx;
for (idx=0; idx<5; idx++) {
pInt = pInt + 1; /* entspricht pInt++ */
*pInt = 0;
printf("Komponente %d hat die Adresse %d \n", idx, pInt);
}
- Für Zeiger-Variablen sind spezielle Additions-/Subtraktions-Operatoren definiert (dargestellt durch die üblichen Zeichen: + und –).
Beispiel:
- Folgende Definitionen sind möglich:
a) char nachricht1[11] = “Hallo Welt“; /* Feld */
b) char nachricht2[] = “Hallo Welt“; /* Feld */
c) char *pNachricht = “Hallo Welt“; /* Zeiger */
- Fall a) und b) sind identisch:
Jeweils Platz für 11 Zeichen reserviert.
Ihre Inhalte sind beliebig veränderbar.
nachricht1 und nachricht2 sind (als Adressen) nicht veränderbar.
- Fall c) ist anders:
Platz für einen char-Zeiger reserviert.
Änderungen des Inhalts sind systemabhängig.
pNachricht ist veränderbar.
10.6 Zeichenketten (Strings)
H a l l o W e l t \0
Beispiel:
H a l l o W e l t \0
nachricht1 pNachricht
Bemerkungen:
- Strings sind in C kein echter Datentyp.
- Direkte Vergleiche (==) sind nicht möglich.
- Zuweisungen haben oft unerwünschte Ergebnisse („Seiteneffekte“).
- C bietet Funktionen zur Stringbehandlung (#include <string.h>)
- Beispiel:
struct complex {float re, im;};
struct complex cmpl_zhl;
struct complex *pCmpl_zhl = &cmpl_zhl;
- Zugriff auf die einzelnen Komponenten mit ->
Es gilt: cmpl_zhl.re bzw. cmpl_zhl.im Aber: pCmpl_zhl->re bzw. pCmpl_zhl->im
- pCmpl_zhl->re ist gleichwertig zu (*pCmpl_zhl).re