|
pa = a;
verwendet werden, wo auf der rechten Seite der Feldname a steht.
Die einzelnen Elemente des Feldes a können auf drei verschiedene
Weisen angesprochen werden:
a[i] i-tes Feldelement
*(pa+i) Pointer pa + i * (Laenge eines Elementes von a)
*(a+i) Arrayanfang + i * (Laenge eines Elementes von a)
a ist also in C äquivalent zu &a[0]. So kann man
statt pa = &a[0] auch schreiben pa = a. Die Verwandtschaft
zwischen Arrays und Pointern kann beim Programmieren recht praktisch und effizient
sein, führt aber auch zu unsauberem Stil und unverständlichen Programmen.
Das folgende Beispiel soll die Äquivalenz von Arrays und Pointern etwas
verdeutlichen:
void arraytest(char x[])
{ /* Ersetzt im Array x alle 'e"' durch ".",
Array wird als Parameter uebergeben */
int z;
for (z = 0; x[z] != '\0'; z++)
if (x[z] == 'e') x[z] = '.';
}
void pointertest(char *px)
{ /* Ersetzt im Array x alle 'e"' durch ".",
ein Pointer wird als Parameter uebergeben */
int z;
for (z = 0; *(px+z) != '\0'; z++)
if (*(px+z) == 'e') *(px+z) = '.';
}
Beide Unterprogramme können mit einem Array als Parameter aufgerufen
werden und liefern das gleiche Ergebnis.
Im folgenden Programmbeispiel werden die drei Möglichkeiten in der
Anweisung printf einander gegenübergestellt:
/* Zeiger und Felder */
#include <stdio.h>
int main(void)
{
int i, a[10] = {12, 23, 34, 35, 36, 37, 44, 46, 48, 50} ;
int *pa;
pa = a; /* oder: pa = &a[0]; */
for (i=0; i<10; i++)
printf("a[%d]= %d\t *(pa+%d)= %d\t *(a+%d)= %d\n",
i, a[i], i, *(pa+i), i, *(a+i));
}
Die Korrespondenz von Indizieren und Zeigerarithmetik ist daher sehr
eng: Die Schreibweise pa + i bedeutet die Adresse des
i-ten Objekts hinter demjenigen, auf welches pa
zeigt. Es gilt:
Jeder Array-Index-Ausdruck kann auch als Pointer-Offset-Ausdruck
formuliert werden und umgekehrt.
Der Ausdruck pa + i bedeutet nicht, daß zu
pa der Wert i addiert wird, sondern i
legt fest, wie oft die Länge des Objekt-Typs von pa
addiert werden muß.
|
Es besteht jedoch ein gravierender Unterschied zwischen Array-Namen und Zeigern:
- Ein Zeiger ist eine Variable, deren Wert jederzeit geändert werden darf, z.B.:
int x,*px;
px = &x; /* Veränderung ist jederzeit zulässig */
- Ein Array-Name ist eine Zeiger-Konstante, ihr Wert ist nicht veränderbar! Z. B.:
int z, y[5];
y = &z; /* U n z u l ä s s i g */
Beispiel zur Verwandschaft zwischen Zeigern und Arrays: Äquivalente
Formulierungen der Funktion strcopy()
void strcopy(char strl[] ,char str2[])
{
int i=0;
while((str1[i]=str2[i]) != '\0')
i++;
}
void strcopy(char *strl ,char *str2)
{
int i=0;
while((str1[i]=str2[i]) != '\0')
i++;
}
void strcopy(char *strl ,char *str2)
{
int i=0;
while((*(str1+i)= *(str2+i)) != '\0')
i++;
}
void strcopy(char *strl ,char *str2)
{
while((*str1 = *str2) != '\0')
{
str1++;
str2++;
}
}
void strcopy(char *strl ,char *str2)
{
while (*str1++ = *str2++);
}
Beispiel: Zeichenketten und Pointer:
void main(void)
{
int i = 0;
char zeichenKette1[] = "Hier stehen ganz viele Zeichen",
zeichenKette2[32] = {'D','i','e',' ','z','w','e','i','t','e',
' ','Z','e','i','c','h','e','n','k','e',
't','t','e','\0'},
*zeichenKette3 = "Heiter geht's weiter!",
*zeichenPtr = NULL;
printf("String 1: %s\n",zeichenKette1);
printf("String 2: %s\n",zeichenKette2);
printf("String 3: %s\n\n",zeichenKette3);
printf("Zeichen 5 aus String 1: %c\n",zeichenKette1[5]);
printf("String 1 ab Zeichen 5: %s\n\n", &zeichenKette1[5]);
zeichenPtr = zeichenKette2;
printf("ZeichenPtr zeigt auf: %s\n",zeichenPtr);
printf("ZeichenPtr + 4 zeigt auf: %s\n",zeichenPtr+4);
for(i = 0; i < 24; i++)
{
printf("%c",*(zeichenPtr+i));
}
printf("\n\n");
}
Ausgabe:
String 1: Hier stehen ganz viele Zeichen
String 2: Die zweite Zeichenkette
String 3: Heiter geht's weiter!
Zeichen 5 aus String 1: s
String 1 ab Zeichen 5: stehen ganz viele Zeichen
ZeichenPtr zeigt auf: Die zweite Zeichenkette
ZeichenPtr + 4 zeigt auf: zweite Zeichenkette
Die zweite Zeichenkette
Im nächsten Programmbeispiel werden drei Versionen einer Funktion
vom Datentyp int einander gegenübergestellt, die zur Ermittlung
der Länge einer Zeichenkette dienen.
#include <stdio.h>
int strlen0(char s[]);
int strlen1(char *s);
int strlen2(char *s);
int main(void) /* Laenge einer Zeichenkette ermitteln */
{
char name[51];
printf("Zeichenkette mit max. 50 Zeichen eingeben:\n");
scanf("%s",name);
printf(" name: %s\n",name);
printf("Laenge von name mit strlen0: %d\n",strlen0(name));
printf("Laenge von name mit strlen1: %d\n",strlen1(name));
printf("Laenge von name mit strlen2: %d\n",strlen2(name));
}
int strlen0(char s[]) /* liefert die Laenge von s */
{
int i;
for (i = 0; s[i] != '\0'; i++)
;
return i;
}
int strlen1(char *s) /* 1.Zeigerversion */
{
int i;
for (i = 0; *s != '\0'; s++, i++)
;
return i;
}
int strlen2(char *s) /* 2.Zeigerversion */
{
char *p;
for (p = s; *p != '\0'; p++)
;
return p - s;
}
Die Funktion strlen0
verwendet keine Zeiger und hat als Parameter ein Feld vom Datentyp
char unbestimmter Länge. In der for Anweisung der Funktion
strlen0 wird die Laufvariable i ausgehend vom Anfangswert
Null solange inkrementiert, wie das i-te Zeichen der beim
Aufruf übergebenen Zeichenkette vom Nullzeichen '\0' am
Ende der Zeichenkette verschieden ist. Der Wert von i, für den
s[i] gleich '\0' gilt, wird nicht mehr inkrementiert
und über die return Anweisung als Länge der Zeichenkette
an die rufende Funktion main zurückgegeben.
Die Funktionen strlen1 und
strlen2 haben jeweils einen Zeiger auf den Datentyp char
als Parameter. Wenn beim Aufruf von strlen1 eine Zeichenkette
als Argument übergeben wird, wird die Adresse des ersten Elements
übergeben, so daß der Zeiger s auf das erste Element der
übergebenen Zeichenkette zeigt. In der for Anweisung von
strlen1 wird geprüft, ob das Objekt, auf welches s
zeigt, vom Nullzeichen am Ende der Zeichenkette verschieden ist. Ist
dies nicht der Fall, werden der Zeiger s und die Laufvariable
i inkrementiert. Durch die Inkrementierung von s weist
diese Zeigervariable auf das nächste Element der beim Aufruf
übergebenen Zeichenkette. Wenn aber der Zeiger s auf das
Nullzeichen am Ende der Zeichenkette zeigt, findet keine Inkrementierung
von s und i mehr statt. Der Wert von i wird
durch die return Anweisung an main zurückgegeben.
In der Funktion strlen2 wird p als Zeiger auf den Datentyp
char vereinbart. Im ersten Ausdruck in der for Anweisung
wird p so initialisiert, daß p auf das erste Zeichen
der als Argument übergebenen Zeichenkette zeigt. In der
Schleifenbedingung wird geprüft, ob das Nullzeichen am Ende der
Zeichenkette erreicht ist. Ist dies noch nicht der Fall, wird der
Zeiger p inkrementiert, so daß er auf das nächste Element
der Zeichenkette zeigt. Ist das Nullzeichen aber erreicht, findet
eine Inkremetierung von p nicht mehr statt. Die Zeigerdifferenz
p-s, die als Ergebnis zurückgegeben wird, gibt die Anzahl der
Inkrementierungen von p an, also die Länge der Zeichenkette.
Das folgende Beispiel zeigt, wie man ein Array von Zeigern initialisieren kann:
char *month_name(int n)
{
static char *name[] = { /* Array von Zeigern */
"falscher Monat", /* Ein String ist ein char-Array */
"Januar", /* und daher durch seine */
"Februar", /* Anfangsadresse charakterisiert */
"Maerz",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Dezember"
};
return ((n < 1 || n > 12) ? name[0] : name[n]);
}
Beispiel: Sortieren von n eingegebenen int-Werten
An die Sortierfunktion ssort werden nur zwei Zeiger übergeben,
die auf das erste und letzte Element des Feldes zeigen. Innerhalb von
ssort wird auch nur mit Zeigern gearbeitet.
#include <stdio.h>
#define MAX 100
void ausgabe(char *text, int *feld, int m);
void ssort(int *first, int *last);
int main(void)
{
int feld[MAX];
int m, anz;
printf("Eingabe von n int-Werten (unsortiert) - bis EOF\n");
for (m = 0; m < MAX; m++)
{ /* Einlesen bis EOF erreicht wurde
oder bis MAX Werte eingelesen wurden */
anz = scanf("%d", &feld[m]);
if (anz == EOF) break;
}
printf("\n");
ausgabe("Unsortiert:",feld, m);
ssort(&feld[0], &feld[m]);
/* oder kuerzer: ssort(feld, &feld[m]); */
ausgabe("Sortiert:",feld, m);
return(0);
}
void ausgabe(char* text, int *feld, int m)
{ /* Array ausgeben */
int j;
printf("%s\n",text);
for(j = 0; j < m; j++)
{
printf(" %6d\n", feld[j]);
}
printf("\n");
}
void ssort( int *first, int *last)
/* Sortieren durch direktes Einfuegen */
{
int *i, *j, temp;
for(i = first; i < last; i++)
{
for(j = i-1; j >= first && *j > *(j+1); j--)
{
temp = *(j+1);
*(j+1) = *j;
*j = temp;
}
}
}
Zeiger auf mehrdimensionale Arrays
Zur Erinnerung: bei statischer Speicherbelegung sind mehrdimensionale Arrays
intern eindimensional angelegt, die Arrayzeilen liegen alle hintereinander.
Definiert man beispielsweise
float matrix[N][M];
Dann berechnet sich die die Adresse von matrix[1][2] mit der
Zeigerarithmetik zu:
Der letzte Ausdruck ist von der Form, wie er intern tatsächlich
berechnet wird. Die Zeilenlänge M muß dem
Compiler also explizit bekannt sein!
Alternative: Adressen zu den Zeilenanfängen können explizit
abgespeichert werden.
Beispiel:
int aa[N][M], *a[N], i;
for(i=0; i<N; i++)
a[i] = aa[i];
ab dann nur noch Verwendung von a[k][l].
"echt 2-dimensional"; Adressen werden nicht berechnet,
sondern nur gelesen; Zeilenlänge muß dazu nicht bekannt sein.
Nachteil: es ist (geringfügig) mehr Speicherplatz nötig.
Zeiger auf Strukturen
Strukturpointer finden so häufig Verwendung, daß es dafür eine eigene
Syntax gibt; bei gegebener Adresse auf eine Strukturvariable werden deren Elemente mit
'->' angesprochen, in der Form:
Zeiger auf Strukturvariable ->Elementname
Beispiel: mit der Strukturdefinition
struct point
{
double px, py;
int farbe;
} punkt, sprueh[100];
sind folgende Aufrufe äquivalent:
punkt.px | entspricht | (&punkt)->px |
sprueh[20].px | entspricht | (sprueh+20)->px |
Nun wird noch ein Zeiger auf punkt definiert:
struct point *zeiger;
Mit der Anweisung zeiger = &punkt; kann man zeiger auf punkt
zeigen lassen. Der Zugriff auf die Komponente px von punkt würde
in reiner Pointerschreibweise lauten (erster Versuch):
irgendwas = *zeiger.px;
Diese Notation ist aber zweideutig. Bedeutet das jetzt "Das worauf zeiger
zeigt, Komponente px" oder "Das worauf zeiger.px zeigt"?
Diese Zweideutigkeit wird man los durch (zweiter Versuch):
irgendwas = (*zeiger).px;
Nun ist es eindeutig "Das worauf zeiger zeigt, Komponente px".
Der Operator -> vereinfacht das nun nur noch und wir schreiben (Endfassung):
irgendwas = zeiger->>px;
|
|
|