OOProg L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 Inhaltsverzeichnis 1 Ein- und Ausgabe 1.1 Streamkonzept . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Eingabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4 Formatierte Ein- und Ausgabe: Manipulatoren ohne Parameter 1.5 Formatierte Ein- und Ausgabe: Manipulatoren mit Parameter . 1.6 Streams und Dateien C++ Kap. A.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 4 4 4 5 5 2 Lexikalische Elemente 2.1 Sprachbeschreibung mit Grammatik C++ Kap. 3.1 2.2 Bezeichner/Namen C++ Kap. 3.2 . . . . . . . . . . 2.3 Schlüsselwörter C++ Kap. 3.3 . . . . . . . . . . . . 2.4 Literale C++ Kap. 3.4 . . . . . . . . . . . . . . . . 2.5 Operatoren und Begrenzer C++ Kap. 3.5 . . . . . . 2.6 Kommentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 6 6 7 7 7 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 7 8 8 8 8 8 9 9 4 Ausdrücke und Operatoren 4.1 Auswertungsreihenfolge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 L-Werte und R-Werte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Operatoren im einzelnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 9 10 11 5 Anweisungen 5.1 Ausdrucksanweisungen . . . . . 5.2 Sprunganweisungen C Kap. 8.4 5.3 Sequenz C Kap. 8.1 . . . . . . . 5.4 Blockanweisungen . . . . . . . . 5.5 Selektionsanweisung C Kap. 8.2 5.6 Iteration C Kap. 8.3 . . . . . . . . . . . . . 11 11 11 12 12 12 13 . . . . . 13 13 13 13 14 14 . . . . . . 3 Einfache Deklarationen und Basisdatentypen C++ 3.1 Definition und Deklaration C++ Kap. 4.1 . . . . . . 3.2 Variablendeklaration . . . . . . . . . . . . . . . . . . 3.3 Variableninitialisierung . . . . . . . . . . . . . . . . . 3.4 Die One Definition Rule C++ Kap. 4.2 . . . . . . . . 3.5 Basisdatentypen C++ Kap. 4.3 . . . . . . . . . . . . 3.6 Übersicht über alle Standard-Datentypen C Kap. 5.2 3.7 Deklaration von Konstanten C++ Kap. 4.5 . . . . . . 3.8 Enumerations (Aufzählungstyp) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kap. 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Funktionen 6.1 Aufgaben einer Funktion . . . . . . . . . . . . . . . . . . . . . 6.2 Definition von Funktionen C Kap. 9.3.1 , C++ Kap. 7.2 . . . . 6.3 Eingaben/Ausgaben einer Funktion C Kap. 9.3 , C++ Kap. 7.3 6.4 Deklaration von Funktionen C Kap. 9.4 , C++ Kap. 7.2 . . . . 6.5 Überladene Funktionen C++ Kap. 7.7.1 . . . . . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OOProg - Zusammenfassung 6.6 6.7 Seite 2 von 55 (Revision : 5. August 2014) Vorbelegte Parameter C++ Kap. 7.6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . inline-Funktionen vs. C-Makros C++ Kap. 7.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 15 7 Höhere Datentypen und strukturierte Datentypen C++ Kap. 8 7.1 Pointer C Kap. 6 , C++ Kap. 8.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 Vektoren C Kap. 10.7.1 , C++ Kap. 8.3 . . . . . . . . . . . . . . . . . . . . . . . . 7.3 Zeichenketten C Kap. 10 , C++ Kap. 8.4 . . . . . . . . . . . . . . . . . . . . . . . . 7.4 Referenzen C++ Kap. 8.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5 Pointer und Referenzen als Rückgabewert und Parameterübergabe C++ Kap. 8.6 7.6 Zugriff auf Class und Struct Elemente C++ Kap. 8.7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 15 18 18 20 20 20 8 Gültigkeitsbereiche, Namensräume und Sichtbarkeit 8.1 Sichtbarkeit C++ Kap. 9.1.1 . . . . . . . . . . . . . . . 8.2 Namensräume C++ Kap. 9.1.2 . . . . . . . . . . . . . . 8.3 Deklarationen C++ Kap. 9.2 . . . . . . . . . . . . . . . 8.4 Initialisierung von Objekten C++ Kap. 9.3 . . . . . . . 8.5 Type-cast C++ Kap. 9.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 20 21 21 21 22 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 22 22 22 23 23 23 23 23 23 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 24 24 24 25 26 26 26 28 28 29 10.11Überladen von Operatoren C++ Kap. 11.7.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.12Strukturen und Unionen C Kap. 11 , C++ Kap. 11.8.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 30 11 Templates 11.1 Motivation C++ Kap. 12.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Funktions-Templates C++ Kap. 12.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.3 Klassen-Templates C++ Kap. 12.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 32 32 33 11.4 Klassen-Templates und getrennte Übersetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 9 Module und Datenkapseln C++ Kap. 10 9.1 Motivation C++ Kap. 10.1 . . . . . . . . . . . . . 9.2 Nomenklatur Modul vs. Unit . . . . . . . . . . . 9.3 Ziele der Modularisierung . . . . . . . . . . . . . 9.4 Vom Modul zur Datenkapsel C++ Kap. 10.2 . . . 9.5 Unitkonzept / Module und Datenkapseln in C++ 9.6 Die Schnittstellen-/Headerdatei C++ Kap. 10.3.1 9.7 Beispiel Unit Rechteck . . . . . . . . . . . . . . . 9.8 Die Implementierungsdatei C++ Kap. 10.3.2 . . . 9.9 Buildprozess / Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . C++ Kap. 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C++ Kap. 10.3 . . . . . . . . . . . . . . . . 10 Klassenkonzept 10.1 Begriff der Klasse . . . . . . . . . . . . . . . . . . . . . . 10.2 UML-Notation einer Klasse . . . . . . . . . . . . . . . . 10.3 Üblicher Aufbau einer Klassensyntax C++ Kap. 11.1.1 . 10.4 Elementfunktionen C++ Kap. 11.2 . . . . . . . . . . . . 10.5 static - Klassenelemente C++ Kap. 11.5 . . . . . . . . . 10.6 this - Pointer C++ Kap. 11.3 . . . . . . . . . . . . . . . 10.7 Konstruktor (am Beispiel der Klasse TString) C++ Kap. 10.8 Destruktor C++ Kap. 11.7.2 . . . . . . . . . . . . . . . . 10.9 Kanonische Form von Klassen C++ Kap. 11.7.5 . . . . . 10.10Benutzerdefinierte Typumwandlung C++ Kap. 11.7.6 . . 11.7.1 . . . . . . . . . . . . 12 Vererbung (Inheritance) 12.1 Einsatz der Vererbung C++ Kap. 13.1 . . . . . . . . . . . . 12.2 Ableiten einer Klasse C++ Kap. 13.2 . . . . . . . . . . . . . 12.3 Zugriff auf Elemente der Basisklasse C++ Kap. 13.6 . . . . . 12.4 Slicing Problem C++ Kap. 13.3 . . . . . . . . . . . . . . . . 12.5 Vererbung und Gültigkeitsbereiche C++ Kap. 13.4 . . . . . 12.6 Elementfunktionen bei abgeleiteten Klassen C++ Kap. 13.5 L. Leuenberger, M. Ehrler, C. Ham, L. Däscher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 34 34 34 34 35 35 5. August 2014 OOProg - Zusammenfassung Seite 3 von 55 (Revision : 5. August 2014) 13 Polymorphismus / Mehrfachvererbung / RTTI 13.1 Polymorphismus C++ Kap. 14.1 . . . . . . . . . . 13.2 Virtuelle Elementfunktionen C++ Kap. 14.2 . . . 13.3 Abstrakte Klassen C++ Kap. 14.3 . . . . . . . . . 13.4 Mehrfachvererbung C++ Kap. 14.3 . . . . . . . . 13.5 RTTI (Laufzeit-Typinformation) C++ Kap. 14.3 . . . . . . 36 36 36 37 38 39 14 Exception Handling C++ Kap. 15 14.1 Exception vs. Error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.2 Handling Strategie von System Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.3 Exceptionhandling in C + + C++ Kap. 15.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 39 39 40 15 Beispiele 15.1 Stack als Klasse . . . . . . . . . . . 15.2 Stack als Template . . . . . . . . . 15.3 Vererbung Comiccharacter . . . . . 15.4 Mehrfachvererbung Comiccharacter 15.5 RTTI . . . . . . . . . . . . . . . . 42 42 44 46 49 54 L. Leuenberger, M. Ehrler, C. Ham, L. Däscher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. August 2014 OOProg - Zusammenfassung 1 Seite 4 von 55 (Revision : 5. August 2014) Ein- und Ausgabe 1.1 Um die C++ Ein- und Ausgaben nutzen zu können, muss man die Bibliothek iostream einbinden. Das geschieht mit: #include <i o s t r e a m > Danach müssen die Befehle daraus bekannt gegeben werden, da sie sich in einem speziellen Namespace befinden. Um nun die Ein- und Ausgabebefehle nutzen zu können, muss man dem Compiler sagen: Benutze den Namenspace std. Man könnte vor jeden Ein- Ausgabebefehl std:: schreiben. Da dies aber mühsam ist, kann man mit folgender Zeile dieses Problem umgehen: using namespace s t d ; 1.2 Ausgabe Die Klasse ostream stellt Methoden zur Ausgabe aller vordefinierten Datentypen (char, bool, int, etc) zur Verfügung. Alle Ausgabemethoden sind überladene Versionen des Operators <<. Die verschiedenen Versionen unterscheiden sich dabei in ihren Parametern und haben etwa folgende Schnittstelle: ostream& operator << ( int n ) ; ostream& operator << ( double d ) ; ostream& operator << ( char c ) ; Nutzung mit cout (vordefiniertes Objekt der Klasse ostream): int i = 4 5 ; c o u t << " H a l l o ␣ " << i << e n d l ; endl bewirkt bei einer Bildschirmausgabe einen Sprung zum Anfang der nächsten Zeile und das Leeren des Puffers auf das Ausgabemedium. Es bewirkt eigentlich das gleiche wie die Zeile: c o u t << ’ \n ’ << f l u s h ; flush bewirkt das Leeren des Puffers auf das Ausgabemedium. 1.4 1.2.1 Streamkonzept • Ein Stream repräsentiert einen sequentiellen Datenstrom. • Die Operatoren auf dem Stream sind << und >>. Für vordefinierte Datentypen sind diese Operatoren schon definiert, für eigene selbstdefinierte Klassen können diese Operatoren überladen werden). • C++ stellt 4 Standardstreams zur Verfügung: – cin: Standard-Eingabesteam, normalerweise die Tastatur – cout: Standard-Ausgabestream, normalerweise der Bildschirm – cerr: Standard-Fehlerausgabestream, normalerweise der Bildschirm – clog: mit cerr gekoppelt • Alle diese Streams können auch mit einer Datei verbunden werden. Ausgabebeispiele c o u t << " D i e s e r ␣ Text ␣ s t e h t ␣nun␣ i n ␣ d e r ␣ Kommandozeile ! " ; c o u t << " D i e s e r ␣ Text ␣ s c h l i e s s t ␣ s i c h ␣ d i r e k t ␣an . . . " ; int z a h l 1 = 5 ; int z a h l 2 = 3 ; c o u t << z a h l 1 + z a h l 2 << " , ␣ " << z a h l 1 ∗ z a h l 2 << e n d l ; Ausgabe : 8 , 15 1.3 Eingabe Die Eingabe ist ähnlich organisiert wie die Ausgabe. Die Klasse istream ist die Abstraktion eines Eingabestreams und stellt unter anderem folgende Möglichkeiten zur Verfügung: i s t r e a m& operator >> ( int n ) ; i s t r e a m& operator >> ( double d ) ; i s t r e a m& operator >> ( char c ) ; Nutzung mit cin (vordefiniertes Objekt der Klasse istream): double d ; string str ; c i n >> d >> s t r ; 1.3.1 Eingabebeispiele int z a h l 1 ; int z a h l 2 ; c o u t << " Zwei ␣ ganze ␣ Zahlen : ␣" ; c i n >> z a h l 1 >> z a h l 2 ; Formatierte Ein- und Ausgabe: Manipulatoren ohne Parameter ios, eine Basisklasse von iostream, stellt verschiedene Möglichkeiten (Format Flags) vor, um die Ein- und Ausgabe zu beeinflussen. 1.4.1 boolalpha 1.4.2 bool-Werte werden textuell ausgegeben. bool b = true ; c o u t << b o o l a l p h a << b << e n d l ; c o u t << n o b o o l a l p h a << b << e n d l ; L. Leuenberger, M. Ehrler, C. Ham, L. Däscher showbase Zahlenbasis wird gezeigt. Ausgabe : true 1 Ausgabe : int n = 2 0 ; 0 x14 c o u t << hex << showbase << n << e n d l ; 5. August 2014 OOProg - Zusammenfassung 1.4.3 Seite 5 von 55 (Revision : 5. August 2014) showpoint 1.4.5 Dezimalpunkt wird immer ausgegeben. double a = 3 0 ; c o u t << showpoint << a << e n d l ; Ausgabe : 30.000 showpos Vorzeichen bei positiven Zahlen wird angezeigt. Führende Whitespaces werden nicht angezeigt. int p = int z = int n = c o u t << << 1.4.6 1.4.8 1.4.4 skipws uppercase Alle Kleinbuchstaben in Grossbuchstaben wandeln. c o u t << showbase << hex ; c o u t << u p p e r c a s e << 77 << e n d l ; 1.4.7 Ausgabe : 1; +1 +0 −1 0; −1; showpos << p << ’ \ t ’ << z ’ \ t ’ << n << e n d l ; dec, hex, oct Ausgabe erfolgt in dezimal, hexadezimal oder oktal. Ausgabe : 0X4D unitbuf int n = c o u t << c o u t << c o u t << 70; dec << n << e n d l ; hex << n << e n d l ; o c t << n << e n d l ; Leert Buffer des Outputstreams nach Schreiben. 1.4.9 fixed, scientific 1.4.10 Gleitkommazahlen im Fixpunktformat oder wissenschaftlich. Ausgabe innerhalb Feld bzw. links oder rechtsbündig. double a = 3 . 1 4 1 5 9 2 6 5 3 4 ; double b = 2 0 0 6 . 0 ; double c = 1 . 0 e −10; cout . p r e c i s i o n ( 5 ) ; c o u t << " f i x e d : " << e n d l << f i x e d ; c o u t << a << e n d l << b << e n d l << c << e n d l ; c o u t << e n d l ; c o u t << " s c i e n t i f i c : " << e n d l << s c i e n t i f i c ; c o u t << a << e n d l << b << e n d l << c << e n d l ; 1.5 Ausgabe : fixed : 3.14159 2006.00000 0.00000 scientific : 3 . 1 4 1 5 9 e+00 2 . 0 0 6 0 0 e+03 1 . 0 0 0 0 0 e −10 Ausgabe : 70 46 106 internal, left, right int n = −77; c o u t . width ( 6 ) ; c o u t << i n t e r n a l << n << e n d l ; c o u t . width ( 6 ) ; c o u t << l e f t << n << e n d l ; c o u t . width ( 6 ) ; c o u t << r i g h t << n << e n d l ; Ausgabe : − 77 −77 −77 Formatierte Ein- und Ausgabe: Manipulatoren mit Parameter Hier muss zwingend <iomanip> eingebunden werden! 1.5.1 setw() 1.5.3 Angabe der Feldbreite. Angabe der Genauigkeit einer Zahl. Ausgabe : 77 c o u t << setw ( 8 ) ; c o u t << 77 << e n d l ; 1.5.2 setfill() Kann ein beliebiges Füllzeichen verwendet werden. c o u t << s e t f i l l ( ’ x ’ ) << setw ( 8 ) ; c o u t << 77 << e n d l ; 1.6 Streams und Dateien setprecision() Ausgabe : xxxxxx77 double f = 3 . 1 4 1 5 9 ; c o u t << s e t p r e c i s i o n ( 5 ) << e n d l ; c o u t << s e t p r e c i s i o n ( 9 ) << e n d l ; c o u t << f i x e d ; c o u t << s e t p r e c i s i o n ( 5 ) << e n d l ; c o u t << s e t p r e c i s i o n ( 9 ) << e n d l ; << f << f Ausgabe : 3.1416 3.14159 3.14159 3.141590000 << f << f C++ Kap. A.5 Streams sind Abstraktionen für die zeichenweise Ein-/Ausgabe. Dabei ist es unerheblich, ob die Ausgabe auf den Bildschirm erfolgt oder auf eine Datei. Dateien können also über die normalen Möglichkeiten der Ein-/Ausgabe beschrieben werden. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Seite 6 von 55 (Revision : 5. August 2014) • Dateien müssen geöffnet werden. Das Öffnen einer Datei assoziiert die physikalische Datei mit dem enstprechenden Stream. • Dateien müssen nach ihrer Verwendung geschlossen werden. Jede Datei wird automatisch geschlossen, wenn der assoziierte Stream geschlossen wird. • Die Verarbeitung von Dateien kann im Textmodus oder im Binärmodus erfolgen. – Binärmodus: Streams werden Zeichen für Zeichen eingelesen/geschrieben ohne jegliche Transformation. – Textmodus: Im Textmodus werden einzelne Zeichen umgewandelt. So wird zum Beispiel endl in die plattformübliche Zeilenende-Sequent (0x0A 0x0D) 1.6.1 Öffnen von Dateien C++ Kap. A.5.1 Das Öffnen einer Datei erfolgt beim Anlegen eines Stream-Objektes beziehungsweise über die Funktion open. Dabei werden der Name der Datei und der Öffnungsmodus als Parameter übergeben. // D e f a u l t −Modi i f s t r e a m readfrom ( " Eingabe . t x t " ) ; o f s t r e a m writeTo ( " Ausgabe . t x t " ) ; // Für Eingabe ö f f n e n i f s t r e a m readFrom ( " Eingabe . t x t " , i o s _ b a s e : : i n ) ; // Binäre Eingabe i f s t r e a m readFrom ( " Eingabe . t x t " , i o s _ b a s e : : i n | ios_base : : bin ) ; // Binäre Ausgabe und D a t e i l e e r e n // f a l l s s i e e x i s t i e r t o f s t r e a m writeTo ( " Ausgabe . t x t " , i o s _ b a s e : : out | ios_base : : bin | ios_base : : trunc ) ; Modus in out app Kommentar Datei für Eingabe öffnen Datei für Ausgabe öffnen Schreiboperationen am Dateiende ausführen ate Nach dem Öffnen der Datei sofort an das Dateiende verzweigen trunc Zu öffnende Datei zerstören, falls sie bereits existiert bin[ary] Ein-/Ausgabe wird binär durchgeführt und nicht im Textmodus 1.6.2 Lesen und Setzen von Positionen C++ Kap. A.5.2 Die aktuelle Position innerhalb einer Datei kann beliebig verändert werden. Dazu stehen folgende Typen und Methoden zur Verfügung: • streampos ist der Typ einer Dateiposition. • seekg(offset, direction) zum Beispiel setzt die aktuelle Dateiposition einer Datei. offset gibt die Position vom Dateianfang bzw. -ende aus an, direction legt fest, von wo aus die Position bestimmt wird. – ios::beg: Dateianfang – ios::cur: Aktuelle Position – ios::end: Dateiende • tellg liefert die aktuelle Position in der Datei. • seekp und tellp sind die entsprechenden Versionen für die Put-Varianten. 2 2.1 Lexikalische Elemente Sprachbeschreibung mit Grammatik C++ Kap. 3.1 Die Grammatik einer Programmiersprache besteht (analog der Grammatik von natürlichen Sprachen) aus einer Menge von Regeln, die angibt, wie die einzelnen Sätze (Anweisungen), aus denen sich ein Programm zusammensetzt, aufgebaut sein müssen. Eine Regel besteht aus einem zu definierenden Symbol, gefolgt von einem Doppelpunkt und der Definition des Symbols. Alle Symbole einer Grammatik, die auf der linken Seite einer Regel (vor dem Doppelpunkt) erscheinen, werden als Non-Terminalsymbole bezeichnet. Symbole, die ausschliesslich auf der rechten Seite vorkommen, als Terminalsymbole. 2.2 Bezeichner/Namen C++ Kap. 3.2 Bezeichner bezeichnen in einem C++ Programm: • Variablen • Funktionen • selbst definierte Datentypen • Klassen • Objekte • ... L. Leuenberger, M. Ehrler, C. Ham, L. Däscher Bezeichner können bestehen aus: • Buchstaben a-z, A-Z • Ziffern 0-9 • Underscore _ Das erste Zeichen eines Bezeichners darf keine Ziffer sein! Styleguide Variablen & Funktionen: • mit Kleinbuchstaben beginnen • erster Buchstaben von zusammengesetzten Wörtern ist gross • keine Underscores Beispiele: counter, maxSpeed, getCount(), setMaxSpeed(), init(), 5. August 2014 OOProg - Zusammenfassung 2.3 Schlüsselwörter Seite 7 von 55 (Revision : 5. August 2014) C++ Kap. 3.3 Schlüsselwörter sind reservierte Bezeichner mit einer vorgegebenen Bedeutung und dienen zur Beschreibung von Aktionen und Objekten in C++ Programmen. Sie dürfen daher nicht anderweitig verwendet werden. • asm • do • inline • short • typeid • auto • double • int • signed • typename • bool • dynamic_cast • long • sizeof • union • break • else • mutable • static • unsigned • case • enum • namespace • static_cast • using • catch • explicit • new • struct • virtual • char • extern • operator • switch • void • class • false • private • template • volatile • const • float • protected • this • wchar_t • const_cast • for • public • throw • while • continue • friend • register • true • default • goto • reinterpret_cast • try • delete • if • return • typedef 2.4 Literale C++ Kap. 3.4 Literale sind Zahlen, Wahrheitswerte oder Zeichenketten im Programmtext. So wie alle anderen Symbole eines Programms müssen auch sie nach bestimmten Regeln aufgebaut sein. 2.4.1 Ganze Zahlen 254 (dez), 035 (okt), 0x3f (hex), -34, 14L (long), 14U (unsigned), 14UL (unsigned long), ... 2.4.2 Fliesskommazahlen 254.89, -13.0, 3.45e23 (exp. Schreibweise), 4.65f (float - Konstante), 3.14159L (long double), ... 2.4.3 Zeichen Ein Zeichen-Literal wird in einfache Hochkommas eingeschlossen angegeben. Zeichen-Literale umfassen neben den druckbaren Zeichen auch Steuerzeichen. Um diese (nicht druckbaren) Zeichen darzustellen, wird eine sogenannte Escape-Sequenz verwendet. Sie wird mit dem Zeichen \ eingeleitet und bestimmt ein Zeichen aus dem ASCII mittels einer oktalen oder hexadezimalen Zahl. Um das Zeichen \ selbst darzustellen, wird \\ verwendet. ’A’, ’\’’ (einfaches Hochkomma), ’\\’ (Backslash), ’\b’ (Backspace), ’\n’ (Neue Zeile), ’\t’ (Tabulator), ’\v’ (Vertikaltabulator), ’\xFE’ (Zeichen mit ASCII-Wert 18), ... 2.4.4 Zeichenketten Ein Zeichenketten-Literal ist eine (möglicherweise auch leere) Sequenz von Zeichen, die in doppelten Hochkommas eingeschlossen ist. "Hallo", (leere Zeichenkette), "Ha \x41" (Ha A), ... 2.5 Operatoren und Begrenzer Beispiel "Ritchie" C++ Kap. 3.5 Operatoren und Begrenzer sind einzelne Sonderzeichen bzw. Sequenzen von Sonderzeichen oder reservierten Wörter mit vordefinierter Bedeutung. Operatoren bestimmen Aktionen, die auf Programmobjekte ausgeführt werden können. Begrenzer wiederum trennen Symbole des Programmtexts voneinander. 2.6 Kommentare Kommentare sind Anmerkungen im Programmtext, die für den Leser bestimmt sind. Der Compiler ignoriert sie und entfernt sie vor dem Übersetzen des Programms in Maschinencode aus dem Quelltext. 3 3.1 Einfache Deklarationen und Basisdatentypen Definition und Deklaration // Einzeilige Kommentare /* */ Kommentare über mehrere Zeilen C++ Kap. 4 C++ Kap. 4.1 Die Begriffe Deklaration und Definition werden oft synonym verwendet. Sie bezeichnen aber verschiedene Dinge: Eine Deklaration führt einen oder mehrere Namen in einem Programm ein. Dem Compiler werden zwar mit dem Namen Informationen über einen Typ oder eine Funktion bekanntgegeben, es wird aber kein Programmcode erzeugt oder Speicherplatz für ein Objekt angelegt. Eine Definition wiederum vereinbart konkrete Objekte im Programm, also Variablen (inklusive deren Speicherplatz) oder ausführbaren Code. Jede Definition ist damit zugleich eine Deklaration. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 3.2 Variablendeklaration 3.4 int a , b , c ; Variableninitialisierung Um eine Variable zu initialisieren, gibt es mehrere Möglichkeiten: int var1 = 5 ; f l o a t var2 = 5 . 2 ; char var3 = ’ a ’ ; C++ Kap. 4.2 // a1 . cpp struct A { int a ; } // a2 . cpp struct A { int a ; } int var1 ( 5 ) ; f l o a t var2 ( 5 . 2 ) ; char var3 ( ’ a ’ ) ; int main ( ) { ... } int a = 7 , b = 8 , c = 9 ; float a = 7.1 , b = 8.2 , c = 9 . 3 ; char a = ’H ’ , b = ’ o ’ , c = ’ i ’ ; Hier liegt keine Verletzung vor. A ist in beiden Definitionen identisch und wird daher als eine einzelne Definition betrachtet. Ein Fehler läge dann vor, wenn die beiden Definitionen unterschiedlich wären. int a ( 7 ) , b ( 8 ) , c ( 9 ) ; float a (7.1) , b (8.2) , c ( 9 . 3 ) ; char a ( ’H ’ ) , b ( ’ o ’ ) , c ( ’ i ’ ) ; 3.5 Eine Variable muss nicht sofort mit einem Wert initialisiert werden. Es ist auch möglich, sie zunächst nur zu definieren und ihr später einen Wert zuzuweisen. 3.6 Die One Definition Rule Die One Definition Rule besagt vereinfacht dargestellt, dass ein Name genau einmal in einem Programm definiert sein darf. Es gibt jedoch einen Fall, bei dem diese Regel nicht verletzt wird und man trotzdem zwei Mal denselben Namen verwenden kann: int var1 ; f l o a t var2 ; 3.3 Seite 8 von 55 (Revision : 5. August 2014) Anzahl Bytes 1 1 1 4 (in der Regel) 4 (in der Regel) 2 (in der Regel) 2 (in der Regel) 4 (in der Regel) 4 (in der Regel) 4 (in der Regel) 8 (in der Regel) 4 (in der Regel) C++ Kap. 4.3 Basisdatentypen sind vordefinierte einfache Datentypen. Sie umfassen Wahrheitswerte (bool), Zahlen (int, short int, long int, float, double), Zeichen (char, wchar_t) und den Typ "nichts" (void). Übersicht über alle Standard-Datentypen Datentyp char unsigned char signed char int unsigned int short int unsigned short int long int unsigned long int float double long double Basisdatentypen C Kap. 5.2 Wertebereich (dezimal) −128 bis +127 0 bis +255 −128 bis +127 −20 1470 4830 648 bis +20 1470 4830 647 0 bis +40 2940 9670 295 −320 768 bis +320 767 0 bis +650 535 −20 1470 4830 648 bis +20 1470 4830 647 0 bis +40 2940 9670 295 −3.4 ∗ 1038 bis +3.4 ∗ 1038 −1.7 ∗ 10308 bis +1.7 ∗ 10308 −1.1 ∗ 104932 bis +1.1 ∗ 104932 Typ Ganzzahltyp Ganzzahltyp Ganzzahltyp Ganzzahltyp Ganzzahltyp Ganzzahltyp Ganzzahltyp Ganzzahltyp Ganzzahltyp Gleitpunkttyp Gleitpunkttyp Gleitpunkttyp Verwendung speichern eines Zeichens speichern eines Zeichens speichern eines Zeichens effizienteste Grösse effizienteste Grösse kleine ganzzahlige Werte kleine ganzzahlige Werte grosse ganzzahlige Werte grosse ganzzahlige Werte Gleitpunktzahl höhere Genauigkeit noch höhere Genauigkeit 3.6.1 Datentyp bool C++ Kap. 4.3.1 Der Datentyp für Wahrheitswerte heisst in C++ bool, was eine Abkürzung für boolean ist. Er kann nur zwei Zustände annehmen: true (wahr) oder false (falsch). Obwohl eigentlich 1 Bit ausreichen würde, hat bool mindestens eine Grösse von einem Byte (also 8 Bit), denn 1 Byte ist die kleinste adressierbare Einheit und somit die Minimalgrösse für jeden Datentyp. 3.6.2 Datentyp void C++ Kap. 4.3.5 void ist ein spezieller Typ, der anzeigt, dass kein Wert vorhanden ist. Es ist nicht möglich, ein Objekt vom Typ void anzulegen. Vielmehr findet der Datentyp Anwendung bei der Deklaration von speziellen Zeigern, von denen nicht bekannt ist, auf welchen Typ sie verweisen, oder bei Funktionen, die keinen Rückgabewert liefern. void i n i t D a t a ( ) ; void ∗ p t r ; // Funktion ohne Rueckgabewert // Z e i g e r ohne k o n k r e t e Typangabe L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 3.7 Deklaration Seite 9 von 55 (Revision : 5. August 2014) von Konstanten C++ Kap. 4.5 Eine Konstante wird deklariert, indem vor dem eigentlichen Typ das Schlüsselwort const notiert wird: 3.7.1 Zeichenkonstanten const char e r s t e r B u c h s t a b e = ’A ’ ; const char b a c k s l a s h = ’ \\ ’ ; 3.7.2 Integerkonstanten const int v a l = 1 2 ; Wird versucht, während der Programmausführung der Konstante val einen Wert zuzuweisen, so führt dies zu einem Übersetzungsfehler. Wichtig: Es gäbe noch eine Variante mit #define (vorallem C). Diese Variante sollte in C++ keinesfalls verwendet werden, da nur eine textuelle Ersetzung erfolgt! 3.8 3.7.3 Fliesskommakonstanten const f l o a t p i = 3 . 1 4 1 5 9 2 ; const double s t i l b = 1 0 0 0 0 . ; const double p a r s e c = 3 . 0 8 5 6 7 8 E16 ; Enumerations (Aufzählungstyp) enum S t a t e { i d l e , standby = 3 4 , startingUp , running , blocked = 897 , shuttingDown } ; enum S t a t e s = i d l e ; 3.8.1 const short p e n s i o n s a l t e r = 6 5 ; const int n r O f C y c l e s = 1 6 3 8 4 ; const long l i c h t g e s c h w i n d i g k e i t = 2 9 9 7 9 2 4 5 8 ; // 0 // 34 // 35 // 36 // 897 // 898 • Aufzählungskonstanten haben einen konstanten ganzzahligen Wert. • Die erste Konstante erhält den Wert 0, die zweite 1, etc. • Werte können auch explizit zugewiesen werden Anonyme Enumerations enums können auch verwendet werden, um ganzzahlige symbolische Konstanten zu definieren. Der enum erhält dann keinen Namen, er wird nur dazu verwendet, die einzelnen Konstanten festzulegen. Bessere Alternative zu #define für ganzzahlige Konstanten! 4 enum { l i s t L e n g t h = 4 0 , commandLength = 8 , dateLength = 1 2 8 } ; Ausdrücke und Operatoren Ähnlich wie mathematische Ausdrücke stellen auch Ausdrücke in C++ Berechnungen dar und bestehen aus Operanden und Operatoren. Die Auswertung jedes Ausdrucks liefert einen Wert, der sich aus der Verknüpfung von Operanden durch Operatoren ergibt. • Arithmetische Ausdrücke: Ausdrücke, deren Ergebnis als Skalar geschrieben werden kann (char-, int- oder floatTypen). • Logische Ausdrücke: Ausdrücke, die Wahrheitswerte beschreiben. Sie entstehen durch Vergleiche oder logische Verknüpfungen. • Andere Ausdrücke: Darunter fallen zum Beispiel Typumwandlungen (Cast-Ausdrücke) ebenso wie typeid-Ausdrücke. 4.1 Auswertungsreihenfolge Priorität 1 2 3 Operator :: ++ -() [] . -> ++ -+ ! ˜ (type) * & sizeof new, new[] delete, delete[] L. Leuenberger, M. Ehrler, C. Ham, L. Däscher Beschreibung Bereichsauflösung Suffix-/Postfix-Inkrement und -Dekrement Funktionsaufruf Arrayindizierung Elementselektion einer Referenz Elementselektion eines Zeigers Präfix-Inkrement und -Dekrement unäres plus und unäres Minus logisches NOT und bitweises NOT Typkonvertierung Dereferenzierung Adresse von Typ-/Objektgrösse Reservierung Dynamischen Speichers Freigabe Dynamischen Speichers Assoziativität von links nach rechts von links nach rechts von rechts nach links 5. August 2014 OOProg - Zusammenfassung Priorität 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Seite 10 von 55 (Revision : 5. August 2014) Operator .* ->* * / % + << >> < <= > >= == != & ^ | && || ?: = += -= *= /= %= <<= >>= &= ^= |= throw , Beschreibung Zeiger-auf-Element Multiplikation, Division und Rest Addition und Subtraktion bitweise Rechts- und Linksverschiebung kleiner-als und kleiner-gleich grösser-als und grösser-gleich gleich und ungleich bitweises AND bitweise XOR bitweises OR logisches AND logisches OR bedingte Zuweisung einfache Zuweisung Zuweisung nach Addition/Subtraktion Zuweisung nach Multiplikation, Division, Rest Zuweisung nach Links-, Rechtsverschiebung Zuweisung nach bitweisem AND, XOR und OR Ausnahme werfen Komma (Sequenzoperator) Assoziativität von links nach rechts von links nach rechts von links nach rechts von links nach rechts von links nach rechts von von von von von von links links links links links links nach nach nach nach nach nach rechts rechts rechts rechts rechts rechts von rechts nach links von rechts nach links von links nach rechts (Priorität 1 hat Vorrang vor allen anderen) 4.1.1 Assoziativität Die Assoziativität gibt Auskunft über die Auswertungsreihenfolge der Operanden eines Ausdrucks. So wird zum Beispiel im Ausdruck p++ zuerst p ausgewertet und dann die linke Seite des Operators ++ (p) erhöht, während der Ausdruck ++p zuerst p erhöht und dann den Ausdruck auswertet. int i = 6 ; c o u t << i ++; c o u t << ++i ; 4.1.2 // g i b t i (= 6) aus und e r h o e h t danach i // e r h o e h t i (= 7+1) und g i b t i dann aus Priorität Die Priorität von Operatoren wiederum gibt an, in welcher Reihenfolge die verschiedenen Operanden eines Ausdrucks ausgewertet werden. Die multiplikativen Operatoren weisen zum Beispiel eine höhere Priorität als die additiven Operatoren auf. 4.2 L-Werte und R-Werte • Ausdrücke haben eine unterschiedliche Bedeutung, je nachdem, ob sie links oder rechts vom Zuweisungsoperator stehen. • Ein Ausdruck stellt einen L-Wert (lvalue oder left value) dar, wenn er sich auf ein Speicherobjekt bezieht. Ein solcher Ausdruck kann links (und rechts) des Zuweisungsoperators stehen. • Ein Ausdruck, der sich nicht auf ein Speicherobjekt bezieht, kann nur rechts des Zuweisungsoperators stehen. Er wird als R-Wert (rvalue oder right value) bezeichnet. Einem R-Wert kann nichts zugewiesen werden. a 6 a b 4.2.1 = = ∗ = 5 ∗ a; a; b = c; 3++; // ok // n i c h t z u l a e s s i g , 6 i s t k e i n l v a l u e // n i c h t z u l a e s s i g , ( a∗ b ) i s t k e i n l v a l u e // n i c h t z u l a e s s i g , 3 i s t k e i n l v a l u e Zugriff auf L- und R-Werte • Ein lvalue erfordert immer Schreibzugriff • Auf einen rvalue wird nur lesend zugegriffen • Es gibt auch nicht modifizierbare lvalues. Auf diese kann auch nur lesend zugegriffen werden. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 4.3 4.3.1 Operatoren im einzelnen Bereichsoperator (Scope Operator) :: Der Bereichs-Operator ist erst in C++ verfügbar und liefert dem Compiler den Hinweis, in welchem Namespace er nach einem Symbol suchen soll. Der Namespace steht dabei links der beiden Doppelpunkte :: und der Symbolname steht rechts davon. 4.3.2 Unäre arithmetische Operatoren • • • • • • 4.3.3 • • • • • 4.3.4 Positiver Vorzeichenoperator +A Negativer Vorzeichenoperator -A Postfix-Inkrementoperator A++ Präfix-Inkrementoperator ++A Postfix-Dekrementoperator A-Präfix-Dekrementoperator --A • • • • Binäre arithmetische Operatoren 5 • • • • • • Relationale Operatoren (Vergleichsoperatoren) Gleichheitsoperator A==B Ungleichheitsoperator A!=B Grösseroperator A>B Kleineroperator A<B Grössergleichoperator A>=B Kleinergleichoperator A<=B Logische Operatoren • Logisch UND (AND) A&&B • Logisch ODER (OR) A||B • Logisch NICHT (NOT) !A 0 = false, falsch 1 = true, wahr (genauer: ungleich 0) 4.3.8 Additionsoperator A+B Subtraktionsoperator A-B Multiplikationsoperator A*B Divisionsoperator A/B Modulooperator A%B Schiebe- (Shift-) Operatoren • Rechts-Shift um n Bits A>>n • Links-Shift um n Bits A<<n 4.3.9 Bedingungsoperator (Ternärer Operator) A?B:C ist eine verkürzte Schreibweise für Zuweisungsoperatoren Bit-Operatoren Bitweises Bitweises Bitweises Bitweises 4.3.6 4.3.7 • Zuweisungsoperator A=B • Kombinierte Zuweisungsoperatoren – Alle arithmetischen und logischen Operatoren haben zusammen mit dem Zuweisungsoperator eine verkürzte Form, die das Schreiben verkürzt (mehr nicht) – Beispiel: a=a/b; kann verkürzt geschrieben werden als a/=b; 4.3.5 Seite 11 von 55 (Revision : 5. August 2014) AND A&B OR A|B NOT (Inverter) ~A XOR A^B i f (A) B; else C; Beispiel Maximum von zwei Zahlen a, b ermitteln: int maximum = a > b ? a : b ; entspricht: i f ( a>b ) maximum = a ; else maximum = b ; Anweisungen These der strukturierten Programmierung: 3 Anweisungen reichen aus, um jedes algorithmische Problem zu lösen: • Sequenz: Aufeinanderfolgende Anweisungen • Iteration: Die selbe Anweisung n-mal ausführen • Selektion: Anweisungen in Abhängigkeit einer Bedingung 5.1 5.1.1 Ausdrucksanweisungen Nullanweisung Alleinstehender Strichpunkt while(i < 5); 5.2 Sprunganweisungen 5.1.2 Zuweisung Einem Lvalue mittels = , *= , /=, += oder -= einen Wert zuweisen. a=b=0 // entspricht a=(b=0) 5.1.3 Funktionsaufruf getForFree(a, b); C Kap. 8.4 Sprunganweisungen führen zu schlechtem Programmierstil und sollten nur in bestimmen Fällen eingesetzt werden, wie zum Bsp. break bei switch. • break: do-while-, while-, for-Schleife und switch-Anweisung abbrechen • continue: in den nächsten Schleifendurchgang (Schleifenkopf) springen bei do-while-, while- und for-Schleife • return: aus Funktion an aufrufende Stelle zurückspringen mit Rückgabe des Funktionswertes • goto: innerhalb einer Funktion an eine Marke (Label) springen L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 5.3 Sequenz 5.3.1 C Kap. 8.1 Die Sequenz ist eine zeitlich geordnete Abfolge von Anweisungen. 5.4 Seite 12 von 55 (Revision : 5. August 2014) Block • Erfordert die Syntax genau eine Anweisung, so können dennoch mehrere Anweisungen geschrieben werden, wenn man sie in Form eines Blocks zusammenfasst. • Ein Block wird mit geschweiften Klammern eingefasst. {. . . } Ein Block zählt syntaktisch als eine einzige Anweisung. Blockanweisungen Anweisungen und Ausdrücke innerhalb geschweifter Klammern. Ausgabe: { x=15 y=20 int x=5, y =10; x=5 y=10 { kein Fehler, da der Gültigkeitsbereich nur innerhalb des int x=15 , y =20; Blockes ist. Die ersten Variablen x und y werden im inc o u t << "x=" << x << "y=" << y << " \n" ; neren Block lediglich überdeckt. } c o u t << "x=" << x << "y=" << y << " \n" ; } 5.5 Selektionsanweisung C Kap. 8.2 Von Selektion spricht man zum einen, wenn man eine Anweisung nur dann ausführen will, wenn eine bestimmte Bedingung zutrifft. Zum anderen möchte man mit Selektionsanweisungen zwischen zwei Möglichkeiten (entweder/oder) bzw. zwischen mehreren Möglichkeiten genau eine auswählen. 5.5.1 Einfache Alternative i f ( Ausdruck ) Anweisung wenn wahr ; else Anweisung wenn f a l s c h ; 5.5.2 Bedingte Anweisung i f ( Ausdruck ) Anweisung wenn wahr ; 5.5.3 Mehrfache Alternative - else if i f ( Ausdruck 1 ) Anweisung wenn Ausdruck 1 wahr ; e l s e i f ( Ausdruck 2 ) Anweisung wenn Ausdruck 2 wahr ; else Anweisung wenn a l l e f a l s c h ( o p t i o n a l ) ; Wird innerhalb eines if eine Variable deklariert, gilt sie bis zum Ende des if. 5.5.4 Mehrfache Alternative - switch case • Für eine Mehrfach-Selektion, d.h. eine Selektion unter mehreren Alternativen, kann die switch-Anweisung verwendet werden, falls die Alternativen ganzzahligen Werten eines Ausdrucks von einem Integer-Typ entsprechen. • Hat der Ausdruck der switch-Anweisung den gleichen Wert wie einer der konstanten Ausdrücke der case-Marken, wird die Ausführung des Programms mit der Anweisung hinter dieser case-Marke weitergeführt. • Stimmt keiner der konstanten Ausdrücke mit dem switch-Ausdruck überein, wird zu default gesprungen. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher switch ( Ausdruck ) { case Wert 1 : Anweisung 1 ; break ; case Wert 2 : Anweisung 2 ; break ; default : Anweisung wenn n i c h t s z u t r i f f t ( optional ); } 5. August 2014 OOProg - Zusammenfassung 5.6 Iteration Seite 13 von 55 (Revision : 5. August 2014) C Kap. 8.3 5.6.1 While while ( Ausdruck ) Anweisung ; Schleifenrumpf wird ausgeführt solange Bedingung true ergibt. do..while und for können grundsätzlich aus while gebaut werden. 5.6.2 For-Schleife for ( Ausdruck_init ; Ausdruck ; Ausdruck_update ) Anweisung ; 5.6.3 Do-While do Anweisung ; while ( Ausdruck ) ; Laufvariablen lokal deklarieren: for(int i=0; i<100; ++i) Damit gilt sie nur innerhalb der Schleife. Schleifenrumpf wird mind. einmal ausgeführt und wiederholt falls Bedingung true ergibt. 5.6.4 Endlosschleife for ( ; ; ) while ( 1 ) Anweisung ; Anweisung ; 5.6.5 Wann wird welche Schleife eingesetzt? • For-Schleife: Bei Zählschleifen, d.h. wenn die Anzahl Durchläufe (kann auch variabel sein) im voraus feststeht. • Do-While-Schleife: Wenn es keine Zählschleife ist, und die Schleife muss mindestens einmal durchlaufen werden • While-Schleife: In allen anderen Fällen 6 Funktionen 6.1 Aufgaben einer Funktion • Gleichartige, funktional zusammengehörende Programmteile unter einem eigenen Namen zusammenfassen. Der Programmteil kann mit diesem Namen aufgerufen werden. • Einige Funktionen (im speziellen mathematische) sollen parametrisiert werden können, z.B. die Cosinusfunktion macht nur Sinn, wenn sie mit unterschiedlichen Argumenten aufgerufen werden kann. • Divide et impera (divide and conquer, teile und herrsche): Ein grosses Problem ist einfacher zu lösen, wenn es in mehrere einfachere Teilprobleme aufgeteilt wird. 6.2 Definition von Funktionen C Kap. 9.3.1, C++ Kap. 7.2 • Funktionskopf: legt die Aufrufschnittstelle (Signatur) der Funktion fest. Er besteht aus Rückgabetyp, Funktionsname und Parameterliste. • Funktionsrumpf: Lokale Vereinbarungen und Anweisungen innerhalb eines Blocks 6.3 6.3.1 Eingaben/Ausgaben einer Funktion Eingabedaten Es sind folgende Möglichkeiten vorhanden um Daten an Funktionen zu übergeben: • Mithilfe von Werten, welche an die Parameterliste übergeben werden • Mithilfe von globalen Variablen 6.3.3 C Kap. 9.3, C++ Kap. 7.3 6.3.2 Ausgabedaten Es sind folgende Möglichkeiten vorhanden um Daten zurückzugeben: • Mithilfe des Rückgabewertes einer Funktion (return) • Mithilfe von Änderungen an Variablen, deren Adresse über die Parameterliste an die Funktion übergeben wurde • Mithilfe von Änderungen an globalen Variablen Beispiele Parameterlos und ohne Rückgabewert: Parameter und Rückgabewert: void p r i n t G e s t r i c h e l t e L i n i e ( void ) { p r i n t f ( "−−−−−−−−−−−−−−−−−−−" ) ; } ... printGestrichelteLinie (); // A u f r u f int getSumme ( int a , int b ) { return ( a + b ) ; } ... int summe ; summe = getSumme ( 1 3 5 4 ) ; // A u f r u f L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Seite 14 von 55 (Revision : 5. August 2014) Parameter und ohne Rückgabewert: void printSumme ( int a , int b ) { p r i n t f ( "%d" , a + b ) ; } ... int z a h l = 1 4 ; printSumme ( z a h l , 5 4 ) ; // A u f r u f 6.4 Deklaration von Funktionen C Kap. 9.4, C++ Kap. 7.2 Es ist festgelegt, dass die Konsistenz zwischen Funktionskopf und Funktionsaufrufen vom Compiler überprüft werden soll. Dazu muss beim Aufruf der Funktion die Schnittstelle der Funktion, d.h. der Funktionskopf, bereits bekannt sein. Steht aber die Definition einer Funktion im Programmcode erst nach ihrem Aufruf, so muss eine Vorwärtsdeklaration der Funktion erfolgen, indem vor dem Aufruf die Schnittstelle der Funktion mit dem Funktionsprototypen deklariert wird. Desweitern ist zu beachten, dass Parameternamen im Funktionsprototyp und in der Funktionsdefinition nicht übereinstimmen müssen. Es ist jedoch zu empfehlen. 6.4.1 Beispiel 6.4.2 #include <s t d i o . h> void i n i t ( int b e t a ) ; /∗ F u n k t i o n s p r o t o t y p ∗/ int main ( void ) { ... } void i n i t ( int a l p h a ) /∗ F u n k t i o n s d e f i n i t i o n ∗/ { ... } Was passiert wenn der Prototyp vergessen geht? • Fehlt der Prototyp ganz, so wird die Funktion implizit (automatisch vom System) deklariert. Ihr Rückgabetyp wird als int angenommen, die Parameter werden nicht überprüft. • Wenn die Funktion später definiert wird und nicht int als Rückgabetyp hat, bringt der Compiler eine Fehlermeldung. 6.4.3 Funktionsprototypen in der Praxis C Kap. 9.4 • Funktionsprototypen, welche die Schnittstelle der Unit beschreiben, kommen in das entsprechenden Headerfile. • Jedes C-File, welches diese Schnittstelle nutzt, inkludiert dieses Headerfile und somit die Funktionsprototypen. • Funktionsprototypen von internen Funktionen der Unit werden zuoberst im C-File aufgelistet und kommen nicht ins Headerfile. 6.5 Überladene Funktionen C++ Kap. 7.7.1 • Die Identifikation einer Funktion erfolgt über die Signatur, nicht nur über den Namen. Die Signatur besteht aus dem Namen der Funktion plus der Parameterliste (Reihenfolge, Anzahl, Typ). Der Returntyp wird nicht berücksichtigt. • Der Name der Funktionen ist identisch. • Die Implementation muss für jede überladene Funktion separat erfolgen. • Overloading sollte zurückhaltend eingesetzt werden. Wenn möglich sind Default-Argumente vorzuziehen. 6.5.1 Regeln • Entsprechen Rückgabetyp und Parameterliste der zweiten Deklaration denen der ersten, so wird die zweite als gültige Redeklaration der ersten aufgefasst. • Unterscheiden sich die beiden Deklarationen nur bezüglich ihrer Rückgabetypen, so behandelt der Compiler die zweite Deklaration als fehlerhafte Re-Deklaration der ersten. Der Rückgabetyp von Funktionen kann nicht als Unterscheidungskriterium verwendet werden. • Nur wenn beide Deklarationen sich in Anzahl oder Typ ihrer Parameter unterscheiden, werden sie als zwei verschiedene Deklarationen mit demselben Funktionsnamen betrachtet (überladene Funktionen). 6.5.2 Beispiel Default-Parameter vs. Overloading C++ Kap. 7.7.2 // V a r i a n t e mit O v e r l o a d i n g // 3 u n t e r s c h i e d l i c h e Funktionen b e l e g e n S p e i c h e r und müssen g e w a r t e t werden void p r i n t ( int i ) ; • Auf keinen Fall sind Defaultvoid p r i n t ( int i , int width ) ; Parameter in überladenen Funktionen void p r i n t ( int i , char f i l l c h a r , int width ) ; zu verwenden! // V a r i a n t e mit D e f a u l t −Parameter // Eine e i n z i g e Funktion b e l e g t S p e i c h e r und muss g e w a r t e t werden void p r i n t ( int i , int width =0, char f i l l c h a r =0); L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 6.6 Vorbelegte Parameter C++ Kap. 7.6 • Parametern können im Funktionsprototypen Defaultwerte zugewiesen werden. • Beim Funktionsaufruf können (aber müssen nicht) die Parameter mit Defaultwerten weggelassen werden. • Achtung: Hinter (rechts von) einem Default-Argument darf kein nicht vorbelegter Parameter mehr folgen, d.h. wenn bei einem Parameter ein Default definiert wird, dann müssen bei allen weiteren Parametern dieser Funktion ebenfalls Defaults definiert werden. 6.7 6.7.1 inline-Funktionen vs. C-Makros C-Makros • C-Makros werden definiert mit #def ine. • C-Makros bewirken eine reine Textersetzung ohne jegliche Typenprüfung. • Bei Nebeneffekten (welche zwar vermieden werden sollten) verhalten sich Makros nicht wie beabsichtigt. • C-Makros lösen zwar das Problem mit dem Overhead, sind aber sehr unsicher. #define MAX( a , b ) ( ( a)>(b ) ? ( a ) : ( b ) ) 7 Seite 15 von 55 (Revision : 5. August 2014) void prtDate ( int day =1, int month=3, int y e a r =2009); // e r l a u b t s i n d z .B. d i e f o l g e n d e n A u f r u f e : prtDate ( ) ; // 1−3−2009 prtDate ( 2 3 ) ; // 23−3−2009 prtDate ( 1 5 , 6 ) ; // 15−6−2009 prtDate ( 2 4 , 7 , 2 0 1 2 ) ; // 24−7−2012 // n i c h t e r l a u b t s i n d z .B. d i e s e D e k l a r a t i o n e n : void prtDate2 ( int day =7, int month , int y e a r =2009); void prtDate3 ( int day , int month=3, int y e a r ) ; C++ Kap. 7.5 6.7.2 inline-Funktionen • • • • Lösen das Overhead-Problem. Der Code wird direkt eingefügt, kein Funktionsaufruf findet statt. Eine Typenprüfung wird durchgeführt. Einsetzen wenn der Codeumfang der Funktion sehr klein ist und die Funktion häufig aufgerufen wird (z.B. in Schleifen). • Rekursive Funktionen und Funktionen, auf die mit einem Funktionspointer gezeigt wird, werden nicht inlined. i n l i n e int max( int a , int b ) { return a > b ? a : b ; } Höhere Datentypen und strukturierte Datentypen 7.1 7.1.1 Pointer C++ Kap. 8 C Kap. 6, C++ Kap. 8.2 Arbeisspeicher - Memory Map 7.1.2 Pointer C Kap. 6.1 C Kap. 6.1 • Der gesamte Speicher besteht aus einer Folge von einzelnen Bytes, welche durchnumeriert werden. • Diese eindeutige Nummer einer Speicherzelle wird als Adresse bezeichnet. • Bei einem byteweise adressierbaren Speicher (ist üblich) liegt an jeder Adresse genau 1 Byte. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher • Ein Pointer ist eine Variable, welche die Adresse einer im Speicher befindlichen Variablen oder Funktion aufnehmen kann. • Man sagt, der Pointer zeige (to point) auf diese Speicherzelle. • Pointer in C sind typisiert, sie zeigen auf eine Variable des definierten Typs. • Der Speicherbereich, auf den ein bestimmter Pointer zeigt, wird entsprechend des definierten Pointer-Typs interpretiert. • Der Speicherbedarf einer Pointervariablen ist unabhängig vom Pointer-Typ. Er ist so gross, dass die maximale Adresse Platz findet (z.B. 32 Bit). 5. August 2014 OOProg - Zusammenfassung 7.1.3 Definition Seite 16 von 55 (Revision : 5. August 2014) einer Pointervariablen C Kap. 6.1 , C++ Kap. 8.2.1 Typname∗ pointerName ; int ∗ p t r 1 ; // p t r 1 i s t e i n P o i n t e r a u f i n t double∗ p t r 2 ; // p t r 2 i s t e i n P o i n t e r a u f d o u b l e 7.1.5 Der Adressoperator (Referenzierung) C Kap. 6.1 , C++ Kap. 8.2.2 Ist x eine Variable vom Typ Typname, so liefert der Ausdruck &x einen Pointer auf die Variable x, d.h. er liefert die Adresse der Variablen x. int wert ; // V a r i a b l e w e r t vom Typ i n t wi rd // d e f i n i e r t int ∗ p t r ; // P o i n t e r p t r a u f den Typ i n t wird // d e f i n i e r t // p t r z e i g t a u f e i n e n i c h t d e f i n i e r t e // Adresse p t r = &wert ; // p t r z e i g t nun a u f d i e V a r i a b l e wert , // d . h . p t r e n t h a e l t d i e Adresse d e r // V a r i a b l e n w e r t 7.1.6 7.1.4 Initialisierung mit Nullpointer C Kap. 6.1 NULL ist vordefiniert (in stddef.h) und setzt den Pointer auf einen definierten Nullwert. Besser ist es, statt NULL direkt 0 zu verwenden. int ∗ p t r = 0 ; Der Inhaltsoperator * (Dereferenzierung) C Kap. 6.1 , C++ Kap. 8.2.3 Ist ptr ein Pointer vom Typ Typname, so liefert der Ausdruck *ptr den Inhalt der Speicherzelle, auf welche ptr zeigt. int wert ; // V a r i a b l e w e r t vom Typ i n t wi rd d e f i n i e r t int ∗ p t r ; // P o i n t e r p t r a u f den Typ i n t wird d e f i n i e r t // p t r z e i g t a u f e i n e n i c h t d e f i n i e r t e // Adresse p t r = &wert ; // p t r z e i g t nun a u f d i e V a r i a b l e wert , d . h . // p t r e n t h a e l t d i e Adresse d e r V a r i a b l e n // w e r t ∗ ptr = 23; // i n d i e S p e i c h e r z e l l e , a u f w e l c h e p t r // z e i g t ( h i e r : a u f d i e V a r i a b l e w e r t ) , // w ird 23 g e s c h r i e b e n . A e q u i v a l e n t : // w e r t = 2 3 ; 7.1.7 Pointerarithmetik C Kap. 10.1.1 , C++ Kap. 8.3.2 Addition und Subtraktion: Zuweisung: • Zu einem Pointer darf eine ganze Zahl oder ein ande• Pointer unterschiedlicher Datentypen dürfen einander rer Pointer desselben Typs addiert werden. nicht zugewiesen werden (Schutzmechanismus). • Von einem Pointer kann eine ganze Zahl oder ein an• Einem Pointer eines bestimmten Typs dürfen Pointer derer Pointer desselben Typs subtrahiert werden. dieses Typs oder void-Pointer zugewiesen werden. • Wenn eine ganze Zahl n addiert / subtrahiert wird, • Einem void-Pointer dürfen beliebige Pointer zugeso bewegt sich der Pointer auf das nächste Elewiesen werden (nützlich aber gefährlich). ment des Pointertyps. Die Zahl n wird also nicht als Byte interpretiert, der Pointer bewegt sich um Vergleiche: n*sizeof(Typ) Bytes. • Bei Pointern desselben Typs funktionieren Vergleiche wie ==, !=, <, >, >=, etc. Andere Operationen: • Hintergrund: ein Pointer ist eine Adresse, d.h. die Ver• Andere Operationen sind nicht erlaubt! gleiche passieren mit den Adressen. Daraus ist klar, was die Vergleiche bewirken. 7.1.8 Pointer auf void C++ Kap. 8.2.7 • Wenn bei der Definition des Pointers der Typ der Variablen, auf die der Pointer zeigen soll, noch nicht feststeht, wird ein Pointer auf den Typ void vereinbart. int a ; • Ein Pointer auf void umgeht die Typenprüfung des Compilers. Er kann einem int ∗ p i = &a ; typisierten Pointer zugewiesen werden aber er kann keine Zuweisung von einem void ∗ pv = p i ; typisierten Pointer erhalten (in C erlaubt). // ok • Abgesehen von einem Pointer auf void, darf ohne explizite Typenkonvertierung double∗ pd = pv ; kein Pointer auf einen Datentyp an einem Pointer mit einem anderen Datentyp // Error ( i n C e r l a u b t ) zugewiesen werden. pd = s t a t i c _ c a s t <double∗>pv ; • Jeder Pointer kann durch Zuweisung in den Typ void* und zurück umgewan// ok delt werden, ohne dass Informationen verloren gehen. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Seite 17 von 55 (Revision : 5. August 2014) 7.1.9 Pointer auf Funktionen C Kap. 10.8 , C++ Kap. 8.2.7 • Jede Funktion befindet sich an einer definierten Adresse im Codespeicher. • Diese Adresse kann ebenfalls ermittelt werden. • Interessant wäre, dynamisch zur Laufzeit in Abhängigkeit des Programmablaufs eine unterschiedliche Funktion über einen Funktionspointer aufzurufen (z.B. um unterschiedliche Integrale zu berechnen). Vereinbarung eines Pointers #include <s t d i o . h> int f o o ( char ch ) int ( ∗ p ) ( char ) ; { ptr ist hier ein pointer auf eine Funktion mit int i ; Rückgabewert vom Typ int und einem Übergabefor ( i = 1 ; i <= 1 0 ; i ++) parameter vom Typ char. Die Klammern müssen p r i n t f ( "%c ␣ " , ch ) ; unbedingt gesetzt werden. return i ; } Zuweisung einer Funktion int main ( void ) { p = funktionsname ; int ( ∗ p ) ( char ) ; \\ o d e r // D e k l a r a t i o n d e s F u n k t i o n s p o i n t e r s p = &f u n k t i o n s n a m e ; int r e t ; p = foo ; Aufruf einer Funktion // e r m i t t l e Adresse d e r Funktion f o o ( ) a = ( ∗ p ) ( Uebergabeparameter ) ; r e t = p ( ’A ’ ) ; \\ o d e r // A u f r u f von f o o ( ) u e b e r F u n k t i o n s p o i n t e r a = p ( Uebergabeparameter ) ; return 0 ; } 7.1.10 Anlegen von dynamischen Objekten C++ Kap. 8.2.4 int ∗ p I n t = new int ; // S p e i c h e r f u e r i n t a l l o z i e r t char∗ pCh1 = new char ; // S p e i c h e r f u e r cha r a l l o z i e r t char∗ pCh2 = new char ; // S p e i c h e r f u e r cha r a l l o z i e r t ∗ pInt = 23; // Wert z u w e i s e n pCh2 = pCh1 ; // pCh2 z e i g t nun auch a u f d i e S p e i c h e r s t e l l e , a u f // w e l c h e pCh1 z e i g t . Damit g e h t a b e r d e r // Z u g r i f f a u f d i e S p e i c h e r s t e l l e v e r l o r e n , a u f // d i e pCh2 g e z e i g t h a t ( memory l e a k ! ) 7.1.12 7.1.12.1 7.1.11 Zerstören von dynamischen Objekten C++ Kap. 8.2.5 d e l e t e pInt ; d e l e t e pCh1 ; d e l e t e pCh2 ; // S p e i c h e r w i e d e r f r e i g e b e n C++ verfügt über keine automatische Speicherverwaltung (garbage collection), explizit angeforderte Speicherstellen müssen daher mit delete freigegeben werden. const bei Pointern und Arrays C Kap. 10.4 , C++ Kap. 8.2.6 const bei Pointer - konstanter Pointer 7.1.12.2 const bei Pointer - konstanter String char s t r [ ] = " Ein ␣ S t r i n g " ; char∗ const t e x t = s t r ; // e r l a u b t char ch = t e x t [ 1 ] ; text [ 1 ] = ’ s ’ ; s t r [ 4 ] = ’A ’ ; // n i c h t e r l a u b t t e x t = " Ein ␣ a n d e r e r ␣ S t r i n g " ; char s t r [ ] = " Ein ␣ S t r i n g " ; const char∗ t e x t = s t r ; // e r l a u b t char ch = t e x t [ 1 ] ; t e x t = " Ein ␣ a n d e r e r ␣ S t r i n g " ; s t r [ 4 ] = ’A ’ ; // n i c h t e r l a u b t text [ 1 ] = ’ s ’ ; Hier ist nun der Pointer text konstant. Die Position von const ist sehr relevant! Dies bedeutet nicht, dass der Pointer text konstant ist, sondern dass text auf einen konstanten String zeigt. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 7.1.12.3 Seite 18 von 55 (Revision : 5. August 2014) const bei Arrays 7.1.12.4 const bei Pointer - konstanter Pointer auf konstanten String const int a r r [ ] = { 1 4 , −2, 4 5 6 } ; arr[0], arr[1] und arr[2] sind alle konstant und können somit nach der Initialisierung nicht mehr abgeändert werden. char s t r [ ] = " Ein ␣ S t r i n g " ; const char∗ const t e x t = s t r ; // e r l a u b t char ch = t e x t [ 1 ] ; s t r [ 4 ] = ’A ’ ; // n i c h t e r l a u b t text [ 1 ] = ’ s ’ ; t e x t = " Ein ␣ a n d e r e r ␣ S t r i n g " ; Bei dieser Variante ist sowohl der Pointer text als auch der String, auf welchen text zeigt, konstant. 7.2 Vektoren C Kap. 10.7.1, C++ Kap. 8.3 Ein Pointer ist eine Variable, in der die Adresse eines anderen Speicherobjektes gespeichert ist. Entsprechend einem eindimensionalen Vektor von gewöhnlichen Variablen kann natürlich auch ein eindimensionaler Vektor von Pointervariablen gebildet werden. Arbeitet man mit mehreren Zeichenketten, deren Länge nicht von vorherein bekannt ist, so verwendet man ein Array von Pointern auf char. Will man nun beispielsweise diese Strings sortieren, so muss dies nicht mit Hilfe von aufwändigen Kopieraktionen für die Strings durchgeführt werden. Es werden lediglich die Pointer so verändert, dass die geforderte Sortierung erreicht wird. char∗ s t r T a b l e [ ] = { " Pflaume " , " Apfel " , " Johannisbeere " }; 7.2.1 Initialisierung int a r r 1 [ 5 ] ; arr [ 5 ] = 4; // B e r e i c h s u e b e r s c h r e i t u n g g e h t i n C++ int a r r 2 [ 6 ] = {3 ,5 , −6} // Die e r s t e n d r e i Elemente werden e x p l i z i t // i n i t i a l i s i e r t . Der r e s t wird a u f 0 // g e s e t z t . int a r r 3 [ ] = { 5 , 7 , 6 } // Compiler e r s t e l l t a u t o m a t i s c h 3−e r Array . 7.3 7.3.1 Zeichenketten Formale Parameter für die Übergabe eines Arrays können in der Notation eines offenen Arrays ohne Längenangabe geschrieben werden. strPointer[] ist demzufolge ein Vektor. Der Vektor besteht aus Pointern auf char. 7.2.2 Dynamische Allozierung int ∗ p I n t = new int [ 1 0 0 ] ; p I n t [ 2 2 ] = −45; d e l e t e p I n t ; // F e h l e r : nur p I n t [ 0 ] wird // f r e i g e g e b e n d e l e t e [ ] p I n t ; // k o r r e k t e r B e f e h l C Kap. 10, C++ Kap. 8.4 Initialisierung von Zeichenketten C Kap. 10.1.5 und Kapitel 10.1.6 char s t r [ 2 0 ] = { ’ Z ’ , ’ e ’ , ’ i ’ , ’ c ’ , ’ h ’ , ’ e ’ , ’ n ’ , ’ k ’ , ’ e ’ , ’ t ’ , ’ t ’ , ’ e ’ , ’ \0 ’ } ; // u m s t a e n d l i c h char s t r [ 2 0 ] = " Z e i c h e n k e t t e " ; // b e v o r z u g t char s t r [ 2 0 ] = { " Z e i c h e n k e t t e " } ; // u n u e b l i c h char s t r [ ] = " Z e i c h e n k e t t e " ; // h a e u f i g , Compiler s o l l c h a r s z a e h l e n L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 7.3.2 Kopieren eines Strings C Kap. 10.5 7.3.2.1 Variante mit Laufvariable char a l p h a [ 3 0 ] = " zu ␣ k o p i e r e n d e r ␣ S t r i n g " ; char b e t a [ 3 0 ] ; int i ; for ( i = 0 ; b e t a [ i ] = a l p h a [ i ] ; i ++); 7.3.3 • • • • 7.3.2.2 Variante mit Pointer char a l p h a [ 3 0 ] = " zu ␣ k o p i e r e n d e r ␣ S t r i n g " ; char b e t a [ 3 0 ] ; char∗ palpha = a l p h a ; char∗ pbeta = b e t a ; while ( ∗ pbeta++ = ∗ palpha ++); Standardfunktionen für Strings und Speicher C Kap. 10.6 Funktionen für die String- und Speicherverarbeitung sind prinzipiell dasselbe. Diese Funktionen werden in der Bibliothek string.h zur Verfügung gestellt. Funktionen die mit str beginnen, dienen der Stringverarbeitung und erkennen das ’\0’-Zeichen. Funktionen die mit mem beginnen, dienen der Speicherverarbeitung und erkennen das ’\0’-Zeichen nicht. 7.3.3.1 String kopieren C Kap. 10.6.1.1 #include <s t r i n g . h> char∗ s t r c p y ( char∗ d e s t , const char∗ s r c ) ; • Dies Funktion kopiert einen String von src nach dest inklusive ’\0’. • Hat als Rückgabewert den Pointer auf dest. • dest muss auf einen Bereich zeigen, der genügend gross ist. Ist der zu kopierende Buffer grösser als der Zielbuffer, dann werden nachfolgende Speicherbereiche überschrieben (Buffer overflow). 7.3.3.2 Strings zusammenfügen C Kap. 10.6.1.2 #include <s t r i n g . h> char∗ s t r c a t ( char∗ d e s t , const char∗ s r c ) ; • Diese Funktion hängt einen String src an dest an, inklusive ’\0’. Das ursprüngliche ’\0 von dest wird überschrieben. • Hat als Rückgabewert den Pointer auf dest. • dest muss auf einen Bereich zeigen, der genügend gross ist. Ist der zu kopierende Buffer grösser als der Zielbuffer, dann werden nachfolgende Speicherbereiche überschrieben (Buffer overflow). 7.3.4 Seite 19 von 55 (Revision : 5. August 2014) Funktionen zur Speicherbearbeitung C Kap. 10.6.2 Die grundsätzlichen Unterschiede zu den Stringfunktionen sind: • Formelle Parameter sind vom Typ void* statt char*. • Die mem-Funktionen arbeiten byteweise. • Im Gegensatz zu den strFunktionen wird das ’\0’-Zeichen nicht speziell behandelt. • Die Bufferlänge muss als Parameter übergeben werden. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 7.3.3.3 Strings vergleichen C Kap. 10.6.1.3 #include <s t r i n g . h> int strcmp ( const char∗ s1 , const char∗ s 2 ) ; int strncmp ( const char∗ s1 , const char∗ s2 , size_t n ) ; • Dies Funktion vergleicht die beiden Strings, die auf s1 und s2 zeigen. Bei der Funktion strncmp werden nur die ersten n Zeichen verglichen. • Dies Funktionen hat die folgenden Rückgabewerte: <0 : *s1 ist lexikographisch kleiner als *s2 ==0 : *s1 und *s2 sind gleich >0 : *s1 ist lexikographisch grösser als *s2 7.3.3.4 Stringlänge bestimmen C Kap. 10.6.1.5 #include <s t r i n g . h> s i z e _ t s t r l e n ( const char∗ s ) ; • Diese Funktion bestimmt die Länge von s, d.h. die Anzahl der char-Zeichen. Das ’\0’-Zeichen wird dabei nicht mitgezählt. • Hat als Rückgabewert die Länge von s. 7.3.4.1 Funktionen C Kap. 10.6.2.1 bis Kap. 10.6.2.5 #include <s t r i n g . h> // S p e i c h e r b e r e i c h k o p i e r e n void ∗ memcpy ( void ∗ d e s t , const void ∗ s r c , s i z e _ t n ) ; // S p e i c h e r b e r e i c h v e r s c h i e b e n void ∗ memmove( void ∗ d e s t , const void ∗ s r c , s i z e _ t n ) ; // S p e i c h e r b e r e i c h e v e r g l e i c h e n void ∗ memcmp( const void ∗ s1 , const void ∗ s2 , s i z e _ t n ) ; // Z e i c h e n i n S p e i c h e r b e r e i c h suchen void ∗ memchr ( const void ∗ s , int c , s i z e _ t n ) ; // S p e i c h e r b e r e i c h mit Wert b e l e g e n void ∗ memset ( const void ∗ s , int c , s i z e _ t n ) ; Bei memcpy() dürfen sich die Buffer nicht überlappen, memmove() kann auch mit überlappenden Buffern umgehen. 5. August 2014 OOProg - Zusammenfassung 7.4 Referenzen Seite 20 von 55 (Revision : 5. August 2014) C++ Kap. 8.1 Referenzen sind alternative Namen oder Alias für ein Objekt. In 2 Situationen anwenden: • Parameterübergabe in Funktionen (call by Reference) int &r 1=x ; // S c h r e i b w e i s e 1 : & v o r Variablenname • Referenz Rückgabetyp anstatt Pointertyp (Obint& r 1=x ; // S c h r e i b w e i s e 2 : & nach Typangabe , jekte einer Klasse immer by reference überge// b e s s e r E r s i c h t l i c h ben!) Niemals Pointer oder Referenz auf lokale Variable als int& r 4=x ; r 5=x ; // r5 i s t k e i n e R e f e r e n z ! B e s s e r return Wert bei Funktionen. // e i n z e l n D e k l a r i e r e n . int x = 1 2 ; 7.5 Pointer und Referenzen als Rückgabewert und Parameterübergabe C++ Kap. 8.6 Bei Variablenübergabe (call by value) werden Kopien übergeben, welche nicht verändert werden können. Bei Referenzübergabe (call by reference) kann die Subroutine die Werte bleibend verändern. 7.5.1 void swap ( int& a , int& b ) { int tmp = a ; a = b; b = tmp ; } int main ( ) { int x = 4 ; int y = 3 ; swap ( x , y ) ; // OK! return 0 ; } 7.6 7.5.2 call by reference void swap ( int ∗ a , int ∗ b ) { int tmp = ∗a ; ∗a = ∗b ; ∗b = tmp ; } int main ( ) { int x = 4 ; int y = 3 ; swap(&x , &y ) ; // OK! return 0 ; } Zugriff auf Class und Struct Elemente call by value void swap ( int a , int b ) { int tmp = a ; a = b; b = tmp ; } int main ( ) { int x = 4 ; int y = 3 ; swap ( x , y ) ; // k e i n e Auswirkung return 0 ; } C++ Kap. 8.7 Ähnlich wie bei Vektoren können natürlich auch die einzelnen Elemente von Klassen angesprochen werden. Für den direkten Zugriff verwendet man die Operatoren . und ->. Da es sich bei Klassen nicht um eine Aneinanderreihung von Elementen gleichen Types handelt, wird kein Index zur Adressierung der Elemente verwendet, sondern der Komponentenname. Der Unterschied zwischen den beiden Operatoren . und -> besteht darin, dass . auf eine Variable eines Klassentypes angewandt wird, während -> für Zeiger auf KlassenBirthda y b ; typen benutzt wird. Birthda y ∗ p ; Der Operator -> stellt nichts anderes als eine vereinfachte Schreibweise für eine Kombination von * und . dar: b . y e a r = 1991 p->month = 12; p = &b ; ist äquivalent zu: p−>day = 2 3 ; c o u t << b . day << b . month << b . y e a r ; (*p).month = 12; struct Bi rth day { int y e a r ; int month ; int day ; }; 8 8.1 Gültigkeitsbereiche, Namensräume und Sichtbarkeit Sichtbarkeit C++ Kap. 9 C++ Kap. 9.1.1 C++ kennt verschiedene Gültigkeitsbereiche: • Blockanweisungen führen einen eigenen Gültigkeitsbereich ein, den so genannten lokalen Gültigkeitsbereich oder Local Scope. Alle dort deklarierten Bezeichner gelten genau in diesem Block, genauer gesagt von ihrer Deklaration an bis zum Ende des aktuellen Blocks. Unter diesen Punkt fallen auch die Kontrollanweisungen if, switch, for sowie while. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Seite 21 von 55 (Revision : 5. August 2014) • Der Gültigkeitsbereich von Funktionsprototypen erstreckt sich bis ans Ende der Deklaration und umfasst die Funktionsparameter. Der Gültigkeitsbereich von Funktionen erstreckt sich über die gesamte Funktion. • Die so genannten Namensräume (Namespaces) sind eigene Gültigkeitsbereiche, die alle darin deklarierten Bezeichner umfassen. Ein Bezeichner, der in einem Namensraum deklariert ist, gilt von seiner Deklaration bis an das Ende des Namensraums. • Jede Klasse hat einen eigenen Gültigkeitsbereich (Class Scope), der sich über die gesamte Klasse erstreckt. Ein Klassenelement gilt in seiner Klasse von seiner Deklaration an bis zum Ende der Klassendeklaration und kann nur in Verbindung mit einer entsprechenden Variablen dieses Klassentyps verwendet werden. 8.2 Namensräume namespace MyLib1 { int i ; void f o o ( ) ; } namespace MyLib2 { int i ; void f o o ( ) ; } MyLib1 : : f o o ( ) ; // f o o aus MyLib1 MyLib2 : : i = 1 7 ; // i aus MyLib2 8.3 8.3.1 • • • • • Deklarationen C++ Kap. 9.1.2 Namensräume sind Gültigkeitsbereiche, in denen beliebige Bezeichner (Variablen, Klassen, Funktionen, andere Namensräume, Typen, etc.) deklariert werden können. • Ein Namensraum kann deklariert werden. Alle enthaltenen Objekte werden diesem Namensraum zugeordnet. Auf Bezeichner eines Namensraumes kann mit dem Scope Operator :: zugegriffen werden. • Einem Namensraum kann ein so genannter Alias zugeordnet werden, über den er angesprochen wird. namespace FBSSLIB = Financial_Branch_and_System_Service_Library; • Eine so genannte Using-Deklaration erlaubt den direkten Zugriff auf einen Bezeichner eines Namensraumes. using MyLib1::foo; foo(); • Mit einer so genannten Using-Direktive kann auf alle Bezeichner eines Namensraums direkt zugegriffen werden. using namespace MyLib1; foo(); C++ Kap. 9.2 Speicherklassenattribute C++ Kap. 9.2.1 auto: gilt als Standard wenn nichts anderes steht. Gültigkeitsbereich der auto Variablen ist innerhalb des Blockes in dem sie deklariert wurde. register: Hinweis an den Compiler möglichst die Variable in einem Register abzulegen. static: Variablen leben von ihrer Deklaration bis zum Programmende. Geeignet um zum Bsp. Funktionsaufrufe zu zählen anstatt mit globaler Variable. extern: Zugriff auf eine static Variable in einem anderen File, welches zu einem gesamten Programm gelinkt wurde. mutable: Klassenelemente mit const oder static Attributen können nachträglich verändert werden. 8.3.2 Typqualifikatoren C++ Kap. 9.2.2 • const: Objekte dürfen nicht verändert werden. RValues. • volatile: Objekte werden evtl. von Aussen im Programmverlauf verändert, und dürfen daher vom Compiler nicht zu Optimierungszwecken zwischengespeichert werden. Sie werden immer aus dem Hauptspeicher eingelesen. 8.3.3 Funktionsattribute C++ Kap. 9.2.3 • inline: Compileranweisung den Funktionsinhalt einer inline-Funktion direkt an die Aufrufstelle zu substituieren. Laufzeitoptimierung (kein wirklicher Funktionsaufruf) • virtual: Wird im Zusammenhang mit Klassen gebraucht. • explicit: Wird im Zusammenhang mit Klassen gebraucht. 8.3.4 typedef C++ Kap. 9.2.4 typedef int Number ; typedef int Vector [ 2 5 ] Number a ; // D e k l a r a t i o n e i n e r i n t −Z a h l Vector s ; // D e k l a r a t i o n e i n e s 25 e r i n t −a r r a y s 8.4 Initialisierung von Objekten Das Schlüsselwort typedef ermöglicht die Einführung neuer Bezeichner, die dann im Programm anstelle von anderen Typen verwendet werden können. typedef führt allerdings keine neuen Typen, sondern Synonyme für einen existierenden Datentyp ein. C++ Kap. 9.3 int c = 2 3 ; // V a r i a b l e wird a u f Wert g e s e t z t , n i c h t z u g e w i e s e n ! int x ; // Wert von x i s t unbestimmt s t a t i c int y ; // y = 0 ( s t a t i c Elem . werden mit 0 i n i t i a l i s i e r t ) L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Seite 22 von 55 (Revision : 5. August 2014) Folgende Regeln müssen beachtet werden: • Alle Initialisatoren müssen Konstantenausdrücke sein. • Konstantenausdrücke müssen eine Konstante oder die Adresse eines externen oder statischen Objektes plus minus einer Konstante liefern. • Bei Konstantenausdrücken dürfen nur unäre und binäre Operatoren sowie Funktionen verwendet werden. • Die Werte von Vektoren und strukturierten Datentypen werden durch die Angabe der einzelnen Komponenten in geschwungenen Klammern festgelegt. 8.5 Type-cast C++ Kap. 9.4 8.5.1 Standard-Typumwandlung C++ Kap. 9.4.1 Ausdrücke werden bei einer Zuweisung automatisch in den erwarteten Typ umgewandelt. c o u t << a ; // e r f o r d e r t RValue , f a l l s a LValue wir d e s autom . k o n v e r t i e r t float f = 1.23 int i = f ; // Gleitkomma −> I n t e g e r ( aufrunden , abrunden i m p l e m e n t a t i o n s a b h a e n g i g ) f = 1; // I n t e g e r −> Gleitkomma 8.5.2 explizite-Typumwadlung C++ Kap. 9.4.2 Mögliche Umwandlungen im C-Stil: (Problematisch) int i = ( int ) t r u e ; // b o o l −>i n t char∗ s t r = " H a l l o " ; int ∗ p I n t = ( int ∗ ) s t r ; // s t r −>i n t P o i n t e r neue Typumwandlungen in C++: const char∗ s t r ; c o n s t _ c a s t <char∗> s t r ; // Wegoperieren d e s c o n s t −A t t r i b u t e s −−> nur e i n s e t z e n f a l l s zum Bsp . // e i n e Funktion k e i n e c o n s t s a k z e p t i e r t dynamic_cast<SuperHero∗>p ; // Wandelt P o i n t e r p i n e i n e n O b j e k t p o i n t e r d e r K l a s s e SuperHero . // Geht nur wenn p b e r e i t s K l a s s e n o b j e k t war . s t a t i c _ c a s t <SuperHero>Batman ; // Umwandeln e i n e s K l a s s e n o b j e k t i n e i n O b j e k t s e i n e r // B a s i s k l a s s e . r e i n t e r p r e t _ c a s t <int∗> s t r // char P o i n t e r s t r wird i n i n t −P o i n t e r g e w a n d e l t . 9 Module und Datenkapseln 9.1 Motivation C++ Kap. 10.1 • Arbeitsteilung: Grosse Programme werden von mehreren Personen entwickelt. Praktikabel ist, wenn nur eine Person an einer bestimmten Datei arbeitet. • Effizienz: Eine Übersetzungseinheit (Datei) muss bei jeder Änderung neu übersetzt werden (je grösser die Datei desto langsamer die Übersetzung) • Strukturierung: Ein grosses Programm in mehrere vernünftige Teile (Baugruppen, Units) aufteilen (Divide and conquer) L. Leuenberger, M. Ehrler, C. Ham, L. Däscher C++ Kap. 10 9.2 • • • • 9.3 Nomenklatur Modul vs. Unit Ein Programmbaustein wird traditionell mit Modul bezeichnet Der Test eines Moduls heisst folglich Modultest Das Vorgehen, welches Module generiert, heisst Modularisierung Heute üblicher wird Modul mit Unit, der Test mit Unittest bezeichnet, das Vorgehen heisst weiterhin Modularisierung Ziele der Modularisierung • Klare, möglichst schlanke Schnittstellen definieren • Units so bilden, das Zusammengehörendes in einer Unit isoliert wird (Kohäsion soll hoch sein) • Schnittstellen zwischen den Units sollen klein sein (Kopplung soll klein sein) • Abhängigkeiten unter den Units sollen eine Hierarchie bilden, zirkuläre (gegenseitige) Abhängigkeiten müssen vermieden werden 5. August 2014 OOProg - Zusammenfassung 9.4 Seite 23 von 55 (Revision : 5. August 2014) Vom Modul zur Datenkapsel C++ Kap. 10.2 9.5 Eigenschaften einer Unit (eines Moduls): • realisiert eine in sich abgeschlossene Aufgabe • kommuniziert über ihre Schnittstelle mit der Umgebung • kann ohne Kenntnisse ihres inneren Verhaltens in ein Gesamtsystem integriert werden (include Header) • ihre Korrektheit kann ohne Kenntnis ihrer Einbettung in einem Gesamtsystem nachgewiesen werden (mittels Unittest) • Die Datenkapsel fordert nun zusätzlich, dass auf die Daten nicht direkt zugegriffen werden darf, sondern nur über Zugriffsfunktionen. Die Schnittstelle beschreibt, was das Modul zur Verfügung stellt, verbirgt dabei wie das Verhalten konkret realisiert ist (Geheimnisprinzip, Information Hiding). Der User der Unit darf keine Annahme über den inneren Aufbau machen. Der Entwickler der Unit kann deren inneren Aufbau verändern, solange die Schnittstelle dadurch nicht ändert. 9.6 Die Schnittstellen-/Headerdatei C++ Kap. 10.3.1 Jede .h-Datei enthält als erste Anweisungsfolge eine Include-Guard welche Mehrfacheinfügen verhindert. Der Syntax lautet: #i f n d e f FOO_H_ #define FOO_H_ // D e k l a r a t i o n e n ( Punkt 2−7 n a c h f o l g e n d e L i s t e ) #endif /∗ FOO_H_ ∗/ Deklarationsreihenfolge in Headerdatei (*.h) (Beispiel C++ Kap. 16.1 ) 1. Dateikommentar 2. #include der verwendeten System-Header (iostream, etc.) #include <...> 3. #include der projektbezogenen Header (#include "...") 4. Konstantendefinitionen 5. typedefs und Definition von Strukturen 6. Allenfalls extern-Deklaration von globalen Variablen 7. Funktionsprototypen, inkl. Kommentare der Schnittstelle, bzw. Klassendeklarationen 9.8 Die Implementierungsdatei Unitkonzept / Module und Datenkapseln in C++ C++ Kap. 10.3 • Interface definiert die Schnittstelle, d.h. die Deklarationen wie Funktionsprototypen, etc. (Schaufenster) • Implementation: in diesem Teil sind die Unterprogramme definiert, d.h. auscodiert (Werkstatt) • Das Interface wird in einer Headerdatei (*.h) beschrieben, die Implementation liegt in einer *.cpp- Datei 9.7 Beispiel Unit Rechteck // i n t e r n e Daten double a ; // 1 . S e i t e double b // 2 . S e i t e // S c h n i t t s t e l l e ( I n t e r f a c e ) // Funktionen setA ( ) , setB ( ) , // getA ( ) , getB ( ) , g e t A r e a ( ) void setA ( double newA) { a = newA ; } double getArea ( void ) { return a ∗ b ; } C++ Kap. 10.3.2 Deklarationsreihenfolge in Implementierungsdatei (*.cpp) (Beispiel C++ Kap. 16.1 ) 1. Dateikommentar 2. #include der verwendeten System-Header (iostream, etc.) #include <...> 3. #include der projektbezogenen Header (#include "...") 4. Verwendung von using namespace 5. allenfalls globale Variablen und statische Variablen 6. Präprozessor-Direktiven 7. Funktionsprototypen von lokalen, internen Funktionen 8. Definition von Funktionen und Klassen (Kommentare aus Headerdatei nicht wiederholen!) 9.9 Buildprozess / Makefile Der Buildprozess erstellt aus den einzelnen Dateien einen ausführbaren Code. Dazu werden zuerst alle *.cpp-Files compiliert. Die daraus entstandenen Objektdatei müssen anschliessend gelinkt und somit zu einer auführbaren Datei zusammengesetzt. Die Eingabe in der Konsole sieht wie folgt aus: Abhängigkeitsliste gemäss UML-Notation: g++ −c f o o . cpp // c o m p i l e Unit , do w i t h a l l ∗ . cpp g++ −o f o o . exe f o o . o goo . o hoo . o // l i n k Unit Es wäre mühsam, wenn diese Befehle jedesmal neu eingetippt werden müssten. Deshalb wird in der Praxis oft ein Buildtool eingesetzt, z.B. make. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 9.9.1 Seite 24 von 55 (Revision : 5. August 2014) Make-File • In einem make-File können Abhängigkeiten definiert werden • Wenn eine Datei geändert wurde, dann werden alle Operationen ausgeführt mit den Dateien, welche von dieser geänderten Datei abhängen • Der Befehl (g++) wird z.B. nur dann ausgeführt, wenn sich an den Dateien, zu denen eine Abhängigkeit besteht, etwas geändert hat 10 10.1 Klassenkonzept Begriff der Klasse • Eine Klasse ist eine Struktur (eine Struktur besteht nur aus Daten), die mit den Funktionen, welche auf diesen Daten arbeiten, erweitert wurde. • Eine Klasse ist also eine Struktur, welche die Daten und die Funktionen auf diesen Daten in ein syntaktisches Konstrukt packt. • Die Klasse ist die Umsetzung der Datenkapsel. • Eine Klassendeklaration ist eine Typendefinition. Die Variablen einer Klasse werden als Objekte bezeichnet. 10.3 10.2 ClassName -attribute1: int = 0 -attribute2: int = 0 +method1() +method2() • Eine Klasse ist der Bauplan für Objekte. • Eine Klasse besteht aus Daten (Attribute) und den Funktionen (Methoden) auf diesen Daten. • Sichtbarkeit: – + : public – - : private – # : protected Üblicher Aufbau einer Klassensyntax C++ Kap. 11.1.1 c l a s s Classname // D e k l a r a t i o n d e r K l a s s e { public : ... protected : ... private : ... }; // S t r i c h p u n k t n i c h t v e r g e s s e n 10.3.2 Operationen einer Klasse Operationen eine Klasse (= Funktionen, die im Klassenrumpf definiert sind) werden als Elementfunktionen oder Methoden bezeichnet. Üblicherweise beginnen Elementfunktionen mit einem Kleinbuchstaben und werden in camelCase (mixedCase) notiert. isEmpty(); L. Leuenberger, M. Ehrler, C. Ham, L. Däscher UML-Notation einer Klasse 10.3.1 Zugriffsschutz C++ Kap. 11.4 • public - Elemente können innerhalb und von ausserhalb der Klasse angesprochen werden. – fast alle Methoden sind public – Attribute sollen nie public sein • protected - Elemente können von innerhalb der Klasse und von abgeleiteten Klassen angesprochen werden. – nur sparsam einsetzen! • private - Elemente können nur innerhalb der Klasse angesprochen werden. – grundsätzlich für alle Attribute und für einzelne (lokale) Methoden 10.3.3 Information Hiding • Klassen exportieren generell ausschliesslich Methoden. Alle Daten sind im Innern (private-Abschnitt) verborgen, der Zugriff erfolgt über die so genannten Elementfunktionen. • Jede Klasse besteht damit aus zwei Dateien, der Schnittstellendatei (.h) und der Implementierungsdatei (.cpp). 5. August 2014 OOProg - Zusammenfassung 10.3.3.1 Seite 25 von 55 (Revision : 5. August 2014) f riend-Elemente C++ Kap. 11.4.2 • friend - Jede Klasse kann andere Klassen oder Funktionen zum Freund erklären. Dadurch werden die Zugriffsregeln durchbrochen. • Jeder friend darf auf alle Elemente der Klasse zugreifen. • friend ist eine C++ - Spezialität, welche die meisten anderen Programmiersprachen (z.B. Java) nicht anbieten. • friends, insbesondere friend-Klassen, können ein Anzeichen für schlechtes Design sein. Sie durchbrechen wichtige Prinzipien der objektorientierten Programmierung. 10.3.4 Beispiel an der Klasse Rechteck // K l a s s e n d e k l a r a t i o n i n r e c t a n g l e . h class Rectangle { public : void setA ( double newA ) ; void setB ( double newB ) ; double getA ( ) const ; double getB ( ) const ; double getArea ( ) const ; private : double a ; double b ; }; Rectangle -a : double -b : double +setA(in newA : double) +setB(in newB : double) +getA() : double +getB() : double +getArea() : double 10.4 Elementfunktionen C++ Kap. 11.2 • sind Funktionen, die in der Schnittstelle der Klasse spezifiziert sind. • Elementfunktionen haben vollen Zugriff auf alle Klassenelemente (auch auf solche, die mit private: gekennzeichnet sind. • Auf Elementfunktionen kann nur unter Bezugnahme auf ein Objekt der Klasse, bzw. mit dem ScopeOperator (::) zugegriffen werden. • Elementfunktionen sollen prinzipiell in der Implementierungsdatei (.cpp) implementiert werden. Dem Funktionsnamen muss dabei der Klassenname gefolgt von :: vorangestellt werden. (Beispiel: int Stack::pop()) 10.4.2 // K l a s s e n d e f i n i t i o n i n r e c t a n g l e . cpp #include " r e c t a n g l e . h" void R e c t a n g l e : : setA ( double newA) { a = newA ; } void R e c t a n g l e : : setB ( double newB) { b = newB ; } double R e c t a n g l e : : getA ( ) const { return a ; } double R e c t a n g l e : : getB ( ) const { return b ; } double R e c t a n g l e : : getArea ( ) const { return a∗b ; } 10.4.1 Klassifizierung von Elementfunktionen • Konstruktoren / Destruktoren – Konstruktor: erzeugen eines Objekts – Destruktur: vernichten, freigeben eines Objekts • Modifikatoren – ändern den Zustand eines Objekts (Attribute ändern) • Selektoren – greifen nur lesend auf ein Objekt zu (immer const definieren!) – Beispiel: bool Stack::isEmpty() const; • Iteratoren – Erlauben, auf Elemente eines Objekts in einer definierten Reihenfolge zuzugreifen inline-Funktionen C++ Kap. 11.2.1 • Elementfunktionen, die innerhalb der Deklaration der Klassenschnittstelle (im .h-File) implementiert sind, werden als (implizite) inline - Funktionen behandelt. • Elementfunktionen können in der Klassenimplementation explizit mit dem Schlüsselwort inline gekennzeichnet werden. • Implizite inline - Funktionen verletzen zwar das Information Hiding Prinzip und sollten deshalb grundsätzlich vermieden werden. • Jedoch: die impliziten inline - Funktionen sind die Funktionen, die garantiert immer inline verwendet werden (mit einigen wenigen Ausnahmen). L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 10.4.3 mutable - Attribut Ein Datenelement, das nie const werden soll (auch nicht bei const-Elementfunktionen) kann mit mutable gekennzeichnet werden. c l a s s Stack { public : int pop ( ) ; int peek ( ) const ; bool isEmpty ( ) const ; private : int elem [ maxElems ] ; int top ; mutable bool e r r o r ; }; int Stack : : peek ( ) const { e r r o r = top == 0 ; if (! error ) elem [ top − 1 ] ; else return 0 ; } 10.6 this - Pointer Seite 26 von 55 (Revision : 5. August 2014) 10.4.4 const - Elementfunktion C++ Kap. 11.2.2 • Elementfunktionen, die den Zustand eines Objekts nicht ändern (Selektoren) sollen explizit mit dem Schlüsselwort const gekennzeichnet werden. • Das Schlüsselwort const muss sowohl im Prototypen als auch in der Implementierung geschrieben werden. bool Stack : : isEmpty ( ) const ; ... bool Stack : : isEmpty ( ) const { return top == 0 ; } 10.5 static - Klassenelemente C++ Kap. 11.5 • Grundsätzlich besitzt jedes Objekt einer Klasse seine eigene private Instanz aller Attribute einer Klasse. • Wenn ein Attribut mit static gekennzeichnet wird, dann teilen sich alle Objekte dieser Klasse eine einzige Instanz dieses Attributs, d.h. ein statisches Attribut ist nur einmal für alle Objekte einer Klasse im Speicher vorhanden. • static - Elemente befinden sich ausserhalb eines Objektkontexts. • static - Elemente können auch über den Klassennamen angesprochen werden (da sie sich im Kontext einer Klasse befinden). C++ Kap. 11.3 Der this-Pointer ist ein Pointer auf das eigene aktuelle Objekt, welches eine Methode aufgerufen hat. AnyClass& AnyClass : : aMethod ( const AnyClass& o b j ) { this−>anyFoo ( ) ; i f ( t h i s == &o b j ) // t e s t e n , ob e i g e n e Adresse g l e i c h ... // Adresse von o b j i s t return ∗ t h i s ; // e i g e n e s O b j e k t z u r ü c k g e b e n } 10.7 10.7.1 Konstruktor (am Beispiel der Klasse TString) Aufgaben des Konstruktors • die Neugründung eines Objekts einer Klasse • das saubere Initialisieren des Objekts, d.h. alle Attribute des Objekts müssen auf einen definierten Wert gesetzt werden • Der Konstruktor hat in C++ denselben Namen wie die Klasse, hat keinen Rückgabetyp (auch nicht void) und kann überladen werden. Beispiel: Stack::Stack(); 10.7.3 10.7.2 C++ Kap. 11.7.1 Aufruf des Konstuktors • Der Konstruktor soll nie explizit aufgerufen werden. • Der Konstruktor wird vom System automatisch (implizit) aufgerufen, wenn ein Objekt erzeugt wird: Stack s; • Wenn durch den new-Operator Speicher angefordert und erhalten wird, dann wird der Konstruktor vom System ebenfalls automatisch aufgerufen: Stack* pS = new Stack; Default-Konstruktor • Der Default-Konstruktor ist der Konstruktor ohne Parameter: Stack::Stack(); Er wird immer aufgerufen, wenn bei der Objekterzeugung keine Parameter mitgegeben werden: Stack s; • Der Default-Konstruktor wird vom System automatisch erzeugt, wenn für eine Klasse kein Konstruktor explizit definiert ist. • Der Default-Konstruktor kann selbst definiert werden. – Das ist insbesondere dann notwendig, wenn innerhalb des Objekts Speicher dynamisch alloziert werden muss (bei der Objekterzeugung). L. Leuenberger, M. Ehrler, C. Ham, L. Däscher class TString { public : TString ( ) ; int getLen ( ) const ; private : int l e n ; char∗ s t r ; }; 5. August 2014 OOProg - Zusammenfassung 10.7.4 Seite 27 von 55 (Revision : 5. August 2014) Implementation/Initialisierung Es gibt zwei Arten den Konstruktor zu implementieren. 10.7.4.1 Implementation mit Anweisung TString : : TString ( ) { len = 0; str = 0; } 10.7.4.2 Implementation mit Initialisierungsliste TString : : TString ( ) : len (0) , str (0) { } Objektinitialisierungen werden, sofern dies möglich ist, über die Initialisierungsliste des Konstruktors und nicht im Anweisungsteil durchgeführt. (Effizienzgründe) 10.7.5 Überladen von Konstruktoren • Der Default-Konstruktor wird implizit aufgerufen mit TString str; • Ein TString-Objekt soll auch z.B. mit folgenden Anweisungen gegründet werden können: TString str1 = "Hello"; // implicit call TString str2 = TString("Guten Morgen"); // explicit call • Dazu bedarf es anderer (überladener) Konstruktoren. 10.7.6 class TString { public : TString ( ) ; T S t r i n g ( const char∗ p ) ; int getLen ( ) const ; private : int l e n ; char∗ s t r ; }; T S t r i n g : : T S t r i n g ( const char∗ p ) { i f ( p == 0 ) { len = 0; str = 0; } else { len = strlen (p ) ; s t r = new char [ l e n + 1 ] ; memcpy ( s t r , p , l e n +1); } } Konstruktoren und Function Casts • Konstruktoren mit nur einem Parameter können dazu verwendet werden, ein Objekt vom Typ T aus einem anderen Objekt zu erzeugen (Typumwandlung). • Beispiel: TString soll so erweitert werden, dass dem Konstruktor eine ganze Zahl übergeben wird und dieser daraus den entsprechenden String erzeugt. T S t r i n g : : T S t r i n g ( int number ) ; // e x p l i c i t c a l l : TString s t r 1 = TString ( 1 2 3 4 5 ) ; // e r z e u g t "12345" // i m p l i c i t c a l l s : TString s t r 2 = 12345; // e r z e u g t "12345" s t r 2 = 7 8 9 ; // e r z e u g t t e m p o r ä r e s O b j e k t "789" und k o p i e r t i n s t r 2 10.7.7 Explizite Konstruktoren • Die implicit calls (bei Konstruktoren mit einem Parameter) TString str2 = 12345; str2 = 789; sind gelegentlich nicht erwünscht. • Wenn der Konstruktor mit explicit gekennzeichnet wird, kann dieser Konstruktor nicht mehr implizit, sondern nur explizit aufgerufen werden. e x p l i c i t T S t r i n g : : T S t r i n g ( int number ) ; // ok ( e x p l i c i t ) TString s t r 1 = TString ( 1 2 3 4 5 ) ; // n i c h t e r l a u b t ( i m p l i c i t c a l l ) TString s t r 2 = 12345; str2 = 78; str1 = 567; L. Leuenberger, M. Ehrler, C. Ham, L. Däscher class TString { public : TString ( ) ; T S t r i n g ( const char∗ p ) ; e x p l i c i t T S t r i n g ( int nr ) ; int getLen ( ) const ; private : int l e n ; char∗ s t r ; }; 5. August 2014 OOProg - Zusammenfassung 10.7.8 Seite 28 von 55 (Revision : 5. August 2014) Copy-Konstruktor • Der Copy-Konstruktor wird dazu verwendet, Objekte zu kopieren. • Der Copy-Konstruktor erhält als Parameter immer eine konstante Referenz auf ein Objekt der Klasse. Für TString sieht er wie folgt aus: TString(const TString& s); Der Copy-Konstruktor wird automatisch aufgerufen, wenn ... • ... ein Objekt mit einem anderen Objekt derselben Klasse initialisiert wird. • ... ein Objekt als Wertparameter (by value) an eine Funktion übergeben wird (nicht aber bei Referenzparametern). • ... ein Objekt by value als Resultat einer Funktion zurückgegeben wird (nicht bei Referenzrückgabewerten). Ein Copy-Konstruktor wird nur dann benutzt, wenn ein neues Objekt erzeugt wird, aber nicht bei Zuweisungen, also Änderungen von Objekten. Bei Zuweisungen wird der vom System bereitgestellte Zuweisungsoperator benutzt, sofern kein eigener definiert wurde. 10.7.8.1 Shallow Copy vs. Deep Copy class TString { public : TString ( ) ; T S t r i n g ( const T S t r i n g& s ) ; T S t r i n g ( const char∗ p ) ; e x p l i c i t T S t r i n g ( int nr ) ; int getLen ( ) const ; private : int l e n ; char∗ s t r ; }; T S t r i n g : : T S t r i n g ( const T S t r i n g& s ) : len ( s . len ) { i f ( s . s t r == 0 ) { str = 0; } else { s t r = new char [ l e n + 1 ] ; memcpy ( s t r , s . s t r , l e n +1); } } • Wenn für eine Klasse kein Copy-Konstruktor definiert wird, erzeugt das System einen Standard-Copy-Konstruktor. • Dieser kopiert alle Datenelemente (memberwise assignment). Bei Pointern, welche auf den Heap zeigen, wird nur die Adresse kopiert, nicht aber der Speicher auf dem Heap. Man nennt das shallow copy. (shallow = flach). • Bei einer deep copy werden auch die Speicherbereiche, auf welche Pointer zeigen, kopiert. Die deep copy muss in einem selbst definierten Copy-Konstruktor implementiert werden. 10.7.8.3 10.7.8.2 Shallow-Copy 10.8 10.8.1 Destruktor C++ Kap. 11.7.2 Aufgaben des Destruktors • die vollständige Zerstörung eines nicht mehr benötigten Objekts • das saubere Entfernen eines Objekts • die häufigste Aufgabe ist die Freigabe von nicht mehr benötigtem Speicher auf dem Heap • sehr häufig (wenn kein Speicher auf dem Heap vorhanden ist) wird kein Destruktor definiert, da das System dann automatisch aufräumt 10.9 Deep-Copy Kanonische Form von Klassen 10.8.2 Eigenschaften des Destruktors • Destruktoren haben keine Argumente und keinen Rückgabetyp • Ihr Name besteht aus dem Klassennamen mit vorgestellter Tilde. Der Destruktor soll meist virtual deklariert werden (wenn es einen Destruktor braucht): virtual TString(); • Destruktoren werden automatisch aufgerufen, wenn der Gültigkeitsbereich des definierten Objektes ausläuft • Die Reihenfolge des Aufrufs der Destruktoren ist umgekehrt wie die der Konstruktoren • Nicht definierte Destruktoren werden automatisch erzeugt C++ Kap. 11.7.5 • Als kanonische Form einer Klasse bezeichnet man jene Form, die es erlaubt, eine Klasse wie einen "normalen"Datentyp zu benutzen. Dies ist für alle Klassen anzustreben. • Dazu müssen drei Bedingungen erfüllt sein: – Ein korrekter Default-Konstruktor, plus evtl. weitere Konstruktoren müssen vorhanden sein – Wenn die Klasse dynamische Daten enthält, braucht es auch einen Zuweisungsoperator und einen Copy-Konstruktor – Ein (virtueller) Destruktor garantiert die korrekte Zerstörung von Objekten L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 10.10 Seite 29 von 55 (Revision : 5. August 2014) Benutzerdefinierte Typumwandlung C++ Kap. 11.7.6 Wenn zwei ganze Zahlen unterschiedlichen Typs (z.B. int und short) addiert werden, so ist der Additionsoperator vom System für folgende Varianten definiert: • int+int • int+short • short+int • short+short Wenn nun eine neue Klasse VeryLargeInt eingeführt wird, so sind die Operatoren für diese Klasse noch nicht definiert. Nur schon für den Additionsoperator zwischen VeryLargeInt und int müssten folgende Varianten definiert werden: • int+VeryLargeInt • VeryLargeInt+int • VeryLargeInt+VeryLargeInt Dasselbe gilt auch für alle weiteren Operatoren. Für die Grundoperatoren +, -, *, /, +=, -=, *=, /= müssten somit 24 Operatoren definiert werden. Die einfachere Variante ist, wenn für jeden Typ eine Typumwandlung definiert wird. Somit braucht es pro Typ eine Umwandlungsfunktion, die Operatoren arbeiten anschliessend nur noch mit der Klasse VeryLargeInt. • VeryLargeInt+VeryLargeInt Für die Grundoperatoren +, -, *, /, +=, -=, *=, /= müssten nur noch die 8 Operatoren definiert werden. Zusätzlich müsste noch die Typumwandlung von jedem Typ (short, int, etc.) in VeryLargeInt definiert werden. Häufig werden Typumwandlungen aber auch mit Hilfe von Konstruktoren implementiert: VeryLargeInt(int); 10.11 Überladen von Operatoren C++ Kap. 11.7.3 Operatoren (z.B. +, ==, etc.) können wie Funktionen überladen werden. 10.11.1 Überladbare Operatorfunktionen in C++ • new • * • delete • ~ • += • / • new[ ] • ! • -= • % • • = • *= delete[ • ^ • < • /= • & ] • > • %= • | • + • 10.11.2 Randbedingungen • • • • • • • • • ^= &= |= « » • • • • • »= «= == != <= • • • • • >= && || ++ - • • • • • , ->* -> () [ ] Die Anzahl der Operanden (Argumente) muss gleich sein wie beim ursprünglichen Operator. Die Priorität des überladenen Operators kann nicht ändern. Neue Operatoren können nicht eingeführt werden. Default-Argumente sind bei Operatoren nicht möglich. 10.11.3 Operator Overloading als Elementfunktion Der neu definierte Operator wird als Elementfunktion implementiert. Damit ist der Zugriff auf private und protected Attribute der Klasse möglich. class TString { ... bool operator <(const T S t r i n g& s ) const ; ... }; bool T S t r i n g : : operator <(const T S t r i n g& s ) const { ... } Zwingend als Elementfunktion zu implementieren sind: Zuweisungsoperator =, Indexaufruf [ ], Funktionsaufruf () und Zeigeroperator -> L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 10.11.4 Operator Overloading als normale Funktion Die Operatorfunktionen werden meist als normale Funktion implementiert. Dadurch besteht jedoch kein Zugriff mehr auf die private und protected Elemente der Klasse. Die Operatorfunktion muss deshalb als friend deklariert werden. class TString { ... friend bool operator <(const T S t r i n g& s1 , const T S t r i n g& s 2 ) ; ... }; bool operator <(const T S t r i n g& s1 , const T S t r i n g& s 2 ) { ... } 5. August 2014 OOProg - Zusammenfassung 10.12 Strukturen und Unionen 10.12.1 Strukturen C Kap. 11.1 10.12.1.1 • • • • Seite 30 von 55 (Revision : 5. August 2014) C Kap. 11, C++ Kap. 11.8.2 Eigenschaften Daten, welche logisch zusammengehören, können zusammengenommen werden Die Struktur ist ein zusammengesetzter Datentyp, sie setzt sich aus den Feldern zusammen Die einzelnen Felder der Strukturen können (müssen aber nicht) unterschiedliche Typen haben Jedes Feld wird mit einem innerhalb der Struktur eindeutigen Namen versehen → Strukturspezifische Präfixe für die Feldnamen (z.B. Angestellter_Vorname) sind deshalb sinnlos. 10.12.1.2 Definition von Strukturtypen 10.12.1.3 struct StructName { FeldTyp1 f e l d 1 ; FeldTyp2 f e l d 2 ; FeldTyp3 f e l d 3 ; ... FeldTypN f e l d N ; }; struct A d r e s s e { char s t r a s s e [ 2 0 ] ; int hausnummer ; int p l z ; char o r t [ 2 0 ] ; }; struct A n g e s t e l l t e r { int personalnummer ; char name [ 2 0 ] ; char vorname [ 2 0 ] ; struct A d r e s s e wohnort ; struct A d r e s s e a r b e i t s o r t ; float gehalt ; }; • StructName kann frei gewählt werden • struct StructName ist hier ein selbst definierter Typ, der weiter verwendet werden kann • Der Datentyp ist definiert durch den Inhalt der geschweiften Klammer • Der Feldtyp kann wiederum eine Struktur sein 10.12.1.4 struct struct struct // e i n Beispiele für die Definition von Strukturvariablen Angestellter Angestellter Angestellter Array von 20 10.12.1.5 Beispiel mueller ; bonderer ; vertrieb [20]; S t r u k t u r v a r i a b l e n d e s Typs s t r u c t A n g e s t e l l t e r Operationen auf Strukturvariablen • Zuweisung: liegen zwei Strukturvariablen a und b vom gleichen Strukturtyp vor, so kann der Wert der einen Variablen der anderen zugewiesen werden → a=b; • Ermittlung der Grösse der Struktur: mit sizeof-Operator • Ermittlung der Adresse der Strukturvariablen: mit Adressoperator & 10.12.1.6 Zugriff auf eine Strukturvariable und deren Felder Der Zugriff auf ein Feld einer Strukturvariablen erfolgt über • den Namen der Strukturvariablen, • gefolgt von einem Punkt • und dem Namen des Feldes 10.12.1.7 ... wenn der Zugriff über einen Pointer erfolgt, über • den Namen des Pointers, • gefolgt von einem Pfeil (–>) • und dem Namen des Feldes Beispiele für den Zugriff auf eine Strukturvariable m u e l l e r . personalnummer = 3 4 2 5 9 ; b o n d e r e r . wohnort . p l z = 7 2 0 8 ; s t r c p y ( m u e l l e r . vorname , " F r i t z " ) ; p r i n t f ( "%s \n" , v e r t r i e b [ 1 4 ] . name ) ; p M i t a r b e i t e r −>personalnummer = 6 5 4 3 3 ; // e i n f a c h e Form b e i P o i n t e r ( ∗ p M i t a r b e i t e r ) . personalnummer = 6 5 4 3 3 ; // a l t e r n a t i v e Form p M i t a r b e i t e r −>a r b e i t s o r t . p l z = 8 6 4 0 ; L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 10.12.1.8 Seite 31 von 55 (Revision : 5. August 2014) Lage im Speicher • Die Felder einer Strukturvariablen werden nacheinander gemäss der Definition in den Speicher gelegt. • Gewisse Datentypen verlangen unter Umständen, dass sie auf eine Wortgrenze (gerade Adresse) gelegt werden. Dies nennt man Alignment. • Durch das Alignment kann es vorkommen, dass einzelne Bytes nicht verwendet werden, d.h. im Speicher ausgelassen werden. • Die Grösse einer Strukturvariablen kann nicht durch Addieren der Grössen der Felder ermittelt werden, nur sizeof () liefert den genauen Wert 10.12.1.9 struct B { int wert ; char t e x t [ 3 ] ; int z a h l ; }; Das int-Feld zahl muss auf einer geraden Adresse beginnen! Übergabe und Rückgabe von Strukturvariablen • Strukturvariablen können komplett an Funktionen übergeben werden • Der Rückgabetyp einer Funktion kann eine Struktur sein. Dabei wird die Strukturvariable direkt komplett übergeben • Zu beachten ist der Kopieraufwand bei der Übergabe, bzw. Rückgabe eines Wertes. In der Praxis soll deshalb mit Pointern gearbeitet werden! void f o o ( struct A n g e s t e l l t e r a ) ; // g r o s s e r Kopieraufwand , n i c h t i d e a l void f o o ( struct A n g e s t e l l t e r ∗ pa ) ; // nur P o i n t e r u e b e r g a b e , e f f i z i e n t void fooRead ( const struct A n g e s t e l l t e r ∗ pa ) // nur P o i n t e r u e b e r g a b e , read o n l y durch c o n s t 10.12.1.10 Initialisierung einer Strukturvariablen Eine Initialisierung einer Strukturvariablen kann direkt bei der Definition der Strukturvariablen mit Hilfe einer Initialisierungsliste durchgeführt werden (Reihenfolge beachten). Natürlich muss der Datentyp struct Angestellter bereits bekannt sein. struct A n g e s t e l l t e r maier = { 56321 , // personalnummer " Maier " , // name [ 2 0 ] "Hans" , // vorname [ 2 0 ] { " S c h i l l e r p l a t z " , // s t r a s s e [ 2 0 ] 14 , // hausnummer 75142 , // p l z " Esslingen " // o r t [ 2 0 ] } }; L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 10.12.2 10.12.2.1 Unions C Kap. 11.2 , C++ Kap. 11.8.2 Eigenschaften • ähnlich wie Struktur • beinhaltet auch mehrere Felder unterschiedlichen Typs • im Gegensatz zur Struktur ist aber nur ein einziges Feld jeweils aktiv (abhängig vom Typ) • Die Grösse einer Union ist so gross wie das grösste Feld der Union • Bei der Union sind dieselben Operationen wie bei einer Struktur definiert 10.12.2.2 Definition von Uniontypen • UnionName kann frei gewählt werden • union UnionName ist ein hier selbst definierter Typ, der weiter verwendet werden kann • Der Datentyp ist definiert durch den Inhalt der geschweiften Klammer • Der Feldtyp kann wiederum eine Union oder auch eine Struktur sein union UnionName { FeldTyp1 feld1 ; FeldTyp2 feld2 ; FeldTyp3 feld3 ; ... FeldTypN f e l d N ; }; 5. August 2014 OOProg - Zusammenfassung 10.12.2.3 Seite 32 von 55 (Revision : 5. August 2014) Beispiel 10.12.3 union Vario { int intNam ; long longNam ; double doubleNam ; } 10.12.3.1 Allgemeines zu Strukturen und Unions Codierstil • Strukturname und Unionname mit einem grossen Buchstaben beginnen! struct Angestellter; union Vario; • Struktur- und Unionvariablen mit einem kleinen Buchstaben beginnen • Bei Feldern von Strukturen und Union soll kein Präfix bei den Feldnamen verwendet werden 10.12.3.2 Vorsicht bei Unions • Der Programmierer muss verfolgen, welcher Typ jeweils in der Union gespeichert ist. Der Datentyp, der entnommen wird, muss der sein, der zuletzt gespeichert wurde. 11 11.1 Templates Motivation C++ Kap. 12.1 Wesentliche Vorteile von Templates sind: • Single-Source-Prinzip: Für x Varianten derselben Datenstruktur existiert genau eine Version des Sourcecodes, der geändert und gewartet werden muss. • Höhere Wiederverwendbarkeit: Klassen-Templates sind bei geeigneter Wahl ihrer Parameter allgemein einsetzbar und einfach wiederverwendbar. • Statische Bindung: Die Bindung zur Übersetzungszeit hat in Bezug auf Typsicherheit und Fehlererkennung zweifellos grosse Vorteile gegenüber generischen C-Lösungen mit void*-Zeigern, aber zum Teil auch gegenüber typisch objektorientierten Varianten wie sie zum Beispiel in Smalltalk üblich sind. • Dead Code: Traditionelle Bibliotheken belegen Speicher unabhängig davon, ob eine einzelne Funktion wirklich verwendet wird. Dies kann zu Dead Code führen, d.h. zu Code, der niemals ausgeführt wird. 11.2 Funktions-Templates C++ Kap. 12.2 • Templates verwenden den Typ als Variable. • Die Algorithmen können unabhängig vom Typ (generisch) implementiert werden. • Templates sind keine Funktionsdefinitionen, sie beschreiben dem Compiler nur, wie er den Code definieren soll, d.h. der Compiler nimmt den konkret verwendeten Typ, setzt diesen in das Template ein und compiliert den so erhaltenen Code. • Die Bindung zum konkreten Typ geschieht bereits zur Compiletime (early binding), sobald bekannt ist, mit welchem Typ das Template aufgerufen (benutzt) wird. 11.2.1 Syntax • Vor den Funktionsnamen wird das Schlüsselwort template, gefolgt von einer in spitzen Klammern eingeschlossenen Parameterliste gestellt. • Die Parameterliste enthält eine (nicht leere) Liste von Typ- und Klassenparametern, die mit dem Schlüsselwort class oder typename beginnen. Die einzelnen Parameter werden mit Komma getrennt. 11.2.2 template<typename ElemType> ElemType minimum ( ElemType e l e m F i e l d [ ] , int f i e l d S i z e ) ; template<typename A, typename B> int f o o (A a , B b , int i ) ; inline bei Templates inline muss zwischen lctemplate und dem Returntyp stehen. Achtung: Bei Verwendung von lcinline speziell zusammen mit Templates besteht die Gefahr von Code Bloat. 11.2.3 Überladen C++ Kap. 12.2.2 • Funktions-Templates können mit anderen Funktionstemplates und auch mit normalen Funktionen überladen werden. • Namensauflösung: – Compiler geht Liste der möglicherweise passenden Funktions-Templates durch und erzeugt die entsprechenden Template-Funktionen. – Ergebnis ist eine Reihe von (eventuell) passenden Template-Funktionen, ergänzt durch die vorhandenen normalen Funktionen. – Aus dieser ganzen Auswahl wird die am besten passende Funktion ausgewählt. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Seite 33 von 55 (Revision : 5. August 2014) 11.2.4 Ausprägung C++ Kap. 12.2.1 • Sobald ein Typ in einem FunktionsTemplate verwendet wird, erkennt der Compiler, dass es sich um ein Template handelt und prägt es für diesen Typ aus (implizite Ausprägung). • Für die Auflösung werden nur die Funktionsparameter betrachtet, der Rückgabetyp wird nicht ausgewertet. 11.2.5 Explizite Qualifizierung • Funktions-Templates können explizit mit einem Typ qualifiziert werden. int iF [ ] = { 1 , 5 4 , 3 4 , 2 3 , 6 7 , 4 } ; int i = minimum<int >(iF , s i z e o f ( iF ) / s i z e o f ( iF [ 0 ] ) ) ; int iF [ ] = { 1 , 5 4 , 3 4 , 2 3 , 6 7 , 4 } ; int i = minimum ( iF , s i z e o f ( iF ) / s i z e o f ( iF [ 0 ] ) ) ; 11.3 Klassen-Templates C++ Kap. 12.3 11.3.1 Definition C++ Kap. 12.3.1 • Klassen-Templates sind mit Typen oder Konstanten parametrisierbare Klassen. • Im Gegensatz zu Funktions-Templates können in Klassen-Templates auch die Attribute der Klassen mit variablen Typen ausgestattet sein. • Ein Klassen-Template kann auch von Ausdrücken abhängig sein. Diese Ausdrücke müssen aber zur Compiletime aufgelöst werden können. 11.3.2 Syntax • Die Syntax ist analog zu den FunktionsTemplates. • Vor die Klassendeklaration wird das Schlüsselwort template, gefolgt von einer in spitzen Klammern eingeschlossenen Parameterliste gestellt. • Die Parameterliste enthält eine (nicht leere) Liste von Typ- und Klassenparametern, die mit dem Schlüsselwort class oder typename beginnen oder auch von Ausdrücken. Die einzelnen Parameter werden mit Komma getrennt. 11.4 11.4.1 // D e k l a r a t i o n template<typename ElemType , int s i z e =100> c l a s s Stack { public : Stack ( ) ; ~Stack ( ) ; void push ( const ElemType& elem ) ; ElemType pop ( ) ; bool wasError ( ) const ; bool isEmpty ( ) const ; private : ElemType e l e m s [ s i z e ] ; int top ; bool i s E r r o r ; }; // D e f i n i t i o n template<typename ElemType , int s i z e > void Stack<ElemType , s i z e > : : push ( const ElemType& elem ) { } // Nutzung Stack<int , 10> // s1 i s t e i n Stack<int> // s2 i s t e i n Stack<double> // s3 i s t e i n s1 ; S t a c k mit 10 i n t ’ s s2 ; S t a c k mit 100 i n t ’ s ( D e f a u l t ) s3 ; S t a c k mit 100 d o u b l e ’ s Klassen-Templates und getrennte Übersetzung Möglichkeit 1 L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 11.4.2 Möglichkeit 2 5. August 2014 OOProg - Zusammenfassung 12 Seite 34 von 55 (Revision : 5. August 2014) Vererbung (Inheritance) Vererbung ist ein Konzept, das es erlaubt, neue Klassen auf Basis von alten Klassen zu definieren. Die neuen (Unter-, Sub-) Klassen besitzen, ohne Eingriffe in den Sourcecode der bereits bestehenden (Ober-, Basis-, Super-) Klassen, all deren Eigenschaften, sie erben deren Verhalten und Daten. Den Vorgang der Vererbung nennt man Ableiten. 12.1 Einsatz der Vererbung C++ Kap. 13.1 • Bestehende Klassen erweitern (zusätzliche Attribute und Elementfunktionen) • Bestehende Methoden einer Basisklasse ändern (überschreiben) • Einsatz nur wenn eine IST-EIN (is a) Beziehung besteht (z.B. Baum ist eine Pflanze, Blume ist eine Pflanze) 12.3 Zugriff auf Elemente der Basisklasse C++ Kap. 13.6 Bei Vererbung mit public (Normalfall): • Zugriff möglich auf alle public- und protected- Elemente der Basisklasse, die Zugriffsrechte (public, protected) der Basisklasse werden in der abgeleiteten Klasse beibehalten Bei Vererbung mit protected: • Zugriff möglich auf alle public- und protected- Elemente der Basisklasse, die Zugriffsrechte von public und protected der Basisklasse werden in der abgeleiteten Klasse zu protected Bei Vererbung mit private: • Zugriff möglich auf alle public- und protected- Elemente der Basisklasse, die Zugriffsrechte von public und protected der Basisklasse werden in der abgeleiteten Klasse zu private Bei allen drei: kein Zugriff auf private-Elemente der Basisklasse SuperHero sh ( " Speed " ) ; SuperHero p = new SuperHero ( "Power" ) ; 12.2 Ableiten einer Klasse C++ Kap. 13.2 Der Syntax der Ableitung einer Klasse ist oben aufgeführt. Als weiteres Beispiel ist im Anhang das Beispiel des ComicCharacters und SuperHero eingefügt. SuperHero ist ein ComicCharacter. • friend-Beziehungen werden nicht vererbt • Ein Objekt einer Oberklasse kann Objekte einer beliebigen Unterklasse aufnehmen • Ein Objekt einer Unterklasse kann keine Objekte der Oberklasse aufnehmen • Ein Objekt einer vererbten Klasse enthält alle Teile der Basisklasse und zusätzlich noch die spezifischen eigenen Teile. • Das Objekt ist somit mindestens so gross wie jenes der Basisklasse (es gibt keine Vererbung by reference) sh . f i g h t ( ) ; p−>p r i n t ( ) ; p−>dance ( ) ; sh . name = "X" ; // F e h l e r ! name i s t p r i v a t e i n // d e r B a s i s k l a s s e 12.4 Slicing Problem C++ Kap. 13.3 Links: Beim Kopieren werden nur die ComicCharacter-Teile berücksichtigt. Durch das Kopieren wird alles überflüssige weggeschnitten, übrig bleibt ein reines ComicCharacter Objekt im Fall von s führt dies dazu, dass die erweiterten SuperHero Daten und Funktionen verloren gehen. Rechts: Hier wird dank des Referenzparameters der gesamte Superheld ausgegeben. class SuperClass {}; c l a s s SubClass : public S u p e r C l a s s { } ; SuperClass super ; SubClass sub ; s u p e r = sub ; // ok sub = s u p e r ; // g e h t n i c h t L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 12.5 Seite 35 von 55 (Revision : 5. August 2014) Vererbung und Gültigkeitsbereiche C++ Kap. 13.4 Die Klasse C enthält alle Elemente von B und somit auch von A. A jedoch hat kein i und kann auch von keiner Oberklasse erben, dies ergibt den Fehler. B hat zwar auch kein j, erbt aber das von A. 12.6 12.6.1 Elementfunktionen bei abgeleiteten Klassen C++ Kap. 13.5 KonstruktorenC++ Kap. 13.5.1 In einem Konstruktor müssen alle Elemente eines Objekts (auch die ererbten) initialisiert werden. Folgendes Beispiel zeigt die direkte Initialisierung aller Elemente. Vor allem bei grossen oder mehreren Klassen ist dies nicht zielführend. Stattdessen wird das Chaining Prinzip angewandt. Falls kein Aufruf eines Basislklassen-Konstruktors in der Initialisierungsliste eines Konstruktors erscheint, so fügt der Compiler automatisch den Default-Konstruktor der Basisklasse ein. 12.6.3 Copy-Konstruktor C++ Kap. 13.5.2 • Wenn kein Copy Constructor explizit definiert wird, so erzeugt das System einen • Darin wird immer (ebenfalls automatisch) zuerst der Copy Constructor der Basisklasse aufgerufen Book : : Book ( const s t r i n g& aName , 12.6.4 Destruktor C++ Kap. 13.5.3 int aCode , double a P r i c e , • Auch Destruktoren werden nach dem int aRating , Chaining-Prinzip aufgebaut const s t r i n g& aComment , • Jede Klasse kümmert sich um die eiconst s t r i n g& aAuthor , genen Attribute und überlässt jene const s t r i n g& a T i t l e , der Basisklasse auch der Basisklasse const s t r i n g& a I s b n ) : • Destruktoren müssen nie explizit aufa u t h o r ( aAuthor ) , t i t l e ( a T i t l e ) , i s b n ( a I s b n ) // e i g e n e A t t r i b u t e gerufen werden. Der Destruktor der { Basisklasse wird am Schluss des DesetName ( aName ) ; // A t t r i b u t e d e r B a s i s k l a s s e struktors immer automatisch aufgesetCode ( aCode ) ; rufen setPrice ( aPrice ) ; Ein leerer Destruktor der Art s e t R a t i n g ( aRating ) ; ~SuperHero ( ) ; setComment ( aComment ) ; } ruft automatisch den Basisklassen-Destrukor (von 12.6.2 Chaining ComicCharacter) auf. Jede Klasse erledigt nur die eigenen Aufgaben. Aufgaben, die ererbte Methoden übernehmen können, werden diesen delegiert (Aufruf der jeweiligen Konstruk12.6.5 Überschreiben von ererbten toren) MethodenC++ Kap. 13.5.3 Wichtig: die Elemente der Basisklasse müssen immer als erste initialisiert werden • Falls ererbte Methoden nicht das erfüllen, was eine bestimmte KlasBook : : Book ( const s t r i n g& aName , se möchte, dann können diese Meint aCode , thoden neu definiert (überschrieben) double a P r i c e , werden. int aRating , • Methoden, welche in einer abgeleiconst s t r i n g& aComment , teten Klasse überschrieben werden const s t r i n g& aAuthor , können, müssen in der Basisklasse const s t r i n g& a T i t l e , mit virtual gekennzeichnet sein. const s t r i n g& a I s b n ) : • Im Anhang wird dieses überA r t i c l e ( aName , aCode , a P r i c e , aRating , aComment ) , schreiben einer Methode beim a u t h o r ( aAuthor ) , t i t l e ( a T i t l e ) , i s b n ( a I s b n ) SuperHero für die Funktion { dance() vorgenommen. Während } ein normaler ComicCharacter tanzt, wird diese Funktion beim SuperHero überschrieben und mit tanzt nicht überschrieben. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 13 Seite 36 von 55 (Revision : 5. August 2014) Polymorphismus / Mehrfachvererbung / RTTI Dieses Kapitel beschreibt die dynamischen objektorientierten Sprachmerkmale von C++. Erst durch diese wird C++ zu einer echten objektorientierten Programmiersprache. 13.1 13.1.1 Polymorphismus C++ Kap. 14.1 dynamische vs. statische Bindung Werden von einer Kasse A die Klassen B und C abgeleitet, so können Objekte vom Typ Zeigerauf A auch auf B- oder C-Objekte verweisen. Implementieren alle drei Klassen eine Operation foo jeweils verschieden so bewirkt die Anweisung anAPointer−>f o o ( ) ; in normalen Programmiersprachen den Aufruf von A::foo(). Dabei wird bereits zur Übersetzungszeit (so früh wie möglich; Early Binding) vom Compiler die Funktion foo der Klasse A eingebunden. Diese Art des Bindens wird statische Bindung (static binding) genannt, da sie unveränderbar ist. Die Variable anAPointer kann in C++ auch für Objekte der Klasse B oder C stehen. In echten objektorientierten Programmiersprache wird der obige Aufruf nicht zur Übersetzungszeit, sondern erst zur Laufzeit gebunden (dynamische Bindung, dynamic Binding). Beim Aufruf von anAPointer−>f o o ( ) ; wird der Typ des Objekts untersucht. In Abhängigkeit davon wird die Methode A::foo, B::foo oder C::foo aufgerufen. Dieses dynamische Verhalten wird als Polymorphismus bezeichnet. Damit dynamisch (zur Laufzeit) die verschiedenen Funktionen foo aufgerufen werden können, müssen diese Funktionen virtual sein. Im Beispiel rechts wird die Verwendung klar: • Der statische Datentyp bezeichnet den Datentyp bei der Deklaration. Im Beispiel: a ist ein Array von Pointer auf Article • Der dynamische Datentyp bezeichnet den effektiven Datentyp zur Laufzeit Im Beispiel: a[0] ist ein Pointer auf Book, a[1] ein Pointer auf CD, etc. 13.2 Virtuelle Elementfunktionen C++ Kap. 14.2 Virtuelle Elementfunktionen sind spezielle Funktionen, die nicht zur Übersetzungs- sondern zur Laufzeit gebunden werden. Es wird erst beim Auruf der Funktion entschieden, welche tatsächlich ausgeführt wird A::foo, B::foo oder C::foo • Funktionen, die dynamisch gebunden werden, muss bei der Deklaration das Schlüsselwort virtual vorangestellt werden (zwingend!). In der abgeleiteten Klasse soll (muss aber nicht) die Funktion auch mit virtual gekennzeichnet werden. Dies sieht wie folgt aus: class A { public : v i r t u a l A∗ f o o ( ) { } ; }; c l a s s B : public A { public : // v i r t u a l kann w e g g e l a s s e n werden v i r t u a l A∗ f o o ( ) { } ; // u e b e r s c h r e i b t A : : foo , // v i r t u e l l }; • Faustregel: Eine Funktion sollte als virtual deklariert werden, wenn sie in der abgeleiteten Klasse neu definiert (überschrieben) wird, sonst nicht! • Achtung: nicht mit Funktionsüberladung (gleicher Name aber unterschiedliche Signatur) verwechseln • Die neue (überschriebene) Methode muss dieselbe Signatur wie die Methode der Basisklasse haben. Sonst wird neue Methode eingeführt. - L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Seite 37 von 55 (Revision : 5. August 2014) 13.2.1 Aufruf von virtuelle Elementfunktionen C++ Kap. 14.2.2 Eine dynamische Methodenauflösung erfolgt über Zeiger Ein Aufruf mit einem Objekt und der Punktnotation wird oder Pointer: statisch aufgelöst: void printCC1 ( const ComicCharacter& r ) { // A u f r u f v i a R e f e r e n z r . p r i n t ( ) ; // dynamische A u f l o e s u n g } void printCC2 ( const ComicCharacter ∗ p ) { // A u f r u f v i a P o i n t e r p−>p r i n t ( ) ; // dynamische A u f l o e s u n g } SuperHero superman ( "Superman" , " can ␣ f l y " ) ; ComicCharacter d a g o b e r t ( " Dagobert " ) ; superman . p r i n t ( ) ; dagobert . print ( ) ; // s t a t i s c h e A u f l o e s u n g Dies kommt daher, dass ein echtes Objekt sein Typ nicht verändern kann (nicht polymorph) und der Compiler somit schon zur Übersetzungszeit entscheidet welche Funktion aufgerufen wird. Eine statische Auflösung wird auch erzwungen, wenn der Gültigkeitsbereich explizit angegeben wird: void printCC ( const ComicCharacter ∗ p ) { p−>ComicCharacter : : p r i n t ( ) ; // A u f r u f von ComicCharacter : : p r i n t ( ) } Wichtig ist auch: innerhalb von Konstruktoren und Destruktoren alle Methodenaufrufe statisch aufgelöst werden. 13.2.2 Polymorphe Klassen (virtuelle) Abstrakte Klassen Repräsentation polymorpher Objekte im Speicher C++ Kap. 14.2.6 • Eine Klasse, welche mindestens eine virtuelle Funktion deklariert, heisst virtuell (polymorph) • Virtuelle Klassen bewirken einen Mehraufwand für den Compiler und sind darum langsamer in der Ausführung • Konstruktoren sind nie virtuell • Destruktoren virtueller Klassen müssen immer als virtuell deklariert werden, sonst wird nur der Destruktor der Basisklasse aufgerufen • Nicht virtuelle Methoden dürfen nicht überschrieben werden (könnten technisch gesehen, führt aber zu unüberschaubaren Fehlern) 13.3 13.2.3 • In der Virtual Function Table (vtbl) vermerkt das System der Reihe nach die Adressen der für eine Klasse gültigen virtuellen Elementfunktionen • Das System legt für jede polymorphe Klasse eine vtbl an • Jedes Objekt einer polymorphen Klasse enthält einen Virtual Pointer vptr, welcher auf die vtbl der entsprechenden Klasse zeigt C++ Kap. 14.3 Eine abstrakte Klasse ist ein Klasse, die mehr oder weniger vollständig ist und dazu dient, Gemeinsamkeiten der abgeleiteten Klassen festzuhalten (z.B. ComicCharacter). ComicCharacter legt fest, dass alle Comicfiguren die Methoden print(), dance() und sing() verstehen. • Ein Kreis ist z.B. ein Spezialfall einer Ellipse. Es ist aber nicht sinnvoll, ihn so zu programmieren, da er sonst Eigenschaften erbt, die nicht verwendet werden • Es wäre möglich, Kreis und Ellipse als zwei unabhängige Klassen zu programmieren. Dann müssten aber alle Eigenschaften, die diese gemeinsam haben, doppelt programmiert werden • Dies versucht die objektorientierte Programmierung zu vermeiden • Es ist besser, die Eigenschaften, die Kreise und Ellipsen gemein haben, in einer Basisklasse zu programmieren • Die Kreis- und Ellipsenklasse erben dann parallel von der gemeinsamen Basisklasse • Die Basisklasse ist aber unvollständig, es handelt sich um eine abstrakte Klasse L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Seite 38 von 55 (Revision : 5. August 2014) • Es können keine Objekte von abstrakten Klassen gebildet werden • In C++ können rein virtuelle Funktionen (pure virtual functions) deklariert werden, die in der Basisklasse nicht von einer Definition begleitet werden v i r t u a l double getArea ( ) = 0 ; v i r t u a l double getArea ( ) = 0 { ... } • Klassen, die mindestens eine rein virtuelle Funktion deklarieren, sind abstrakte Klassen • Ist eine Klasse erst einmal als abstrakt definiert, kann diese nur durch Vererbung vervollständigt und dadurch nutzbar gemacht werden So ist die folgende Klasse eine abstrakte Klasse (da mind. 1 Funktion rein virtuell ist) Der folgende Aufruf führt daher beim Übersetzen zu einem Fehler. (Es können keine Objekte aus abstrakten Klassen erstellt werden) c l a s s ComicCharacter { ComicCharacter c c ; // F e h l e r public : // O b j e k t kann n i c h t e r s t e l l t werden ComicCharacter ( ) { } ; ComicCharacter ( const T S t r i n g& a S t r ) : name ( a S t r ) { } ; v i r t u a l ~ComicCharacter ( ) { } ; v i r t u a l void p r i n t ( ) = 0 ; // r e i n v i r t u e l l private : T S t r i n g name ; }; 13.4 Mehrfachvererbung C++ Kap. 14.3 Bei der Mehrfachvererbung wird eine Klasse von mehreren Basisklassen abgeleitet. So kann z.B. eine Klasse DuckHero definiert werden, die sowohl von SuperHero als auch von SingingComicCharacter erbt. Der wie Syntax bei der folgt (Basisklassen Mehrfachvererbung lautet durch Komma getrennt): c l a s s DuckHero : public Duck , public SuperHero { public : DuckHero ( const s t d : : s t r i n g& aName = " " , const s t d : : s t r i n g& aPower = " noPower " ) ; v i r t u a l ~DuckHero ( ) ; v i r t u a l void p r i n t ( ) const ; }; Durch die Mehrfachverbung treten oft Probleme auf. Problem 1 ist jenes der Mehrdeutigkeit von Methoden. Im Fall von print() ergeben sich mehrere Möglichkeiten. Um die Mehrdeutigkeit zu umgehen, muss der Gültigkeitsbereich angegeben werden: DuckHero dh ; dh . p r i n t ( ) ; // F e h l e r : m e h r d e u t i g ! ! dh . Duck : : p r i n t ( ) // ok Oder noch besser: Guter Einsatz der Mehrfachvererbung ist, wenn alle ausser höchstens einer Basisklasse ausschliesslich aus rein virtuellen Funktionen bestehen (Interfaces). Die neue Klasse implementiert dann die aufgelisteten Interfaces. Das obige Beispiel ist im Anhang unter 15.4 angehängt. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher v i r t u a l void DuckHero : : p r i n t { // b e s s e r e Loesung Duck : : p r i n t ( ) ; } Das Problem 2 ist das von mehrfachen Basisklassen (linker und rechter Baum). DuckHero ist von Duck und SuperHero abgeleitet und beinhaltet somit zwei ComicCharacter-Teile. Diese Mehrdeutigkeit kann durch virtuelle Basisklassen verhindert werden (siehe C++ Kap. 14.5 ) 5. August 2014 OOProg - Zusammenfassung 13.5 Seite 39 von 55 (Revision : 5. August 2014) RTTI (Laufzeit-Typinformation) C++ Kap. 14.3 RTTI (Run-Time Type Information) ist die Möglichkeit den Typ eines Objekts einer polymorphen Klasse festzustellen. Er steht ausschliesslich für polymorphe Klassen zur Verfügung und sollte sehr zurückhaltend eingesetzt werden. Der RTTI-Mechanismus besteht im Wesentlichen aus zwei Operatoren und einer Struktur: • Operator dynamic_cast • Operator typeid • Klasse type_info 13.5.1 Operator dynamic_cast Syntax: dynamic_cast<SuperHero ∗>(p ) • Versucht, den Zeiger p in einen Zeiger auf ein Objekt des Typs SuperHero umzuwandeln • Der dynamische Datentyp von p ist massgebend • Umwandlung wird dann durchgeführt, wenn p tatsächlich auf ein Objekt vom Typ SuperHero, bzw. auf eine davon abgeleitete Klasse zeigt. • Andernfalls ist das Resultat der Umwandlung der Nullpointer! 13.5.2 Operator typeid • Ermitteln des dynamischen Datentyps eines polymorphen Objekts • Ergibt eine Referenz auf ein Objekt des Typs type_info. Diese Klasse beinhaltet u.a. eine Methode name(), welche den Namen der Klasse zurückgibt. Beispiel: c o u t << "p␣ i s t ␣ e i n ␣ " << typeid ( ∗ p ) . name ( ) << " Objekt " ; 13.5.3 Struktur type_info Die Struktur muss eingebunden werden #include <t y p e i n f o > Sie bietet mind. folgende Funktionalität: • die Operatoren == und != • die Methode before • die Methode name (siehe Beispiel oben) 14 14.1 Exception Handling C++ Kap. 15 Exception vs. Error • Error: Abweichung zur Spezifikation ("falsch implementiert"). Errors sollten bei der Verifikation (Testen) entdeckt und eliminiert werden. • Exception: abnormale (aber vorhersehbare und mögliche) Bedingung bei der Programmausführung. 14.2 14.1.1 Mögliche Reaktionen auf Ausnahmen C++ Kap. 15.1 • Ignorieren: Motto: Augen zu und durch, eine sehr risikoreiche Variante. • Programmabbruch: Merkt immerhin, dass etwas nicht in Ordnung ist, die Reaktion ist aber unbefriedigend. Ist Exception Detection aber nicht eigentlich Exception Handling. • Exceptioncodes (nicht Fehlercodes): Funktionen geben als Rückgabewert, als Parameter oder global einen Ausnahmecode an. Handling Strategie von System Exceptions • In Java und C# gelangen die System Exceptions in die Sprache, d.h. eine LowLevel Exception wird in eine Exception der Programmiersprache gemappt. • Die Sprache C++ betreibt kein solches Exception Mapping, d.h. Low-Level Exceptions werden nicht von C++ geworfen und können auch nicht mit catch(...) abgefangen werden. • Der Hauptgrund dafür ist einmal mehr Effizienz. Wenn ständig Exceptions herumfliegen (auch wenn sie nicht abgefangen werden), dann beeinträchtigt das die Performance. • Einzelne Systemumgebungen betreiben dennoch Exception Mapping in C++ (z.B. Microsoft in Visual C++). L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung Exceptionhandling in C + + 14.3 C++ Kap. 15.2 • Exceptions werden in Form eines Objekts am Ort ihres Auftretens ausgeworfen (explizit oder auch äutomatisch"). • Exception Handler versuchen, diese Exception-Objekte aufzufangen. 14.3.1 Auslösen (Werfen) von Ausnahmen • Ausnahmen können mit dem Schlüsselwort throw explizit ausgeworfen werden. • Nach einem throw-Befehl wird das Programm abgebrochen und beim ersten passenden umgebenden Handler fortgesetzt. • Dabei werden alle lokalen Objekte wieder automatisch zerstört (Stack unwinding). • Geworfen werden kann ein beliebiges Objekt (üblich: ein spezifisches Ausnahmeobjekt). • (Ausschliesslich) innerhalb eines Exception Handlers ist auch die Form throw; erlaubt. Dadurch wird die Exception an den nächsten Handler weitergereicht (Exception propagation). 14.3.2 Seite 40 von 55 (Revision : 5. August 2014) Exception-Hierarchie in c l a s s Xcpt { public : Xcpt ( const char∗ t e x t ) ; ~Xcpt ( ) ; const char∗ g e t D i a g S t r ( ) const ; private : const char∗ d i a g S t r ; }; void a l l o c a t e F o o ( ) { b1 ( ) ; i f ( 0 == a l l o c a t i o n ( ) ) throw Xcpt ( " A l l o c a t i o n ␣ f a i l e d ! " ) ; b2 ( ) ; } // Testprogramm void t e s t F o o ( ) { a1 ( ) ; try { a2 ( ) ; allocateFoo ( ) ; a3 ( ) ; } catch ( const Xcpt& exc ) { c o u t << " Caught ␣ e x c e p t i o n . ␣ Text : ␣ " << exc . g e t D i a g S t r ( ) << e n d l ; } a4 ( ) ; } C++ C++ Kap. 15.4 Ausnahmeobjekte können beliebigen Typs sein (z.B. auch int). Meist werden jedoch spezifische hierarchisch organisierte Ausnahmeklassen verwendet. 14.3.3 Laufzeit- vs. Logische Fehler • Logische "Fehler"(logic_error) – Ausnahmen im Programmablauf, die bereits zur Entwicklungszeit ihre Ursache haben. – Theoretisch könnten diese Ausnahmen verhindert werden. • Laufzeit "Fehler"(runtime_error) – Nicht vorhersehbare (?) Ausnahmen wie z.B. arithmetische Überläufe. – Diese Ausnahmen treten erst zur Laufzeit auf, z.B. durch eine nicht erlaubte Benutzereingabe. 14.3.5 Excpetions und ihre Header 14.3.4 • • • • Exception Handler C++ Kap. 15.5 Ein oder mehrere Exception Handler können hintereinander definiert werden. Die einzelnen catch-Handler müssen sich in den Parametern unterscheiden. Wenn eine Exception geflogen kommt, wird der erste passende Handler genommen. Ein passender Handler macht ein catch auf genau diese Exception oder auf eine Basisklasse derselben. Deshalb (sehr wichtig): Der allgemeinste Handler (am meisten oben in der Hierarchie) muss als letzter definiert werden. L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 14.3.5.1 Seite 41 von 55 (Revision : 5. August 2014) Aufruf • Wenn kein Handler passt, dann wird im Aufrufstack nach oben gesucht, ob ein passender Handler vorhanden ist. • Wenn auch dort keiner gefunden wird, dann wird die Funktion terminate() aufgerufen. • terminate() beendet das Programm, kann aber auch selbst definiert werden. • Catch all: Der folgende Handler fängt ausnahmslos alle Exceptions ab (und muss wenn gewünscht deshalb immer als letzter aufgeführt werden): catch(...) { } L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 14.3.5.2 Exception Specification void foo() throw(/* Liste der Exceptions */); • Liste beschreibt, welche Exceptions von einem Aufrufer von foo() erwartet werden müssen. • Aber: garantiert auch, dass das Programm abstürzt, wenn eine andere als die spezifizierten Exceptions ausgeworfen wird, d.h. foo() muss dafür sorgen, dass wirklich nur die aufgelisteten Exceptions ausgeworfen werden. • Genauer: falls eine nicht spezifizierte Exception ausgeworfen wird, dann wird die Funktion unexpected() aufgerufen, welche üblicherweise das Programm abbricht. • unexpected() kann selbst definiert werden. 5. August 2014 OOProg - Zusammenfassung 15 15.1 (Revision : 5. August 2014) Seite 42 von 55 Beispiele Stack als Klasse // D a t e i : s t a c k . h // S c h n i t t s t e l l e n d e f i n i t i o n f ü r S t a c k // R. Bonderer , 1 1 . 0 4 . 2 0 1 3 #i f n d e f STACK_H_ #define STACK_H_ c l a s s Stack { public : Stack ( ) ; // D e f a u l t Ctor , i n i t i a l i s i e r t den S t a c k void push ( int e ) ; // l e g t e i n Element a u f den Stack , f a l l s d e r S t a c k noch n i c h t v o l l i s t // wasError ( ) g i b t Auskunft , ob push ( ) e r f o l g r e i c h war int pop ( ) ; // nimmt e i n Element vom Stack , f a l l s d e r S t a c k n i c h t l e e r i s t // wasError ( ) g i b t Auskunft , ob pop ( ) e r f o l g r e i c h war int peek ( ) const ; // l i e s t das o b e r s t e Element vom Stack , f a l l s d e r S t a c k n i c h t l e e r i s t // wasError ( ) g i b t Auskunft , ob p e e k ( ) e r f o l g r e i c h war bool isEmpty ( ) const ; // r e t u r n : t r u e : S t a c k i s t l e e r // f a l s e : sonst bool i s F u l l ( ) const ; // r e t u r n : t r u e : S t a c k i s t v o l l // f a l s e : sonst bool wasError ( ) const ; // r e t u r n : t r u e : O p e r a t i o n war f e h l e r h a f t // f a l s e : sonst private : Stack ( const Stack& s ) ; // v e r h i n d e r t das Kopieren von Stack −O b j e k t e n enum {maxElems = 1 0 } ; // Anzahl S t a c k e l e m e n t e int elem [ maxElems ] ; // Array f u e r S p e i c h e r u n g d e s S t a c k s int top ; // A r r a y i n d e x d e s n a e c h s t e n f r e i e n Elements mutable bool e r r o r ; // t r u e : F e h l e r p a s s i e r t ; f a l s e : s o n s t // m u t a b l e : auch c o n s t −Methoden können d i e s e s A t t r i b u t s e t z e n }; #endif // STACK_H_ // D a t e i : s t a c k . cpp // i m p l e m e n t i e r t S t a c k o p e r a t i o n e n // R. Bonderer , 1 1 . 0 4 . 2 0 1 3 #include " s t a c k . h" Stack : : Stack ( ) : top ( 0 ) , e r r o r ( f a l s e ) L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung (Revision : 5. August 2014) Seite 43 von 55 { } void Stack : : push ( int e ) { error = isFull (); if (! error ) { elem [ top ] = e ; ++top ; } } int Stack : : pop ( ) { e r r o r = isEmpty ( ) ; if (! error ) { −−top ; return elem [ top ] ; } else return 0 ; } int Stack : : peek ( ) const { e r r o r = isEmpty ( ) ; if (! error ) return elem [ top − 1 ] ; else return 0 ; } bool Stack : : isEmpty ( ) const { return top == 0 ; } bool Stack : : i s F u l l ( ) const { return top == maxElems ; } bool Stack : : wasError ( ) const { return e r r o r ; } L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 15.2 (Revision : 5. August 2014) Seite 44 von 55 Stack als Template /∗ ∗ stack . h ∗ ∗ Created on : 2 1 . 0 5 . 2 0 1 3 ∗ Author : r b o n d e r e ∗/ #i f n d e f STACK_H_ #define STACK_H_ template<typename ElemType , int s i z e = 10> c l a s s Stack { public : Stack ( ) ; // D e f a u l t −K o n s t r u k t o r void push ( const ElemType& e ) ; // l e g t e i n Element a u f den Stack , f a l l s d e r S t a c k noch n i c h t v o l l i s t // wasError ( ) g i b t Auskunft , ob push ( ) e r f o l g r e i c h war ElemType pop ( ) ; // nimmt e i n Element vom Stack , f a l l s d e r S t a c k n i c h t l e e r i s t // wasError ( ) g i b t Auskunft , ob pop ( ) e r f o l g r e i c h war ElemType peek ( ) const ; // l i e s t das o b e r s t e Element vom Stack , f a l l s d e r S t a c k n i c h t l e e r i s t // wasError ( ) g i b t Auskunft , ob p e e k ( ) e r f o l g r e i c h war bool isEmpty ( ) const ; // r e t u r n : t r u e : S t a c k i s t l e e r // f a l s e : sonst bool wasError ( ) const ; // r e t u r n : t r u e : O p e r a t i o n war f e h l e r h a f t // f a l s e : sonst private : ElemType e l e m s [ s i z e ] ; // S p e i c h e r f ü r S p e i c h e r u n g d e s S t a c k s int top ; // A r r a y i n d e x d e s n a e c h s t e n f r e i e n Elements mutable bool e r r o r ; // t r u e : F e h l e r p a s s i e r t ; f a l s e : s o n s t // m u t a b l e : auch c o n s t −Methoden können d i e s e s A t t r i b u t s e t z e n }; // u g l y i n c l u d e #include " s t a c k . cpp " #endif // STACK_H_ /∗ ∗ s t a c k . cpp ∗ ∗ Created on : 2 1 . 0 5 . 2 0 1 3 ∗ Author : r b o n d e r e ∗/ template<typename ElemType , int s i z e > Stack<ElemType , s i z e > : : Stack ( ) : top ( 0 ) , e r r o r ( f a l s e ) L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung (Revision : 5. August 2014) Seite 45 von 55 { } template<typename ElemType , int s i z e > void Stack<ElemType , s i z e > : : push ( const ElemType& e ) { e r r o r = top == s i z e ; if (! error ) { e l e m s [ top ] = e ; top++; } } template<typename ElemType , int s i z e > ElemType Stack<ElemType , s i z e > : : pop ( ) { e r r o r = top == 0 ; if (! error ) { −−top ; return e l e m s [ top ] ; } else return 0 ; } template<typename ElemType , int s i z e > ElemType Stack<ElemType , s i z e > : : peek ( ) const { e r r o r = top == 0 ; if (! error ) return e l e m s [ top − 1 ] ; else return 0 ; } template<typename ElemType , int s i z e > bool Stack<ElemType , s i z e > : : isEmpty ( ) const { return top == 0 ; } template<typename ElemType , int s i z e > bool Stack<ElemType , s i z e > : : wasError ( ) const { return e r r o r ; } L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 15.3 (Revision : 5. August 2014) Seite 46 von 55 Vererbung Comiccharacter /∗ ∗ ComicCharacter . h ∗/ #i f n d e f COMICCHARACTER_H_ #define COMICCHARACTER_H_ #include <s t r i n g > c l a s s ComicCharacter { public : ComicCharacter ( const s t d : : s t r i n g& aName = " " ) ; v i r t u a l ~ComicCharacter ( ) ; v i r t u a l void p r i n t ( ) const ; v i r t u a l void dance ( ) const ; v i r t u a l void s i n g ( ) const ; void setName ( const s t d : : s t r i n g& aName ) ; const s t d : : s t r i n g& getName ( ) const ; private : s t d : : s t r i n g name ; }; #endif /∗ COMICCHARACTER_H_ ∗/ /∗ ∗ ComicCharacter . cpp ∗/ #include <i o s t r e a m > #include <s t r i n g > #include " ComicCharacter . h" using namespace s t d ; ComicCharacter : : ComicCharacter ( const s t r i n g& aName ) : name ( aName ) { } ComicCharacter : : ~ ComicCharacter ( ) { } void ComicCharacter : : p r i n t ( ) const { c o u t << "Name␣ o f ␣ ComicCharacter : ␣ " << name << e n d l ; } void ComicCharacter : : dance ( ) const { c o u t << name << " ␣ d a n c e s " << e n d l ; } void ComicCharacter : : s i n g ( ) const { c o u t << name << " ␣ s i n g s " << e n d l ; } L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung (Revision : 5. August 2014) Seite 47 von 55 void ComicCharacter : : setName ( const s t r i n g& aName ) { name = aName ; } const s t r i n g& ComicCharacter : : getName ( ) const { return name ; } /∗ ∗ SuperHero . h ∗/ #i f n d e f SUPERHERO_H_ #define SUPERHERO_H_ #include <s t r i n g > #include " ComicCharacter . h" c l a s s SuperHero : public ComicCharacter { public : SuperHero ( const s t d : : s t r i n g& aName = " " , const s t d : : s t r i n g& thePower = " noPower " ) ; v i r t u a l ~SuperHero ( ) ; v i r t u a l void dance ( ) const ; v i r t u a l void f i g h t ( ) const ; const s t d : : s t r i n g& getSuperPower ( ) const ; void setSuperPower ( const s t d : : s t r i n g& thePower ) ; private : s t d : : s t r i n g superPower ; }; #endif /∗ SUPERHERO_H_ ∗/ /∗ ∗ SuperHero . cpp ∗/ #include <i o s t r e a m > #include <s t r i n g > #include " SuperHero . h" using namespace s t d ; SuperHero : : SuperHero ( const s t r i n g& aName , const s t r i n g& thePower ) : ComicCharacter ( aName ) , superPower ( thePower ) { } SuperHero : : ~ SuperHero ( ) { } void SuperHero : : f i g h t ( ) const { c o u t << getName ( ) << " ␣ f i g h t s " << e n d l ; } L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung (Revision : 5. August 2014) Seite 48 von 55 void SuperHero : : dance ( ) const // Ã 14 b e r s c h r e i b t d i e v i r t u a l Methode d e r B a s i s k l a s s e { c o u t << " S u p e r h e r o e s ␣don ’ t ␣ dance ! " << e n d l ; } void SuperHero : : setSuperPower ( const s t r i n g& thePower ) { superPower = thePower ; } const s t r i n g& SuperHero : : getSuperPower ( ) const { return superPower ; } /∗ ∗ ComicTest . cpp ∗/ #include <s t r i n g > #include <i o s t r e a m > #include " ComicCharacter . h" #include " SuperHero . h" using namespace s t d ; int main ( ) { ComicCharacter c c ( " Roadrunner " ) ; cc . sing ( ) ; c c . dance ( ) ; ComicCharacter ∗ pcc = new ComicCharacter ; pcc−>setName ( "Tom" ) ; pcc−>s i n g ( ) ; pcc−>dance ( ) ; SuperHero sh ( " Lucky ␣ Luke" , " Speed " ) ; sh . f i g h t ( ) ; c o u t << "Power␣ o f ␣ " << sh . getName ( ) << " ␣ i s ␣ " << sh . getSuperPower ( ) << e n d l ; sh . dance ( ) ; delete pcc ; return 0 ; } L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 15.4 (Revision : 5. August 2014) Seite 49 von 55 Mehrfachvererbung Comiccharacter /∗ ∗ ComicCharacter . h ∗/ #i f n d e f COMICCHARACTER_H_ #define COMICCHARACTER_H_ #include <s t r i n g > c l a s s ComicCharacter { public : ComicCharacter ( const s t d : : s t r i n g& aName = " " ) ; v i r t u a l ~ComicCharacter ( ) ; v i r t u a l void p r i n t ( ) const = 0 ; void setName ( const s t d : : s t r i n g& aName ) ; const s t d : : s t r i n g& getName ( ) const ; private : s t d : : s t r i n g name ; }; #endif /∗ COMICCHARACTER_H_ ∗/ /∗ ∗ ComicCharacter . cpp ∗/ #include <i o s t r e a m > #include <s t r i n g > #include " ComicCharacter . h" using namespace s t d ; ComicCharacter : : ComicCharacter ( const s t r i n g& aName ) : name ( aName ) { } ComicCharacter : : ~ ComicCharacter ( ) { } void ComicCharacter : : setName ( const s t r i n g& aName ) { name = aName ; } const s t r i n g& ComicCharacter : : getName ( ) const { return name ; } /∗ ∗ SuperHero . h ∗/ #i f n d e f SUPERHERO_H_ #define SUPERHERO_H_ L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung (Revision : 5. August 2014) Seite 50 von 55 #include <s t r i n g > #include " ComicCharacter . h" c l a s s SuperHero : v i r t u a l public ComicCharacter { public : SuperHero ( const s t d : : s t r i n g& aName = " " , const s t d : : s t r i n g& aPower = " noPower " ) ; v i r t u a l ~SuperHero ( ) ; v i r t u a l void f i g h t ( ) const ; v i r t u a l void p r i n t ( ) const ; const s t d : : s t r i n g& getSuperPower ( ) const ; void setSuperPower ( const s t d : : s t r i n g& thePower ) ; private : s t d : : s t r i n g superPower ; }; #endif /∗ SUPERHERO_H_ ∗/ /∗ ∗ SuperHero . cpp ∗/ #include <i o s t r e a m > #include <s t r i n g > #include " SuperHero . h" using namespace s t d ; SuperHero : : SuperHero ( const s t r i n g& aName , const s t r i n g& aPower ) : ComicCharacter ( aName ) , superPower ( aPower ) { } SuperHero : : ~ SuperHero ( ) { } void SuperHero : : f i g h t ( ) const { c o u t << getName ( ) << " ␣ f i g h t s " << e n d l ; } void SuperHero : : p r i n t ( ) const { c o u t << " S u p e r h e r o ␣ " << getName ( ) << " ␣ Superpower : ␣ " << superPower << e n d l ; } void SuperHero : : setSuperPower ( const s t r i n g& thePower ) { superPower = thePower ; } const s t r i n g& SuperHero : : getSuperPower ( ) const { return superPower ; } /∗ ∗ SingingComicCharacter . h L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung (Revision : 5. August 2014) Seite 51 von 55 ∗/ #i f n d e f SINGINGCOMICCHARACTER_H_ #define SINGINGCOMICCHARACTER_H_ #include <s t r i n g > #include " ComicCharacter . h" c l a s s S i n g i n g C o m i c C h a r a c t e r : v i r t u a l public ComicCharacter { public : S i n g i n g C o m i c C h a r a c t e r ( const s t d : : s t r i n g& aName = " " ) ; v i r t u a l ~S i n g i n g C o m i c C h a r a c t e r ( ) ; v i r t u a l void dance ( ) const ; v i r t u a l void s i n g ( ) const ; }; #endif /∗ SINGINGCOMICCHARACTER_H_ ∗/ /∗ ∗ S i n g i n g C o m i c C h a r a c t e r . cpp ∗/ #include <i o s t r e a m > #include " S i n g i n g C o m i c C h a r a c t e r . h" using namespace s t d ; S i n g i n g C o m i c C h a r a c t e r : : S i n g i n g C o m i c C h a r a c t e r ( const s t r i n g& aName ) : ComicCharacter ( aName ) { } SingingComicCharacter : : ~ SingingComicCharacter ( ) { } void S i n g i n g C o m i c C h a r a c t e r : : dance ( ) const { c o u t << getName ( ) << " ␣ d a n c e s " << e n d l ; } void S i n g i n g C o m i c C h a r a c t e r : : s i n g ( ) const { c o u t << getName ( ) << " ␣ s i n g s " << e n d l ; } /∗ ∗ Duck . h ∗/ #i f n d e f DUCK_H_ #define DUCK_H_ #include <s t r i n g > #include " S i n g i n g C o m i c C h a r a c t e r . h" c l a s s Duck : public S i n g i n g C o m i c C h a r a c t e r { public : Duck ( const s t d : : s t r i n g& aName = " " ) ; L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung (Revision : 5. August 2014) Seite 52 von 55 v i r t u a l ~Duck ( ) ; v i r t u a l void p r i n t ( ) const ; void c a c k l e ( ) const ; }; #endif /∗ DUCK_H_ ∗/ /∗ ∗ Duck . cpp ∗/ #include <i o s t r e a m > #include "Duck . h" using namespace s t d ; Duck : : Duck ( const s t r i n g& aName ) : S i n g i n g C o m i c C h a r a c t e r ( aName ) { } Duck : : ~ Duck ( ) { } void Duck : : p r i n t ( ) const { c o u t << "Duck␣ " << getName ( ) << e n d l ; } void Duck : : c a c k l e ( ) const { c o u t << getName ( ) << " ␣ c a c k l e s " << e n d l ; } /∗ ∗ DuckHero . h ∗/ #i f n d e f DUCKHERO_H_ #define DUCKHERO_H_ #include "Duck . h" #include " SuperHero . h" c l a s s DuckHero : public Duck , public SuperHero { public : DuckHero ( const s t d : : s t r i n g& aName = " " , const s t d : : s t r i n g& aPower = " noPower " ) ; v i r t u a l ~DuckHero ( ) ; v i r t u a l void p r i n t ( ) const ; }; #endif /∗ DUCKHERO_H_ ∗/ /∗ ∗ DuckHero . cpp ∗/ #include <s t r i n g > L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung (Revision : 5. August 2014) Seite 53 von 55 #include "DuckHero . h" using namespace s t d ; DuckHero : : DuckHero ( const s t r i n g& aName , const s t r i n g& aPower ) : ComicCharacter ( aName ) , // must be h e r e b e c a u s e o f v i r t u a l i n h e r i t a n c e Duck ( aName ) , SuperHero ( aName , aPower ) { } DuckHero : : ~ DuckHero ( ) { } void DuckHero : : p r i n t ( ) const { Duck : : p r i n t ( ) ; SuperHero : : p r i n t ( ) ; } /∗ ∗ ComicTest . cpp ∗/ #include <i o s t r e a m > #include <t y p e i n f o > #include "DuckHero . h" using namespace s t d ; int main ( ) { DuckHero dh ( " CrazyDuck " , " F l a s h s p e e d " ) ; dh . f i g h t ( ) ; dh . s i n g ( ) ; dh . p r i n t ( ) ; return 0 ; } L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung 15.5 (Revision : 5. August 2014) Seite 54 von 55 RTTI /∗ ∗ DynCastTest . cpp ∗/ #include <i o s t r e a m > #include <iomanip> #include <t y p e i n f o > using namespace s t d ; class A { public : v i r t u a l void fooA ( ) {} v i r t u a l ~A( ) {} private : int data ; }; class B { public : v i r t u a l void fooB ( ) { } v i r t u a l ~B( ) {} private : double data ; }; c l a s s C : public A, public v i r t u a l B { }; int main ( ) { A∗ pa = new A; B∗ pb = new B ; C∗ pc1 = (C∗ ) pa ; C∗ pc2 = dynamic_cast<C∗>(pa ) ; // C∗ pc3 = (C∗) pb ; // g e h t n i c h t , da C von B v i r t u a l e r b t C∗ pc4 = dynamic_cast<C∗>(pb ) ; cout cout cout cout cout << << << << << hex << showbase ; "pa␣ ␣=␣ " << pa << e n d l ; "pb␣ ␣=␣ " << pb << e n d l ; " pc1 ␣=␣ " << pc1 << e n d l ; " pc2 ␣=␣ " << pc2 << e n d l ; c o u t << " pc4 ␣=␣ " << pc4 << e n d l ; c o u t << "Typ␣ von ␣pa␣ : ␣ " << typeid ( ∗ pa ) . name ( ) << e n d l ; c o u t << "Typ␣ von ␣pb␣ : ␣ " << typeid ( ∗ pb ) . name ( ) << e n d l ; c o u t << "Typ␣ von ␣ pc1 : ␣ " << typeid ( ∗ pc1 ) . name ( ) << e n d l ; delete pa ; delete pb ; return 0 ; } /∗ Der m ö g l i c h e Output s i e h t wie f o l g t aus : pa = 0 x80069008 pb = 0 x 8 0 0 6 f c c 8 pc1 = 0 x80069008 L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014 OOProg - Zusammenfassung pc2 pc4 Typ Typ Typ (Revision : 5. August 2014) Seite 55 von 55 = 0 = 0 von pa : 1A von pb : 1B von pc1 : 1A Bemerkungen : pc1 und pa haben d i e s e l b e Adresse a b e r u n t e r s c h i e d l i c h e Typen . pc1 i s t unschön (C−Cast ) . Es i s t zwar e i n e k o r r e k t e Anweisung , i s t a b e r u n s i n n i g , da e i n P o i n t e r n i c h t a u f e i n O b j e k t s e i n e r O b e r k l a s s e z e i g e n s o l l ( umgekehrt i s t ok ) . pc2 i s t d i e s a u b e r e V a r i a n t e von pc1 . Der T y p e c a s t i s t e r f o l g r e i c h , f a l l s pa w i r k l i c h a u f e i n O b j e k t d e r K l a s s e C o der a u f e i n e UNTER−K l a s s e von C z e i g t . Da d i e s n i c h t d e r F a l l i s t , e r h ä l t pc2 den N u l l p o i n t e r . pc3 f u n k t i o n i e r t n i c h t , da B e i n e VIRTUELLE B a s i s k l a s s e von C i s t . Zudem i s t e s w i e d e r e i n unschöner C−Cast , d e r n i c h t v e r w e n d e t werden s o l l t e . pc4 e r g i b t aus diesem Grund den N u l l p o i n t e r , i s t a b e r e i n e k o r r e k t e Anweisung . ∗/ L. Leuenberger, M. Ehrler, C. Ham, L. Däscher 5. August 2014