Programmieren in C Zeiger und Zeichenketten Prof. Dr. Nikolaus Wulff
Zeiger Variablen benötigen zur Laufzeit einen bestimmten Speicherplatz. Die Größe des Bedarfs richtet sich nach dem Typ der Variablen, ein char belegt 1 Byte, ein int 4 Byte und ein long 8 Byte, etc. C bietet sogenannte Zeiger, mit denen explizit auf den Speicherplatz, d.h. die Adresse der jeweiligen Variablen zugegriffen werden kann. Zeiger werden bei der Deklaration durch ein * gekennzeichnet, int* u ist ein Zeiger auf ein int, double* f ein Zeiger auf ein double etc. Prof. Dr. Nikolaus Wulff Programmieren in C 2
Adressen Der Adress-Operator & liefert die Adresse einer Variablen (nicht zu verwechseln mit Bit-AND!). int i = 5; long l = 2; int* p = &i; var adress value i: 0xFF00 5 l: 0xFF04 2 p: 0xFF10 0xFF00 Das Codefragment zeigt drei Variablen i, l, p vom Typ int, long und Zeiger auf int. Die Variable i mit dem Wert 5 hat die (willkürliche) Speicheradresse 0xFF00. Dem Zeiger p wurde die Adresse von i zugewiesen. Er hat den Wert 0xFF00. Prof. Dr. Nikolaus Wulff Programmieren in C 3
Dereferenzierung Der Derefenzierungs-Operator * liefert den Wert einer Zeigervariablen: int i = 5; long l = 2; int* p = &i; printf("i=%d i_adr=%p p=%p p_adr=%p *p=%d\n", i, &i, p, &p, *p); Ausgabe: > i=5 i_adr=0xff00 p=0xff00 p_adr=0xff10 *p=5 Mit dem Ausdruck *p wird also der Wert der Speicherzelle auf den p zeigt geliefert. In diesem Fall ist dies der Wert der Variablen i gleich 5. Prof. Dr. Nikolaus Wulff Programmieren in C 4
Indirekte Zuweisung Mit dem *-Operator lassen sich nicht nur Speicheradressen auslesen sondern auch modifzieren: p = &i; i = 1; printf("i = %d \n",i); *p = 2; printf("i = %d \n",i); Ausgabe: i = 1 i = 2 Es gibt jetzt also zwei Möglichkeiten die Variable i zu lesen oder schreiben, direkt oder indirekt per *p. Beachten Sie die unterschiedliche Bedeutung der Operatoren & und * je nach Kontext! Prof. Dr. Nikolaus Wulff Programmieren in C 5
Zeichenketten char* ist ein Zeiger auf ein Zeichen vom Typ char. Eine Zeichenkette ist ein Feld von Zeichen, dass mit dem Zeichen '\0' terminiert wird. Ebenso wie normale int oder long Variablen lassen sich Zeiger inkrementieren. Hierbei wird allerdings nicht eins hinzu addiert sondern immer die aktuelle Bytegröße der jeweiligen Referenz. Es ist einfach mit Zeigern über ein Feld zu iterieren. Es soll eine Funktion printstring zum Anzeigen einer Zeichenkette implementiert werden. Prof. Dr. Nikolaus Wulff Programmieren in C 6
PrintString Ein char Array soll angezeigt werden: void printstring(char* str) { char c = *str; while(c) { printf("%c", c); str++; c = *str; char str[] ="hallo world\n"; printstring(&str[0]); Mit dem Ausdruck &str[0] wird explizit die Adresse des 0-ten Elements des Feldes übergeben. Prof. Dr. Nikolaus Wulff Programmieren in C 7
Zeiger über Zeiger... Es ist allerdings genauso gut möglich eine Zeigervariable zu übergeben oder direkt die Speicherposition des Feldes: char str[] = {'h','a','l','l','o','\0'; char* pstr1 = str; char* pstr2 = &str[0]; printstring(str); printstring(&str[0]); printstring(pstr1); printstring(pstr2); Beachten Sie die explizite Terminierung mit '\0' im Gegensatz zu char str[] = hallo pstr1 und pstr2 zeigen auf den selben Speicherbereich. Alle Aufrufe von printstring bewirken das Selbe. Prof. Dr. Nikolaus Wulff Programmieren in C 8
Zeichenketten kopieren Mit Hilfe von Zeigern soll eine Funktion zum Kopieren einer Zeichenkette implementiert werden: void stringcopy(char* src, char* dest) { while(*src) { *dest++ = *src++; Die Schleife terminiert sobald *src == 0 ist. Beachten Sie das Zusammenspiel des Inkrementoperators ++ und des Dereferenzierungsoperators *, es wird erst inkrementiert nachdem das Zeichen von src nach dest kopiert wurde. Prof. Dr. Nikolaus Wulff Programmieren in C 9
PrintString Kurzversion Es gibt immer viele Implementierungsmöglichkeiten eine Funktion zu implementieren. Hierbei gilt es Laufzeitverhalten, Wartbarkeit und Lesbarkeit abzuwägen: void printstring(char* str) { while(*str) { printf("%c",*str++); Diese Version ist kürzer als die Ursprüngliche. Statt printf("%c"...) wird häufig die Funktion putchar zur Ausgabe eines Zeichen verwendet... Prof. Dr. Nikolaus Wulff Programmieren in C 10
char* str versus char str[] Statt Zeiger kann auch explizit mit Feldindizes gearbeitet werden: void printstring(char str[]) { int i; for(i=0; str[i]; i++) { putchar(str[i]); Diese Implementierung betont mehr den Feldcharakter mit einem Laufindex. Beachten Sie die Abbruchbedingung der for- Schleife. Prof. Dr. Nikolaus Wulff Programmieren in C 11
Zeigerarithmetik Eine letzte Variante zeigt das auch Arithmetik mit Zeigern möglich ist: void printstring(char str[]) { int i; for(i=0; *(str+i); i++) { putchar(*(str+i)); Dies bedeutet das i-te Element der Zeichenkette str[] lässt sich unterschiedlich addressieren: str[i] entspricht dem Ausdruck *(str + i) und natürlich auch i-mal str++ und dann *str Es muss explizit geklammert werden! *str + i!= *(str+i) Prof. Dr. Nikolaus Wulff Programmieren in C 12
ByValue versus byreferenz C übergibt immer Kopien der Variablen an Funktionen. Es ist daher nur dann möglich die Werte von Variablen des aufrufenden Programms zu ändern wenn nicht die Kopien der Variablen sondern die Adressen der Variablen übergeben werden. Diese Adressen sind zwar auch Kopien jedoch zeigen diese auf die selben Variablen. Diese Art der Parameterübergabe nennt sich Call byreference im Gegensatz zu Call by Value. Es soll eine Funktion swap implementiert werden, die zwei double Gleitkommazahlen vertauscht. Prof. Dr. Nikolaus Wulff Programmieren in C 13
swap byreference void swap(double x, double y){ double tmp = x; x = y; y = tmp; By Value ist falsch! Obige Version vertauscht nur innerhalb von swap. Nur die by Reference Version von swap vertauscht tatsächlich auch die Variablen des Aufrufers: void swap(double* x, double* y){ double tmp = *x; *x = *y; *y = tmp; By Reference funktioniert... Prof. Dr. Nikolaus Wulff Programmieren in C 14