Folien zur AG: Objektorientiertes Programmieren in C++ Einheit 5: Funktionen Michael Hahsler, Gottfried Rudorfer und Werner J. Schönfeldinger 4. Dezember 2000 1 Aufbau dieser Einheit Aufbau von Funktionen Geltungsbereich von Funktionen und Variablen (Scope) Prototypen und Argumente von Funktionen Überladen von Funktionen und Default-Werte Bsp: Filterprogramm MAG. MICHAEL HAHSLER - 2-4. DEZEMBER 2000
Funktionen Funktionen stellen den dynamischen Teil der Objekte dar. Sie verarbeiten Informationen von außerhalb des Objekts (Argumente beim Aufruf) und von innerhalb des Objekts (Attribute) und geben ein Ergebnis zurück. Im funktionalen Sinn: erlauben eine bessere Abstraktion bei der Lösung eines Problems, das Programm kann modularisiert aufgebaut werden. Es entspricht einem guten Programmierstil wenn eine Funktion nur auf 1. die Argumente der Funktion 2. die lokalen Variable der Funktion 3. die Elemente der eigenen Instanz zugreift. MAG. MICHAEL HAHSLER - 3-4. DEZEMBER 2000 Datentyp des Rückgabewertes Variable, die in die Funktion importiert werden Lokale Variable float sum(float num1, float num2) float answer; answer = num1 + num2; return answer; Der zurückgegebene Wert MAG. MICHAEL HAHSLER - 4-4. DEZEMBER 2000
Die Funktion main() Die Funktion wird folgendermaßen deklariert: int main(int argc, char *argv[]) Die Funktion main() gibt einen Wert vom Typ int zurück (mit exit() oder return). Das Argument argc ist von Typ int und gibt die Größe des Arrays argv an (Anzahl der Argumente). Das Argument argv ist ein Array, jedes Element argv[0], argv[argc-1] ist vom Typ char * und zeigt auf ein Argument. MAG. MICHAEL HAHSLER - 5-4. DEZEMBER 2000 Beispiel: Ein Programm mit dem Namen cmd wird aufgerufen als: cmd arg1 arg2 Die Variable haben dann folgende Werte: argc ist argv[0] Pointer auf den Befehlsnamen argv[1] Pointer auf den String arg1 argv[2] Pointer auf den String arg2 argv[3] ist 0 (NULL) MAG. MICHAEL HAHSLER - 6-4. DEZEMBER 2000
Das Programm echo gibt alle Argumente aus, außer argv[0] int main(int argc, char *argv[]) int i = 1; while (i < argc) cout << argv[i++] << " "; cout << endl; return 0; MAG. MICHAEL HAHSLER - 7-4. DEZEMBER 2000 Scope in C++-Programmen Der Scope ist der Bereich der Gültigkeit von C++-Objekten (auch Variablen und Funktionen). Man unterscheidet: Global/File-Scope Sichtbar vom Punkt der Deklaration bis zum Ende der Datei. Function-Scope Sichtbar innerhalb der Funktion. Local-Scope Nur sichtbar im Block (... ), in dem die Deklaration ist. Class-Scope Nur sichtbar für Member-Funktionen. MAG. MICHAEL HAHSLER - 8-4. DEZEMBER 2000
Bsp: Scope int i; // Global Scope int sum(int num1, int num2) int result=num1+num2; // Function Scope return result; int main for (int i=0; i<=100; i++) float f=23.0; // Block Scope MAG. MICHAEL HAHSLER - 9-4. DEZEMBER 2000 Prototyp von Funktionen In C++ müssen alle Funktionen definiert sein bevor sie verwendet werden können. Die Definition kann erfolgen durch: Funktion (Programmcode) float sum (float number1, float number2) return number1+number2; Prototyp (der Code für die Funktion kommt später) float sum (float, float); Prototypen werden auch in der Klassendefinition verwendet. MAG. MICHAEL HAHSLER - 10-4. DEZEMBER 2000
Argumente zu Funktionen Möglichkeiten zur Übergabe von Argumenten: Pass-by-value Kopien der Argumente werden an die Funktion übergeben. Pass-by-reference Es werden nur Referenzen (Pointer) auf die Argumente übergeben. Änderungen der Argument-Variablen wirken sich auch außerhalb der Funktion aus! Achtung: Arrays werden immer by-reference übergeben (Pointer auf das erste Element). MAG. MICHAEL HAHSLER - 11-4. DEZEMBER 2000 Bsp.: Pass-by-Value void f(int j) j++; cout << "j in der Funktion " << j <<endl; main() int i=2; cout << "i vorher " << i <<endl; f(i); cout << "i nachher " << i <<endl; Ergebnis miss: /lehre/2.stud/cppag/folien/test> call-val vorher 2 in der Funktion 3 nachher 2 MAG. MICHAEL HAHSLER - 12-4. DEZEMBER 2000
Bsp.: Pass-by-Reference void f(int& j) j++; cout << "j in der Funktion " << j <<endl; main() int i=2; cout << "i vorher " << i <<endl; f(i); cout << "i nachher " << i <<endl; Ergebnis miss: /lehre/2.stud/cppag/folien/test> call-ref vorher 2 in der Funktion 3 nachher 3 MAG. MICHAEL HAHSLER - 13-4. DEZEMBER 2000 Bsp.: C-Approach für call-by-reference void f(int *p) (*p)++; cout << "in der Funktion " << *p <<endl; main() int i=2; cout << "vorher " << i <<endl; f(&i); cout << "nachher " << i <<endl; MAG. MICHAEL HAHSLER - 14-4. DEZEMBER 2000
Bsp.: Übergabe eines Arrays void f(int j[]) j[1]++: cout << "j [1] in der Funktion " << j[1] <<endl; main() int i[4]= 1, 2, 3, 4; cout << "i[1] vorher " << i[1] <<endl; f(i); cout << "i[1] vorher " << i[1] <<endl; MAG. MICHAEL HAHSLER - 15-4. DEZEMBER 2000 Bsp.: Rückgabe eines Arrays void g(int j[]) j[0]=8; j[1]=12; j[2]=24; j[3]=7; cout << "Array wurde für main() mit Werten belegt. " << endl; main() int i[4]= ; // Leeres Array erstellen cout << "i[1] vorher " << i[1] <<endl; g(i); // Array an g() als Referenz uebergeben cout << "i[1] nachher " << i[1] <<endl; Ergebnis i[1] vorher 0 Array wurde für main mit Werten belegt. i[1] nachher 12 MAG. MICHAEL HAHSLER - 16-4. DEZEMBER 2000
Rekursion Viele Probleme lassen sich durch einen Algorithmus lösen, der das Problem in viele Teilprobleme zerteilt, die der selben Natur sind. Beispiel: Größte gemeinsame Teiler (ggt) x := ggt(a,b) ja b == 0 nein r := a - int(a/b)*b x := a x := ggt(b,r) Rückgabe := x MAG. MICHAEL HAHSLER - 17-4. DEZEMBER 2000 extern "C" #include <stdlib.h> // for atoi(), exit() int ggt(int a, int b) if ( b == 0 ) return(a); else return(ggt(b, a%b)); int main(int argc, char *argv[]) int num1, num2, erg; if (argc!= 3) cerr << "Usage: " << argv[0] << " number1 number2" <<endl; exit(1); num1 = atoi(argv[1]); num2 = atoi(argv[2]); if ((!num1) && (!num2)) cerr << argv[0] << ": Der größte gemeinsame Teiler von " << "0, 0 ist nicht definiert" << endl; exit(1); MAG. MICHAEL HAHSLER - 18-4. DEZEMBER 2000
erg = ggt(num1, num2); cout << argv[0] << ": Der größte gemeinsame Teiler von " << num1 << ", " << num2 << " ist: " << erg << endl; return 0; Übersetzen g++ -g -o ggt ggt.cc Verwendung ggt 2970 1265 55 Aufgabenstellung: Untersuchen Sie die Funktion ggt() mit dem Debugger! MAG. MICHAEL HAHSLER - 19-4. DEZEMBER 2000 Function Overloading Function overloading erlaubt gleichnamige Funktionen zu verwenden, die sich nur durch die Art und/oder Anzahl der Argumente unterscheiden. Der Kompiler entscheidet dann anhand der Argumente selbst, welche Funktion aufgerufen werden muß. Entscheidend sind: Anzahl Reihenfolge Typ der Argumente (= function signature). Der Typ des Rückgabewertes wird nicht nicht als Unterscheidungsmerkmal herangezogen. MAG. MICHAEL HAHSLER - 20-4. DEZEMBER 2000
Folgende Funktionsdeklarationen sind im selben Programm erlaubt: int f(int a); float f(float a); int f(int a, int b); int f(int a, float b); int f(int a, int b, int c); MAG. MICHAEL HAHSLER - 21-4. DEZEMBER 2000 Auflösung von Funktionsaufrufen Der Kompiler ermittelt beim Übersetzen des Programmes, welche Funktion für einen Funktionsaufruf zu verwenden ist. 1. Exakte Übereinstimmung - Funktion aufrufen. 2. Übereinstimmung durch Typ-Aufstieg (Promotion = Konvertierung von einem engeren Datentyp zu einen weiteren z.bsp.: char int, float double, ) finden. 3. Übereinstimmung durch Konvertierung von weiteren zum engeren Datentypen (int float, float int, int unsigned int ) finden. (Warning) 4. Eine Fehlermeldung bei keiner Übereinstimmung. MAG. MICHAEL HAHSLER - 22-4. DEZEMBER 2000
Beispiel: Welche Funktion (Folie: Func. Overloading) wird für welchen Funktionsaufruf verwendet? int i, j, k; float x, y, z; char c; f(i); f(i,j); f(x); f(c); // promotion char -> int f(x,y,z); // conversion float -> int MAG. MICHAEL HAHSLER - 23-4. DEZEMBER 2000 Defaultwerte bei den Argumenten Werden Defaultwerte bei den Argumenten einer Funktion verwendet, ist die resultierende Funktion einer Menge von Funktionen mit variierender Anzahl von Argumenten: void f(int a=1, int b=2, int c=3); entspricht folgenden Funktionen: void f() void f(int) void f(int, int) void f(int, int, int) MAG. MICHAEL HAHSLER - 24-4. DEZEMBER 2000
Ein weiteres Beispiel: void g(int a, int b, int c=3); entspricht folgenden Funktionen: void g(int, int) void g(int, int, int) Die damit überladenen Funktionen dürfen nicht mehr deklariert werden! Wird bei einem Argument ein Standardwert definiert, so muß dies auch bei allen rechts davon stehenden (folgenden) Argumenten geschehen. Ein Standardwert darf nur EINMAL im Programm definiert werden (zumeist im Headerfile). MAG. MICHAEL HAHSLER - 25-4. DEZEMBER 2000 Probleme bei Auflösung wenn wir die Funktionsdeklarationen (Folie: Func. Overloading) um die Funktion float f(int a); erweitern, wird folgende Fehlermeldung ausgegeben: overload.cc:9: new declaration float f(int) overload.cc:3: ambiguates old declaration int f(int) wenn wir die Funktionsdeklarationen der (Folie: Func. Overloading) um die Funktion int f(int a, int b, int c=1); erweitern, wird folgende Fehlermeldung ausgegeben: overload.cc:47: call of overloaded f is ambiguous overload.cc:26: candidates are: f(int, float) overload.cc:9: f(int, int, int) overload.cc:22: f(int, int) MAG. MICHAEL HAHSLER - 26-4. DEZEMBER 2000
I/O bei Filterprogrammen Filterprogramme lesen vom Standard-Input und schreiben auf den Standard-Output. Die I/O-Ströme werden vom Betriebssystem von und zum Programm geleitet. Beispiel (UNIX): Ermöglichen das Lesen und Schreiben auf Dateien ohne diese vom Programm explizit anzusprechen. cat InFile filterprog > OutFile filterprog < InFile > OutFile Ausführung im Programm: Zeichenweises Einlesen des Inputstromes. Bearbeitung des Inputstromes. Zeichenweises Ausgeben des Outputstromes. MAG. MICHAEL HAHSLER - 27-4. DEZEMBER 2000 Beispiele zu Filtern main() char c; while(cin >> c) cout << c << c << flush; cout << endl; main() char c; while(cin.read(&c,1)) if(c== \n ) cout << endl; else cout << c << c << flush; cout << endl; MAG. MICHAEL HAHSLER - 28-4. DEZEMBER 2000
Übungsprogramm: Number-Compression Implementieren Sie ein Kompressionsprogramm, welches nach dem folgenden Regeln von cin gelesene Daten komprimiert und auf cout ausgibt. Kompressionsregeln: 1. Wenn ein Zeichen mehrmals hintereinander vorkommt wird es durch \\zeichenanzahl des vorkommens ersetzt. Somit wird die Zeichenfolge aaaabbbbbbbb zu \a4\b8. 2. Das Zeichen \ wird zu \\. MAG. MICHAEL HAHSLER - 29-4. DEZEMBER 2000 State Transition Diagram (UML) Analyse des dynamischen Verhaltens von Instanzen der Klassen Die Diagramme zeigen den Zustandsraum (State-Space) einer Klasse welche Ereignisse (Events) zu einem Übergang in einen anderen Zustand führen (Transition) Bezeichnung der Zustände muß eindeutig sein alle von einem Zustand ausgehenden Ereignisse müssen eindeutig sein MAG. MICHAEL HAHSLER - 30-4. DEZEMBER 2000
Class: HTML-Formular Eingabe beendet /Eingabe absenden Ergebnis erhalten [gültig] Eingabe do/formular anzeigen Zeichen eingeben Ergebnis erhalten [ungültig] Warten Fehler do/fehlermeldung Ergebnis do/ergebnis anzeigen State: Name Activity Transition: event(args)[condition] /action MAG. MICHAEL HAHSLER - 31-4. DEZEMBER 2000 S-T Diagram: Compression Einlesebereit keine weiteren Zeichen do/zeichen einlesen Zeichen eingelesen [beiden letzten Zeichen sind gleich] Zeichen Verarbeiten Kompression do/zähler erhöhen Zeichen eingelesen [beiden letzten Zeichen sind nicht gleich] Ausgabe do/zeichen ausgeben Zeichen verarbeitet MAG. MICHAEL HAHSLER - 32-4. DEZEMBER 2000
Bsp.: Implementation mycompress.cc // Simple compression by GR&MFH // // Usage: cat textfile thisprog // void out(const char c) if (c == \\ ) cout << \\ << \\ << flush; else cout << c << flush; void out(const int count, const char c) cout << \\ << \\ << \\ << count + 1 else cout << \\ << c << count + 1 << flush; << flush; // special MAG. MICHAEL HAHSLER - 33-4. DEZEMBER 2000 int main(void) char p, c; int count =0; if (cin.read(&p, 1)) // cin.read(char& buffer, int number); // reads number characters and stores them in buffer while (cin.read(&c, 1)) if ( p == c ) continue; p = c; // write the last characters if (count) out(count, p); else out(p); cout << endl; MAG. MICHAEL HAHSLER - 34-4. DEZEMBER 2000