Verlässliches Programmieren in C/C++ Teil B: Einführung in OOP mit C++ Prof. Dr. F. Belli Universität Paderborn, Fakultät EIM, Fachbereich Elektrotechnik und Informationstechnik Angewandte Datentechnik (Softwaretechnik) Universität Paderborn, ADT Gliederung 1. Einleitung 2. Programmgestalt und Sprachkonstrukte 3. Variablen und Datenstrukturen 4. Steuerstrukturen 5. Funktionen 6. Klassen 7. Vererbung 8. Templates 9. Ausnahmebehandlung 10. Kommandozeilen und Dateiverarbeitung 11. Zusammenfassung Universität Paderborn, ADT Verlässliches Programmieren in C/C++ 1. Einleitung 1. Einleitung Universität Paderborn, ADT 1.1 Verlässliches Programmieren in C/C++ 1. Einleitung Historie von C++ – Die Programmiersprache C wurde 1970 von B. Kernighan und D. Ritchie zur Implementation des Betriebssystems UNIX entworfen (rein prozedural) – C ist seit 1989 normiert (ANSI Standard X3.159-1989, ISO/IEC Standard 9899:1990 ) – C++ ist von B. Stroustrup 1980 bei den Bell Labs (Bell AT&T) als Erweiterung von C entworfen worden – Wichtigstes neues Sprachkonzept: Klassen (ursprünglich hieß die Sprache C with Classes) – C++ ist im Wesentlichen Obermenge von C, ANSI-C-Programme können oft mit einem C++Compiler übersetzt werden – ISO–C++–Standard 14882) seit 1998 Universität Paderborn, ADT (ISO/IEC 1.2 Verlässliches Programmieren in C/C++ 1. Einleitung Programmierparadigmen – Programmiersprachen sind Ergebnis von Denkweisen und Erfahrungen über den Programmierprozeß – Durch Generalisierung entstehen aus Denkprozessen Modellmethoden für den Entwurf und die Implementierung von Programmen (Paradigma) – Paradigmen beziehen sich auf Programmentwurf, auf Verwendung von Abstraktion und Strukturierung Universität Paderborn, ADT – Programmiersprache unterstützt ein Paradigma, wenn ihre Leistungsmerkmale die Anwendung der Methode erleichtern – Gemeinsamkeit von Programmierparadigmen: auf Abstraktion beruhender Entwurf, der mit mit den Elementen des Programmierproblems korrespondiert 1.3 Verlässliches Programmieren in C/C++ 1. Einleitung Prozedurale Programmierung Programm Problem Funktionen – Weitverbreitetes Modell – Programm wird aus einer Reihe von Funktionen zusammengestellt – Entwurf, Strukturierung und Implementierung erfolgt nach funktionaler Zerlegung Universität Paderborn, ADT f1(x, y) f2(y,z) f3(z,w) Methode 1. Identifizierung von Funktionen zur Lösung des Programmierproblems (abstrakte Operatoren) 2. Aufteilung von Funktionen in Moduln (Strukturierung) 1.4 Verlässliches Programmieren in C/C++ 1. Einleitung Datenabstraktion Problem Datenstrukturen Programm d1{op1,op2} d2{op3,op4} d3{op5,op6} – Daten, nicht Funktionen stehen im Mittelpunkt des Interesses – Datenstruktur ist definiert durch die darauf ausführbaren Operationen, nicht durch die Struktur der Implementierung – Umgebung einer Datenstruktur durch einen abstrakten Datentyp (Datenkapselung) Universität Paderborn, ADT – Zugriff auf die Datenstruktur durch Operationen (Methoden), die Teil des Datentyps sind – Datenabstraktion ergänzt die Sichtweise prozeduraler Programmierung 1.5 Verlässliches Programmieren in C/C++ 1. Einleitung Objektorientierte Programmierung (OOP) (a) Programm Problem Objekte O1 Methode1 Methode2 O3 Methode3 Methode4 – Modell der Programmerstellung als Reihe abstrakter Datentypinstanzen – Typen, die im Programmierproblem die Objekte darstellen, stehen im Mittelpunkt der Betrachtung Universität Paderborn, ADT O2 Methode1 Methode2 O4 Methode3 Methode4 – Operationen in den Objekttypen sind abstrakte Operatoren zur Lösung des Problems – Objekttyp dient als wiederverwendbares Modul zur Lösung gleicher und ähnlicher Probleme 1.6 Verlässliches Programmieren in C/C++ 1. Einleitung Objektorientierte Programmierung (OOP) (b) – Starke Koppelung von Daten und Operationen (Verhalten) – Erste objektorientierte Programmiersprache war Simulationsprache (SIMULA67, 1967) – Berechnung wird als Simulation von Verhalten betrachtet – Gemeinsamkeit aller objektorientierten Programmiersprachen – Simuliert werden Objekte durch Abstraktion • Datenkapselung • Polymorphie • Vererbung Universität Paderborn, ADT 1.7 Verlässliches Programmieren in C/C++ 1. Einleitung Datenkapselung Beispiel Kartenspiel – Dies sind Typen, Teilaspekt des Kartenspiels – Repräsentation soll vor der Außenwelt (Benutzer) verborgen werden und die Benutzung nicht beeinflussen – Repräsentation im z. B. durch Zahlen – Eigenschaften von Daten Operationen eines Objekts – Spielfarben: ♣, ♥, ♦, ♠ ♣ = 0 ♥ = 1 ♦ = 2 ♠ = 3 Universität Paderborn, ADT Programm und • gekapselt (private): interne Repräsentation • offen (public): Schnittstelle (Interface) zu gekapselten Daten und Operationen 1.8 Verlässliches Programmieren in C/C++ 1. Einleitung Polymorphie Polymorphie (griechisch): Vielgestaltigkeit, Verschiedengestaltigkeit – Beispiel Graphiksystem – Erlaubt Konstruktion allgemeiner Schnittstellen und Operatoren – Lokalisiert die Verantwortlichkeit für das Verhalten (Bindung der Operation an einen Datentyp) – Beispiel arithmetische Operationen: ganze, reelle, komplexe Zahlen Universität Paderborn, ADT • Darstellung verschiedener geometrischer Objekte auf dem Bildschirm • Zeichnen der Objekte kann intern verschieden sein (Kreise, Polygone, etc.) • Allgemeine Operation (draw) kann objektunabhängig benutzt werden 1.9 Verlässliches Programmieren in C/C++ 1. Einleitung Vererbung – Ermöglicht hierarchische Klassifizierung von Objekten – Methode zur Komplexität Reduzierung der – Fördert Wiederverwendung von Programm-Code: aus genereller Beschreibung (Klasse) durch Hierarchisierung zu Spezialisierung (Objekt) für das aktuelle Programmierproblem – Beispiel Obst – Spezialisierung von Objekten: Vererbung aller Eigenschaften der Eltern, Definition von Daten und Operationen, die zu den Eltern abgrenzen • Ein Apfel ist eine Frucht • Eine Frucht ist Nahrung • Früchte besitzen alle Eigenschaften von Nahrung • Äpfel besitzen alle Eigenschaften von Früchten Universität Paderborn, ADT 1.10 Verlässliches Programmieren in C/C++ 1. Einleitung Vorteile von OOP – Gut anwendbar auf sehr große Programmierprojekte – Datenkapselung erlaubt gute Aufteilbarkeit auf mehrere Entwickler und Programmierer – Polymorphie erlaubt leichte intuitive Benutzung von Operationen (Methoden) und reduziert die Komplexität für Entwickler und Programmierer – Vererbung erlaubt gute Strukturierung des Programmierproblems durch Hierarchisierung von Typen und Objekten – Objektorientierte Programme sind oft robuster und besser zu warten und zu ändern als prozedurale Programme Universität Paderborn, ADT 1.11 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte 2. Programmgestalt und Sprachkonstrukte Universität Paderborn, ADT 2.1 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Ein einfaches Programmierproblem Wurzelberechnung Newton-Verfahren – Iterationsverfahren f (x) T1 = f (x0)(x − x1 ) T2 = f (x1)(x − x2 ) f (x0) 1. Starte mit Schätzwert x0 2. Berechne f (xn ) xn+1 := xn − f (xn) bis |xn+1 − xn| ≤ ε, wobei ε Genauigkeit ist f (x1) 0 0 x2x1 x0 Schnittpunkt der Tangente T1 mit der x-Achse f (x0) f (x0) = x0 − x1 f (x0) ⇐⇒ x1 = x0 − f (x0) Universität Paderborn, ADT – Spezialfall f (x) = x2 − c, √ Berechnung von c −c x2 xn+1 = xn − n 2xn 2.2 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Ein einfaches C++-Programm /* Wurzelberechnung * Eingabe: positive reelle Zahl x * Ausgabe: Wurzel aus x */ #include <iostream> // Konstanten und Variablen const float genauigkeit = 1e-4; int main () { using namespace std; float wert, wurzel, altwurzel, fehler; // Eingabe cout << "Bitte eine positive Zahl eingeben: "; cin >> wert; if (wert <= 0) { cout << wert << " ist nicht positiv!"; cout << endl; return 1; } Universität Paderborn, ADT // Algorithmus nach Newton wurzel = wert; // Erster Schaetzwert do { altwurzel = wurzel; wurzel = wurzel - (wurzel * wurzel - wert) / (2 * wurzel); if (altwurzel <= wurzel) { fehler = wurzel - altwurzel; } else { fehler = altwurzel - wurzel; } } while (fehler >= genauigkeit); // Ausgabe cout << "Wurzel aus " << wert; cout << " = " << wurzel << endl; // Rueckgabewert return 0; } 2.3 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Elemente eines C++-Programms Ein einfaches Programm, generelle Struktur 1. Kommentar 2. Präprozessoranweisungen 3. Konstantendeklarationen 4. Variablendeklarationen 5. Eingabeanweisungen 6. Programmanweisungen 7. Ausgabeanweisungen 8. Rückgabewert Universität Paderborn, ADT /* ... */ #include <iostream> const float genauigkeit = 1e-4; int main () { ... cin >> ...; ... wurzel = wert; // Erster Schaetzwert do { ... } while (fehler >= genauigkeit); ... cout << ...; return 0; } 2.4 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Kommentare – Kommentare erläutern, was ein Programm oder Programmteil bewirkt – Programmanweisungen beschreiben, wie etwas bewirkt wird – Kommentarblock, mehrzeilig Benutzung zu längeren Erläuterungen, insbesondere zu Beginn des Programms – Einzeiliger Kommentar Benutzung zu kurzen Erläuterungen, z. B. zur Beschreibung von Variablen Universität Paderborn, ADT – Kommentarblock beginnt mit “/*” und endet mit “*/” /* Wurzelberechnung * Eingabe: positive reelle Zahl x * Ausgabe: Wurzel aus x */ – Einzeiliger Kommentar beginnt mit “//” und endet mit dem Zeilenende // Konstanten und Variablen // Eingabe // Algorithmus nach Newton wurzel = wert; // Erster Schaetzwert // Ausgabe // Rueckgabewert 2.5 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Präprozessoranweisungen – Präprozessor ist vorgeschaltet dem Compiler – Führt Ergänzungen und Veränderungen des Quellprogramms durch – Präprozessoranweisungen nen mit “#” begin- – Zunächst wichtig: Einfügen von Programmtext (include) #include Datei – Eingebunden werden meist Header -Dateien, sie enthalten Datenund Funktions- bzw. Klassendeklarationen Universität Paderborn, ADT – Zur Benutzung von Ein- und Ausgabeoperatoren ist die Datei iostream in das Quellprogramm einzubinden #include <iostream> – Ein Dateiname in spitzen Klammern (<>) bedeutet eine Systemdatei (Suche in den Systemverzeichnissen) – Ein Dateiname in Anführungszeichen ("") wird im aktuellen Verzeichnis gesucht 2.6 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Datendeklaration – Konstanten und Variablen müssen vor Ihrer Benutzung deklariert werden – Konstante, eine Fließkommazahl mit Wert – gekennzeichnet durch Namen und Typ const float genauigkeit = 1e-4; – Datendeklarationen werden wie alle Programmkonstrukte mit Ausnahme von Kommentaren und Präprozessoranweisungen durch ein Semikolon “;” abgeschlossen Universität Paderborn, ADT 0.0001 = 1 · 10−4 – Variablen, vier Fließkommazahlen float wert, wurzel, altwurzel, fehler; 2.7 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte main ()-Funktion – Ein C++-Programm besteht aus mindestens einer Funktion – Eine Funktion besteht aus • Funktionskopf mit Namen und Argumentliste, eingeschlossen in Klammern (()) • Funktionsrumpf mit in geschweiften Klammern ({}) eingeschlossenen Programmkonstrukten – Eine Funktion wird beendet mit der Anweisung return wert; – Jedes C++-Programm muß die Funktion main () enthalten – int main () { // Eingabe ... // Algorithmus nach Newton ... // Ausgabe ... // Rueckgabewert return 0; } Rückgabewert ist wert Universität Paderborn, ADT 2.8 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Eingabeanweisung – Stream-orientierte C++ Eingabe in – Mehrere Eingaben: – Benutzt wird die Klasse cin mit dem Operator >> – Die Deklarationen zu Streamorientierter Ein- und Ausgabe sind in <iostream> enthalten. – Zur Benutzung #include <iostream> cin >> wert1 >> wert2 >> wert3; – “Leerzeichen” (Blank, Tabulator, Return) werden als Ende der Eingabe interpretiert! – Eingabe 20 30 40 – cin >> wert; – Die Variable wert erhält den Wert, der über die Tastatur eingegeben wird Universität Paderborn, ADT wert1=20; wert2=30; wert3=40; 2.9 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Ausgabeanweisung – cout << "Bitte ... eingeben: "; – Analog zu Eingabe in C++ – Benutzt wird die Klasse cout mit dem Operator << – Mehrere Ausgaben cout << "Wurzel aus " << wert; cout << " = " << wurzel << endl; – Zur Benutzung #include <iostream> – Wert des Ausdrucks wird auf dem Bildschirm ausgegeben – Strings werden durch Anführungszeichen ("") eingeschlossen – Spezielle Konstante für Zeilenumbruch endl Universität Paderborn, ADT 2.10 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Ausdrücke und Zuweisungen – Zuweisung geschieht Operator = – Zunächst arithmetische Ausdrücke – wurzel - (wurzel * wurzel - wert) / (2 * wurzel) wurzel2 − wert wurzel − 2 ∗ wurzel dem Wert von – variable = ausdruck – variable erhält ausdruck – – entspricht mit den wurzel = wurzel - (wurzel * wurzel - wert) / (2 * wurzel) – Zuweisung wird oft mit Vergleich (==) verwechselt! Universität Paderborn, ADT 2.11 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Verzweigung – if (wert <= 0) { cout << wert << " ist nicht positiv!"; cout << endl; return (1); – Bedingte Anweisungen – Programmkonstrukt 1. if (ausdruck) { anweisungen } 2. if (ausdruck) { anweisungen } else { anweisungen } – ausdruck kann die Werte wahr oder falsch annehmen } – if (altwurzel <= wurzel) { fehler = wurzel - altwurzel; } else { fehler = altwurzel - wurzel; } implementiert wurzel = xn+1 |xn+1 − xn | = mit altwurzel xn − xn+1 xn+1 − xn = xn , falls xn+1 ≤ xn ; andernfalls. – Arithmetischer Vergleich (wert <= 0) entspricht wert ≤ 0 Universität Paderborn, ADT 2.12 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Schleife – Bedingte Wiederholung – Programmkonstrukt nicht abweisende Schleife do { anweisungen } while (ausdruck); – anweisungen werden solange ausgeführt, wie ausdruck wahr ist Universität Paderborn, ADT – do { altwurzel = wurzel; wurzel = wurzel - (wurzel * wurzel - wert) / (2 * wurzel); if (altwurzel <= wurzel) { fehler = wurzel - altwurzel; } else { fehler = altwurzel - wurzel; } } while (fehler >= genauigkeit); 2.13 Verlässliches Programmieren in C/C++ 2. Programmgestalt und Sprachkonstrukte Rückgabewert – if (wert <= 0) { – Für die Funktion main () gibt der Rückgabewert den Status der Programmausführung an das Betriebssystem zurück – Unter UNIX: • Wert 0 bedeutet, daß das Programm ordnungsgemäß beendet ist • Wert ungleich 0 bedeutet, daß das Programm nicht ordnungsgemäß beendet ist Universität Paderborn, ADT cout << wert << " ist nicht positiv!"; cout << endl; return 1; } – Nicht ordnungsgemäß, falsche Eingabe – int main () { ... return 0; } – Ordnungsgemäß, durchgeführt Berechnung 2.14 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen 3. Variablen und Datenstrukturen Universität Paderborn, ADT 3.1 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Variablendeklaration typ variablenname [, variablenname]*; – Eine Variable muß deklariert werden, bevor sie benutzt wird – Ziele der Deklaration • Definition des Namens • Angabe des Typs • Beschreibung für ser/Programmierer den Le- – typ ist ein gültiger C++-Datentyp – variablenname beginnt mit einem Buchstaben oder einem Unterstrich (_), gefolgt von einer Anzahl von Buchstaben, Ziffern oder Unterstrichen int studentenanzahl; char initial; // Anfangsbuchstabe des Namens float mittelwert; int i, j; // Zeilen und Spaltenindex der Matrix Universität Paderborn, ADT 3.2 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Initialisierung 1. typ variablenname(wert); 2. typ variablenname = wert; – Variablen haben nach ihrer Deklaration einen undefinierten Wert (einen Zufallswert) – Zwei Methoden 1. in C++-Syntax 2. in alter C-Syntax – Initialisierung, d. h. Zuweisung eines Anfangswerts, kann bei der Deklaration erfolgen – Auch mehrere Variablendeklarationen mit Initialisierung int studentanzahl(40); // Anfangswert 40 int studentenanzahl = 40; // Anfangswert 40 int i, j = 5, k = 4; // i nicht initialisiert Universität Paderborn, ADT 3.3 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Konstanten und Referenzen 1. const typ konstantenname = wert; 2. typ &variablenname1 = variablenname2; 1. Ein im Programm unveränderlicher Wert heißt Konstante 2. Variablen können referenziert werden • variablenname1 ist ein Alias-Name für variablenname2 – Guter Stil: • Variablennamen in Kleinbuchstaben • Konstantennamen buchstaben in Groß- const float PI = 3.1415926; // Die Kreiskonstante int zaehler; // Anzahl von Gegenstaenden int &akt_zaehler = zaehler; // Alias fuer zaehler zaehler = 33; // akt_zaehler = 33 akt_zaehler = 44; // zaehler = 44 Universität Paderborn, ADT 3.4 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Vordefinierte Datentypen – Grundlegende Datentypen: ein boolescher Typ, Zeichentypen, ganzzahlige Typen und Gleitkommatypen – Aufzählungstyp enum, Typ void (tut die Abwesenheit von Informationen kund) – Zeiger-, Feld- und Referenztypen, Datenstrukturen und Klassen – Es gibt eine Ordnung auf den Zeichen (ASCII-Anordnung) Bezeichner char int float double void Universität Paderborn, ADT Standard-Datentypen Erklärung Wertebereich von bis Zeichen −128 127 ganze Zahlen −32.768 32.767 Fließkommazahl 3, 4 × 10−38 3, 4 × 10+38 Fließkommazahl 1, 7 × 10−308 1, 7 × 10+308 kein Wert Beispiel ’w’ 10 2.345E+10 2.345E+10 3.5 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Modifiers – Wertebereichsmodifikation der Standard-Datentypen – signed, unsigned, long, short Typ char unsigned char signed char int unsigned int signed int short int unsigned short int signed short int long int signed long int unsigned long int float double long double Universität Paderborn, ADT Modifiers Bit-Länge Wertebereich von bis 8 −128 127 8 0 255 8 −128 127 16 −32.768 32.767 16 0 65.535 16 −32.768 32.767 16 −32.768 32.767 16 0 65.535 16 −32.768 32.767 32 −2.147.483.648 2.147.483.647 32 −2.147.483.648 2.147.483.647 32 0 4.294.967.295 −38 32 3, 4 × 10 3, 4 × 10+38 64 1, 7 × 10−308 1, 7 × 10+308 80 3, 4 × 10−4932 1, 1 × 10+4932 3.6 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Datentyp bool – In C existiert ein boolescher Datentyp erst seit dem C99-Standard – Jeder Ausdruck ist logisch interpretierbar – auch Zeiger (ein Nullzeiger wird als wahr, jeder andere als falsch interpretiert) – Logische Operatoren liefern immer 0 oder 1, d.h. !(!n) kann sich von n unterscheiden! Universität Paderborn, ADT – In C++ gibt es den Datentyp bool, der die Werte true oder false annehmen kann – true hat per Definition den Wert 1, false den Wert 0 3.7 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Datentyp char Bezeichner \b \f \n \t \" \’ \0 \\ \v \a \? \N \xN Universität Paderborn, ADT Sonderzeichen in char Bedeutung Backspace Formfeed Newline horizontaler tab Anführungszeichen (Double-quote) einfaches Anführungszeichen (single-quote) Null Backslash vertikaler Tabulator alert Fragezeichen Oktalzahl N Hexadezimalzahl N 3.8 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Felder (Array) typ variablenname[größe][[größe]]*; – Ein Feld (Array ) ist eine endliche Sequenz von Daten desselben Typs – Elementanzahl wird bei der Deklaration festgelegt: größe (muss ein konstanter Ausdruck sein!) – Indizierung größe − 1 mit Indizes 0 – Auch mehrdimensionale Felder bis – Initialisierung int feld[5] = {1, 2, 3, 4, 5}; int feld[] = {1, 2, 3, 4, 5}; int matrix[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; int feld[20]; // 20 Zahlen int matrix[5][10]; // 5x10-Matrix Universität Paderborn, ADT 3.9 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Zeichenketten (String) in C – Es gibt in C keinen Datentyp für Zeichenketten – Zeichenketten sind Felder vom Typ char – Vereinfachte Syntax: Zeichenkette wird in Anführungszeichen (") eingeschlossen – Besonderheit: letztes Zeichen ist das Null-Zeichen (\0), wird automatisch angehängt – Für eine Zeichenkette string der Länge n muß ein Feld der Länge n + 1 deklariert werden (char string[n + 1]) "Hallo!" entspricht H a l l o ! "%$#^*" entspricht % $ # ^ * \0 "" entspricht \0 Universität Paderborn, ADT \0 3.10 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen String-Funktionen (C-Stil) #include <cstring> Funktion Beschreibung strlen(string) gibt die Länge von string zurück, dabei wird das abschließende Null-Zeichen \0 nicht mitgezählt. strcmp(string1 , string2 ) vergleicht string1 und string2 ; der Rückgabewert ist 0, falls sie gleich sind. Ist string1 lexikographisch größer als string2 , wird eine positive Zahl zurückgegeben, ist string1 lexikographisch kleiner als string2 , eine negative Zahl. strcat(string1 , string2 ) hängt string2 an das Ende von string1 an, string2 bleibt unverändert. strcpy(string1 , string2 ) kopiert den Inhalt von string2 in string1 . Universität Paderborn, ADT 3.11 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Datentypdeklaration typedef typ typbezeichner ; – Mit typedef kann ein bereits bestehender Datentyp über einen neuen Namen angesprochen werden – Sinnvoll z. B. bei Feldern – Portabilität: Maschinenabhängige Datentypen müssen unter Benutzung von typedef bei einer Portierung nur einmal geändert werden – Erhöhung der Verständlichkeit des Quellcodes (?) typedef char string[80]; // Neuer Datentyp string mit 80 Zeichen string zeile; // Variable vom Typ String Universität Paderborn, ADT 3.12 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Zeichenketten in C /************************************************************** * Zusammenfassung von Vor- und Nachnamen * **************************************************************/ #include <cstring> #include <iostream> typedef char string[80]; // Neuer Datentyp string mit 80 Zeichen int main() { string vorname, nachname; // Vor- und Nachname einer Person string name; // vollstaendiger Name einer Person std::cout << "Gib Vor- und Nachnamen ein: "; std::cin >> vorname >> nachname; // Lese Namen ein strcpy(name, nachname); strcat(name, ", "); // Komma zwischen Nach- und Vornamen strcat(name, vorname); std::cout << name << std::endl; // Namen ausgeben return 0; } Universität Paderborn, ADT 3.13 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Zeichenketten (String) in C++ – Konkatenation mittels des Operators + – In C++ stellt die Standardbibliothek den string-Typ zur Verfügung – C-Strings sollten nach Möglichkeit nicht benutzt werden – Anhängen von Strings durch += – viele weitere Operationen zur Manipulation von Strings werden bereitgestellt – Beispiel: – Anmerkung: Der Typ string ist eine sogenannte Klasse Universität Paderborn, ADT string s1 = "Hello, "; string s2 = "World!"; string s3 = s1 + s2; 3.14 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Zeichenketten in C++ /************************************************************** * Zusammenfassung von Vor- und Nachnamen * **************************************************************/ #include <iostream> int main() { using namespace std; string vorname, nachname; // Vor- und Nachname einer Person string name; // vollstaendiger Name einer Person cout << "Gib Vor- und Nachnamen ein: "; cin >> vorname >> nachname; // Lese Namen ein name = nachname; name += ", "; name += vorname; cout << name << endl; // Namen ausgeben return 0; } Universität Paderborn, ADT 3.15 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Aufzählungtypen enum typbezeichner { element1 [= Zahlenwert], element2 [= Zahlenwert], ... elementN [= Zahlenwert], }; – Die Elemente erhalten eine Numerierung gemäß ihrer Aufzählungsreihenfolge, beginnend bei 0 – Es können auch explizite Zahlenwerte zugewiesen werden Universität Paderborn, ADT – enum wochentag { Sonntag, // Montag, // Dienstag, // Mittwoch, // Donnerstag, // Freitag, // Samstag // }; = = = = = = = 0 1 2 3 4 5 6 – enum alter { Hugo = 25, Berta, // = 26 Helmut = Hugo + 10 // = 35 }; 3.16 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Aggregattypen (Strukturen) struct strukturbezeichner { typ1 bezeichner1 ; typ2 bezeichner2 ; ... typN bezeichnerN; }; – struct datum { wochentag w_tag; int tag; char monat[10]; int jahr; }; – Strukturen sind Zusammenfassungen von Daten verschiedenen Typs – Die einzelnen Daten heißen Komponenten – Zugriff auf Komponenten Punkt-Operator (.) Universität Paderborn, ADT mit – datum geburtstag, hugo_geb_tag = { // Initialisierung Montag, 12, "Januar", 1970 }; geburtstag.w_tag = Donnerstag; geburtstag.tag = 15; strcpy(geburtstag.monat, "Januar"); geburtstag.jahr = 1970; 3.17 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Vereinigungstypen union [typbezeichner ] { typ1 bezeichner1 ; typ2 bezeichner2 ; ... typN bezeichnerN; }; – union zahl_oder_zeichen { – Ein Vereinigungstyp (union) ist eine Speicherstelle, die von Daten verschiedenen Datentyps belegt wird int i; char c; }; zahl_oder_zeichen zz; zz.i = 55; cout << zz.i << endl; zz.c = ’a’; cout << zz.c << endl; – Eine union kann bezeichnet oder anonym sein – Anonyme unions Fehlerquellen Universität Paderborn, ADT sind typische 3.18 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Typkonvertierung typ(ausdruck) // C++-Stil (typ)ausdruck // C-Stil – C++ nimmt automatisch Typkonvertierungen vor – int gewonnen, verloren; float ratio; // Verhaeltnis gewonnen/verloren gewonnen = 5; verloren = 3; ratio = gewonnen / verloren; // ratio = 1.0! // entspricht float(gewonnen / verloren) – ratio = float(gewonnen) / float(verloren); // ratio = 1.66667 ratio = (float)gewonnen / (float)verloren; // C-Stil Universität Paderborn, ADT 3.19 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Zeiger (Pointer) (a) Adresse (Zeiger) typ *variablenbezeichner ; Speicherstelle 0x100 Inhalt 22 Wert – Ein Zeiger (Pointer ) ist eine Variable, die eine Speicheradresse enthält – Die Speicheradresse ist gewöhnlich die Stelle einer anderen Variablen – Wenn x die Adresse von y enthält, dann zeigt x auf y – int *p; // Zeiger auf int float *q; // Zeiger auf float Universität Paderborn, ADT 3.20 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Zeiger (Pointer) (b) – Es gibt zwei Operatoren zur Benutzung von Zeigern, die sich komplementär verhalten – Der Referenzoperator & liefert die Adresse des Operanden int erg, *erg_ptr; erg_ptr = &erg; // erg_ptr zeigt auf erg – Der Dereferenzierungsoperator * liefert den Wert der Variablen, auf die der Operand zeigt int erg, *erg_ptr; erg = *erg_ptr; // erg erhaelt den Wert auf den erg_ptr zeigt Universität Paderborn, ADT 3.21 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Zeigerindizierung – Zeiger und Felder können (fast) synonym verwendet werden – char str[80]; char *p; float *f, feld[80]; p = str; f = feld; – Zuweisung von Zeigern zu Feldern: Zeiger zeigt auf das erste Element des Feldes Universität Paderborn, ADT – Zeigerarithmetik: – str[4] und *(p+4) ergeben das fünfte Element von str[] – feld[3] und *(f+3) ergeben das vierte Element von feld[] – Bei der Addition/Subtraktion von ganzen Zahlen auf einen Zeiger wird automatisch die Größe des Datentyps berücksichtigt 3.22 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Beispiel: Tokenizer (C-Strings) /* Extrahiere Worte aus einem Satz */ #include <iostream> char str[80], token[80], *p, *q; int main() { using namespace std; cout << "Gib einen Satz ein: "; cin.getline(str, sizeof(str)); // Lesen eines Strings mit White-spaces p=str; // Lies ein Token (Wort) aus dem Satz while (*p) { // p zeigt nicht auf das Null-Zeichen \0 q = token; // q zeigt auf den Anfang von token // Lese Zeichen bis entweder ein Leerzeichen oder das Null-Zeichen erscheint while (*p != ’ ’ && *p) { // && ist logisches UND, != ist "ungleich" *q = *p; q++; // q = q + 1 p++; // p = p + 1 } if (*p) p++; // uebergehe das Leerzeichen *q = ’\0’; // beende das Token (Wort) cout << token << endl; } return 0; } Universität Paderborn, ADT 3.23 Verlässliches Programmieren in C/C++ 3. Variablen und Datenstrukturen Dynamische Speicherverwaltung – Zur dynamischen Speicher-Allokation gibt es in C++ das Konstrukt new int *zahl1, *zahl2, *feld; // Zeiger auf int zahl1 = new int; // neuer Speicher fuer Zahl1 zahl2 = new int(9); // neuer Speicher fuer zahl2, initialisiert mit 9 feld = new int[9]; // neuer Speicher fuer 9er Feld von int-Zahlen – Zur dynamischen Speicherfreigabe gibt es in C++ das Konstrukt delete – Soll ein Array freigegeben werden, so muß delete [] verwendet werden delete zahl1; // Speicher fuer Zahl1 freigegeben delete [] feld; // Speicher fuer gesamtes feld freigegeben – Synonym bei Zugriff auf dereferenzierte Struktur (struct, class): zeiger ->komponente für (*zeiger ).komponente Universität Paderborn, ADT 3.24 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen 4. Steuerstrukturen Universität Paderborn, ADT 4.1 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Bedingungen – In C++ gibt es den Datentyp bool, der die Wahrheitswerte true und false annehmen kann – Verzweigung: if, switch – Schleife: for, while, do-while – Sprung: break, continue, return, goto – Verzweigungen und Schleifen werden durch eine Bedingung mit den Werten wahr oder falsch gesteuert Universität Paderborn, ADT – Es ist aber auch jeder andere Grundtyp logisch interpretierbar – Beispiel: int a; a = ... if (a) { ... } 4.2 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Operatoren – Vergleich, Logik Relational kleiner größer kleiner gleich größer gleich Gleichheit gleich ungleich Logik Negation UND ODER < > <= >= == != ! && || – Die Operatoren liefern int-Werte 0 oder 1, verkürzte Auswertung – Vorsicht bei Verwechslung von == und =, z. B. ist if (i = 1) immer wahr ! Universität Paderborn, ADT – Logische Negation kann auf jeden Ausdruck angewendet werden, z. B. (i !< 5) – Vorsicht: ! ist keine Negation (¬) im mathematischen Sinn – Mathematik: ¬(¬x) = x für beliebigen Ausdruck x – C++: !(!5) = 1, weil !5 = 0 und !0 = 1 – Bedingungs-Operator term1 ? term2 : term3 // x = max {y, z} x = (y < z) ? y : z; 4.3 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen if-Konstrukt – if (ausdruck) { anweisungen } if (i) { if (j) { anweisung1; – if (ausdruck) { } anweisungen if (k) { } anweisung2; else { } anweisungen else { } anweisung3; // zu if (k) – Bei Schachtelungen bezieht sich } das else auf das nächste vorher- else anweisung4; // zu if(i) gehende if im selben Block ({}) Universität Paderborn, ADT 4.4 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Beispiel 1 if-Konstrukt /* Berechnung des Quotienten aus zwei Zahlen * Eingabe: Zwei ganze Zahlen * Quotient aus erster und zweiter Zahl */ #include <iostream> int main() { using namespace std; int a, b; cout << "Gib zwei ganze Zahlen ein: "; cin >> a >> b; if (b) { // b ist ungleich 0 cout << a / b << endl; return 0; } else { cerr << "Division durch 0" << endl; // cerr: Standard-Fehlerkanal return 1; } } Universität Paderborn, ADT 4.5 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Beispiel 2 if-Konstrukt // Simulation von case (if ladder) #include <iostream> int main() { using namespace std; int x; for (x = 0; x < 6; x++) { if (x == 1) { cout << "x ist eins" << endl; } else if(x == 2) { cout << "x ist zwei" << endl; } else if(x == 3) { cout << "x ist drei" << endl; } else if(x == 4) { cout << "x ist vier" << endl; } else { cout << "x ist nicht zwischen eins und vier" << endl; } } return 0; } Universität Paderborn, ADT 4.6 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Beispiel 3 if-Konstrukt /* Lösung einer quadratischen Gleichung * a * x * x + b * x +c = 0 */ else if (discr < 0.0) cout << "Lösung ist return 1; } else { wurzel1 = wurzel2 = } cout << wurzel1 << ", return 0; #include <iostream> #include <cmath> int main() { using namespace std; float a, b, c; // Koeffizienten float wurzel1, wurzel2; // Loesungen float discr, sq_disc; // Diskiminante cout << "Koeffizienten a, b, c? cin >> a >> b >> c; discr = b * b - 4 * a * c; if ((discr > 0.0) && (sq_disc = sqrt(discr))) wurzel1 = (-b + sq_disc) / (2 wurzel2 = (-b - sq_disc) / (2 } Universität Paderborn, ADT "; { * a); * a); { komplex" << endl; -b / (2 * a); " << wurzel2 << endl; } – Das Programm nutzt die verkürzte Auswertung von && aus – Zuweisung als Ausdrucks! zweiter Teil des &&- 4.7 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen switch-Konstrukt – ausdruck muß vom Datentyp int oder char sein (oder enum) switch (ausdruck) { case konstante1 : anweisungen [break;] ... case konstanteN : anweisungen [break;] [default :] anweisungen } Universität Paderborn, ADT – ausdruck wird sukzessiv gegen die Konstanten der case-Zeilen getestet – Sobald eine Konstante gleich ausdruck ist, werden die folgenden anweisungen bis zum nächsten break ausgeführt (auch über weitere cases hinweg!) – Die auf die default-Zeile folgenden anweisungen werden ausgeführt, falls keine Konstante gleich ausdruck ist 4.8 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Beispiel switch-Konstrukt Ausgabe switch ohne break und default #include <iostream> int main() { using namespace std; for (int i = 0; i < 6; i++) { switch (i) { case 0: cout << "kleiner 1\n"; case 1: cout << "kleiner 2\n"; case 2: cout << "kleiner 3\n"; case 3: cout << "kleiner 4\n"; case 4: cout << "kleiner 5\n"; } cout << endl; } kleiner kleiner kleiner kleiner kleiner 1 2 3 4 5 kleiner kleiner kleiner kleiner 2 3 4 5 kleiner 3 kleiner 4 kleiner 5 kleiner 4 kleiner 5 return 0; } Universität Paderborn, ADT kleiner 5 4.9 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen for-Schleife for (initialisierung; ausdruck; inkrement) { anweisungen } // Summierung von 5 Zahlen – initialisierung ist eine Zuweisung, die den Anfangswert der Schleifen- #include <iostream> Steuervariable setzt, es können int main() { auch Schleifenvariablen definiert using namespace std; werden int summe = 0; // Summe aller Zahlen – ausdruck ist eine Bedingung, die zur Terminierung der Schleife führt – inkrement definiert den Wert, um den bei jedem Schleifendurchlauf die Schleifen-Steuervariable verändert wird } Universität Paderborn, ADT int aktuell; // Einzugebender Wert int zaehler; // Schleifen-Steuervariable for (zaehler = 0; zaehler < 5; ++zaehler) { cout << "Zahl? "; cin >> aktuell; summe += aktuell; // summe = summe + aktuell } cout << "Summe: " << summe << endl; return 0; 4.10 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Beispiel for-Schleife – Teile des Schleifenkopfs können leer sein – /* Eingabe einer Zahl bis diese gleich 123 ist * Eingabe: Ganze Zahlen * Ausgabe: keine */ #include <iostream> int main() { for (int x = 0; x != 123;) { std::cout << "Gib eine Zahl ein: "; std::cin >> x; } return 0; } Universität Paderborn, ADT 4.11 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Besonderheiten for-Schleife – Beliebiges Inkrement // Abwaertszaehlen in Fuenfer-Schritten for (i = 100; >= -100; i -= 5) – Mehrfach-Initialisierungen, Mehrfach-Inkremente for ( x = 0, y = 0; x <= 10; ++x, --y) { cout << x << ", " << y << endl; – Endlos-Schleife for (;;) { // Irgendwas } Universität Paderborn, ADT 4.12 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen while und do-while-Schleife while (ausdruck) { anweisungen } – Abweisende Schleife – anweisungen werden so lange ausgeführt, wie ausdruck wahr (ungleich 0) ist – Rumpf der Schleife kann leer sein /* Erzeuge so lange * Zufallszahlen, * bis die Zahl gleich 100 ist */ while (rand() != 100); Universität Paderborn, ADT do { anweisungen } while (ausdruck); – Nichtabweisende Schleife – anweisungen werden einmal ausgeführt und dann so lange, wie ausdruck wahr ist – Eingabeschleife do { cout << "Positive Zahl? "; cin >> zahl; } while (zahl <= 0); 4.13 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Beispiel do-while-Schleife #include <iostream> #include <cstdlib> int main() { using namespace std; int magic; // zu ratende Zahl int rat; // geratene Zahl else { cout << "...Pech gehabt."; if (rat > magic) { cout << " zu gross\n"; } else { cout << " zu klein\n"; } } } while (rat != magic); magic = rand(); // Zufallszahl do { cout << "Gib einen Tip ein: "; cin >> rat; return 0; if (rat == magic) { } cout << "**Treffer** "; cout << magic << " ist richtig!\n"; } Universität Paderborn, ADT 4.14 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Sprungkonstrukte break, continue – break: Terminierung einer Schleife (for, while, do-while) und Verzweigung (switch) – break bewirkt einen Sprung hinter das Ende der Schleife bzw. Verzweigung – continue: Wiederholung der Schleife (for, while, do-while) – continue bewirkt einen Sprung zur Schleifenbedingung Universität Paderborn, ADT 4.15 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Beispiel break, continue #include <iostream> int main() { using namespace std; int summe(0), summand; if (summand < 0) { continue; // Neue Eingabe } summe += summand; } while (true) { cout << "Summe: " << summe << endl; cout << "Betrag? (0 fuer ende) "; return 0; cin >> summand; } if (summand == 0) { break; // beendet die Schleife } Universität Paderborn, ADT 4.16 Verlässliches Programmieren in C/C++ 4. Steuerstrukturen Sprungkonstrukt goto goto marke; marke: anweisung; ... for (x = x; x < X_LIMIT; ++x) { for (y = 0; y < Y_LIMIT; ++y) { – goto bewirkt einen Sprung zu einer if (data[x][y] == 0) { Marke goto gefunden; } – goto sollte möglichst nicht verwen} } det werden! cout << "Nicht gefunden" << endl; – Für jedes Programm, daß goto- ... Konstrukte beinhaltet, gibt es ein gefunden: wirkungsgleiches Programm ohne cout << "Gefunden bei " cout << "(" << x << ", " goto-Konstrukt << y << ")" << endl; ... Universität Paderborn, ADT 4.17 Verlässliches Programmieren in C/C++ 5. Funktionen 5. Funktionen Universität Paderborn, ADT 5.1 Verlässliches Programmieren in C/C++ 5. Funktionen Funktionsdefinition [typ] funktionsbezeichner (parameterdeklarationen) { anweisungen } – typ spezifiziert den Datentyp des Rückgabewertes (Ergebnistyp) – Soll eine Funktion keinen Rückgabewert haben, so ist typ mit void anzugeben – parameterdeklarationen geben Anzahl, Position und Datentyp der Funktionsargumente an – Beendet wird eine Funktion durch die Anweisung return [ausdruck]; ausdruck ist Rückgabewert – Bei void-Funktionen ausdruck Universität Paderborn, ADT entfällt 5.2 Verlässliches Programmieren in C/C++ 5. Funktionen Prototypen [typ] funktionsbezeichner (parameterdeklarationen); – Eine Funktion kann deklariert werden, bevor sie definiert wird (Funktionsprototyp) – Damit kann eine Funktion im Programm benutzt werden, ohne daß sie vorher definiert ist – Funktionsprototypen werden zur besseren Strukturierung eines Programms eingesetzt Universität Paderborn, ADT 5.3 Verlässliches Programmieren in C/C++ 5. Funktionen Beispiel Funktion #include <iostream> // Funktionsprototyp int min(int x, int y); int main() { int j, k, m; std::cout << "Zwei ganze Zahlen? "; std::cin >> j >> k; m = min(j,k); std::cout << m << " = min(" << j << ", " << k << ")\n"; // Funktionsdefinition int min(int x, int y) { if(x < y) { return x; } else { return y; } } return 0; } Universität Paderborn, ADT 5.4 Verlässliches Programmieren in C/C++ 5. Funktionen Default-Argumente – Bei der Funktionsdefinition und der Funktionsdeklaration können vordefinierte Argumente angegeben werden (Default-Argumente) – Vordefinition durch Zuweisung in den parameterdeklarationen typ bezeichner = ausdruck – Beim Aufruf wird dann, falls das entsprechende Argument nicht angegeben wird, der vordefinierte Wert verwendet – Andernfalls wird der Wert des Aufruf-Arguments verwendet – Sinnvoll, wenn ein Argument oder mehrere Argumente einer Funktion häufig mit den selben Werten belegt werden Universität Paderborn, ADT 5.5 Verlässliches Programmieren in C/C++ 5. Funktionen Beispiel Default-Argumente #include <iostream> // Zweites Arg k ist als 2 vordef. int power(int n, int k = 2); int main() { using namespace std; int n; cout << "Ganze Zahl? "; cin >> n; // Aufruf mit Default Arg (2) cout << power(n) << " = " << n << "^2\n"; // Aufruf mit explititem Arg (3) cout << power(n, 3) << " = " << n << "^3\n"; // k = 2 ist default int power(int n, int k) { if(k == 2) { return (n * n); } else { // Rekursion return (power(n, k - 1) * n); } } return 0; } Universität Paderborn, ADT 5.6 Verlässliches Programmieren in C/C++ 5. Funktionen Overloading – Funktionen können in einem Programm mehrere Bedeutungen haben (Polymorphie) – in C++ heißt dies Overloading bzw. Überladen – Beispiel Mittelwert von Zahlenfolgen double mean(const int a[], int size) { int sum = 0; for(int i = 0; i < size; ++i) { sum += a[i]; // int Arithmetik } // Typ-Konversion int->double return(double(sum)/double(size)); } Universität Paderborn, ADT double mean(const double a[], int size) { double sum = 0.0; for(int i = 0; i < size; ++i) { sum += a[i]; // double Arithmetik } return(sum/size); } 5.7 Verlässliches Programmieren in C/C++ 5. Funktionen Gültigkeitsbereiche (a) – Gültigkeitsbereiche von Bezeichnern sind in C++ durch Blöcke definiert und durch { und } begrenzt – Alle Bezeichner (z. B. von Variablen), die im Rumpf einer Funktion deklariert sind, sind außerhalb der Funktion nicht gültig (bekannt) – Dasselbe gilt für die Bezeichner der parameterdeklarationen des Funktionskopfs { int a=2; cout << a << ’\n’ { int a=7; cout << a << ’\n’ } cout << ++a << ’\n’ // aeusserer Block a // druckt 2 // innerer Block a // druckt 7 // druckt 3 } Universität Paderborn, ADT 5.8 Verlässliches Programmieren in C/C++ 5. Funktionen Gültigkeitsbereiche (b) – In C++ können Variablendeklarationen an jeder Stelle eines Blocks erscheinen – Gültigkeitsbereich ist vom Anfang der Deklaration bis zum Ende des innersten einschließenden Blocks int max(int* pArr, int size) { cout << "Feldgroesse ist " << size << ’\n’; int comp = pArr[0]; // Deklaration von comp for(int i = 1; i < size; ++i) // Deklaration von i if(pArr[i]>comp) { comp=pArr[i]; } return comp; } Universität Paderborn, ADT 5.9 Verlässliches Programmieren in C/C++ 5. Funktionen Argumentübergabe – Call-by-value: Wert des Arguments wird kopiert, mit dieser Kopie wird gearbeitet – Call-by-reference: Es wird mit der Variablen selbst gearbeitet Argumenttypen Typ Deklaration Value function(int var) Constant-value function(const int var) Reference function(int &var) Constant-reference function(const int &var) Array function(int array[]) Address function(int *var) Universität Paderborn, ADT 5.10 Verlässliches Programmieren in C/C++ 5. Funktionen Beispiel 1 Argumentübergabe – Call-by-reference durch Call-by-address (üblich in C) – Umständlich, schwer verständlich int main() { int i = 7, j = 3; void ordne(int *p, int *q); void ordne(int *p, int *q) { int temp; if(*p > *q) { temp = *p; *p = *q; *q = temp; } cout << i << ’\t’ << j << ’\n’; ordne(&i, &j); cout << i << ’\t’ << j << ’\n’; return 0; } Universität Paderborn, ADT } 5.11 Verlässliches Programmieren in C/C++ 5. Funktionen Beispiel 2 Argumentübergabe – Call-by-reference direkt (in C++ üblich) – Leicht verständlich, leicht benutzbar int main() { int i = 7, j = 3; void ordne(int &p, int &q); void ordne(int &p, int &q) { int temp; if (p > q) { temp = p; p = q; q = temp; } cout << i << ’\t’ << j << ’\n’; ordne(i, j); cout << i << ’\t’ << j << ’\n’; return 0; } Universität Paderborn, ADT } 5.12 Verlässliches Programmieren in C/C++ 5. Funktionen Call-by-value vs. Call-by-reference Falsch: Richtig: void eingabe(int i) { cout << "Zahl? "; cin >> i; } void eingabe(int &i) { cout << "Zahl? "; cin >> i; } int main() { int x = 0; eingabe(x); cout << x << ’\n’; int main() { int x = 0; eingabe(x); cout << x << ’\n’; return 0; return 0; } } Eingabe und Ausgabe: Eingabe und Ausgabe: Zahl? 2 0 Zahl? 2 2 Universität Paderborn, ADT 5.13 Verlässliches Programmieren in C/C++ 5. Funktionen Operatoren typ operator op(parameterdeklarationen) { anweisungen } – In C++ können auch Operatoren Funktionsbezeichner sein. – Operatorfunktionen werden für Overloading von Zuweisungsoperatoren, relationalen und arithmetischen Operatoren etc. verwendet. Universität Paderborn, ADT – So können für einen neu definierten Datentyp entsprechende Operatoren definiert werden. Das erhöht die Lesbarkeit des Programmtextes. – Alle C++-Operatoren können so für entsprechende Datentypen neu definiert werden. 5.14 Verlässliches Programmieren in C/C++ 5. Funktionen Beispiel Operatoren // Datentyp complex struct complex { float re; float im; }; // Operatorfunktion +, Addition komplexer Zahlen complex operator +(complex &zahl1, complex &zahl2) { complex erg; erg.re = zahl1.re + zahl2.re; erg.im = zahl1.im + zahl2.im; return erg; } Universität Paderborn, ADT 5.15 Verlässliches Programmieren in C/C++ 5. Funktionen Beispiel Operatoren (Forts.) #include <iostream> #include "complex.h" int main () { using namespace std; complex z1, z2, z3; cout << "Komplexe Zahl z1? "; cin >> z1.re >> z1.im; cout << "Komplexe Zahl z2? "; cin >> z2.re >> z2.im; z3 = cout z3 = cout z3 = cout z1 << z1 << z1 << + z2; "z1 + z2 = " << z3.re << " + i" << z3.im << ’\n’; * z2; "z1 * z2 = " << z3.re << " + i" << z3.im << ’\n’; / z2; "z1 / z2 = " << z3.re << " + i" << z3.im << ’\n’; return 0; } Universität Paderborn, ADT 5.16 Verlässliches Programmieren in C/C++ 6. Klassen 6. Klassen Universität Paderborn, ADT 6.1 Verlässliches Programmieren in C/C++ 6. Klassen Einleitung – Eine Klasse faßt Datenstrukturen und zugehörige Funktionen zusammen – Sie kann Datenelemente (Member-Variablen) und Funktionselemente (MemberFunktionen, Methoden) enthalten – Member-Variablen repräsentieren einen Typ und MemberFunktionen repräsentieren Operationen, die auf dem Datentyp ausführbar sind Universität Paderborn, ADT – Member-Variablen und MemberFunktionen stellen die Schnittstelle einer Klasse dar – Teile der Klasse können entweder verborgen (gekapselt) oder explizit verfügbar gemacht werden; so kann eine Klasse ihre Datenstruktur vollständig von der Umgebung isolieren – Eine Instanz einer Klasse heißt ein Objekt 6.2 Verlässliches Programmieren in C/C++ 6. Klassen Klassendefinition class klassenbezeichner { [private:] private daten und funktionen [public:] öffentliche daten und funktionen }; – Eine Klasse definiert einen Datentyp, der zur Erzeugung von Objekten dient – private daten und funktionen sind nur innerhalb der Klasse, d. h. für Funktionen innerhalb der Klasse, zugänglich – öffentliche daten und funktionen sind auch von Funktionen außerhalb der Klasse zugänglich – Wird keines der Schlüsselworte private oder public angegeben, so werden alle Daten und Funktionen als privat angenommen Universität Paderborn, ADT 6.3 Verlässliches Programmieren in C/C++ 6. Klassen Definition/Deklaration – Funktionen (Member-Funktionen) einer Klasse können bei der Klassendefinition • vollständig implementiert sein • als Prototypen deklariert sein – Bei Implementierung der als Prototypen angegebenen Funktionen ist als Qualifikator der Klassenbezeichner voranzustellen Klassendefinition Implementierung class klasse { ... int func(. . .); // Prototyp ... }; int klasse::func(. . .) { ... } Universität Paderborn, ADT 6.4 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Schlange q.h /* FIFO Datenorganisation * Datentyp int * Dateneingabe: put(element) * Datenausgabe: get() */ const unsigned int QLEN = 5; // maximale Laenge class queue { private: unsigned int laenge; // aktuelle Laenge int data[QLEN]; // Elemente der Schlange int erster, letzter; public: queue(); // Konstruktor, Initialisierung void put(int element); int get(void); }; Universität Paderborn, ADT 6.5 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Schlange q.cc #include <iostream> #include "q.h" queue::queue() { erster = letzter = laenge = 0; cerr << "Schlange initialisiert" << endl; } void queue::put(int element) { if (laenge == QLEN) { cerr << "Schlange ist voll" << endl; } else { data[letzter] = element; letzter = (letzter + 1) % QLEN; ++laenge; } return; } Universität Paderborn, ADT int queue::get(void) { int element; if (! laenge) { cerr << "Schlange ist leer" << endl; return(0); } else { element = data[erster]; erster = (erster + 1) % QLEN; --laenge; return(element); } } 6.6 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Schlange qmain.cc #include <iostream> #include "q.h" int main() { queue a; // Erzeugung einer Instanz int element; char aktion; // p = put, g = get, x = ende do { cout << "Aktion? "; cout << "(p = put, g = get, "; cout << "x = ende) "; cin >> aktion; switch (aktion) { case ’x’: break; case ’p’: cin >> element; a.put(element); break; Universität Paderborn, ADT case ’g’: cout << a.get() << endl; break; default: continue; // Neue Eingabe } // end switch } while (aktion != ’x’); return 0; } 6.7 Verlässliches Programmieren in C/C++ 6. Klassen Spezielle Member-Funktionen – Jede Klasse enthält mindestens vier Member-Funktionen 1. Default constructor. Dient der Initialisierung 2. Copy constructor. Dient der Duplizierung von Klasseninstanzen (Variablen) 3. Destructor. Dient der Zerstörung von Klasseninstanzen 4. Assignment operator. Dient der Zuweisung von Klasseninstanzen (Variablen) – Falls diese Member-Funktionen nicht im Programm implementiert sind, werden sie vom Compiler automatisch generiert Universität Paderborn, ADT 6.8 Verlässliches Programmieren in C/C++ 6. Klassen Konstruktoren – klasse::klasse() Default constructor. Wird ausgeführt, wenn eine Instanz (Variable) von klasse deklariert wird (Achtung: werden Member-Variablen nicht initialisiert, so ist ihr Wert undefiniert!) – klasse::klasse(const klasse &org klasse) Copy constructor. Wird bei Call-by-value Parameterübergabe, Initialisierung und Funktionsrückgabe ausgeführt; kopiert alle Member-VariablenWerte von org klasse in die neue Instanz; kann auch zur Duplizierung von Instanzen verwendet werden: klasse var1; ... klasse var2(var1); // var2 ist eine Kopie von var1 – Konstruktoren haben keine Typangabe (als Ergebniswert) Universität Paderborn, ADT 6.9 Verlässliches Programmieren in C/C++ 6. Klassen Destruktor und Zuweisung – klasse::~klasse() Destructor. Wird ausgeführt, wenn eine Instanz zerstört wird; erfolgt, wenn eine Instanz ihren Gültigkeitsbereich verläßt – klasse klasse::operator =(const klasse &org klasse) Zuweisungsoperator. Zielobjekt existiert bereits. Dient der Zuweisung von Instanzen von klasse; kopiert alle Member-Variablen von org klasse in die neue Instanz klasse var1, var2; var1 = var2; // ‘operator =’ wird aufgerufen klasse var3 = var2; //Initialisierung => Aufruf copy constructor! – Destruktoren haben keine Typangabe (als Ergebniswert) Universität Paderborn, ADT 6.10 Verlässliches Programmieren in C/C++ 6. Klassen Konstruktor mit Parameter – Es kann mehrere Konstruktoren für eine Klasse geben – Typischer Einsatz bei parametrisierten Objekten class queue { private: // aktuelle Laenge unsigned int laenge; // Elemente der Schlange int data[QLEN]; int erster, letzter; int name; // Kennung der Schlange public: queue(); // Schlange der Laenge QLEN // Numerierung der Instanzen queue(int id); void put(int element); int get(void); }; Universität Paderborn, ADT // Konstruktor mit Parameter queue::queue(int id) { erster = letzter = laenge = 0; name = id; cerr << "Schlange " << name << " initialisiert\n"; } 6.11 Verlässliches Programmieren in C/C++ 6. Klassen Instanzen einer Klasse erzeugen Objekte können auf zwei Arten erzeugt werden (statisch durch Variablendefinition oder durch Zeigerdefinition mit Anforderung von dynamischen Speicher): Default constructor Copy constructor Destructor Assignment operator Klasse var1; Aufruf durch Variablendefinition impliziter Aufruf bei Funktionsaufrufen mit Call-by-value, Initialisierungen und Funktionsrückgaben impliziter Aufruf durch Blockbzw. Programmende Klasse var2; var2 = var1; Universität Paderborn, ADT Klasse *pVar1 = new Klasse; expliziter Aufruf durch new Klasse; – expliziter Aufruf delete pVar1; – durch 6.12 Verlässliches Programmieren in C/C++ 6. Klassen Dynamische mehrdimensionale Felder (a) – Erzeugung eines Feldes von Zeigern auf ein Feld, z. B. Basistyp double **elem – Eleganter Zugriff auf Feldelemente durch Überschreiben des Operators () – Im Konstruktor – double &operator()(int i, int j) { // Matrix-Index von // 1 bis zeilen bzw. spalten, // Feld von 0 bis zeilen - 1 // bzw. spalten - 1 return(elem[i - 1][j - 1]); } double **elem; elem = new double*[zeilen]; for (i = 0; i < zeilen; ++i) { elem[i] = new double[spalten]; } – Im Destruktor for (i = 0; i < zeilen; ++i) { delete [] elem[i]; } delete [] elem; Universität Paderborn, ADT – Überschreiben ermöglicht Zugriff auf Feldelemente mit Ausdruck m(3, 4), z. B. cout << m(3, 4); // m[2][3] 6.13 Verlässliches Programmieren in C/C++ 6. Klassen Dynamische mehrdimensionale Felder (b) const int DIM = 3; class matrix { private: const int zeilen, spalten; double **elem; public: matrix(); matrix(int dim); matrix(int zeilen, int spalten); ~matrix(); matrix(const matrix &old_matrix); matrix &operator=(const matrix &m); double &operator()(int i, int j); void input(); void print(); }; Universität Paderborn, ADT matrix::matrix(int z, int s) : zeilen(z), spalten(s) { int i; elem = new double*[zeilen]; for (i = 0; i < zeilen; ++i) { elem[i] = new double[spalten]; } init(); } matrix::~matrix() { int i; for (i = 0; i < zeilen; ++i) { delete [] elem[i]; } delete [] elem; } double &matrix::operator()(int i, int j) { return(elem[i - 1][j - 1]); } 6.14 Verlässliches Programmieren in C/C++ 6. Klassen Statische Member-Variablen – Eine Member-Variable gilt für Instanzen einer Klasse, d. h., jede Instanz besitzt ihre eigene Variable – Eine statische Member-Variable gilt für die Klasse, d. h., es gibt nur eine solche Variable für alle Instanzen class queue { private: // ... public: // Anzahl von Schlangen static int anzahl; queue() // Implementierung in Def. { ++anzahl; // Neue Schlange // ... } ~queue() // Implementierung in Def. { --anzahl; // Eine Schlange weniger } }; – statische Member-Variablen können beispielsweise zum Zählen von Instanzen einer Klasse int queue::anzahl = 0; // Initialisierung queue q1, q2; // queue::anzahl nun 2 verwendet werden // Zugriff cout << "Anzahl von Schlangen: " << queue::anzahl; Universität Paderborn, ADT 6.15 Verlässliches Programmieren in C/C++ 6. Klassen Statische Member-Funktionen – Sind statische Member-Variablen privat, so kann vom außen nicht auf sie zugegriffen werden – Zum Zugriff auf private statische Member-Variablen von außerhalb einer Klasse gibt es statische Member-Funktionen class queue { private: // Aktuelle Anzahl von Schlangen static int anzahl; // ... public: static int schlangen_anzahl(void) { return(anzahl); } // ... }; // Zugriff cout << "Aktive Schlangen: << queue::schlangen_anzahl(); Universität Paderborn, ADT 6.16 Verlässliches Programmieren in C/C++ 6. Klassen Bedeutung von static static-Deklaration Benutzung Bedeutung Variable außerhalb Gültigkeitsbereich der Variable ist die Datei, in der Funktion sie steht Variable innerhalb einer Variable ist permanent, sie wird nur einmal initialiFunktion siert und nur einmal erzeugt, auch wenn die Funktion mehrfach bzw. rekursiv aufgerufen wird Funktion Gültigkeitsbereich der Funktion ist die Datei, in der sie steht Member-Variable Nur eine Variable pro Klasse (nicht eine pro Instanz) Member-Funktion Funktion kann nur auf statische MemberVariablen der Klasse zugreifen Universität Paderborn, ADT 6.17 Verlässliches Programmieren in C/C++ 6. Klassen Initialisierung von Member-Variablen (a) – Member-Variablen können in Konstruktoren statt durch Zuweisungen direkt initialisiert werden – class drei_d { private: int x, y, z; // Koordinaten public: drei_d(int a, int b, int c) : x(a), y(b), z(c) {} }; entspricht class drei_d { private: int x, y, z; // Koordinaten public: drei_d(int a, int b, int c) {x = a; y = b; z = c; } }; Universität Paderborn, ADT 6.18 Verlässliches Programmieren in C/C++ 6. Klassen Initialisierung von Member-Variablen (b) – Bei konstanten Member-Variablen kann eine Zuweisung nicht erfolgen – Die Initialisierung mit dem “:”-Konstrukt ist dann erforderlich – Initialisierung geschieht in Reihenfolge der Deklaration! Richtig: Falsch: class vektor { private: const int streckfaktor; // <int x, y, z; // Koordinaten // <public: vektor(int s) : streckfaktor(s), x(0), y(0), z(0) {} vektor(int s, int a, int b, int c) : streckfaktor(s), x(a*streckfaktor), y(b*streckfaktor), z(c*streckfaktor) {} }; class vektor { private: int x, y, z; // Koordinaten // <const int streckfaktor; // <public: vektor(int s) : streckfaktor(s), x(0), y(0), z(0) {} vektor(int s, int a, int b, int c) : streckfaktor(s), x(a*streckfaktor), y(b*streckfaktor), z(c*streckfaktor) {} }; Universität Paderborn, ADT 6.19 Verlässliches Programmieren in C/C++ 6. Klassen Operator-Overloading und Selbstreferenz – Für jede Klasse können Operatoren neu definiert werden – Die Neudefinition geschieht analog zu der Weise, wie sie bei normalen Datentypen erfolgt – Bei jedem Aufruf einer Member-Funktion wird automatisch ein Zeiger, der auf das aktuelle Objekt zeigt, übergeben; der Zeiger heißt this – Der this-Zeiger ist ein impliziter Parameter jeder Member-Funktion – Der this-Zeiger wird als Selbstreferenz bezeichnet Universität Paderborn, ADT 6.20 Verlässliches Programmieren in C/C++ 6. Klassen Dynamische Speicherverwaltung (a) – Oft wird bei Klassenkonstruktoren mit new eine Datenstruktur erzeugt – Beispiel: Matrix class matrix { public: float **data; // Matrix von Zahlen matrix() { data = new float[3][3]; } // Standard 3x3 matrix(int groesse) { data = new float[groesse][groesse]; } matrix(int zeilen, int spalten) { data = new float[zeilen][spalten]; } ~matrix() { delete [] data; } // Operatoren, Zuweisung, etc. }; int main() { matrix a; // eine 3x3-Matrix matrix b(5); // eine 5x5-Matrix matrix c(3, 4); // eine 3x4-Matrix ... } Universität Paderborn, ADT 6.21 Verlässliches Programmieren in C/C++ 6. Klassen Dynamische Speicherverwaltung (b) – Bei dynamischer Speicherverwaltung sind die automatisch erzeugten CopyKonstruktoren, Destruktoren und Zuweisungsoperatoren meist unbrauchbar – Bei dynamischer Speicherverwaltung sind die relevanten Member-Variablen Zeiger – Der Copy-Konstruktor erzeugt einen neuen Zeiger, der auf die Daten der zu kopierenden Instanz zeigt! Änderungen in der Kopie bewirken Änderungen im Original – Bei Feldern gibt der Destruktor nur den Speicher für das erste Element frei – Der Zuweisungsoperator weist nur die Zeiger zu Universität Paderborn, ADT 6.22 Verlässliches Programmieren in C/C++ 6. Klassen Dynamische Speicherverwaltung (c) class matrix { public: const int zeilen, spalten; float **data; // Konstruktoren // ... // Copy-Konstruktor matrix(const matrix &org_matrix) : zeilen(org_matrix.zeilen), spalten(org_matrix.spalten) { data = new float[zeilen][spalten]; for (int i = 0; i < zeilen; ++i) { for (int j = 0; j < spalten; ++j) { data[i][j] = org_matrix.data[i][j]; } } } // ... Universität Paderborn, ADT matrix operator =(const matrix &org_matrix) { if ((zeilen != org_matrix.zeilen) || (spalten != org_matrix.spalten)) { cerr << "Matrizen haben "; cerr << "verschiedenes Format. "; cerr << "Keine Zuweisung"; cerr << endl; return(*this); } for (int i = 0; i < zeilen; ++i) { for (int j = 0; j < spalten; ++j) { data[i][j] = org_matrix.data[i][j]; } } return(*this); } // ... }; 6.23 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Raumkoordinaten dreid.h class drei_d { – Ganzzahlige Raumkoordinaten als private: int x, y, z; // Koordinaten Klasse public: drei_d() // Konstruktor – Neben Konstruktor und Ausgabe{ x = y = z = 0; funktion sind Zuweisungsoperator }; (=) und die Inkrementierungsoperatoren (++) als Overloading realisiert – Inkrementierungsoperatoren sowohl als präfix als auch suffix – Inkrementierungsoperatoren benutzen den impliziten Parameter this Universität Paderborn, ADT drei_d(int i, int j, int k) // Konstruktor { x = i; y = j; z = k; }; drei_d operator =(const drei_d op2); drei_d operator ++(void); // praefix drei_d operator ++(int nil); // suffix void show(void); }; 6.24 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Raumkoordinaten dreid.cc #include <iostream> #include "dreid.h" // Overload = drei_d drei_d::operator =(const drei_d op2) { x=op2.x; y=op2.y; z=op2.z; return *this; // Overload suffix ++ // Parameter int nil wird nicht benutzt! // Bewirkt suffix drei_d drei_d::operator ++(int nil) { drei_d erg=*this; ++x; // ++ fuer int ++y; // ++ fuer int ++z; // ++ fuer int } return erg; // Overload praefix ++ drei_d drei_d::operator ++(void) { ++x; // ++ fuer int ++y; // ++ fuer int ++z; // ++ fuer int return *this; } // Ausgabe einer 3D-Koordinate void drei_d::show(void) { cout << x << ", " << y << ", " << z << endl; } } Universität Paderborn, ADT 6.25 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Raumkoordinaten dreidmain.cc #include "dreid.h" int main() { drei_d x(1,2,3), y; x.show(); y.show(); ++y; y.show(); y++; y.show(); y=x++; y.show(); x.show(); Ausgabe 1, 0, 1, 2, 1, 2, 2, 0, 1, 2, 2, 3, 3 0 1 2 3 4 } Universität Paderborn, ADT 6.26 Verlässliches Programmieren in C/C++ 6. Klassen Friend-Funktion (a) friend datentyp funktionsbezeichner (parameterdeklarationen); – Nicht-Member-Funktionen können auf private Daten einer Klasse nicht zugreifen – Der Zugriff auf private Daten kann in der Klassendefinition für bestimmte Nicht-Member-Funktionen zugelassen werden – Durch Friend-Funktionen hat man für eine Klasse die Kontrolle darüber, welche externen Funktionen Zugriff auf die Daten einer Klasse haben Universität Paderborn, ADT 6.27 Verlässliches Programmieren in C/C++ 6. Klassen Friend-Funktion (b) Eine gewöhnliche Deklaration einer Member-Funktion spezifiziert drei logisch unterschiedliche Dinge: 1. Die Funktion kann auf private Teile der Klasse zugreifen 2. Die Funktion liegt im Sichtbarkeitsbereich der Klasse 3. Aufruf der Funktion erfolgt über ein Objekt – statische Member-Funktionen besitzen nur die ersten beiden Eigenschaften – für als friend deklarierte Funktionen gilt nur die erste Eigenschaft Universität Paderborn, ADT 6.28 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Komplexe Zahlen complex.h class complex { private: double re, im; // Real-, Imaginaerteil public: complex() // Konstruktor { re = 0.0; im = 0.0; }; void input(); void output(); friend complex operator +(complex z1, complex z2); friend complex operator *(complex z1, complex z2); friend complex operator /(complex z1, complex z2); complex operator +=(complex z2); complex operator =(complex z2); }; Universität Paderborn, ADT 6.29 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Komplexe Zahlen complex.cc (a) #include <iostream> #include "complex.h" // Eingabe von komplexen Zahlen void complex::input() { cin >> re >> im; } // Ausgabe von komplexen Zahlen void complex::output() { cout << re << " + i" << im; } Universität Paderborn, ADT 6.30 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Komplexe Zahlen complex.cc (b) // Operatorfunktion +, Addition komplexer Zahlen, friend complex operator +(complex z1, complex z2) { complex erg; erg.re=z1.re+z2.re; erg.im=z1.im+z2.im; return erg; } // Operatorfunktion *, Multiplikation komplexer Zahlen, friend complex operator *(complex z1, complex z2) { complex erg; erg.re=z1.re*z2.re-z1.im*z2.im; erg.im=z1.re*z2.im+z2.re*z1.im; return erg; } Universität Paderborn, ADT 6.31 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Komplexe Zahlen complex.cc (c) // Operatorfunktion += complex complex::operator +=(complex z2) { re=re+z2.re; im=im+z2.im; return *this; } // Operatorfunktion =, Zuweisung komplexer Zahlen complex complex::operator =(complex z2) { re=z2.re; im=z2.im; return *this; } Universität Paderborn, ADT 6.32 Verlässliches Programmieren in C/C++ 6. Klassen Beispiel Komplexe Zahlen complexmain.cc #include <iostream> #include "complex.h" int main() { complex a, b, c; a.output(); cout << " + "; b.output(); cout << " = "; a+=b; // a hat jetzt den wert von a+b a.output(); cout << ’\n’; cout << "komplexe Zahl? "; a.input(); cout << "komplexe Zahl? "; b.input(); a.output(); cout << " / "; b.output(); cout << " = "; c=a/b; c.output(); cout << ’\n’; Universität Paderborn, ADT return(0); } 6.33 Verlässliches Programmieren in C/C++ 7. Vererbung 7. Vererbung Universität Paderborn, ADT 7.1 Verlässliches Programmieren in C/C++ 7. Vererbung Konzept (a) – Datenabstraktion ist eine effektive Methode zur Erweiterung des vordefinierten Typsystems, wenn ein einziges, klar definiertes Konzept vorliegt, z. B. komplexe Zahlen – Oft steht aber nur ein abstrakter Datentyp, eine Klasse, zur Verfügung, die eine bestimmte Situation nur teilweise bzw. annähernd beschreibt – Es kann auch eine Zusammenstellung von Klassen verfügbar sein, die sich in Bedeutung und Implementierung ähnlich sind, aber nicht identisch – Hier ist Vererbung eine nützliche Methode zur Ergänzung der Datenabstraktion Universität Paderborn, ADT 7.2 Verlässliches Programmieren in C/C++ 7. Vererbung Konzept (b) – Vererbung bietet die Möglichkeit hierarchischer Klassifizierung von Datentypen (Klassen) – Eine Klasse kann aus einer anderen Klasse durch • Erweiterung oder • Spezialisierung abgeleitet werden Universität Paderborn, ADT – Die Klasse, aus der abgeleitet wird, heißt Basisklasse, sie vererbt ihre Members (Variablen und Funktionen) – Die Klasse, die ableitet, heißt abgeleitete Klasse • sie erbt die Members ihrer Basisklasse • sie kann Leistungsmerkmale an bestimmte Anforderungen anpassen und neue Leistungsmerkmale hinzufügen 7.3 Verlässliches Programmieren in C/C++ 7. Vererbung Konzept (c) Ableitung von Klassen ermöglicht – die Implementierung von generischen Datentypen – durch spezielle Anpassung (Ableitung) die Wiederverwendung bewährten Programm-Codes – Strukturierung von Datentypen Universität Paderborn, ADT 7.4 Verlässliches Programmieren in C/C++ 7. Vererbung Definition class klassenbezeichner : [public|protected|private] basisklasse { [private:] private daten und funktionen [protected:] geschützte daten und funktionen [public:] öffentliche daten und funktionen }; – Im Kopf der Klassendefinition einer abgeleiteten Klasse kann ein Zugriffsmodus (public, protected oder private) auf die Basisklasse angegeben werden – Der Zugriffsmodus bestimmt die Möglichkeiten des Zugriffs auf MemberVariablen und Member-Funktionen innerhalb der Vererbungshierarchie Universität Paderborn, ADT 7.5 Verlässliches Programmieren in C/C++ 7. Vererbung Zugriffsmodi – private: geschützte und öffentliche Members der Basisklasse sind privat in der abgeleiteten Klasse (default, wenn kein Modus angegeben) – protected: geschützte und öffentliche Members der Basisklasse sind geschützt in der abgeleiteten Klasse – public: geschützte Members der Basisklasse sind geschützt und öffentliche Members der Basisklasse sind öffentlich in der abgeleiteten Klasse – Private Members der Basisklasse sind in der abgeleiteten Klasse nicht zugreifbar – Geschützte Members der Basisklasse sind nur in der Basisklasse und in abgeleiteten Klassen zugreifbar Universität Paderborn, ADT 7.6 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel: Vererbung class base { private: int i; protected: int j; public: int k; void seti(int a) { i = a; } int geti() { return i; } }; class derived: protected public: // j ist protected void setj(int a) { j = // k ist protected void setk(int a) { k = int getj() { return j; int getk() { return k; }; Universität Paderborn, ADT int main() { derived ob; // illegal, da protected in derived! // ob.seti(10); // illegal, da protected in derived! // cout << ob.geti(); // illegal, da k protected! // ob.k = 10; base { ob.setk(10); cout << ob.getk() << ’ ’; ob.setj(12); cout << ob.getj() << ’ ’; a; } a; } } } return 0; } 7.7 Verlässliches Programmieren in C/C++ 7. Vererbung Wiederverwendung – Klassenhierarchien können verwendet werden, um generische Klassen zu bilden – Generische Klassen geben nur eine bestimmte Implementierungsstruktur vor, sie sind Basisklassen – Eine konkrete Implementierung erfolgt in abgeleiteten Klassen für spezifische Ausprägungen der Basisklasse – Generische Klassen bilden eine Schnittstelle zu bestimmtem ProgrammCode, der in abgeleiteten Klassen wiederverwendet wird Universität Paderborn, ADT 7.8 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Binärer Suchbaum (a) – Ein binärer Suchbaum ist eine Datenstruktur, die Elemente gemäß einer Sortierung speichert – Jeder Knoten des Baumes besitzt zwei Nachfolger (left, right) – Die Nachfolger sind ebenfalls binäre Suchbäume – Im Nachfolger left befinden sich alle Elemente, die kleiner sind als das Element im Knoten – Im Nachfolger right befinden sich alle Elemente, die größer sind als das Element im Knoten – Jeder Knoten besitzt zwei Dateneinträge: (1) Element (data), (2) Vielfachheit (count) des Elements Universität Paderborn, ADT 7.9 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Binärer Suchbaum (b) t left data 7 count 2 right Universität Paderborn, ADT t.insert(13); 4 15 1 3 2 11 19 1 1 1 8 13 4 1 7.10 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Binärer Suchbaum (c) gentree.h // Generische binaere Suchbaeume // generischer Zeiger, verweist in // abgeleiteter Klasse auf Daten typedef void *p_gen; class bnode { // Knoten private: friend class gen_tree; p_gen data; bnode *left; bnode *right; int count; // Vielfachheit eines Datums // Konstruktor bnode(p_gen d, bnode *l, bnode *r) : data(d), left(l), right(r), count(1) {} // Vergleich von Daten friend int comp(p_gen a, p_gen b); friend void print(bnode *n); }; Universität Paderborn, ADT class gen_tree { // Baum protected: bnode *root; p_gen find(bnode *r, p_gen d); void print(bnode *r); public: // Konstruktor gen_tree() { root=0; // leerer Baum } void insert(p_gen d); p_gen find(p_gen d) { return find(root, d); } void print() { print(root); } }; 7.11 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Binärer Suchbaum (d) – Benutzung eines void-Zeigers (generischer Zeiger ) für die Elemente, damit in abgeleiteten Klassen beliebige Typen deklariert werden können – Eine Klasse bnode, die einen Knoten des Baums beschreibt – Eine Klasse gen_tree, die den binären Suchbaum beschreibt (Member: bnode, geschützt, damit nur in abgeleiteten Klassen darauf zugegriffen werden kann – Alle Members in bnode sind privat, gen_tree ist friend, kann also auf alle Daten von bnode zugreifen – Konstruktor in bnode mit Initialisierungsliste Universität Paderborn, ADT 7.12 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Binärer Suchbaum (e) – Member-Funktionen in gen_tree: Konstruktor (erzeugt leeren Baum), Einfügen (insert()), Suchen (find()), Ausgeben (print()) – Vergleichsoperation für Elemente (comp()) ist datentypunabhängig, wird außerhalb der Klasse definiert, (friend von bnode) – Gleiches gilt für die Ausgabefunktion print() – Im geschützten Teil von gen_tree gibt es eine Hilfsfunktion find(bnode *r, p_gen d) (sucht im Teilbaum r nach d) – Im öffentlichen Teil gibt es die Funktion find(p_gen d) (sucht im gesamten Baum nach d – Gleiche Konstruktion für die Ausgabe (print()) Universität Paderborn, ADT 7.13 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Binärer Suchbaum (f) #include "gentree.h" void gen_tree::insert(p_gen d) { bnode *temp = root; bnode *old; if (root == 0) { root = new bnode(d, 0, 0); return; } while (temp != 0) { old = temp; if (comp(temp->data, d) == 0) { (temp->count)++; return; } if (comp(temp->data, d) > 0) temp = temp->left; else temp = temp->right; } if (comp(old->data, d) > 0) old->left = new bnode(d, 0, 0); else old->right = new bnode(d, 0, 0); } Universität Paderborn, ADT // In-order Durchlauf, binaere Suche p_gen gen_tree::find(bnode *r, p_gen d) { if (r == 0) return 0; else if (comp(r->data, d) == 0) return r->data; else if (comp(r->data, d) > 0) return find(r->left, d); else return find(r->right, d); } // Pre-order Durchlauf, sortierte Ausgabe void gen_tree::print(bnode *r) { if (r != 0) { print(r->left); print(r); // Nicht-Member-Funktion! print(r->right); } } 7.14 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Binärer Suchbaum (g) – Die generische Klasse ist nicht zur Erzeugung von Instanzen geeignet – Es muß eine Klasse abgeleitet werden, in der der Datentyp der Einträge definiert ist – Die datentypabhängigen Funktionen für den Elementvergleich (comp()) und die Ausgabe (print()) müssen definiert werden – Alle Funktionen, die auf Elemente (generischer Datentyp void *p_gen) zugreifen, müssen mit einer Typkonversion arbeiten – Die abgeleitete Klasse leitet mit dem Zugriffsmodus private von gen_tree ab, denn es soll keine weitere Konkretisierung der Datentypen geben Universität Paderborn, ADT 7.15 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Binärer Suchbaum (h) #include <iostream> #include <cstring> #include "gentree.h" class s_tree: private gen_tree { public: s_tree() { cout << "Baum erzeugt\n"; } void insert(char *d) { gen_tree::insert(p_gen(d)); } char *find(char *d) { return (char*) gen_tree::find(p_gen(d)); } void print() { gen_tree::print(); } }; int comp(p_gen i, p_gen j) { return strcmp((char*)i, (char*)j); } void print(bnode *n) { cout << (char*)n->data << " ("; cout << n->count << ")\t"; } Universität Paderborn, ADT int main() { s_tree daten; // Suchbaum von Zeichenketten char datum[80]; // Zeichenkette char *el; // Element des Suchbaums cout << "Bitte Strings eingeben "; cout << "(Ende mit Ctrl-D):\n"; // cin.good() prueft auf typgerechte Eingabe while (cin >> datum && cin.good()) { el = new char[strlen(datum) + 1]; strcpy(el, datum); daten.insert(el); } daten.print(); cout << ’\n’; return 0; } 7.16 Verlässliches Programmieren in C/C++ 7. Vererbung Zeigerregel – Abgeleitete Klassen sind bezüglich Zeiger kompatibel zur Basisklasse – Ein Zeiger auf eine Basisklasse kann auf davon abgeleitete Klassen zeigen ( upcasting“) ” Bsp.: basis *pB = new abgeleitet; – Ein Zeiger auf eine abgeleitete Klasse kann nicht auf die Basisklasse oder eine andere davon abgeleitete Klasse zeigen – eine explizite Typumwandlung mittels eines Casts ist jedoch möglich ( downcasting“): ” Bsp.: abgeleitet *pA = (abgeleitet*) pB; besser: abgeleitet *pA = dynamic_cast<abgeleitet*>(pB); (bei einem fehlgeschlagenen Cast gibt der Operator dynamic_cast den Wert 0 zurück) Universität Paderborn, ADT 7.17 Verlässliches Programmieren in C/C++ 7. Vererbung Polymorphie und virtuelle Funktionen – Virtuelle Funktionen erlauben Polymorphie von Funktionen – Eine virtuelle Funktion wird in der Basisklasse deklariert und in abgeleiteten Klassen neu definiert – Der Prototyp der neu definierten Funktion muß dieselbe Struktur wie die virtuelle Funktion in der Basisklasse haben (gleiche Parameter, gleicher Datentyp des Rückgabewerts) – Beim Aufruf einer virtuellen Funktion über einen Zeiger auf die Basisklasse wird die aktuelle Funktion durch den Typ des Objekts, auf den der Zeiger zeigt, bestimmt und nicht durch die Basisklasse – Dadurch wird zur Laufzeit die auszuführende Funktion bestimmt, und nicht zur Compile-Zeit (Laufzeitbindung) Universität Paderborn, ADT 7.18 Verlässliches Programmieren in C/C++ 7. Vererbung Virtuelle Funktionen virtual datentyp funktionsbezeichner (parameterdeklarationen) { anweisungen } – Die Eigenschaft, virtuell zu sein, wird durch die Vererbungshierarchie weitergereicht – Ist eine Funktion in der Basisklasse als virtuell deklariert, so ist sie in allen (auch mehrstufig) abgeleiteten Klassen virtuell, ohne dort als virtuell deklariert zu sein Universität Paderborn, ADT 7.19 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Virtuelle Funktion #include <iostream> class basis { public: int i; virtual void print_i() { cout << i << " in basis\n"; } }; class abgeleitet: public basis { public: void print_i() { cout << i << " in abgeleitet\n"; } }; int main() { basis b; basis *b_ptr=&b; abgeleitet d; } d.i = 1 + (b.i = 1); b_ptr->print_i(); // "1 in basis" b_ptr = &d; b_ptr->print_i(); // "2 in abgeleitet" (waere "2 in basis", // falls print_i nicht virtuell) Universität Paderborn, ADT 7.20 Verlässliches Programmieren in C/C++ 7. Vererbung Abstrakte Klassen virtual datentyp funktionsbezeichner (parameterdeklarationen) = 0; – Wenn eine virtuelle Funktion der Basisklasse, die nicht in der abgeleiteten Klasse redefiniert ist, von einem Objekt der abgeleiteten Klasse aufgerufen wird, so wird die Funktion, so wie sie in der Basisklasse definiert ist, ausgeführt – Oft gibt es in der Basisklasse keine sinnvolle Definition der virtuellen Funktion (z. B. Flächenberechnung geometrischer Objekte) – Eine rein virtuelle Funktion besitzt in der Basisklasse keine Definition – Eine Klasse, die mindestens eine rein virtuelle Funktion enthält, heißt abstrakte Klasse – aus ihr können keine Instanzen erstellt werden Universität Paderborn, ADT 7.21 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Geometrische Figuren (a) Abstrakte Klasse figur mit abgeleiteten Klassen dreieck, rechteck und kreis figur dim: x, y flaeche() umfang() dreieck rechteck breite, hoehe flaeche() umfang() Universität Paderborn, ADT breite, hoehe flaeche() umfang() kreis durchmesser flaeche() umfang() 7.22 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Geometrische Figuren (b) figur.h #include <iostream> const double PI = 3.14159; class figur { protected: double x, y; // Dimensionen public: void set_dim(double i, double j = 0) { x = i; y = j; } virtual double flaeche() = 0; virtual double umfang() = 0; }; class dreieck: public figur { // x: Breite, y: Hoehe public: double flaeche() { return(x * 0.5 * y); } double umfang() { return(sqrt((x * 0.5) * (x * 0.5) + y * y) * 2 + x); } }; Universität Paderborn, ADT class rechteck: public figur { // x: Breite, y: Hoehe public: double flaeche() { return(x * y); } double umfang() { return(2 * x + 2 * y); } }; class kreis: public figur { // x: Durchmesser, y nicht benutzt public: double flaeche() { return((x * x / 4) * PI); } double umfang() { return(x * PI); } }; 7.23 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Geometrische Figuren (c) figurmain.cc #include <iostream> #include "figur.h" for (int i = 0; i < dim; ++i) { cout << "Objekt " << i << ": "; cout << fig[i]->flaeche() << endl; tot_flaeche += fig[i]->flaeche(); } cout << "Gesamtflaeche: "; cout << tot_flaeche << endl; int const dim = 3; int main() { double tot_flaeche=0; dreieck d; rechteck r; kreis k; figur *fig[dim]; return 0; } Ausgabe d.set_dim(3.78, 2.87); r.set_dim(2.5, 3.77); k.set_dim(6.23); fig[0] = &d; fig[1] = &r; fig[2] = &k; Universität Paderborn, ADT Objekt 0: 5.4243 Objekt 1: 9.425 Objekt 2: 30.4836 Gesamtflaeche: 45.3329 7.24 Verlässliches Programmieren in C/C++ 7. Vererbung Virtuelle Klassen und Mehrfachvererbung class klassenbezeichner : [zugriff ] basisklasse [, [zugriff ] basisklasse]* { daten und funktionen }; – Eine Klasse kann von mehreren Klassen abgeleitet sein (Mehrfachvererbung, Multiple-inheritance) – Bei Verwendung von Mehrfachvererbung können Mehrdeutigkeiten auftreten (Vererbung von Funktionen gleichen Namens und verschiedener Bedeutung aus verschiedenen Klassen) – Es kann zu Duplizierung von Membervariablen kommen Universität Paderborn, ADT 7.25 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Mehrdeutigkeit class werkzeug { public: int kosten(); // ... }; class teile { public: int kosten(); // ... }; class arbeit { public: int kosten(); // ... }; class rechnung : public werkzeug, public teile, public arbeit { public: int gesamtkosten() { // Korrekt, eindeutig return(werkzeug::kosten() + teile::kosten() + arbeit::kosten()); } }; int fun() { int preis; rechnung *ptr; // Inkorrekt, nicht eindeutig preis = ptr->kosten(); } Universität Paderborn, ADT 7.26 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Duplizierung name name selbst abhaeng steuerbescheid // name ist zweifach steuerbescheid::selbst::name steuerbescheid::abhaeng::name Universität Paderborn, ADT class name { public: char *name; // ... }; class selbst : public name { public: int gehalt; // ... }; class abhaeng : public name { public: int einnahmen; // ... }; class steuerbescheid : public selbst, public abhaeng { // name ist zweifach vorhanden! }; 7.27 Verlässliches Programmieren in C/C++ 7. Vererbung Virtuelle Vererbung class klassenbezeichner : virtual [zugriff ] basisklasse { daten und funktionen }; – Bei Mehrfachvererbung können zwei Basisklassen ihrerseits von einer gemeinsamen Basisklasse abgeleitet sein – Wird eine Klasse von diesen beiden Basisklassen abgeleitet, so hat die abgeleitete Klasse zwei Member-Objekte der gemeinsamen Basis-Basisklasse – Eine solche Duplizierung kann durch virtuelle Vererbung vermieden werden Universität Paderborn, ADT 7.28 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Virtuelle Vererbung class name { public: char *name; // ... }; name selbst abhaeng steuerbescheid class selbst : virtual public name { public: int gehalt; // ... }; class abhaeng : virtual public name { public: int einnahmen; // ... }; class steuerbescheid : public selbst, public abhaeng { // name ist nur einfach vorhanden }; Universität Paderborn, ADT 7.29 Verlässliches Programmieren in C/C++ 7. Vererbung Funktionsmaskierung class basis { – Ist in einer abgeleiteten Klasse ei- public: int fun(int i, int j) ne angeforderte Funktion nicht de{ return(i * j); } float fun(float f) finiert, so wird in der Basisklasse { return(f * 2); } gesucht }; – Probleme treten bei Polymorphie class abgeleitet : public basis { public: auf int fun(int i, int j) { return(i + j); } – Sind in der Basisklasse zwei Funk- }; tionen gleichen Namens definiert int main() { und ist in der abgeleiteten Klasse abgeleitet test; int i; nur eine dieser Funktionen redefifloat f; niert, so ist die andere Funktion i = test.fun(1, 3); // Legal, i = 4 der Basisklasse in der abgeleiteten f = test.fun(4.0); // Illegal, in test Klasse nicht verfügbar // nicht verfügbar } Universität Paderborn, ADT 7.30 Verlässliches Programmieren in C/C++ 7. Vererbung Konstruktoren und Destruktoren – Konstruktoren und Destruktoren verhalten sich in abgeleiteten Klassen anders als normale Member-Funktionen – Bei Deklaration einer Instanz einer abgeleiteten Klasse wird erst der Konstruktor der Basisklasse und danach der Konstruktor der abgeleiteten Klasse ausgeführt – Bei Zerstörung einer Instanz einer abgeleiteten Klasse wird erst der Destruktor der abgeleiteten Klasse und danach der Destruktor der Basisklasse ausgeführt – Probleme gibt es bei Zeigern vom Typ der Basisklasse, die auf Objekte einer abgeleiteten Klasse zeigen Universität Paderborn, ADT 7.31 Verlässliches Programmieren in C/C++ 7. Vererbung Beispiel Konstruktoren und Destruktoren #include <iostream> class basis { public: basis() { cout << "Konstruktor basis\n"; } ~basis() { cout << "Destruktor basis\n"; } }; int main() { abgeleitet *test_ptr = new abgeleitet; delete test_ptr; test_ptr = 0; return 0; } class abgeleitet : public basis { public: abgeleitet() { cout << "Konstruktor abgeleitet\n"; } ~abgeleitet() { cout << "Destruktor abgeleitet\n"; } }; Universität Paderborn, ADT Ausgabe Konstruktor basis Konstruktor abgeleitet Destruktor abgeleitet Destruktor basis 7.32 Verlässliches Programmieren in C/C++ 7. Vererbung Virtuelle Destruktoren // Legale Deklaration basis *basis_ptr = new abgeleitet; delete basis_ptr; basis_ptr = 0; Ausgabe Konstruktor basis Konstruktor abgeleitet Destruktor basis class basis { public: basis() { cout << "Konstruktor basis\n"; } virtual ~basis() { cout << "Destruktor basis\n"; } wird }; – Bei Zeigern auf Klassen die Funktion des Zeigertyps aus- Ausgabe geführt – Ein virtueller Destruktor bewirkt die Ausführung des aktuellen Typs (Laufzeitbindung) Universität Paderborn, ADT Konstruktor basis Konstruktor abgeleitet Destruktor abgeleitet Destruktor basis 7.33 Verlässliches Programmieren in C/C++ 7. Vererbung Initialisierungsregeln – Basisklassen werden in der Reihenfolge ihrer Deklaration initialisiert – Members werden in der Reihenfolge ihrer Deklaration initialisiert – Virtuelle Basisklassen werden vor jeder nicht-virtuellen Basisklasse erzeugt – Destruktoren werden in umgekehrter Reihenfolge der Konstruktoren aufgerufen Universität Paderborn, ADT 7.34 Verlässliches Programmieren in C/C++ 7. Vererbung Konstruktionsschema class abstrakt_basis { private: // meist leer, // beschraenkt spaeteren Entwurf protected: // statt private, // ermoeglicht Vererbung // ... public: // Standard-Konstruktor abstrakt_basis(); // Copy-Konstruktor abstrakt_basis(const abstrakt_basis&); // Destruktor // braucht Laufzeittyp virtual ~abstrakt_basis(); // Ausdruck, rein virtuell virtual void print() = 0; // ... }; Universität Paderborn, ADT class abgeleitet : virtual public abstrakt_basis { private: // ... protected: // statt private fuer Vererbung // ... public: // Schnittstelle // Realisiert die Instanz // Standard-Konstruktor abgeleitet(); // Copy-Konstruktor abgeleitet(const abgeleitet&); // Zuweisung abgeleitet &operator =(const abgeleitet&); // Konkretes Ausdrucken void print(); // ... }; 7.35 Verlässliches Programmieren in C/C++ 7. Vererbung Wichtige Eigenschaften – Eine virtuelle Funktion und ihre abgeleiteten Instanzen müssen dieselbe Parameterstruktur und denselben Datentyp des Rückgabewertes haben – Nicht-virtuelle Member-Funktionen können dieselbe Parameterstruktur haben und verschiedenen Rückgabetyp – Alle Member-Funktionen mit Ausnahme von Konstruktoren und redefinierten new und delete können virtuell sein – Konstruktoren, Destruktoren, Zuweisungsoperator (=) und friends werden nicht vererbt – Zugriff auf Members kann bei Vererbung nur eingeschränkt, nicht erweitert werden Universität Paderborn, ADT 7.36 Verlässliches Programmieren in C/C++ 7. Vererbung Ein Puzzle Aufgabe: Warum funktioniert das Programm nicht? #include <iostream> class liste { private: int zahl; public: virtual void ausnullen() = 0; void naechstes() { ++zahl; } liste() { zahl = 0; } virtual ~liste() { ausnullen(); // (**) } }; Universität Paderborn, ADT class zahlenliste : public liste { public: int feld[100]; void ausnullen() { int i; // Feldindex for (i = 0; i < 100; ++i) { feld[i] = 0; } } }; int main() { zahlenliste *listen_ptr = new zahlenliste; delete listen_ptr; // Es kracht! // an der Stelle (**) listen_ptr = 0; return 0; } 7.37 Verlässliches Programmieren in C/C++ 7. Vererbung Ein Puzzle (Lösung) – Erst wird der Destruktor von zahlenliste ausgeführt, diese Instanz ist zerstört – Danach wird der Destruktor von liste ausgeführt, dieser ruft die Funktion ausnullen() auf – ausnullen() ist rein virtuell, sie muß in der abgeleiteten Klasse gesucht werden – Die Instanz der abgeleiteten Klasse ist bereits zerstört, es gibt ausnullen() nicht mehr! – Es geschehen seltsame Dinge (je nach Compiler) – Fazit: Niemals rein virtuelle Funktionen in einem Destruktor aufrufen! Universität Paderborn, ADT 7.38 Verlässliches Programmieren in C/C++ 8. Templates 8. Templates Universität Paderborn, ADT 8.1 Verlässliches Programmieren in C/C++ 8. Templates Parametrische Polymorphie – Die Benutzung des selben Programm-Codes für verschiedene Datentypen heißt parametrische Polymorphie – Der Datentyp ist ein Parameter des Programm-Codes – Parametrische Polymorphie wird zur Definition von Container-Klassen eingesetzt – Die Definition einer Funktion oder Klasse unter Verwendung parametrischer Polymorphie heißt Template – Templates ermöglichen Wiederverwendung von Programm-Code in einer “typsicheren” Weise Universität Paderborn, ADT 8.2 Verlässliches Programmieren in C/C++ 8. Templates Template-Funktionen template <class klassenbezeichner [, class klassenbezeichner ]*> typbezeichner funktionsbezeichner (parameterdeklarationen) { anweisungen } – klassenbezeichner ist ein formaler Parameter für einen Datentyp, er wird innerhalb der Template-Definition verwendet – Template-Definitionen erzeugen keinen Programm-Code (daher: keine Aufteilung in Implementierung und Header-Datei) – Erst bei der Benutzung einer Template-Funktion wird Programm-Code erzeugt Universität Paderborn, ADT 8.3 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Maximum-Funktion – Typunabhängige Funktion – TYP ist Parameter template <class TYP> TYP max(TYP d1, TYP d2) { if (d1 > d2) { return d1; } else { return d2; } } Universität Paderborn, ADT Maximum– Benutzung, als wäre eine Funktion für den Datentyp, den die Parameter besitzen, definiert – Generierung der konkreten Funktion float f = max(4.8, 7.9); // generiert int i = max(100, 500); // generiert char c = max(’A’, ’X’); // generiert int j = max(600, 800); // nicht generiert 8.4 Verlässliches Programmieren in C/C++ 8. Templates Funktionsweise (a) – Erst bei Benutzung einer Template-Funktion wird Programm-Code erzeugt oder referenziert – Zunächst wird nachgeschaut, ob es eine konkrete Funktionsdefinition für die Datentypen der Parameter gibt – ist sie vorhanden, so wird sie ausgeführt – Ist eine solche konkrete Funktion nicht vorhanden, so wird ein Template gesucht, aus dem sie generiert werden kann; ist ein solches Template vorhanden, so wird die konkrete Funktion generiert und dann ausgeführt – Explizite oder implizite Instanziierung einer Template-Funktion möglich float f = max<double>(4.8, 7.9); // Explizit float f = max(4.8, 7.9); // Implizit (nur wenn eindeutig) Universität Paderborn, ADT 8.5 Verlässliches Programmieren in C/C++ 8. Templates Funktionsweise (b) Quell-Code Generierter Code template <class TYPE> TYP max(TYP d1, TYP d2) { if(d1>d2) return(d1); return(d2); } generiert generiert generiert float max(float d1, float d2) { if(d1>d2) return(d1); return(d2); } main() { float f=max(4.8, int i=max(100, chat ch=max(’A", int j=max(600, ... } Universität Paderborn, ADT 7.9); 500); ’X’); 800); int max(int d1, int d2) { if(d1>d2) return(d1); return(d2); } char max(char d1, char d2) { if(d1>d2) return(d1); return(d2); } main() { float f=max(4.8, int i=max(100, chat ch=max(’A", int j=max(600, ... } 7.9); 500); ’X’); 800); 8.6 Verlässliches Programmieren in C/C++ 8. Templates Funktionsspezialisierung – Template-Funktionen realisieren implizit eine Art von Funktions-Overloading – Template-Funktionen können folgerichtig auch überschrieben werden – Das explizite Überschreiben von Template-Funktionen heißt FunktionsSpezialisierung – Aus der Funktionsweise von Template-Funktionen ergibt sich, daß bei Vorhandensein einer expliziten Funktionsdefinition das Template nicht benutzt wird Universität Paderborn, ADT 8.7 Verlässliches Programmieren in C/C++ 8. Templates Beispiel: Maximum von C-Strings – Die Template-Funktion max() ist nur brauchbar für Datentypen, auf denen der Vergleichoperator > definiert ist – Für C-Strings muß eine andere Funktionsdefinition gegeben werden – Funktions-Spezialisierung für C-Strings char *max(char *d1, char *d2) { if (strcmp(d1, d2) < 0 ) { return d1; } else { return d2; } } Universität Paderborn, ADT 8.8 Verlässliches Programmieren in C/C++ 8. Templates Beispiel: Maximum-Funktion mit Spezialisierung int main() { using std::cout; using std::endl; #include <iostream> #include <cstring> cout << "max(1, 2) = " << max(1, 2); //erzeugt aus Template cout << endl; template <class TYP> TYP max(TYP d1, TYP d2) { if(d1>d2) return d1; return d2; } cout << "max(2, 1) = " << max(2, 1); //nicht erzeugt cout << endl; cout << cout << //nicht cout << // Spezialisierung fuer C-Strings char *max(char *d1, char *d2) { if(strcmp(d1, d2)<0) return d1; return d2; } "max(\"berta\", \"armin\")= "; max((char*)"berta", (char*)"armin"); erzeugt (Spezialisierung) endl; cout << "max(\"armin\", \"berta\")= "; cout << max("armin", "berta") << endl; //erzeugt aus Template (Typ: const char *) return 0; } Universität Paderborn, ADT 8.9 Verlässliches Programmieren in C/C++ 8. Templates Klassen-Template template <class klassenbezeichner [, class klassenbezeichner ]*> class klassenbezeichner { klasssendefinition } – Die Benutzung eines Klassen-Templates erfordert die Angabe eines Datentyps für die formalen class klassenbezeichner (formale Parameter) – Klassendefinitionen mit formalen Parametern heißen Container-Klassen Universität Paderborn, ADT 8.10 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Stack top_of() stack push() pop() – Ein Stack ist Datenstruktur eine LIFO- – Hauptfunktionen push() (Datum einfügen) und pop() (Datum entfernen) max_len – Die Funktionen hängen logisch nicht von dem Datentyp der Daten ab – Implementierung als Klasse stack<TYPE> Universität Paderborn, ADT Container- 8.11 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Stack stack.h // Template stack template <class TYPE> class stack { private: enum { EMPTY = -1}; TYPE *s; int max_len; int top; public: stack(): max_len(1000) { s = new TYPE[1000]; top = EMPTY; } stack(int size): max_len(size) { s = new TYPE[size]; top = EMPTY; } ~stack() { delete []s; } void reset() { top = EMPTY; } void push(TYPE c) { s[++top] = c; } TYPE pop() { return(s[top--]); } TYPE top_of() { return(s[top]); } bool empty() { return top == EMPTY; } bool full() { return top == (max_len - 1); } }; Universität Paderborn, ADT 8.12 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Stack stackmain.cc #include <iostream> #include <cstring> #include "stack.h" // Umdrehen eines C-Strings void reverse(char str[], int n) { stack<char> stk(n); int i; for (i = 0; i < n; ++i) { stk.push(str[i]); } for (i = 0; i < n; ++i) { str[i] = stk.pop(); } }; Universität Paderborn, ADT int main() { char str[80]; std::cout << "Zeichenkette? "; std::cin >> str; reverse(str, strlen(str)); std::cout << str << ’\n’; return 0; } 8.13 Verlässliches Programmieren in C/C++ 8. Templates Klassen-Spezialisierung – Wie bei Template-Funktionen wird eine explizite Definition zuerst benutzt bevor eine konkrete Definition aus einem Template erzeugt wird – Für stack<int> werden die Funktionen stack<int>::push(), stack<int>::pop(), etc. generiert – Sollen C-Strings im Stack gespeichert werden (nicht Zeiger auf char), so kann stack<char *>::push() überschrieben werden (Klassen-Spezialisierung) // s speichert string, nicht Zeiger auf char void stack<char *>::push(char *c) { s[++top] = strdup(c); } Universität Paderborn, ADT 8.14 Verlässliches Programmieren in C/C++ 8. Templates Besonderheiten (a) – Es können Nicht-Typen als Parameter verwendet werden; diese Parameter werden mit Konstanten instanziert – template<class T, int n> class array_n { private: // n wird explizit angegeben T items[n]; }; // w ist 1000er Feld von complex array_n<complex, 1000> w; Universität Paderborn, ADT 8.15 Verlässliches Programmieren in C/C++ 8. Templates Besonderheiten (b) – Eine Friend-Funktion, die keine Template-Spezifikation benutzt, ist Friend aller Instanzen der Klasse – Eine Friend-Funktion, die eine Template-Spezifikation benutzt, ist Friend nur der konkret generierten Klasse template <class T> class matrix { private: // universell friend void foo_bar(); // speziell friend vekt<T> produkt(vekt<T> v); // ... }; Universität Paderborn, ADT 8.16 Verlässliches Programmieren in C/C++ 8. Templates Besonderheiten (c) – Static-Member-Variablen sind nicht universell für alle konkret generierten Klassen, sondern immer nur speziell für einzelne konkret generierte Klassen template <class T> class foo { public: static int zaehler; // ... }; foo<int> a; foo<double> b; – Die static Variablen foo<int>::zaehler und foo<double>::zaehler sind verschieden Universität Paderborn, ADT 8.17 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Binärer Suchbaum (a) – Die Klasse gen_tree kann als Klassen-Template eleganter implementiert werden als mit generischen Zeigern und Vererbung – Der generische Zeiger void *p_gen wird hier zu einem Template Parameter T – Die Funktion bnode<T>::print() ist hier nicht Friend-, sondern MemberFunktion – Die Klasse gen_tree<T> ist nicht für Vererbung vorgesehen; alle Hilfsfunktionen sind privat Universität Paderborn, ADT 8.18 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Binärer Suchbaum (b) – Die Vergleichsfunktion int comp() ist nicht als Friend-Funktion definiert – Die Template-Funktion int comp() setzt voraus, daß die Vergleichsoperatoren == und < auf dem Datentyp T definiert sind – Strings (Datentyp char*) werden durch Funktionsspezialisierung behandelt – Das Klassen-Template gen_tree<T> erfordert nur Änderungen, die Objekte der Klasse bnode betreffen; Ersetzung durch bnode<T> Universität Paderborn, ADT 8.19 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Binärer Suchbaum comp.cc template <class int comp(T i, T { if(i == j) // return 0; else return((i < } T> j) // allgemeiner Fall setzt voraus, dass == fuer T definiert ist j) ? -1 : 1); int comp(char *i, char *j) // speziell fuer Strings { return strcmp(i, j); } Universität Paderborn, ADT 8.20 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Binärer Suchbaum gentree.h // Template-Version generischer binaerer Suchbaeume #include <stream.h> template <class T> #include "comp.h" class gen_tree { private: // forward-Deklaration bnode<T> *root; template <class T> class gen_tree; T find(bnode<T> *r, T d); void print(bnode<T> *r); template <class T> public: class bnode { gen_tree() private: { friend class gen_tree<T>; root = 0; bnode<T> *left; } bnode<T> *right; void insert(T d); T data; T find(T d) int count; { bnode(T d, bnode<T> *l, bnode<T> *r) return find(root, d); : left(l), right(r), } data(d), count(1) {} void print() void print() { { print(root); cout << data << " ("; } cout << count << ")\t"; }; } }; Universität Paderborn, ADT 8.21 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Binärer Suchbaum gentree.cc (a) template <class T> void gen_tree<T>::insert(T d) { bnode<T> *temp = root; bnode<T> *old; if (root == 0) { root = new bnode<T>(d, 0, 0); return; } while (temp != 0) { old = temp; if (comp(temp->data, d) == 0) { (temp->count)++; return; } Universität Paderborn, ADT if(comp(temp->data, d) > 0) temp = temp->left; else temp = temp->right; } if (comp(old->data, d) > 0) old->left = new bnode<T>(d, 0, 0); else old->right = new bnode<T>(d, 0, 0); } 8.22 Verlässliches Programmieren in C/C++ 8. Templates Beispiel Binärer Suchbaum gentree.cc (b) template <class T> T gen_tree<T>::find(bnode<T> *r, T d) { if (r == 0) return 0; else if (comp(r->data, d) == 0) return r->data; else if (comp(r->data, d) > 0) return find(r->left, d); else return find(r->right, d); } Universität Paderborn, ADT template <class T> void gen_tree<T>::print(bnode<T> *r) { if (r != 0) { print(r->left); r->bnode<T>::print(); print r->right; } } 8.23 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung 9. Ausnahmebehandlung Universität Paderborn, ADT 9.1 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Laufzeitfehler – Ein Programm, das in der Praxis eingesetzt wird, muß die Behandlung möglicher Laufzeitfehler vorsehen – Typische Laufzeitfehler treten auf, wenn Speicherplatz alloziert wird und nicht genügend Speicher vorhanden ist, oder wenn eine Division durch 0 erfolgen soll – C++ enthält einen speziellen Mechanismus zur Behandlung von Laufzeitfehlern (Exception-handling) – Exception-handling ermöglicht dem Benutzer einer Klasse, beim Aufruf einer Member-Funktion, auftretende Fehler im Anwendungsprogramm sinnvoll abzufangen Universität Paderborn, ADT 9.2 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung try, catch und throw (a) – Die Ausnahmebehandlung in C++ beruht auf drei Konstrukten – try: Programmteile (Program-statements), die vom Ausnahmemechanismus (Exception-handler) überwacht werden sollen, werden in einem try Block zusammengefaßt – throw: Wenn ein Laufzeitfehler in einem try-Block auftritt, wird eine Ausnahme mittels throw ausgegeben – catch: Die Ausnahme wird mittels catch aufgefangen und behandelt Universität Paderborn, ADT 9.3 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung try, catch und throw (b) try { anweisungen } catch(typ1 parameter ) { anweisungen } catch(typ2 parameter ) { anweisungen } ... catch(typN parameter ) { anweisungen } Universität Paderborn, ADT – Es können mehrere catchAnweisungen zu einem try-Block gehören – Der Typ der Ausnahme bestimmt, welche catch-Anweisung ausgeführt wird – Soll das gesamte Programm mittels Exception-handler überwacht werden, muß die Funktion main() innerhalb eines try-Blocks stehen 9.4 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Funktionsweise (a) #include <iostream> int main() { cout << "Start\n"; try { // Beginn des try-Blocks cout << "Innerhalb eines try-Blocks\n"; throw 99; // Ausgabe einer Ausnahme cout << "Dies wird nicht ausgefuehrt"; } // Ende des try-Blocks catch(int i) { // Fange eine Ausnahme ab cout << "Ausnahme, Wert="; cout << i << ’\n’; } // Ende catch cout << "Ende\n"; return 0; } Ausgabe Start Innerhalb eines try-Blocks Ausnahme, Wert=99 Ende Universität Paderborn, ADT – Die throw-Anweisung führt die Programmsteuerung zum catchAnweisungsblock – Der catch-Block wird nicht aufgerufen, sondern die Programmausführung wird dorthin übertragen – Nach Ausführung des catchAnweisungsblocks fährt die Programmausführung mit der unmittelbar auf den catchAnweisungsblock folgenden Anweisung fort 9.5 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Funktionsweise (b) throw; throw ausdruck; – Der Datentyp von ausdruck muß mit dem Datentyp einer catch-Anweisung übereinstimmen – Eine throw-Anweisung ohne folgenden ausdruck reicht eine Ausnahme weiter (rethrow), wird benutzt, wenn ein weiterer Exception-handler zur Fehlerbehandlung eingesetzt werden soll – Bei geschachtelten try-Blöcken wird der innerste try-Block, in dem eine Ausnahme mittels throw gegeben wird, zur Auswahl des passenden catchAnweisungsblocks gewählt Universität Paderborn, ADT 9.6 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Funktionsweise (c) – Der durch throw gegebene ausdruck ist ein statisches temporäres Objekt, das so lange existiert, bis der zugehörige Exception-handler (catch) verlassen wird – Der Exception-handler kann diesen ausdruck benutzen void foo() { int i; // ... throw i; } Universität Paderborn, ADT int main() { try { foo(); } catch(int n) {...} } 9.7 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Funktionsweise (d) – Bei Ausnahmen in geschachtelten Funktionen wird der Prozeß-Stack soweit zurückgenommen, bis ein Exception-handler gefunden wird – Damit werden beim Verlassen eines lokalen Prozesses (innere Funktion) alle zugehörigen Objekte (Variablen, Instanzen) zerstört Universität Paderborn, ADT void foo() { int i, j; // ... throw i; // ... } void call_foo() { int k; // ... foo(); // ... } int main() { try { call_foo(); // foo() beendet, i, j zerstoert } catch(int n) {...} } 9.8 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Weiterreichen von Ausnahmen – Ausnahmen werden durch eine throw/catch-Sequenz behandelt – Solche Ausnahmen können auch nach außerhalb einer throw/catch-Sequenz weitergereicht werden – Es können mehrfache Ausnahmebehandlungen vorgenommen werden (Verschiedene Exception-handler für verschiedene Aspekte einer Ausnahme) – Weiterreichen von Ausnahmen kann nur innerhalb eines catch-Anweisungsblocks geschehen Universität Paderborn, ADT 9.9 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Weiterreichen von Ausnahmen #include <iostream> using namespace std; int main() { cout << "Start\n";using namespace std; try { Xhandler(); } catch(const char *s) { cerr << "Ausnahme char * "; cerr << "innerhalb von main. Wert: "; cerr << s << ’\n’; } cout << "Ende\n"; void Xhandler() { try { throw "Hallo"; } catch(const char *s) { cerr << "Ausnahme char * "; cerr << "innerhalb von Xhandler. "; cerr << "Wert: "; cerr << s << ’\n’; throw; // Weiterreichen von const char * return 0; } } } Ausgabe Start Ausnahme char * innerhalb von Xhandler. Wert: Hallo Ausnahme char * innerhalb von main. Wert: Hallo Ende Universität Paderborn, ADT 9.10 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Mehrfache Ausnahmen – Es können mehrere catch-Anweisungsblöcke zu einem try-Block implementiert werden – Die catch-Anweisungen müssen jedoch mit verschiedenen Datentypen verbunden sein – Es gibt eine typunabhängige catch-Anweisung, mit der beliebige mit throw gegebene Ausnahmen behandelt werden können (allgemeines catch) catch(...) { anweisungen } Universität Paderborn, ADT 9.11 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Mehrfache Ausnahmen (a) vektor::vektor(int n) { if (n < 1) throw (n); p = new int[n]; if (p == 0) throw ("Kein Speicher mehr"); } void g() { try { vektor a(n), b(n); // ... } catch (int n) {...} // Behandelt inkorrekte Groesse catch (const char *error) {...} // Behandelt Speicherfehler Universität Paderborn, ADT 9.12 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Programmabbruch – Gewöhnlich wird ein catch-Anweisungsblock mit der C++ Standard-Bibliotheksfunktion exit() oder abort() beendet (Ausnahmebehandlung von katastrophalen Fehlern) – void abort() • bewirkt die sofortige Beendigung des Programms • kein Rückgabewert an das Betriebssystem, schließt keine geöffneten Dateien – void exit(int status) • schließt alle geöffneten Dateien • beendet das Programm mit einem Rückgabewert an das Betriebssystem Universität Paderborn, ADT 9.13 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Mehrfache Ausnahmen (b) Standardaktion für Mehrfachausnahmen catch (const char *meldung) { cerr << meldung << endl; exit(1); } catch(...) // Standardaktion { cerr << "Das war’s. Ich weiss nicht mehr weiter." << endl; abort(); } Universität Paderborn, ADT 9.14 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Mehrfache Ausnahmen (c) #include <iostream> void Xhandler(int test) { try { if(test==0) throw "Der Wert ist null"; if(test==1 || test==3) throw test; if(test==2) throw 22.89; else throw "Der Wert ist zu gross"; } catch(int i) { // Fange int cerr << "Fehler! Nr.: " << i << ’\n’; } catch(const char *str) { // Fange String cerr << "Fehler! Text: "; cerr << str << ’\n’; } catch(...) { // Fange andere Typen cerr << "Fehler! Typ unbekannt\n"; } } Universität Paderborn, ADT int main() { cout << "Start\n"; Xhandler(1); Xhandler(2); Xhandler(0); Xhandler(3); cout << "Ende\n"; return 0; } Ausgabe Start Fehler! Fehler! Fehler! Fehler! Ende Nr.: 1 Typ unbekannt Text: Der Wert ist null Nr.: 3 9.15 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Ausnahmen als Klassen – Für Ausnahmebehandlung von komplexen Fehlern sollten Fehlerklassen implementiert werden – Die entsprechenden Objekte enthalten dann Informationen über die Umgebung (Objektzustände, Variablenwerte, etc.) des Fehlerzustands enum error { grenze, speicher, andere }; class vekt_error { private: error e_typ; int og, index, groesse; public: vekt_error(error, int, int); // Grenzen vekt_error(error, int); // out of memory // ... }; // ... throw vekt_error(gr, i, og); // ... Universität Paderborn, ADT 9.16 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Matching von throw und catch – Ein mit throw gegebener Ausdruck paßt auf einen Parameter von catch, falls • die Signatur genau übereinstimmt • der catch-Parameter eine öffentliche Basisklasse des throw-Ausdrucks ist • der throw-Parameter ein Zeiger ist, der konvertierbar zu einem Zeigertyp des catch-Parameters ist – Vorsicht bei der Reihenfolge der catch-Anweisungsblöcke catch(void*) // auch char* passt catch(char*) // wird nie benutzt catch(Basistyp&) // paßt immer auf Abgeleitet catch(Abgeleitet&) // wird nie benutzt Universität Paderborn, ADT 9.17 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Stack – Ausnahmen eines Stack: Bereichsüberschreitungen • Pop auf leeren Stack • Push auf vollen Stack – Klasse stack als Basisklasse für eine Klasse b_stack, die mit Ausnahmebehandlung Bereichsüberschreitungen abfängt – Implementierungsprinzip: • Standardmäßige Klasse stack (ohne Ausnahmebehandlung) • Abgeleitete Klasse, die die relevanten Member-Funktionen (push() und pop()) um Exception-handling erweitert • Fehlerklasse bound_err • Benutzung von Ausnahmeeinschränkung Universität Paderborn, ADT 9.18 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Stack Objektstruktur stack bound_err protected: // daten public: // Funkts public: // konstr abgeleitet public benutzt benutzt bstack main() public: // push // pop // Ausn mit // bound_err try {//...} catch {//...} Universität Paderborn, ADT 9.19 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Stack err.hpp #include <string> class bound_err { public: std::string text; //Was war der Fehler bound_err(std::string err) { text = err; } }; Universität Paderborn, ADT 9.20 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Stack stack.hpp bstack.hpp – Klasse stack ohne Ausnahmebehandlung #include "err.hpp" class stack { protected: enum { EMPTY = -1}; int *s, max_len, top; public: stack(); stack(int size); ~stack(); void reset(); void push(int c); int pop(); int top_of(); bool empty(); bool full(); }; Universität Paderborn, ADT – Klasse stack b_stack abgeleitet von – Ausnahmerelevante MemberFunktionen push(), pop() – Einschränkung der Ausnahmen auf Fehlerklasse bound_err #include "stack.hpp" class b_stack: public stack { public: void push(int c); int pop(); }; 9.21 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Stack bstack.cpp #include "bstack.hpp" void b_stack::push(int c) { if (full()) { bound_err overflow("Push nicht moeglich, Stack ist voll"); throw overflow; } stack::push(c); } int b_stack::pop() { if (empty()) { throw bound_err("Pop nicht moeglich, Stack ist leer"); } return (stack::pop()); } Universität Paderborn, ADT 9.22 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Stack stack.cpp #include "stack.hpp" stack::stack() : max_len(1000) { s=new int[1000]; top=EMPTY; } stack::stack(int size) : max_len(size) { s=new int[size]; top=EMPTY; } stack::~stack() { delete []s; } void stack::reset() { top=EMPTY; } Universität Paderborn, ADT void stack::push(int c) { s[++top]=c; } int stack::pop() { return (s[top--]); } int stack::top_of() { return (s[top]); } bool stack::empty() { return bool(top==EMPTY); } bool stack::full() { return bool(top==max_len-1); } 9.23 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Stack main.cpp int main() { b_stack *test_stack = new b_stack; try { push_viel(test_stack); } catch (bound_err err) { cerr << "Fehler: "; cerr << "Stack-Grenzen ueberschritten\n"; cerr << "Grund: " << err.text << ’\n’; exit(1); } catch (...) { cerr << "Fehler: "; cerr << "unerwartete Ausnahmesituation\n"; exit(1); } return 0; #include <iostream> #include "bstack.hpp" using namespace std; void push_viel(b_stack *stack ) { for(int i=0; i<5000; i++) { stack->push(i); cout << stack->top_of() << ’\n’; } } } Universität Paderborn, ADT 9.24 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Fehlertoleranz – Exception-handler eignen sich gut zum implementieren fehlertoleranter Programme – Fehlertoleranz bedeutet, daß ein Programm auch bei Laufzeitfehlern weiterhin funktionstüchtig bleibt – Eine wichtige Anwendung: Korrektur von illegalen Werten – Vorteil gegenüber direkter Korrektur im entsprechenden Programm-Code: Klare Unterscheidung zwischen einem Fehler und dessen Behandlung Universität Paderborn, ADT 9.25 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel Fehlertoleranz – Exception-handler ersetzt einen il- void g(int m) legalen Wert durch einen stan- { try { dardmäßigen legalen Wert vektor a(m); // ... – Dieses Vorgehen ist besonders sinnvoll bei der Systementwicklung und beim Integrationstest vektor::vektor(int n) { if (n < 1) throw (n); p = new int[n]; if (p == 0) throw ("Kein Speicher mehr"); } Universität Paderborn, ADT } catch(int n) { cerr << "Groessenfehler " << n << endl; g(10); // Versuch mit legalem Wert } catch(const char *error) { cerr << error << endl; abort(); } } 9.26 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Fehlertoleranz und Klassenkonstruktoren – Allgemeines Schema zur Implementierung fehlertoleranter Klassen – Der Objekt-Konstruktor enthält Ausnahmen für illegale Zustände – Es gibt eine hierarchische Struktur von Fehlerklassen – Der try-Block benutzt die Information zur Reparatur oder zum Abbbruch des nicht-korrekten Programm-Codes Universität Paderborn, ADT Objekt::Objekt(parameter ) { if (illegalparameter1 ) throw ausdruck1 ; if (illegalparameter2 ) throw ausdruck2 ; ... // Konstruktion ... } try { // Fehlertoleranter Programm-Code } catch(deklaration1 ) {/* Reparatur 1 */} catch(deklaration2 ) {/* Reparatur 2 */} // . . . // Werte sind jetzt legal 9.27 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Fehlerklassen class objektfehler { public: objektfehler(parameter ); // Members mit Daten des Fehlers virtual void reparatur() { cerr << "Reparatur nicht moeglich"; endl; abort(); } }; class objektfehler1 : public objektfehler { public: objektfehler1(parameter ); // Weitere Members mit Daten des Fehlers void reparatur(); // Passende Reparatur }; // Andere abgeleitete Fehlerklassen Universität Paderborn, ADT 9.28 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Philosophie von Error-recovery – Eine Methode, Fehlertoleranz zu erreichen ist Error-recovery – Error-recovery bedeutet die Reparatur fehlerhafter Zustände im Programmlauf – Erreichbar ist dies durch Eingriff in den Programmsteuerfluß (Ausnahmebehandlung) – Zuviel Eingriff in den Steuerfluß führt zu Chaos – Meist ist Reparatur nur bei Echtzeit-Programmen sinnvoll – In den meisten Fällen ist es sinnvoll, bei Fehlern eine Diagnose auszugeben und das Programm sicher zu beenden Universität Paderborn, ADT 9.29 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Zusicherungen (a) – Programmkorrektheit bedeutet, daß auf korrekte Eingabe eine korrekte Ausgabe erfolgt – Der aufrufende Teil hat die Verantwortung für eine korrekte Eingabe – Der aufgerufene Teil hat die Verantwortung für eine korrekte Ausgabe – Programming-by-contract: Regelung der Verantwortlichkeiten, typisch für objektorientierte Programmierung – In Memberfunktionen von Klassen sollte die Erfüllung der Pflichten geprüft werden Universität Paderborn, ADT 9.30 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Zusicherungen (b) – Die Pflichten aus den Verantwortlichkeiten werden durch Zusicherungen gegeben – Eine Zusicherung (Assertion) ist • Vorbedingung (Precondition), d. h. korrekte Eingabe • Nachbedingung (Postcondition), d. h. korrekte Ausgabe • Invariante (Invariant), d. h. das, was sich nicht ändern darf – C++ bietet eine Möglichkeit, Zusicherungen zu implementieren und zu prüfen Universität Paderborn, ADT 9.31 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Zusicherungen (c) – Die Standard-Bibliothek cassert enthält ein Makro void assert(int expression); – Wirkungsweise: • Wird expression zu falsch ausgewertet, so wird die Programmausführung mit einer Diagnose-Ausgabe abgebrochen • Wird expression zu wahr ausgewertet, so wird mit der Programmausführung fortgefahren Universität Paderborn, ADT – Direkt zu implementieren sind in C++ nur Vorbedingungen und Nachbedingungen – vekt::vekt(int n) { assert(n > 0); //Vorbedingung groesse = n; p = new int[groesse]; assert (p != 0); //Nachbedingung } – Invarianten sollten gesondert implementiert werden 9.32 Verlässliches Programmieren in C/C++ 9. Ausnahmebehandlung Beispiel: Implementierung einer String-Klasse #include <cstring> #include <cassert> class String{ char *Buffer; unsigned int Buflen; void Invariant(); public: String(char *str); ~String(); String &operator=(char *str); }; inline void String::Invariant() { assert(strlen(Buffer) == Buflen); }; String::String(char *str) { assert(str != NULL); Buflen = strlen(str) + 1; Buffer = new char[Buflen]; assert(Buffer != NULL); strcpy(Buffer, str); Invariant(); } Universität Paderborn, ADT String::~String() { Invariant(); delete [] Buffer; } String &String::operator=(char *str) { Invariant(); assert(str != NULL); if (strlen(str) >= Buflen) { delete [] Buffer; Buflen = strlen(str) + 1; Buffer = new char[Buflen]; assert(Buffer != NULL); } strcpy(Buffer, str); return(*this); } 9.33 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung 10. Kommandozeilen und Dateiverarbeitung Universität Paderborn, ADT 10.1 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Parameter von main() (a) – Beim Programmaufruf können auf der Kommandozeile Argumente übergeben werden – Argumente der Kommandozeile werden als Parameter an die Funktion main() übergeben – Es stehen für main() zwei Parameter zur Verfügung – Die Parameter enthalten 1. Anzahl der Kommandozeilen-Argumente (int argc) 2. Argumente als Feld von Zeichenketten (char *argv[], bzw. char **argv) Universität Paderborn, ADT 10.2 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Parameter von main() (b) – int main(int argc, char *argv[]) { // ... } – Kommandozeile (Programmname args) args dies ist ein test – Parameter von main() – Die Parameter müssen nicht argc und argv heißen, Konvention: argc = argument count, argv = argument value – Es werden alle Zeichenketten der Kommandozeile gezählt (argc) Universität Paderborn, ADT argc = 5 argv[0] = argv[1] = argv[2] = argv[3] = argv[4] = "args" "dies" "ist" "ein" "test" 10.3 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung UNIX-Konvention – Schema von Kommandozeilen unter UNIX (gilt auch für viele andere Bereiche) kommando [option]* [dateinamen]* – option beginnt mit einem Strich (-) gewöhnlich gefolgt von einem Zeichen, z. B. -v – option kann zu ihrem Namen ein oder mehrere Argumente besitzen, z. B. -oausgabedatei – Beispiel Kommandozeilenformat (Programmdokumentation) print_file [-v] [-l<length>] [-o<name>] [file1] [file2] ... Universität Paderborn, ADT 10.4 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Kommandozeilenbearbeitung (a) – Schleifenkonstruktion für Optionen while ((argc > 1) && argv[1][0] == ’-’)) { // Optionsbearbeitung ++argv; // naechstes Argument --argc; // ein Argument abgearbeitet } // Argument ist Option – Schleifenkonstruktion für Dateinamen if (argc == 1) { // keine Datei angegeben do_file("file.in"); // Standard-Datei } else { while (argc > 1) { do_file(argv[1]); ++argv; // naechste Datei --argc; // eine Datei bearbeitet } } Universität Paderborn, ADT 10.5 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Kommandozeilenbearbeitung (b) – Optionsbearbeitung switch (argv[1][1]) { case ’v’: verbose = true; break; case ’o’: out_file = &argv[1][2]; break; case ’l’: line_max = atoi(&argv[1][2]); break; default: cerr << "Bad option " << argv[1] << endl; usage(); // Drucke Kommandozeilenformat } – -o<name>: argv[1][0] == ’-’, argv[1][1] == ’o’, argv[1][2] == Beginn von <name> – -l<length>: Bibliotheksfunktion atoi() konvertiert eine Zeichenkette in eine ganze Zahl (ascii to int); erfordert #include <cstdlib> Universität Paderborn, ADT 10.6 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Beispiel (a) #include <iostream> #include <cstdlib> bool verbose = false; // verbose mode (default = false) char *out_file = "print.out"; // output file name char *program_name; // name of the program (for errors) int line_max = 66; // number of lines per page void do_file(char *name) // dummy routine to handle file { cout << "Verbose " << verbose << " Lines " << line_max << " Input " << name << " Output " << out_file << endl; } void usage(void) { cerr << "Usage is " << program_name << " [options] [file-list]\n"; cerr << "Options\n"; cerr << " -v verbose\n"; cerr << " -l<number> Number of lines\n"; cerr << " -o<name> Set output file name\n"; exit (8); } Universität Paderborn, ADT 10.7 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Beispiel (b) if (argc == 1) { int main(int argc, char *argv[]) do_file("print.in"); { } else { program_name = argv[0]; while (argc > 1) { while ((argc > 1) && (argv[1][0] == ’-’)) { do_file(argv[1]); switch (argv[1][1]) { ++argv; case ’v’: --argc; verbose = true; } break; } case ’o’: return 0; out_file = &argv[1][2]; } break; case ’l’: Programmlauf line_max = atoi(&argv[1][2]); print -v -oausgabe -l80 datei1 datei2 break; Verbose 1 Lines 80 Input datei1 Output default: ausgabe cerr << "Bad option " << argv[1]; Verbose 1 Lines 80 Input datei2 Output cerr << endl; ausgabe usage(); } print -oausgabe ++argv; Verbose 0 Lines 66 Input print.in Output --argc; ausgabe } Universität Paderborn, ADT 10.8 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Ein- und Ausgaben – C++ betrachtet Dateien (auch Tastatur und Bildschirm) als Strom von Zeichen (Streams of bytes) – In C++ gibt es drei Klassen zur Ein-Ausgabe-Bearbeitung (<iostream>) • istream für Eingabe • ostream für Ausgabe • iostream für Ein-Ausgabe – Bei Programmstart werden automatisch Variablen angelegt Variable cin cout cerr clog Benutzung Standard Eingabe Standard Ausgabe Standard Fehler Konsole Log Universität Paderborn, ADT 10.9 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Dateien (a) – Für Dateien gibt es in C++ abgeleitete Klassen von istream, ostream und iostream (#include <fstream>) • ifstream für Eingabedateien • ofstream für Ausgabedateien • fstream für Ein-Ausgabedateien – Benutzung ifstream eingabedatei; // Deklaration einer Eingabedatei ofstream ausgabedatei; // Deklaration einer Ausgabedatei eingabedatei.open("zahlen.dat"); // Oeffnen zum Lesen ausgabedatei.open("ergebnis"); // Oeffnen zum Schreiben eingabedatei.close(); // Schliessen ausgabedatei.close(); // Schliessen Universität Paderborn, ADT 10.10 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Dateien (b) – Wie bei anderen Klassen, wird bei Deklaration von Ein/-Ausgabedateien ein Konstruktor der betreffenden Klasse aufgerufen – Für ifstream gibt es neben ifstream::ifstream() den Konstruktor ifstream::ifstream(const char *name); (ebenso für ofstream) – Deklaration und öffnen einer Datei ifstream eingabedatei("zahlen.dat"); – Benutzung wie cin oder cout int var; // ... eingabedatei >> var; ausgabedatei << var; Universität Paderborn, ADT 10.11 Verlässliches Programmieren in C/C++ 10. Kommandozeilen und Dateiverarbeitung Beispiel Eingabedatei /* Lese 100 Zahlen aus der * Datei "numbers.dat" * Warnung: Es gibt keine Ueberpruefung, * ob die Datei auch 100 Zahlen enthaelt! */ for (i = 0; i < DATA_SIZE; ++i) data_file >> data_array[i]; #include <iostream> #include <fstream> using namespace std; int main() { // Number of items in the data const int DATA_SIZE = 100; // The data int data_array[DATA_SIZE]; // The input file ifstream data_file("numbers.dat"); int i; // Loop counter int total; // Total of the numbers total = 0; for (i = 0; i < DATA_SIZE; ++i) total += data_array[i]; cout << "Total of all the numbers is "; cout << total << endl; return 0; } if (!data_file.is_open()) { // Problem beim Oeffnen oder Lesen cerr << "Error: Could not open numbers.dat" << endl; exit (8); } Universität Paderborn, ADT 10.12 Verlässliches Programmieren in C/C++ 11. Zusammenfassung 11. Zusammenfassung Universität Paderborn, ADT 11.1 Verlässliches Programmieren in C/C++ 11. Zusammenfassung – Objektorientierte Programmierung mit C++ – Realisierung großer Projekte (mehr als 10.000 Programmzeilen) – Optimierung unter Kriterien • Korrektheit • Robustheit • Erweiterbarkeit • Wiederverwendbarkeit Universität Paderborn, ADT 11.2 Verlässliches Programmieren in C/C++ 11. Zusammenfassung – Konzepte von C++ – Erweiterung von ANSI-C – Polymorphie (Funktionen, Operatoren, Klassen, Templates) – Vereinfachte Parameter-Übergabeformen an Funktionen (gegenüber C) – Klassenkonzept als erweiterte abstrakte Datentypen (Konstruktoren, Destruktoren, Standard-Operatoren) – Vererbung (Zugriffsrechte, virtuelle Funktionen) – Ausnahmebehandlung (throw, assert, etc.) Universität Paderborn, ADT 11.3