OOProg - Zusammenfassung

Werbung
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
Zugehörige Unterlagen
Herunterladen