EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Kapitel 5 Claudio Moraga; Gisbert Dittrich FBI Unido [email protected] Vorl “EINI-I" Kap 5: Zeiger Gliederung Kapitel 5 • Zeiger – – – – – 20.11.2000 Grundbegriffe Zeiger und Felder Beispiel: Zeichenketten Funktionen als Parameter Mehrdimensionale Felder 2 Vorl “EINI-I" Kap 5: Zeiger Variable • hat: – Namen – Typ • u.a. Wertebereich, aus dem Werte angenommen werden – Wert (aus dem Wertebereich oder "undefiniert") • ist realisiert im Speicher durch: – Speicherplatz, der • hat : Adresse • Anmerkung: Nicht immer alle Angaben verfügbar ! 20.11.2000 3 Vorl “EINI-I" Kap 5: Zeiger Variable • Anmerkungen: – Adresse: Eindeutiger Identifikator des Speicherplatzes – Größe des Speicherplatzes abhängig vom Typ der Variablen. Speicherplatz – Bildliche Darstellungen: Name Adresse Wert oder auch: Beispiel: Adresse 123 Wert 20.11.2000 Typ nicht explizit angegeben ! Name -99 B 4 Vorl “EINI-I" Kap 5: Zeiger Grundbegriffe Zeiger • Neu: Adressen können jetzt Werte von Variablen sein !! (Typisiert!) • Zeiger: – Vereinbarung: T *var; – var ist Variablenname, der Adressen von Variablen vom Typ "T" annimmt. •Typ int Beispiel: var 20.11.2000 34567 1230 1230 -99 B 5 Vorl “EINI-I" Kap 5: Zeiger Grundbegriffe Zeiger Beispiel: var 34567 1230 1230 -99 B Gängige alternative Darstellung: Beispiel: var var 20.11.2000 34567 1230 1230 -99 -99 B 6 Vorl “EINI-I" Kap 5: Zeiger Grundbegriffe Zeiger • Sei var Variablenname: &var liefert die Adresse von var • Sei wo Name eines Zeigers (Speicherreferenz), so liefert *wo die Variable, auf die wo zeigt. • Referenzen sind typisiert: es wird angegeben, welcher Typ sich hinter einer Adresse verbirgt (z. B. Referenz auf einen Wert vom Typ int). 20.11.2000 7 Vorl “EINI-I" Kap 5: Zeiger Beispiele int k; float *t; &k ist die Adresse der ganzen Zahl k, t ist als Zeiger auf eine Variable vom Typ float definiert, beinhaltet also eine Adresse adr einer derartigen Variablen *t = 17.14 speichert damit den Wert 17.14 unter dieser Adresse adr 20.11.2000 8 Vorl “EINI-I" Kap 5: Zeiger Beispiele Definiert seien int A=1, B=-99; int *ZeigerA, *ZeigerB; D.h.: die Variablen ZeigerA und ZeigerB enthalten Adressen ganzer Zahlen. Nach ZeigerA = &A hat ZeigerA also als Wert die Adresse der Variablen A: ZeigerA 1 A -99 B ZeigerB 20.11.2000 9 Vorl “EINI-I" Kap 5: Zeiger Beispiele (Forts.) Es gilt *ZeigerA == 1 Nach *ZeigerA = B enthält der Speicherplatz, dessen Adresse ZeigerA ist, den Wert von B, also: ZeigerA 9801 -99 1 A 9801 -99 B Situation nach: B = B * B; ZeigerB = &B; *ZeigerA = *ZeigerB; 20.11.2000 ZeigerB 10 Vorl “EINI-I" Kap 5: Zeiger Merke • *ZeigerA spricht den Speicherplatz an, auf den ZeigerA zeigt: weil ZeigerA eine Referenz ist, gibt *ZeigerA den Inhalt dieser Referenz an (man spricht von Dereferenzieren: von der Referenz/Adresse zum dadurch bez. Speicherplatz übergehen) • &B auf der linken Seite einer Zuweisung ist illegal: die Adressen werden von Compiler oder vom Laufzeitsystem gesetzt, aber nicht vom Benutzer 20.11.2000 11 Vorl “EINI-I" Kap 5: Zeiger Beispiel Vertausche zwei Werte Was geschieht in Tausch? void Tausch (int a, int b) { Der Tausch int temp; bleibt lokal auf temp = a; die Funktion a = b; b = temp; beschränkt (wg. } call by value) 20.11.2000 12 Vorl “EINI-I" Kap 5: Zeiger Beispiel Was geschieht in AdrTausch? void AdrTausch(int *p, int *q) { int temp; temp nimmt den temp = *p; Inhalt von *p auf *p = *q; *q = temp; } Der Inhalt von *q wird als Inhalt Der Inhalt von *q ist der von *p in temp gespeicherte Wert gespeichert 20.11.2000 13 Vorl “EINI-I" Kap 5: Zeiger Beispiel (Bildchen) p q -121 30 -121 30 temp = *p; *p = *q; *q = temp; temp undef 30 Dadurch ist sog. call by reference möglich 20.11.2000 14 Vorl “EINI-I" Kap 5: Zeiger // K5-P1: // Tauschen Adressen als Parameter // // Demonstriert Adressen als Parameter // #include <iostream.h> void Tausch (int, int); void AdrTausch(int *, int *); // Funktionsprototypen main() { void Tausch(int, int); void AdrTausch(int *, int *); int x = 30, y = -121; cout << "vor Tausch: x = " << x << ", Tausch(x, y); cout << "nach Tausch: x = " << x << ", cout << "oh! (klar: call by value)\n"; cout << "vor AdrTausch: x = " << x << AdrTausch(&x, &y); cout << "nach AdrTausch: x = " << x << } 20.11.2000 y = " << y << endl; y = " << y << endl; ", y = " << y << endl; ", y = " << y << endl; 15 Vorl “EINI-I" Kap 5: Zeiger void Tausch (int a, int b) { int temp; temp = a; a = b; b = temp; } void AdrTausch (int *p, int *q) { int temp; temp = *p; *p = *q; *q = temp; } • Ausführen 20.11.2000 16 Vorl “EINI-I" Kap 5: Zeiger Felder und Zeiger • Sei deklariert int a[10], x, *pa; • Dann: pa = &a[0] setzt pa als Zeiger auf das erste Element von a x = *pa 20.11.2000 würde also a[0] nach x kopieren. 17 Vorl “EINI-I" Kap 5: Zeiger Felder und Zeiger • pa+1,..., pa+9 zeigen auf die Elemente a[1],...,a[9] – es gilt also *(pa+i) = a[i] für i = 0, ..., 9 • Allgemein: ist deklariert T *p;(p ist also ein Zeiger auf Elemente vom Typ T), dann bezeichnet p+i das Element vom Typ T, das von p um i*sizeof(T) entfernt liegt. 20.11.2000 18 Vorl “EINI-I" Kap 5: Zeiger Felder und Zeiger • pa ist eine Zeiger-Variable, a ist ein Feld mit Elementen vom Typ int, also sind z. B. a=pa, a++ illegal. • Bei Funktionsaufrufen werden Felder als aktuelle Parameter als Zeiger auf das jeweils erste Element interpretiert! • Damit erklärt sich, daß Kopiere korrekt funktioniert. 20.11.2000 19 Vorl “EINI-I" Kap 5: Zeiger Felder und Zeiger void strcpy (char nach[ ], char von[ ]) { int i = 0; while ((nach[i] = von[i]) ! = '\0') i++; } Kopiert bekanntlich die Zeichenkette von in die Zeichenkette nach. Die Zeichenketten werden als Felder dargestellt. 20.11.2000 20 Vorl “EINI-I" Kap 5: Zeiger Felder und Zeiger void strcpy (char *s, *t) { while ((*s++ = *t++) ! = '\0'); } *s++ dereferenziert s und schaltet die Adresse dann um sizeof(char) weiter [also zu lesen (*s)++]. 20.11.2000 21 Vorl “EINI-I" Kap 5: Zeiger Felder und Zeiger • merke: (*s++ = *t++) ! = '\0' liefert den Wert 0, falls das Ende der Zeichenkette t erreicht ist (dann soll ja auch die while-Schleife abbrechen) • die eigentliche Arbeit findet in dieser Zuweisung statt, daher ist der Anweisungsblock in der while-Schleife leer. 20.11.2000 22 Vorl “EINI-I" Kap 5: Zeiger Vergleich von Zeichenketten Sind a und b Zeichenketten, so soll der ganzzahlige Wert strcmp(a, b) den lexikographischen Vergleich von a und b ermöglichen. Es soll gelten: – strcmp(a, b) ist negativ, wenn a kleiner als b ist, – strcmp(a, b) ist Null, wenn a gleich b ist, – strcmp(a, b) ist positiv, wenn a größer als b ist, 20.11.2000 23 Vorl “EINI-I" Kap 5: Zeiger Vergleich von Zeichenketten int strcmp (char *s, char *t) { int k = 1; for (;*s == *t; s++, t++) if (*s == '\0') k = 0; if (k > 0) k = (*s - *t); return k; Die Zeichenketten } werden durchlaufen, solange sie identische Zeichen haben sonst wird die Differenz berechnet 20.11.2000 tritt dabei das Ende einer Kette auf, sind sie identisch 24 Vorl “EINI-I" Kap 5: Zeiger Vergleich von Zeichenketten Version mit Feldern int strcmp (char s[], char t[]) { int i = 0, k = 1; while (s[i] == t[i]) if (s[i++] == '\0') k = 0; if (k > 0) k =(s[i] - t[i]); return k; } Felder und Zeiger 20.11.2000 25 Vorl “EINI-I" Kap 5: Zeiger // K5-P2: Zeiger und Felder // // Demonstriert Zeiger und Felder // #include <iostream.h> main(){ int v1[10]; int i; // Feld v1 initialisieren cout << "ursprüngliches Feld v1:\n"; for (i=0;i<10;i++) { v1[i] = 100 + i; cout << v1[i] << " "; } 20.11.2000 26 Vorl “EINI-I" Kap 5: Zeiger cout << "\n\n" << "kopiertes Feld z1:\n"; // kopieren und Ausgabe ueber Zeiger int *z1; z1 = v1; for (i=0;i<10;i++){ cout << *z1 << " z1=z1+1; } cout << '\n'; "; } • Ausführen 20.11.2000 27 Vorl “EINI-I" Kap 5: Zeiger Zur Erinnerung int v1[10]; Adr. v1 v1 v1[0] ... v1[9] ... int *z1; z1 = v1; z1 = z1+1; z1 20.11.2000 28 Vorl “EINI-I" Kap 5: Zeiger #include <iostream.h> main(){ int v1[10]; int i; cout << "ursprüngliches Feld v1:\n"; for (i=0;i<10;i++) { v1[i] = 100 + i; cout << v1[i] << " "; } cout << "\n\n" << "kopiertes Feld z1:\n"; int *z1; z1 = v1; // Feldname als Zeiger for (i=0;i<10;i++){ cout << *z1 << " "; z1=z1+1; } cout << '\n'; } 20.11.2000 29 Vorl “EINI-I" Kap 5: Zeiger Funktionen als Parameter • In C++ nicht direkt möglich. • Wohl aber: Übergabe eines Zeigers auf eine Funktion Zeiger auf Funktionen Beachte: int (*f)() im Vergleich zu int *f(): •int (*f)(): Zeiger auf eine Funktion ohne Argumente mit Rückgabe Wert int •int *f(): Funktion ohne Argument, die einen Zeiger auf int zurückgibt 20.11.2000 30 Vorl “EINI-I" Kap 5: Zeiger Zeiger auf Funktionen; Namen von Funktionen Adr. BspFunktion Funktionsname Speicherbereich, wo das Programm der Funktion abgespeichert ist z char BspFunktion(char, char); char (*z)(char, char); z = BspFunktion; // Zeiger auf Funktion gleicher Art // Übergabe der Adresse der Funktion cout << (*z)(‘A‘, ‘B‘) << endl; 20.11.2000 // Funktionsprototyp // Funktionsaufruf über z 31 Vorl “EINI-I" Kap 5: Zeiger #include<iostream.h> int max(int x, int y){ int k ; if (x > y) k = x; else k = y; return k; } int min(int x, int y){ int k ; if (x < y) k = x; else k = y; return k; } main ( ) { int a = 1936, b = 6391; int (*f)(int, int); //Zeiger auf Funktion while(1) { char c; cout <<"(max = 1),(min = 0),(abbruch = sonst)?\n"; cin >> c; if (c == '1') f = max; //Zuweisung von max else if (c == '0') f = min; //Zuweisung von min else break; cout << (*f)(a,b) << '\n';}} 20.11.2000 32 // Zeiger auf Funktionen // // Demonstriert Zeiger auf Funktionen // #include <iostream.h> int sum1(int); int sum2(int); int Summiere(int (*f)(int)); // Funktionsprototypen main() { cout << "\terste Summe: " << Summiere(sum1) << "\n\n"; cout << "\tzweite Summe: " << Summiere(sum2) << endl; } Vorl “EINI-I" Kap 5: Zeiger int sum1(int n) { int i, s = 0; for(i = 0; i < n; i++) s = s + 1; return s; } int sum2(int n) { int i, s = 0; for(i = 0; i < n; i++) s = s - 2; return s; } int Summiere(int (*f)(int)) { int lauf, sum=0; for (lauf = 0; lauf < 7; lauf++) { cout << "(*f)(" << lauf << ") = " << (*f)(lauf) << endl; sum = sum + (*f)(lauf); } return sum; } 20.11.2000 •Ausführen 34 Vorl “EINI-I" Kap 5: Zeiger Mehrdimensionale Felder • In C++ sind mehrdimensionale Felder möglich (Details später). • Beispiel: int matrix [3][7] – beschreibt eine Matrix mit drei Zeilen und sieben Spalten, deren Elemente vom Typ int sind, – Beachte: int matrix [3][7] beschreibt 7 Elemente vom Typ int matrix [3]. – int matrix [3, 7] ist in C++ syntaktisch nicht legal. 20.11.2000 35