7.1.1 Eindimensionale Vektoren
7.1.3 Mehrdimensionale Vektoren
7.3.1 Vektoren von Zeigern und Zeiger auf Zeiger
7.3.2 Zeiger und mehrdimenisonale Vektoren
7.3.3 Argumente der Kommandozeile
7.1.1 Eindimensionale Vektoren
Zur Zusammenfassung mehrerer Objekte gleichen Typs zu einer Einheit verwendet man Vektoren, oft auch als Felder (arrays) bezeichnet. Sie bilden eine lineare Anordnung ihrer Grundobjekte im Speicher. Sie werden durch die Verwendung von eckigen Klammern ([]) nach dem Variablennamen erzeugt. Innerhalb der Klammern wird die Größe des Vektors (Feldes) in Anzahl Elementen des definierten Typs angegeben:
char s; /* 1 Zeichen */ char sf[20]; /* 20 Zeichen */ int i; /* 1 ganze Zahl */ int iff[100]; /* 100 ganze Zahlen (= 200 Bytes) */
Der Zugriff auf die einzelnen Vektorelemente erfolgt durch Angabe des Vektornamens gefolgt vom Index in eckigen Klammern:
int j=5, k; sf[8] = 'a'; sf[j] = sf[19]; k = iff[j];
Das 1.Element hat immer den festgelegten Index 0, das N-te den Index N-1. Vektorelemente können L-Werte sein, Vektornamen alleine nicht, da sie feste Adressen (die Adresse des 1. Vektorelementes) darstellen. Als dafür zu große Einheit können Vektoren nicht in der Speicherklasse register sein. Vektoren in der Speicherklassse auto können nicht initialisiert werden, dies ist nur bei Vektoren in den Speicherklassen static und extern möglich. Werden diese nicht initialisiert, so haben alle Elemente den Wert 0. Die Initialisierungswerte werden nach einem Gleichheitszeichen in geschweiften Klammern angegeben. Nicht initialisierte Elemente erhalten ebenfalls den Wert 0. Beispiele für Vektorinitialisierungen:
static int iff[10] = { 1,2,3,4,5,6,7,8,9,10 }; static int jf[5] = {0,1,3}; /* restl. Elemente = 0 */ static int kf[] = {1,3,5,7,11,13}; /* Laenge aus Anzahl */ /* Init-Elemente */
Nachfolgend einige Beispiel für Vektorverarbeitung:
/* Feld mit der Summe der natuerlichen Zahlen bis zu diesem Feldelement belegen */ # define MAX 100 int sum[MAX], i, j; for (i=0,j=0; i<MAX; i++) { j = j + i + 1; sum[i] = j; } /* nochmals unter Auslassung des 1.Elementes */ # define MAX 101 int sum[MAX], i, j; for (i=1,j=0; i<MAX; i++) { j = j + i; sum[i] = j; } /* Feld mit den Quadratzahlen belegen 1.Version */ #define MAX 100 int mul[MAX], i; for (i=0; i<MAX; i++) mul[i] = (i+1) * (i+1); /* Feld mit den Quadratzahlen belegen 2.Version */ #define MAX 100 int mul[MAX], i, j; for (i=0,j=1; i<MAX; i++,j++) { mul[i] = j*j;
Zeichenketten sind eindimenionale Vektoren mit mehreren Besonderheiten. Sie können auch in der Speicherklasse auto initialisiert werden. Als Zeichenkette müssen sie mit '\0' abgeschlossen sein. Dies läßt sich aber auch automatisch mit Hilfe der vereinfachten Initialisierung erreichen:
char s[] = {'s','t','r','i','n','g','\0'}; char s[] = "string"; /* \0 intern angehaengt */ char s[10] = "string"; /* restl. Elemente mit 0 init. */
Ansonsten werden Zeichenketten wie normale Vektoren behandelt. Insbesondere ist der Zugriff auf Vektorelemente gleich:
s[0] hat den Wert 's' s[3] hat den Wert 'i'
7.1.3 Mehrdimensionale Vektoren
Mehrdimenisonale Vektoren werden durch 2 oder mehr Paare eckiger Klammern nach dem Objektnamen definiert (Dabei stehen zwischen den Klammern keine Kommas.). Ein 2-dimensionaler Vektor ist ein Vektor, dessen Komponenten Vektoren sind, für höhere Dimensionen gilt Entsprechendes. Die Angabe der Elementanzahl der ersten Dimension kann (in Funktionen) fehlen, die der anderen Dimensionen nicht, da sonst die Indexberechnung nicht richtig arbeiten kann.
/* Deklaration extern oder in der aufrufenden Funktion */ int md[5][10]; funct(md,5); /* in der Funktion */ void funct(mdf,anzahl) /* in anzahl wird die aktuelle */ int mdf[][10]; /* Groesse der 1. Dimension */ int anzahl; /* übergeben */ { int i, j; for (i=0; i<anzahl; i++) for (j=0; j<10; j++) mdf[i][j] = i + j; }
Die gleiche Vektorbelegung wie mit diesem kurzen Programm kann man auch durch Initialisierung erreichen. Diese müßte dann wie folgt aussehen:
static int md[5][10] = { {0,1,2,3,4,5,6,7,8,9}, {1,2,3,4,5,6,7,8,9,10}, {2,3,4,5,6,7,8,9,10,11}, {3,4,5,6,7,8,9,10,11,12}, {4,5,6,7,8,9,10,11,12,13} };
Die Angaben für die 2. (und weitere) Dimension werden also jeweils in eigene geschweifte Klammern geschrieben. Innerhalb der geschweiften Klammern gilt wiederum, daß evtl. nicht initialisierte Elemente auf 0 gesetzt werden. Man kann die inneren geschweiften Klammern auch weglassen, dann wird der ganze Vektor Element für Element initialisiert.
Beispielprogramm P7-1.C
static int day_tab[2][13] = { {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; /* Tag im Jahr aus Monat und Tag bestimmen */ day_of_year(int year, int month, int day) { int i, leap; leap = year%4 == 0 && year%100 != 0 || year%400 == 0; for (i=1; i < month; i++) day += day_tab[leap][i]; return (day); } /* Monat und Tag aus Tag im Jahr */ month_day(int year, int yearday, int *pmonth, int *pday) { int i, leap; leap = year%4 == 0 && year%100 != 0 || year%400 == 0; for (i=1; yearday > day_tab[leap][i]; i++) yearday -= day_tab[leap][i]; *pmonth = i; *pday = yearday; }
Vektoren werden über ihre Anfangsadresse angesprochen und auch mittels dieser Anfangsadresse an Funktionen übergeben. Die Anfangsadresse eines Vektors steht fest, ist also eine Konstante. Im Gegensatz dazu ist ein Zeiger eine Variable, die die Adresse eines (beliebigen) anderen Objektes enthält. Man kann auf dieses Objekt indirekt über einen Zeiger zugreifen.
Die Adresse eines Objektes erhält man durch Anwendung des Adreßoperators &. & kann auf Variablen und Vektorelemente angewendet werden, nicht aber auf Vektornamen selbst (Warum? Ein Vektorname hat keine Adresse, er ist eine Adresse!). Ebenso haben natürlich Variablen in der Speicherklasse register keine Adressen.
Beispiele für die Anwendung des Adreßoperators und das Speichern der Adresse in einem Zeiger:
px = &x; /* px erhält als Wert die Adresse von x */ pf = &f[5]; /* pf erhält als Wert die Adresse des 6. Elementes von f */
Der Zugriff auf ein Objekt, dessen Adresse in einer Variablen (einem Zeiger) steht, geschieht mittels des Operators * (Inhalt von):
y = *px /* y erhält den Wert des Objektes, dessen Adresse in px steht */ px = &x; y = *px; /* y = x; */
Zeiger zeigen immer auf Objekte eines bestimmten Typs. Sie müssen daher deklariert werden. Auch in der Zeigerdefinition wird der Operator * verwendet. Zeigerdefinitionen kann man als Muster verstehen:
int *pk; /* pk ist Zeiger auf int */ char *zs; /* zs ist Zeiger auf char */ int x, y, *px; px = &x; /* Adressen */ y = *px; /* Werte */
Die Kombination *Zeiger kann in Ausdrücken überall dort auftreten, wo auch das Objekt, auf das der Zeiger zeigt, selbst stehen könnte:
y = *px + 10; y = *px + *px; printf("%d\n", *px); *px = 0; py = px; /* falls py auch Zeiger auf int */
Bei der Verwendung des Operators * muß man die Operatorrangfolge und -assoziativität genau beachten. Dies erscheint zunächst etwas schwierig, da dieser Operator ungewohnt ist. Hier einige Beispiele mit dem * Operator und anderen Operatoren:
y = *px + 1; /* Inhalt von px plus 1 */ y = *(px+1); /* Inhalt der Adresse px+1 */ *px += 1; /* Inhalt von px = Inhalt von px plus 1 */ (*px)++; /* Inhalt von px inkrementieren */ *px++; /* wie *(px++); (Assoziativität) Inhalt der Adresse px; px = px plus 1*/ *++px; /* Inhalt der Adresse px+1; px = px plus 1 */
Zeiger haben nur dann sinnvolle Werte, wenn sie die Adresse eines Objektes oder 0 enthalten. Für den Zeigerwert 0 ist garantiert, daß er nirgends hinzeigt (NULL bzw. NIL(in PASCAL)).
Beispielprogramm P7-2.C
/* Funktion zum Vertauschen ihrer Argumente */ /* 1. Versuch, FALSCH, da Werte nur innerhalb */ /* von swap getauscht */ /* in der aufrufenden Funktion */ swap(a,b); /* Werte übergeben */ /* die Tauschfunktion */ swap(int x,int y); /* FALSCH */ { int temp; temp = x; x = y; y = temp; } /* 2. Versuch, RICHTIG, da Objekte, auf die die */ /* Adressen zeigen, ausgetauscht werden */ /* in der aufrufenden Funktion */ swap(&a,&b); /* Adressen übergeben */ /* die Tauschfunktion*/ swap(int *px,int *py); /* *px und *py austauschen */ { int temp; temp = *px; *px = *py; *py = temp; }
Zeiger und Vektoren hängen sehr eng zusammen. Das sieht man schon daran, daß ein Vektorname genau wie ein Zeiger auch eine Adresse ist (allerdings eine Adreßkonstante). Als Konsequenz aus dem engen Zusammenhang zwischen Vektoren und Zeigern ergibt sich die für den Anfänger zunächst etwas verwirrende Tatsache, daß alle Operationen mit Vektorindizes auch mit Zeigern formuliert werden können. Dies ist meist effizienter, aber für Anfänger schwerer zu verstehen. Ausgehend von den Definitionen und Anweisungen:
int a[10]; /* int Vektor mit 10 Elementen */ int *pa, y; /* Zeiger auf int Objekte */ pa = &a[0]; /* pa hat Adresse 1. Vektorelement */ pa = a; /* pa hat ebenfalls Adresse 1. Element */ y = *pa; /* y = 1. Element von Vektor a */
sind die folgenden Angaben äquivalent:
a[i] *(a+i) /* i + 1tes Element des Vektors a */ &a[i] (a+i) /* Adresse des i + 1ten Elementes */ pa[i] *(pa+i)
Statt Vektorname und Indexausdruck kann immer auch ein Zeiger mit Abstand stehen. Als Konsequenz aus Vektorname (=Adresskonstante) und Zeiger (=Adressvariable) ergeben sich folgende erlaubten und nicht erlaubten Zusweisungen:
pa = a; /* erlaubt */ a = pa; /* falsch */ pa++; /* erlaubt */ a++; /* falsch */ pa = &a; /* falsch */
Beispielprogramm P7-3.C
strlen(char *s) /* Laenge der Zeichenkette s */ { int n; for (n=0; *s != '\0'; s++) n++; return (n); }
Mit Zeigern können bestimmte arithmetische Operationen und Vergleiche durchgeführt werden. Es sind natürlich nur die Operationen erlaubt, die zu sinnvollen Ergebnissen führen. Zu Zeigern dürfen ganzzahlige Werte addiert und es dürfen ganzzahlige Werte subtrahiert werden. Zeiger dürfen in- und dekrementiert werden und sie dürfen voneinander subtrahiert werden (Dies ist i.a. nur sinnvoll, wenn beide Zeiger auf Elemente des gleichen Objektes (z.B. Vektor) zeigen.).
Man kann Zeiger mittels der Operatoren >, >=, <, <=, != und == miteinander vergleichen. Wie bei der Zeigersubtraktion ist das aber i.a. nur dann sinnvoll, wenn beide Zeiger auf Elemente des gleichen Vektors zeigen. Eine Ausnahme bildet hier der Zeigerwert NULL.
Alle anderen denkbaren arithmetischen und logischen Operationen (Addition von 2 Zeigern, Multiplikation, Division, Shifts oder Verwendung von logischen Operatoren, sowie Addition und Subtraktion von float oder double Werten) sind mit Zeigern nicht erlaubt.
Wie funktioniert nun aber die Zeigerarithmetik? Sei p ein Zeiger und n eine ganze Zahl, dann bezeichnet p+n das n-te Objekt im Anschluß an das Objekt, auf das p gerade zeigt. Es wird also nicht der Wert n zu p direkt addiert, sondern n wird vorher mit der Typlänge des Typs, auf den p zeigt, multipliziert. Dieser Typ wird aus der Deklaration von p bestimmt.
Beispielprogramm P7-4.C
strlen(char *s) /* Laenge der Zeichenkette s 2.Version */ { char *p = s; while (*p != '\0') p++; return (p-s); }
Beispielprogramm P7-5.C
/* einfache Speicherverwaltung */ #define NULL 0 /* Zeigerwert fuer Fehleranzeige */ #define ALLOCSIZE 1000 /* verfuegbarer Platz */ static char allocbuf[ALLOSIZE]; static char *allocp = allocbuf; /* naechste freie Position */ char *alloc(int n) /* liefert Zeiger auf Platz fuer n Zeichen */ { if (allocp+n <= allocbuf+ALLOCSIZE) { /* reicht */ allocp += n; return (allocp - n); /* alter Zeiger */ } else /* nicht genug Platz */ return (NULL); } free(char *p) /* Speicher ab p freigeben */ { if (p >= allocbuf && p < allocbuf+ALLOCSIZE) allocp = p; }
Beispielprogramm P7-6.C
/* 4 Versionen von strcpy: Kopieren eines Strings */ /* 1. t nach s kopieren Version mit Vektoren */ strcpy(char s[],char t[]) { int i; i = 0; while ((s[i]=t[i]) != '\0') i++; } /* 2. t nach s kopieren 1.Version mit Zeigern */ strcpy(char *s,char *t) { while ((*s=*t) != '\0') { s++; t++; } } /* 3. t nach s kopieren 2.Version mit Zeigern */ strcpy(char *s,char *t) { while ((*s++ = *t++) != '\0') ; } /* 4. t nach s kopieren 3.Version mit Zeigern */ strcpy(char *s,char *t) { while (*s++ = *t++) ; }
Beispielprogramm P7-7.C
/* 2 Versionen von strcmp: Stringvergleich */ strcmp(char s[],char t[]) /* liefert <0 wenn s<t, 0 wenn */ /* s==t und >0 wenn s>t */ { int i; i = 0; while (s[i] == t[i]) if (s[i++] == '\0') return (0); return (s[i] - t[i]); } strcmp(char *s,char *t) /* liefert <0 wenn s<t, 0 wenn */ /* s==t und >0 wenn s>t */ { for ( ; *s == *t; s++,t++) if (*s == '\0') return (0); return (*s - *t); }
In C ist eine Funktion keine Variable, sondern der Funktionsname ist (wie ein Vektorname) eine Adreßkonstante. Es können aber Zeiger auf Funktionen definiert werden, die man dann wie Variablen verwenden kann. Damit wird es möglich, Funktionen an Funktionen zu übergeben.
Ein Zeiger auf eine Funktion wird so vereinbart:
(* Funktionsname) () /* Zeiger auf eine Funktion */
Im Gegensatz zu:
* Funktionsname () /* Funktion, die einen Zeigerwert liefert */
Die Möglichkeit und der Gebrauch von Zeigern auf Funktionen wird am besten an einem Beispiel klar:
/* in der aufrufenden Funktion */ int strcmp(), numcmp(), swap(), tausche(); sort (numcmp,swap); /* sortiere mittels der Funktionen */ /* numcmp und swap */ sort (strcmp,swap); /* sortiere mittels der Funktionen */ /* strcmp und swap */ sort (numcmp,tausche); /* sortiere mittels der Funktionen */ /* numcmp und tausche */ /* in der gerufenen Funktion */ sort(int (*comp)(),int (*exch)()) /* comp und exch sind Zeiger auf Funktionen */ { if ((*comp)(v[j],v[j+gap]) <= 0) /* die Funktion, auf */ /* die comp zeigt, wird mit */ /* den Parametern v[j] und */ /* v[j+gap] aufgerufen */ break; (*exch)(&v[j],&[j+gap]); }
7.3.1 Vektoren von Zeigern und Zeiger auf Zeiger
Genauso wie man Vektoren aus den Grunddatentypen (char, int, float und double) bilden kann, kann man dies auch mit Zeigern tun. Ein Vektor von Zeigern wird so definiert:
* Vektorname []
gelesen von rechts nach links (wegen des Vorranges von []): Vektor von Zeigern. Dagegen ist
(* Vektorname) []
ein Zeiger auf einen Vektor.
Zeiger auf Zeiger sind eine äquivalente Formulierung für Vektoren von Zeigern
int *iff[]; /* sind äquivalent, da iff die int **iff; Adresse des Vektors enthält */
Das folgende Beispiel zeigt, wie man einen Vektor von Zeigern initialisieren kann:
Beispielprogramm P7-8.C
char *month_name(int n) /* liefert Name des n. Monats */ { static char *name[] = { /* Vektor von Zeigern */ "falscher Monat", /* String ist ein char- */ "Januar", /* Vektor und daher durch */ "Februar", /* seine Anfangsadresse */ "Maerz", /* charakterisiert */ "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" }; return ((n < 1 || n > 12) ? name[0] : name[n]); }
Beispielprogramm P7-9.C
/* Programm zum Einlesen, alphabetischen Sortieren und Ausgeben von Eingabezeilen */ #define NULL 0 #define LINES 100 /* maximale Anzahl Zeilen */ main() /* Eingabe Zeilen sortieren */ { char *lineptr[LINES]; /* Zeiger auf Textzeilen */ int nlines; /* Anzahl gelesener Zeilen */ if ((nlines = readlines(lineptr,LINES)) >= 0) { sort(lineptr,nlines); writelines(lineptr,nlines); } else printf ("input too big to sort\n"); } #define MAXLEN 1000 /* Maximallaenge einer Zeile */ /* Eingabezeilen einlesen um sie zu sortieren */ readlines(char *lineptr[],int maxlines) { int len,nlines; char *p, *alloc(), line[MAXLEN]; nlines = 0; while ((len = getline(line,MAXLEN)) > 0) if (nlines >= maxlines) return(-1); else if ((p=alloc(len)) == NULL) return(-1); else { line[len-1] = '\0'; /* Zeilentrenner entfernen */ strcpy(p,line); lineptr[nlines++] = p; } return(nlines); } /* Zeilen ausgeben */ writelines(char *lineptr[],int nlines) { int i; for (i=0; i < nlines; i++) printf("%s\n",lineptr[i]); } /* Zeichenketten v[0],...,[n-1] in aufsteigender Reihen- */ /* folge sortieren, es werden die Zeichenketten */ /* verglichen und ihre Zeiger entsprechend sortiert */ sort(char *v[], int n) { int gap, i, j; char *temp; for (gap=n/2; gap > 0; gap /= 2) for (i=gap; i < n; i++) for (j = i-gap; j >= 0; j -= gap) { if (strcmp(v[j],v[j+gap]) <= 0) break; temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp; } }
7.3.2 Zeiger und mehrdimensionale Vektoren
Ein mehrdimensionaler Vektor ist ja ein Vektor, dessen Elemente selbst wieder Vektoren sind (siehe Abschnitt 7.1.3). Man kann solche Vektoren auch mit Zeigern realisieren. Betrachten wir dazu folgendes Beispiel:
int md[10][10]; /* 2 dimensionaler Vektor mit insgesamt 100 int-Elementen */ int *mp[10]; /* Vektor mit 10 Zeigern auf int-Objekte */
Die Ausdrücke
md[5][5] und mp[5][5]
sind beide möglich und bezeichnen das gleiche Element.
md ist ein Vektor mit 10 Elementen, die selbst wiederum Vektoren mit je 10 int Elementen sind. Insgesamt enthält md also 100 Elemente, für die auch der Speicherplatz bereitgestellt wird.
mp ist ein Vektor mit 10 Elementen, die Zeiger auf int Objekte sind (Speziell könnten diese natürlich auch auf je einen int Vektor mit je 10 Elementen zeigen.). Im Falle von mp wurden aber nur 10 Speicherplätze für die Zeiger angelegt, die noch nicht initialisiert sind. Im Vergleich zu md wird hier für das gleiche Feld insgesamt mehr Speicherplatz und evtl. eine Initialisierung benötigt. Dafür hat man aber 2 Vorteile:
a) wegen einfacherer Adressierung (meist) schnellere Programmausführung
b) Zeiger können auf verschieden lange Vektoren zeigen (höhere Dimensionen der Vektoren haben immer eine feste Länge)
7.3.3 Argumente der Kommandozeile
Bisher haben wir die Funktion main als parameterlos behandelt. Im allgemeinen hat diese Funktion aber 2 Parameter argc und argv, die Angaben über die Kommandoparameter beim Programmaufruf enthalten.
argc ist ein int Parameter und enthält die Anzahl der Paramter der Kommandozeile einschließlich des Programmaufrufes selbst (hat also immer mindestens den Wert 1).
argv ist ein Vektor mit Zeigern auf Zeichenketten. Diese Zeichenketten enthalten die Aufrufparameter der Kommandozeile, wobei der 1. Parameter der Programmaufruf selbst ist. Letztes Argument ist die Vektorkomponente argv[argc - 1].
Beispiel:
/* Programmaufruf auf Betriebssystemebene */ echo Hier ist Echo! /* Werte von argc und argv im aufgerufenen Programm */ argc = 4 argv[0] = "echo" argv[1] = "Hier" argv[2] = "ist" argv[3] = "Echo!"
Beispielprogramm P7-10.C
/* 3 Versionen von echo */ /* Echo der Aufrufargumente 1.Version */ main(int argc,char *argv[]) { int i; for (i=1; i < argc; i++) printf("%s%c",argv[i],(i<argc-1) ? ' ' : '\n'); } /* Echo der Aufrufargumente 2.Version */ main(int argc,char *argv[]) { while (--argc > 0) printf("%s%c",*++argv,(argc > 1) ? ' ' : '\n'); } /* Echo der Aufrufargumente 3.Version */ main(int argc,char *argv[]) { while (--argc > 0) printf((argc > 1) ? "%s " : "%s\n",*++argv); }
Beispielprogramm P7-11.C
/* Programm zum Finden von Zeilen, die ein anzugebendes Suchmuster enthalten bzw. nicht enthalten Aufruf: find [-n] [-x] suchmuster */ #define MAXLINE 1000 /* Zeilen mit Suchmuster finden */ main(int argc,char *argv[]) { char line[MAXLINE], *s; long lineno = 0; int except = 0, number = 0; while (--argc > 0 && (*++argv)[0] == '-') for (s=argv[0]+1; *s != '\0'; s++) switch (*s) { case 'x': except = 1; break; case 'n': number = 1; break; default: printf("find: illegal option %c\n",*s); argc = 0; break; } if (argc != 1) printf("Usage: find -x -n pattern\n"); else while (getline(line,MAXLINE) > 0) { lineno++; if ((index(line, *argv) >= 0) != except) { if (number) printf("%ld: ",lineno); printf("%s",line); } } }
zurück zum Inhaltsverzeichnis