C / C++ - TU Ilmenau

Transcription

C / C++ - TU Ilmenau
Wissenschaftliches Rechnen – Grundlagen
(Studienjahr 2015/2016)
Stefan Brechtken
Technische Universität Ilmenau
FG Numerische Mathematik und Informationsverarbeitung
98684 Ilmenau, Germany
e-mail: [email protected]
Kapitel 4
Programmierung
mit C / C++
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
19. April 2016
Inhaltsverzeichnis
4 C / C++
4.1 Grundlagen . . . . . . . . . . . . . . . . . .
4.1.1 Datentypen . . . . . . . . . . . . . . .
4.1.2 l, r values, L bzw. R Werte . . . . . . .
4.1.3 Konstanten . . . . . . . . . . . . . . .
4.1.4 Variablen . . . . . . . . . . . . . . . .
4.1.5 Operatoren . . . . . . . . . . . . . . .
4.1.6 Zeiger / Pointer . . . . . . . . . . . . .
4.1.7 Typkonvertierung . . . . . . . . . . . .
4.1.8 Funktionen - eine Einführung . . . . . .
4.1.9 Ein erstes Programm . . . . . . . . . .
4.1.10 Speicherklassen und Gültigkeitsbereiche . .
4.2 Programmstrukturen (Kontrollstrukturen)
4.2.1 Ausdruck, Anweisung . . . . . . . . . .
4.2.2 Sequenz . . . . . . . . . . . . . . . .
4.2.3 Alternative . . . . . . . . . . . . . . .
4.2.4 Zyklus . . . . . . . . . . . . . . . . .
4.2.5 Sprünge schwierig! . . . . . . . . . . .
4.3 Speicherverwaltung und Datenstrukturen .
4.3.1 nicht dynamische Arrays (auf dem Stack) .
4.3.2 dynamische Arrays (auf dem Heap) in C .
4.3.3 Speicher Debugger . . . . . . . . . . .
4.4 Funktionen II . . . . . . . . . . . . . . . .
4.4.1 Überladen von Funktionen . . . . . . . .
4.4.2 Standard (default) - Werte . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
77
77
77
78
78
78
80
82
83
84
85
87
88
88
89
90
92
94
95
95
97
99
99
99
99
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
4.4.3
4.4.4
4.4.5
4.4.6
4.4.7
4.5
4.6
4.7
Referenzen . . . . . . . . . . . . . . . . .
Funktionspointer . . . . . . . . . . . . . .
λ Funktionen . . . . . . . . . . . . . . . .
Argumente der Hauptfunktion . . . . . . . .
variadische Funktionen . . . . . . . . . . .
Objektorientierung, Klassen . . . . . . . . . .
4.5.1 Namensräume / Namensbereiche . . . . . . .
4.5.2 Strukturen als Datentypen . . . . . . . . . .
4.5.3 Strukturen mit Methoden . . . . . . . . . .
4.5.4 Strukturen mit Konstruktoren, Destruktoren, Kopierkonstruktoren . . . . . . . . . . . . . .
4.5.5 Objektorientierte Speicherverwaltung: new und
delete . . . . . . . . . . . . . . . . . . .
4.5.6 Überladen von Operatoren . . . . . . . . . .
4.5.7 Klassen . . . . . . . . . . . . . . . . . . .
4.5.8 Freunde . . . . . . . . . . . . . . . . . .
Vererbung . . . . . . . . . . . . . . . . . . . .
4.6.1 Ändern der Zugreifbarkeit . . . . . . . . . .
4.6.2 Konstruktoren und Zuweisung . . . . . . . .
Templates . . . . . . . . . . . . . . . . . . . .
4.7.1 Funktionstemplates . . . . . . . . . . . . .
4.7.2 Klassentemplates . . . . . . . . . . . . . .
4.7.3 variadische Templates . . . . . . . . . . . .
4.7.4 Standardbibliotheken C . . . . . . . . . . .
4.7.5
Standardbibliotheken C++ . . . . . . . . .
76
100
100
101
102
103
104
104
105
106
107
108
109
110
111
111
113
113
116
116
117
119
121
122
Kapitel 4
C / C++
4.1
Grundlagen
4.1.1 Datentypen
In C/C++ muss für jede Variable explizit der Datentyp zugeordnet werden. Die größe eines Datentypes kann von der Implementierung abhängen.
Am häufigstens genutzte grundlegende Datentypen:
Datentyp
kein Datentyp
logisch
Ganzzahl
Typname / keyword
void
bool
unsigned|signed|_ char
unsigned|signed|_ short
unsigned|signed|_ int
unsigned|signed|_ long
unsigned|signed|_ long long
Fließkommaz. unsigned|signed|_ float
unsigned|signed|_ double
unsigned|signed|_ long double
Größe
0 bit, 0 byte
oftmals 8 Bit, 1 Byte
≥8 (oft 8) Bit
≥16 (oft 16) bit
≥16 (oft 32) bit
≥32 (oft 32) bit
≥64 (oft 64) bit
32 bit, IEEE754
64 bit, IEEE754
≥80 (oft 80) bit
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
78
4.1.2 l, r values, L bzw. R Werte
Definition: L Werte besitzen eine Speicheradresse auf die vom ausführenden Programm zugegriffen werden kann (z.B. Variablen). Diese
Werte können immer verändert werden, außer dies wurde explizit ausgeschlossen. Es gibt also modifizierbare und nicht modifizierbare L Werte.
R Werte sind alle Ausdrücke, insbesondere auch L Werte und Ausdrücke
die keine L Werte sind.
4.1.3 Konstanten
Konstanten sind Ausdrücke mit festen Werten.
Syntax numerischer Konstanten:
⟨Vz⟩
⟨Z⟩
⟨lZ⟩
⟨Sg⟩
⟨Ganzzahl⟩
⟨Sf⟩
⟨Exp⟩
⟨Fließkommazahl⟩
::=
::=
::=
::=
::=
::=
::=
::=
+| − |
0|1|2|3|4|5|6|7|8|9
⟨Z⟩{⟨Z⟩}
u | l | ll | ul | ull |
⟨Vz⟩⟨lZ⟩⟨Sg⟩
f|l|
E⟨Vz⟩⟨lZ⟩ |
⟨Vz⟩{⟨Z⟩}.⟨lZ⟩⟨Exp⟩⟨Sf⟩
4.1.4 Variablen
Namen
• Unterscheidung Groß-/Kleinschreibung
• Schlüsselwörter dürfen nicht überdeckt werden
⟨Start⟩ ::= ⟨Buchstabe⟩ | _
⟨Ende⟩ ::= ⟨Buchstabe⟩|⟨Z⟩|_
⟨Name⟩ ::= ⟨Start⟩{⟨Ende⟩}
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
79
Variableneinführung / Wiederholung
• Deklaration:
Die Bekanntmachung des Namens einer Variablen ohne einen Speicherbereich zuzuweisen
• Definition:
Die Zuweisung eines Speicherbereichs an eine Variable
• Initialisierung
Die Zuweisung eines Wertes während der Deklaration
• einfache Variable ist ein Objekt eines Standarddatentyps, der
Wert kann sich im Programmverlauf ändern
• Variablen werden auf dem Stack angelegt, falls der Programmierer
sie nicht explizit auf dem Heap anlegt
• skope - Gültigkeitsbereich einer Variablen
• lifetime - Lebensdauer einer Variablen
• globale Variablen [böse!]
– Gültigkeitsbereich und Lebensdauer sind das gesamte Programm
– alle Variablen die auf der äußersten Ebene (nicht innerhalb eines
Blockes) definiert werden
• lokale Variablen - alle in einem Block definierten Variablen
• statische Variablen
Statische Variablen haben den gleichen Gültigkeitsbereich wie “normale” Variablen aber ihre Lebensdauer entspricht der Programmlaufzeit. Die Deklaration/Definition erfolgt innerhalb des Gültigkeitsbereiches nur genau ein mal.
keyword: static
80
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
• konstante Variablen Veränderung der Variablen nach der Initialisierung nicht möglich. Ausdrücke zur Initialisierung müssen zur
Übersetzungszeit auswertbar sein.
keyword: const
Deklaration und Definition sowie Initialisierung
⟨qualifier⟩
⟨init⟩
⟨VarDef⟩
⟨VarDefinitionen⟩
::=
::=
::=
::=
⟨static⟩ | ⟨const⟩ | ⟨static const⟩ |
= ⟨Ausdruck⟩ | (⟨Ausdruck⟩) |
⟨qualifier⟩ ⟨typ⟩ ⟨Name⟩ ⟨init⟩;
⟨qualifier⟩ ⟨typ⟩ ⟨Name⟩ ⟨init⟩
{, ⟨Name⟩ ⟨init⟩};
Variablen- deklaration/-definition/-initialisierung in C/C++ :
⟨qualifier⟩⟨typ⟩⟨Variablenname⟩⟨init⟩;
4.1.5 Operatoren
Priorität
1
2
2
2
2
2
2
3
3
3
3
Operator
::
+ + −−
()
[]
.
−>
⟨typ⟩()
+ + −−
+−
!∼
(⟨type⟩)
Beschreibung
Gültigkeitsbereichsauflösung
Suffix-Inkrement,-Dekrement
Funktionsaufruf
Arrayaufruf
Elementauswahl per Referenz
Elementauswahl per Pointer
funktionale Typumwandlung
Präfix-Inkrement,-Dekrement
unäres Plus, Minus
logische, bitweise Verneinung
C Typenumwandlung
Rtg
L→R
L→R
L→R
L→R
L→R
L→R
L→R
R→L
R→L
R→L
R→L
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
Priorität
3
3
3
3
3
4
5
6
7
8
8
9
10
11
12
13
14
15
15
15
15
15
15
15
15
16
17
Operator
*
&
sizeof
new new[]
delete delete[]
.* − > ∗
*/%
+<< >>
< <=
> >=
== ! =
&
^
|
&&
||
?:
=
+= -=
*= /=
&= ^= |=
%=
<<=
>>=
throw
,
Beschreibung
Dereferenzierung
Adressoperator
bekannte Funktion
dynamische Speicherallokation
dynamische Speicherdeallokation
Dereferenzzeiger
Multiplikation, Division, Modulo
Addition, Subtraktion
Bitweise Links-, Rechtsverschiebung
vergleichende Operatoren <, ≤
vergleichende Operatoren >, ≥
vergleichende Operatoren =, ̸=
Bitweise UND
Bitweise XOR
Bitweise ODER
logisches UND
logisches ODER
Bedingungsoperator
Zuweisungsoperator
Zuw. und Addition / Subtraktion
Zuw. und Multiplikation / Division
Zuw. und bitweise UND/XOR/ODER
Zuw. und Restberechnung
Zuw. und bitweise links Verschiebung
Zuw. und bitweise rechts Verschiebung
Wurfoperator, Ausnahmebehandlung
Komma
81
Rtg
R→L
R→L
R→L
R→L
R→L
L→R
L→R
L→R
L→R
L→R
L→R
L→R
L→R
L→R
L→R
L→R
L→R
R→L
R→L
R→L
R→L
R→L
R→L
R→L
R→L
R→L
L→R
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
82
4.1.6 Zeiger / Pointer
• Der Adressoperator &
– Syntax: &⟨Name⟩
– Der Adressoperator liefert die Adresse des ersten Bytes des einer
Variablen zugeordneten Speicherbereichs.
• Deklaration von Zeigervariablen (kurz Zeiger / Pointer)
– Diese Adressen haben eigene Datentypen, die spezifizieren welche Art von Daten bei der Adresse beginnen!
Syntax: ⟨Typ⟩ ∗ ⟨ZeigerName⟩
• Inhalts-, Indirektions-, Dereferenzierungs-Operator
– Der Zugriff auf den Wert an einer Adresse, die in einer Zeigervariablen gespeichert ist geschieht mithilfe der Indirektion.
– Syntax: ∗⟨ZeigerName⟩
– Der hier zurückgegebene Datentyp entspricht dem Typen der
Zeigervariablen.
– ∗⟨ZeigerName⟩ ist ein L-Wert !
• konstante Variablen / Pointer, eine exemplarische Übersicht
– int* – pointer auf int
– int const * – pointer auf const int
– int * const – const pointer auf int
– int const * const – const pointer auf const int
– const int * == int const *
– const int * const == int const * const
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
83
• Bemerkungen:
– Zeiger ohne zugewiesene Adresse / Speicher sollte IMMER der
NULL pointer zugewiesen werden, alles andere ist böse!
⟨Typ⟩ ∗ ⟨ZeigerName⟩ = NULL;
– Mehrfachzeiger sind möglich, beliebig viele ∗ möglich.
– Ist der Datentyp der zugrunde liegenden Daten nicht bekannt
kann ein generischer Zeiger verwendet werden (Typ void). Adressarithmetik hier unmöglich.
4.1.7 Typkonvertierung
• gemischter Ausdruck:
Ein Ausdruck heißt gemischt, wenn Operanden unterschiedlichen
Datentyps vorkommen, dies betrifft insbesondere Zuweisungen.
• implizite Konvertierung :
– C++ lässt gemischte Ausdrücke zu und nimmt erforderliche
Konvertierungen automatisch vor.
– Grundregel: unterschiedliche Typen werden in den Typ der genauesten Komponente umgewandelt - entsprechend der Auswertungsreihenfolge von Ausdrücken.
• explizite Konvertierung :
– Casts setzt man zur expliziten Steuerung der Typkonvertierung,
oftmals bei vorgegebenen Datentypen der Operanden, ein.
– Syntax:
i) in C:
ii) in C++:
(⟨neuer Typ⟩) ⟨Variable⟩
⟨neuer Typ⟩ (⟨Variable⟩)
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
84
4.1.8 Funktionen - eine Einführung
Zum Funktionskonzept gehören Mechanismen zur
1. Deklaration und Definition
• C++ fordert, dass jede Funktion vor ihrer erstmaligen Benutzung (Aufruf) dem Compiler bekannt gemacht also mindestens
deklariert wird.
• Funktionen dürfen nur auf globaler Ebene deklariert bzw. definiert werden.
• Syntax Deklaration:
⟨static⟩
⟨const⟩
⟨data_type⟩
⟨Name_init⟩
⟨VarDef_init⟩
static|
const|
⟨const⟩ ⟨typ⟩
⟨Name⟩ | ⟨Name⟩⟨init⟩ |
⟨data_type⟩ ⟨Name_init⟩
{, ⟨data_type⟩ ⟨Name_init⟩}
⟨FktDekl⟩ ::= ⟨static⟩⟨data_type⟩ ⟨Name⟩
(⟨VarDef_init⟩);
::=
::=
::=
::=
::=
• Syntax Definition:
⟨VarDef_no_init⟩ ::= ⟨data_type⟩ ⟨Name⟩
{, ⟨data_type⟩ ⟨Name⟩}
⟨static_inline⟩ ::= static |inline |static inline |
⟨FktDef⟩ ::= ⟨static_inline⟩⟨data_type⟩
⟨Name⟩(⟨VarDef_no_init⟩)
⟨block⟩
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
85
• Syntax Definition und Deklaration:
⟨Fkt⟩ ::= ⟨static_inline⟩⟨data_type⟩ ⟨Name⟩
(⟨VarDef_init⟩)⟨block⟩
• Funktionsblock
– Muss mindestens eine return Anweisung enthalten, außer der
Ergebnistyp ist void,
– Der return Ausdruck muss vom Funktionsergebnistyp sein,
häufig implizites typecasting !
– return ⟨Ausdruck⟩; gibt die Auswertung des Ausdrucks als
Rückgabewert der Funktion an und springt zurück zur Position des Funktionsaufrufs.
– Der Funktionsblock bildet einen eigenen Gültigkeitsbereich
(wie in Matlab)
2. Funktionsaufruf, Funktionsparameter werden später behandelt.
4.1.9 Ein erstes Programm
Programmstrukturierung
• Funktionsdeklarationen werden häufig in “header-files”, mit Endung
.h ausgelagert während Funktionsdefinitionen in Dateien mit Endung .cpp ausgelagert werden. Man spricht bei header-files von der
Schnittstelle (interface), während die .cpp Dateien die Implementation darstellen.
• Es gibt eine große Zahl von Funktionsbibliotheken die über die entsprechenden header-files eingebunden werden können.
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
86
• Man bindet header-files über präprozessordirektiven ein. Syntax:
– #include 〈bibliothek〉
Spitze Klammern werden genutzt um Standardbibliotheken in
den Bibliotheksverzeichnissen des Compilers suchen zu lassen
– #include “header-file”
Anführungszeichen werden genutzt um eigene Bibliotheken /
Header-files im Arbeitsverzeichnis suchen zu lassen
• Jedes Programm besitzt mindestens eine Funktion, die Hauptfunktion. Diese heißt immer main.
– Bei einem Programmaufruf wird nur genau die Hauptfunktion
ausgeführt, sonst nichts.
– Die Hauptfunktion sollte einen int zurück geben.
◦ Der Rückgabewert enthält die Information ob das Programm
erfolgreich oder mit Fehlern beendet wurde.
◦ Erolgreiche Terminierung wird mit 0 symbolisiert, frei definierbare Fehlercodes sind alle Zahlen ungleich 0.
◦ Eine Programmdokumentation sollte die möglichen Fehlercodes und deren Bedeutung dokumentieren.
– Im einfachsten Fall besitzt die Hauptfunktion keine Engabeparameter.
• Vorerst genügen uns genau 2 Bibliotheken:
– 〈cstdio〉, C-Header : 〈stdio.h〉(standard input output)
– 〈cmath〉, C-Header : 〈math.h〉(mathematical functions)
– Der Unterschied zwischen der 1. und 2. Version liegt im BibliotheksNamensbereich (library namespace). Wir nutzen bis zum Erreichen der Namespaces ausschließlich die 2. Variante !
– Funktionen in diesen Bibliotheken:
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
87
◦ http://www.cplusplus.com/reference/cstdio/
◦ http://www.cplusplus.com/reference/cmath/
• Eingabe / Ausgabe vorerst über printf, scanf
4.1.10 Speicherklassen und Gültigkeitsbereiche
• Speicherbereiche
Der Speicherbereich eines Programms wird üblicherweise in folgende
Bestandteile aufgeteilt (siehe GNU Binutils size):
– Codesegment (CS), hier befindet sich der Objektcode
– Datensegment (DS), hier befinden sich statische (auch globale)
Variablen die initialisiert wurden
– “block started by symbol” (BSS), hier befinden sich statische
(und globale) Variablen die nicht initialisiert wurden, diese werden explizit auf 0 initialisiert !
– Stack, hier befinden sich automatisch verwaltete Variablen, automatische Verwaltung nach LIFO Prinzip entsprechend der Lebensdauer (lifetime) einer Variablen
– Heap, hier befinden sich dynamisch verwaltete Variablen, werden
später betrachtet
• Speicherklassen
Die Speicherklasse kann durch die Syntax der Deklaration, die Position im Quellcode oder beides ermittelt werden. Es existieren die
Speicherklassen:
– statisch - im Datensegment
◦ Zuweisung des Speicherplatzes mit Programmstart, besteht
bis zum Programmende
◦ Alle Funktionen sind Objekte mit statischer Dauer (sowie
globale, static Variablen)
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
88
– automatisch - im Stack
◦ Die Objekte werden im Stack oder in einem Register angelegt, sobald die Abarbeitung in einem einschließenden Block
eintritt.
◦ Automatische Freigabe des Speicherplatzes bei Verlassen des
Blocks
– dynamisch - im Heap
◦ Objekte mit dynamischer Dauer werden während der Laufzeit
mit besonderen Funktionsaufrufen angelegt und freigegeben,
unabhängig von der Blockstruktur.
◦ Anlegen, Löschen sind signifikant langsamer als auf dem Stack.
– Register - in einem Register
◦ Es sind nur wenige CPU-Register für explizite Programmierung verfügbar, siehe MMX / SSE / AVX.
4.2
Programmstrukturen (Kontrollstrukturen)
4.2.1 Ausdruck, Anweisung
• Ausdruck (expression)
– Ein Ausdruck ist eine Sequenz von Operatoren und Operanden
die die Berechnung eines Wertes beschreibt oder die ein Objekt
oder eine Funktion bezeichnet oder die Seiteneffekte generiert.
– zugehöriger Operator: “,”
Das Komma ist der Operator mit der niedrigsten Priorität und
separiert innerhalb von Sequenzen von Ausdrücken, also:
〈Ausdruck_1〉,...,〈Ausdruck_n〉
Der Rückgabewert einer solchen Sequenz ist die Auswertung von
〈Ausdruck_n〉
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
89
• Anweisung (statement)
Eine Anweisung ist eine Aktion die ausgeführt werden soll. Dies
ist erkennbar an einem beendenden Semikolon. Die einzigen Anweisungen die nicht auf ein Semikolon enden sind if, while, do, for,
switch, goto Anweisungen, Sprungziele, inline Assemblercode (Keyword asm) sowie Blöcke.
4.2.2 Sequenz
• auch Zusammengesetzte Anweisungen bzw. Block
• Zusammenfassung von Anweisungen mittels { } zu einer Anweisung,
die überall stehen kann, wo eine einzige Anweisung verlangt wird
• Form:
{
Anweisung_1
...
Anweisung_n
}
• Ein Block generiert eine neue Stack-Ebene! Dies hat Auswirkungen
auf alle Variablen innerhalb des Blocks. [Welche?]
• Es ist Standard nach einer geschweiften Klammer eine Einrückung
von mindestens 2 Leerzeichen bis zum Ende des Blocks vorzunehmen
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
90
4.2.3 Alternative
• if - Anweisung
– Syntax:
⟨else_Zweig⟩ ::= else⟨Anweisung⟩ |
⟨if_Anweisung⟩ ::= if(⟨Bedingung⟩)⟨Anweisung⟩ ⟨else_Zweig⟩
– Form:
if( <Bedingung> )
{
<Anweisung_1>
...
<Anweisung_n>
}
else
{
<Anweisung_1>
...
<Anweisung_n>
}
– 〈Bedingung〉 ist ein logischer Ausdruck
– 〈Anweisung〉 ist nur genau eine einzelne Anweisung ! Eine if Bedingung führt also immer nur die unmittelbar folgende erste
Anweisung aus!
– Um mehrere Anweisungen unter zu bringen verwendet man Sequenzen/Blöcke. Es verbessert die Übersichtlichkeit auch einzelne Anweisungen in Blöcke zu schreiben.
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
91
• switch - Anweisung
– Form:
switch( <Ausdruck> )
{
case <Konstante_1>:
<Anweisungen>
break;
case <Konstante_2>:
<Anweisungen>
break;
...
default:
<Anweisungen>
}
– 〈Ausdruck〉 muss ein Ganzzahltyp sein oder explizit konvertiert
werden.
– 〈Ausdruck〉 wird von oben mit jeder Konstanten verglichen
und der Block wird ab der ersten Übereinstimmung ausgeführt,
nach der ersten Übereinstimmung gibt es keine weiteren Vergleiche! Darum :
– break ist in switch Anweisungen optional und hat 2 Bedeutungen:
◦ in Switch Anweisungen bewirkt es einen Sprung zum Ende
des Blocks
◦ in Schleifen bewirkt es einen Sprung zum Ende des Blocks
und einen Abbruch der Schleife
– default wird immer ausgeführt, falls der Block nicht vorher verlassen wurde (durch ein break)
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
92
4.2.4 Zyklus
• while Schleife – kopfgesteuerte Schleife
– Syntax:
⟨while_Schleife⟩ ::= while(⟨Bedingung⟩)⟨Anweisung⟩
– Form:
while( <Bedingung> )
{
<Anweisung_1>
...
<Anweisung_n>
}
– 〈Bedingung〉 ist ein logischer Ausdruck
– 〈Anweisung〉 ist nur genau eine einzelne Anweisung ! Es wird
genau die unmittelbar folgende Anweisung bedingt wiederholt.
– Der Übersichtlichkeit halber bietet es sich an immer Blöcke zu
verwenden.
• do-while Schleife – fußgesteuerte Schleife
– Syntax:
⟨do-while_Schleife⟩ ::= do⟨Anweisung⟩while(⟨Bedingung⟩)
– Form:
do
{
<Anweisung_1>
...
<Anweisung_n>
}
while( <Bedingung> );
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
93
– 〈Bedingung〉 ist ein logischer Ausdruck
– 〈Anweisung〉 ist nur genau eine einzelne Anweisung ! Mehrere
Anweisungen resultieren in einen Übersetzungsfehler.
– Der Übersichtlichkeit halber bietet es sich an immer Blöcke zu
verwenden.
• for Schleife
– Syntax:
⟨for_Schleife⟩ ::= for(⟨Ausdruck_1⟩; ⟨Bedingung⟩;
⟨Ausdruck_2⟩)⟨Anweisung⟩
– Form:
for( <Ausdruck_1>; <Bedingung>; <Ausdruck_2> )
{
<Anweisung_1>
...
<Anweisung_n>
}
– 〈Bedingung〉 ist ein logischer Ausdruck, der zu Beginn eines
jeden Schleifendurchlaufs neu ausgewertet wird.
– 〈Anweisung〉 ist nur genau eine einzelne Anweisung ! Es wird
genau die unmittelbar folgende Anweisung bedingt wiederholt.
– 〈Ausdruck_1〉 enthält typischerweise die Laufvariablendefinitionen mit Initialisierung, die resultierende Anweisung wird genau einmal und als erstes abgearbeitet.
– 〈Ausdruck_2〉 enthält typischerweise die Veränderungen der
Laufvariablen, die zugehörige Anweisung wird am Ende eines
jeden Schleifendurchlaufs abgearbeitet.
– Der Übersichtlichkeit halber bietet es sich an immer Blöcke zu
verwenden.
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
94
4.2.5 Sprünge schwierig!
• Sprungmarken, Sprünge (böse!)
– Diese sollten nur eingesetzt werden, wenn die nicht-Verwendung
von Sprüngen einen extremen Mehraufwand bedeuten oder wenn
Hardware Restriktionen eine extrem effiziente Implementierung
verlangen.
– Sprungmarken
◦ Syntax: 〈Name〉:
◦ Erstellt einen Sprungpunkt zu dem bedingungslos gesprungen
werden kann.
– Sprung
◦ Syntax: goto 〈Name〉;
◦ Springt bedingungslos zum Sprungpunkt mit Namen 〈Name〉
◦ Übergehen von Deklarationen entspricht dem Syntaxfehler
der nicht-Deklaration (compile error)
◦ Verflachung und Vertiefung des Stacks wird durchgeführt,
wobei lokale Variablen verloren gehen können
• Schlüsselwort continue
– Syntax: continue;
– continue; springt zum Ende des Schleifenblocks,
– es wird also der Rest der momentanen Iteration übersprungen
und die nächste beginnt.
• Schlüsselwort break
– Syntax: break;
– break; springt unmittelbar hinter das Ende des Schleifenblocks,
– die Schleife wird hier also vollständig beendet
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
95
• Schlüsselwort return
– Syntax: return 〈Ausdruck〉;
– return springt zum Funktionsaufruf und leitet die Rückgabewertübergabe ein,
– die aufgerufene Funktion wird beendet
4.3
Speicherverwaltung und Datenstrukturen
4.3.1 nicht dynamische Arrays (auf dem Stack)
• Deklaration und Definition sowie ggf. Initialisierung eines nicht dynamischen Arrays:
– Syntax:
⟨dim⟩ ∈ N
⟨a_init⟩ ::= = {⟨Ausdruck_1⟩, . . . , ⟨Ausdruck_n⟩}
⟨Array⟩ ::= ⟨Typ⟩⟨Name⟩[⟨dim⟩]{[⟨dim⟩]}⟨a_init⟩
– Bedeutung:
◦ Es wird eine Zeigervariable mit Namen 〈Name〉 angelegt
◦ diese Variable ist vom Datentyp 〈Typ〉 (*) [dim_2]...[dim_m],
wobei m die Anzahl der Dimensionen des Arrays ist.
◦ Es wird ein eindimensionaler zusammenhängender Speicherbereich der Länge dim_1 · . . . · dim_m angelegt und die
Adresse auf das erste Byte wird der Variablen 〈Name〉 zugewiesen.
◦ Der “exotische” Datentyp der Variablen 〈Name〉 bewirkt eine
besondere Behandlung beim Zugriff auf den zugeordneten
Speicherbereich.
– Bemerkungen
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
96
◦ 〈dim〉muss ein zur Übersetzungszeit (compile-time) auswertbarer Ausdruck sein !
◦ es ist nach C11 / C++11 Standard nicht möglich die größe
eines Feldes im Stack zur Laufzeit festzulegen
◦ die Feldgröße darf also nicht von Nutzereingaben abhängen
◦ von 99 - 2011 gab es VLAs (variable length arrays) und
VLAPs (variable length array pointers) in C, diese gibt es
in g++ immer noch, darum ist es in g++ und möglicherweise anderen Compilern möglich dynamische Stack arrays
zu verwenden oder den Zugriff auf den Heap über VLAPs zu
vereinfachen – BÖSE!!
◦ Compiler - flag -pedantic
◦ standard stack-size [16 KB - 8 MB] ⇐⇒ stack-overflow
• Array-Zugriff
– Adressarithmetik:
◦ Array-Zugriffe verwenden immer Adressarithmetik
◦ Sei eine beliebige Zeigervariable 〈Typ〉* 〈Name〉 gegeben
◦ 〈Name〉+ 1 liefert eine Erhöhung des Wertes von 〈Name〉, also die Erhöhung einer Adresse, um n Byte. Hierbei entspricht
n der größe des Datentyps 〈Typ〉 in Byte.
◦ ist 〈Name〉 ein Array liefert *(〈Name〉 + 1) den Wert des 2.
Elements im Feld.
◦ *〈Name〉 liefert den Wert des 1. Elements im Feld !
– Arrayaufruf [•]
◦ Der Array Aufruf ist definiert als
〈Name〉[n] := *(&〈Name〉[0]+n)
◦ Hieraus ergeben sich alle Eigenschaften des Arrayaufrufs,
denn diese werden vollständig von der zugrunde liegenden
Zeiger - Arithmetik geerbt.
97
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
◦ 〈Name 〉[n] = *(&〈Name〉[0]+n) = n[〈Name 〉]
◦ n muss einen Ganzzahltypen haben !
– mehrdimensionaler Arrayaufruf [•] · · · [•]
◦ Pointer auf VLAs/SLAs (static length arrays) sind “besonders”:
⟨name⟩[n1][n2] · · · [nm]
[ m
m
∏
∏
dimi · n1 +
dimi · n2 + . . .
=⟨name⟩
i=2
] i=3
+ dimm · nm−1 + nm


((int∗)⟨name⟩)=
m
z
}|
{ ∏
= ∗ &⟨name⟩[0] · · · [0] +
dimi · n1 + . . . + nm
i=2
– Übergabe eines statischen Feldes a an Funktionen (eine Auswahl):
i) ⟨Typ⟩(∗⟨Name⟩)[⟨dim2⟩] · · · [⟨dimn⟩]
ii) ⟨Typ⟩⟨Name⟩[ ][⟨dim2⟩] · · · [⟨dimn⟩]
iii) ⟨Typ⟩⟨Name⟩[⟨dim1⟩][⟨dim2⟩] · · · [⟨dimn⟩]
4.3.2 dynamische Arrays (auf dem Heap) in C
• Hier ist die C-Bibliothek 〈stdlib〉 notwendig. Enthaltene Funktionen
und Definitionen (Auswahl)
– Konstanten:
◦ NULL – Null pointer
◦ RAND_MAX – größte Zufallszahl
◦ EXIT_SUCCESS – Code für erfolgreiche Terminierung
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
98
◦ EXIT_FAILURE – Code für gescheiterte Terminierung
– Datentypen:
◦ size_t – nicht negative Ganzzahl, repräsentiert meist Bytegrößen
– Funktionen
◦ Initialisierung des Zufallszahlengenerators
void srand (unsigned int seed);
◦ Zufallszahl zwischen 0 und RAND_MAX generieren
int rand (void);
◦ Beenden des Programms mit Abbruchstatus
void exit (int status);
◦ Ausführen eines Systembefehls
int system (const char* command);
• Allokation
– mit malloc, Anlegen von Speicherplatz ohne Initialisierung:
void* malloc (size_t size);
– mit calloc, Anlegen von Speicherplatz mit 0 Initialisierung:
void* calloc (size_t num, size_t size);
• Deallokation / Reallokation
– mit realloc, Anlegen von neuem Speicherplatz, kopieren der Inhalte und Freigabe des alten Speicherplatzes:
void* realloc (void* pointer, size_t size);
– mit free, Freigabe des Speicherplatzes:
void free (void* pointer);
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
99
4.3.3 Speicher Debugger
Wir verwenden das OpenSource Programm Dr.Memory (verwendbar unter Windows und Linux, für MAC wird valgrind empfohlen). Die Kompilieroption ggdb ist notwendig um den Positionen im Binärcode Zeilen im
Quellcode zuordnen zu können (unter Linux / MAC genügt mit valgrind
die Option g).
Mögliche Speicherfehler, detektierbar:
• Zugriff auf nicht initialisierten Speicher
• Zugriff auf nicht adressierbaren Speicher
• Zugriff auf freigegebenen Speicher
• Mehrfachfreigaben
• Speicherlecks
4.4
Funktionen II
4.4.1 Überladen von Funktionen
• Nur in C++
• Eine Funktion kann mehrfach deklariert/definiert werden, sofern sich
die verschiedenen Definitionen in den Eingangsargumenten unterscheiden.
• Wie erkennt der Compiler welche Funktion verwendet werden soll ?
=⇒ siehe Vorlesungsbeispiel
4.4.2 Standard (default) - Werte
• Nur in C++
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
100
• Verwendete Eingangsparameter werden von links an die Parameterliste übergeben.
• Siehe Beispiel
4.4.3 Referenzen
• in C und C++
• Syntax:
⟨Ref⟩ ::= ⟨Typ⟩&⟨Name⟩ = ⟨Var⟩
• Referenzen gehören eigentlich zur Variablen / Speicherverwaltung.
• Vergleichbar mit pointern, aber
– Es gibt keine Nullreferenz
– sie können Synonym für die zugrundeliegende Variable verwendet
werden
– Referenzen müssen zur Deklaration initialisiert werden.
– Referenzen können ihr Ziel nicht ändern, sie zeigen ab der Initialisierung immer auf das gleiche Objekt
• Welche Übergabemöglichkeiten von Daten an Funktionen gibt es?
Welchen Platz nehmen hier Referenzen ein ? =⇒ Vorlesungsbeispiel
4.4.4 Funktionspointer
• in C und C++
• Syntax:
⟨Fkt_z⟩ ::= ⟨Typ⟩
• Bedeutung:
(
)(
)
∗ ⟨Name⟩ ⟨Typ⟩, . . . , ⟨Typ⟩
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
101
– erster 〈Typ〉 entspricht Rückgabedatentyp der Funktion
– alle anderen Typen (in hinteren Klammern) entsprechen den Typen der Funktionsargumente
– 〈Name〉 ist der Name des Funktionspointers.
• Verwendungsmöglichkeiten (Auswahl):
– Übergabe von Funktionen an Funktionen (z.B. sort)
– Bedingtes Setzen des Funktionspointers am Programmanfang
=⇒ Vermeidung von Fallunterscheidungen ...
• =⇒ siehe Vorlesungsbeispiel
4.4.5 λ Funktionen
• Nur ab C++11
• Entsprechen anonymen Funktionen in Matlab, können in anderen
Funktionen definiert werden
• Man sollte nur Hilfsfunktionen die außerhalb einer Funktion nicht
mehr benötigt werden über lambdas abdecken.
• Syntax:
⟨Fkt_z⟩ = [&⟨Name⟩, . . . , &⟨Name⟩](⟨VarDef_no_init⟩){⟨Block⟩}
• Bedeutung:
– in eckigen Klammern werden Variablen aus der Umgebung erfasst, die vor der λ-Funktion deklariert wurden
– in runden Klammern sind die Eingabeargumente
– in geschweiften Klammern wie gewohnt der Funktionsblock
– der Rückgabetyp wird logisch deduziert
• siehe Vorlesungsbeispiel
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
102
4.4.6 Argumente der Hauptfunktion
• Syntax; Die Hauptfunktion kann 2 Eingangsparameter enthalten:
int main( int argc, char* argv[] )
{
...
}
• Bedeutung:
– argc gibt an wie viele Argumente an das Programm übergeben
wurden, das erste Argument ist der Programmname / Aufruf
– argv ist ein pointer auf ein Feld dessen Elemente Statische Felder
vom Typ char sind
In anderen Worten: ein Feld, dessen Elemente die Übergebenen
Argumente als strings (Zeichenketten) sind
– Argumente dienen Typischerweise der Programmsteuerung
• nützliche Funktionen aus der stdlib.h:
– double atof (const char* str); – Char Feld zu double
– int atoi (const char * str); – Char Feld zu int
– int atol (const char * str); – Char Feld zu long int
• nützliche Funktionen aus der string.h:
– int strcmp ( const char * str1, const char * str2 ); – string
Vergleich
– char * strcpy ( char * destination, const char * source ); – string
Kopie
• siehe Vorlesungsbeispiel
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
103
4.4.7 variadische Funktionen
• Bedeutung: Deklaration einer Funktion mit ”unbestimmt“ vielen
Eingangsparametern.
• Was ist (vermutlich) die obere Grenze für die Anzahl der Eingangsparameter =⇒ Was passiert (vermutlich) im Speicher?
• C-Bibliothek für Parameter der variadischen Funktion notwendig
stdarg.h; Enthaltene Funktionen und Definitionen
– Datentypen:
◦ va_list
– Funktionen:
◦ void va_start( va_list 〈Name1〉, 〈Name2 〉 )
=⇒ stellt Eingangsparameter nach dem benannten Parameter Name2 in Variablenliste Name1 bereit.
◦ 〈Typ〉 va_arg( va_list 〈Name〉, 〈Typ〉 )
=⇒ extrahiert nächsten Parameter aus der Variablenliste
Name und interpretiert ihn entsprechend des Typs.
◦ void va_end( va_list 〈Name〉 )
=⇒ Freigabe / Aufräumen des Stacks, auch bei Fehlern,
muss nach va_start in gleicher Funktion aufgerufen werden!
– Operatoren
◦ ... – Ellipse
• Verwendung: Informationen über Art und Anzahl der Parameter sollten im ersten Parameter enthalten sein. Iteration über die Parameter
ist dann mittels va_arg möglich.
• siehe Vorlesungsbeispiel
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
4.5
104
Objektorientierung, Klassen
Alle folgenden Konzepte existieren (außer bei expliziter gegenteiliger Angabe) nur in C++.
4.5.1 Namensräume / Namensbereiche
• Bedeutung: Vermeidung der Überschneidung von Objekten
(Funktions-, Konstanten-, Typdefinitionen) auf globaler Ebene innerhalb verschiedener Bibliotheken / Projekte / Projektteile
• Syntax
– für die Erzeugung eines Namensraumes:
namespace 〈Name_Namensraum〉
{
〈Definitionen/Deklarationen〉
}
– für die Verwendung eines Objektes aus einem Namensraum:
〈Name_Namensraum〉::〈Name_Objekt〉
– für die generelle Verwendung eines Objektes aus einem Namensraum im momentanen Block (weitere Verwendung im Block ohne Bereichsauflösung)
using 〈Name_Namensraum〉::〈Name_Objekt〉
• Der Namensraum std enthält in c++ die Funktionen, Konstanten
und Typdefinitionen der Standardbibliotheken – vergleiche Unterschied #include <stdio.h> und #include <cstdio>
• siehe Vorlesungsbeispiel
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
105
4.5.2 Strukturen als Datentypen
• Gruppierung mehrerer verschiedener Datentypen unter einem Namen bzw. Benutzerdefinierte Datentypen
– einzelne Elemente innerhalb einer Struktur werden Instanzvariable (Membervariable) genannt.
– einzelne Elemente können unterschiedliche Längen/Größen (in
Byte) haben. −→ Was passiert dann im Speicher?
– Eine Realisierung einer Struktur wird auch als Objekt bezeichnet.
– eine Struktur ”entspricht“ einem Datentyp und ein Objekt ist
eine Variable dieses Datentyps
• Syntax:
– Deklaration
struct 〈Strukturname〉{ 〈Definitionen〉 };
– Deklaration mit Definition der Objekte Obj_1 Obj_2
struct 〈Strukturname〉
{
〈Definitionen〉
} Obj_1, Obj_2;
– Zugriff; Pointer; Pointerzugriff, vereinfachter Pointerzugriff
〈Objektname〉.〈Membername〉
〈Strukturname〉* 〈Pointername〉= &〈Objektname〉
(*〈Pointername〉).〈Membername〉
〈Pointername〉->〈Membername〉
• Verwendung:
– Ein Objekt das eine Struktur ist kann wie ein Datentyp verwandt
werden (Deklaration, Definition, Pointer, Referenzen)
– eine Struktur kann wie ein Stack Feld initialisiert werden
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
106
• siehe Vorlesungsbeispiel
4.5.3 Strukturen mit Methoden
• Man kann Strukturen Methoden (Funktionen) zuweisen. Eine Methode einer Struktur
– ist eine Funktion (mit allen Eigenschaften)
– hat direkten Zugriff auf die Membervariablen des Objektes von
dem aus sie aufgerufen wird
– kann nur von einem Objekt aus aufgerufen werden
• Syntax
– Deklaration
struct 〈Strukturname〉{ 〈FktDekl〉 };
– Definition wie bei Funktionen, allerdings muss die Zugehörigkeit
zum Namensbereich der Struktur angegeben werden
〈Strukturname〉::〈Fkt.Name〉
– Deklaration und Definition
struct 〈Strukturname〉{ 〈Fkt〉 };
– Zugriff; Pointer; Pointerzugriff, vereinfachter Pointerzugriff
〈Objektname〉.〈Fkt.Name〉(...)
〈Strukturname〉* 〈Pointername〉= &〈Objektname〉
(*〈Pointername〉).〈Fkt.Name〉(...)
〈Pointername〉->〈Fkt.Name〉(...)
• Verwendung
– Funktionen die das Objekt verändern (den inneren Zustand) werden häufig als Methoden implementiert.
– Methoden erlauben es Operatoren zu überladen [später].
• siehe Vorlesungsbeispiel
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
107
4.5.4 Strukturen mit Konstruktoren, Destruktoren, Ko-
pierkonstruktoren
• Konstruktor
– Man kann ein neu angelegtes Objekt bei der Definition initialisieren bzw. den internen Zustand festlegen. Dies realisiert eine
spezielle Methode – der Konstruktor.
– Ein Konstruktor wird immer aufgerufen, sobald ein Objekt dieser
Struktur angelegt wird.
– Der Konstruktor ist dadurch gekennzeichnet, dass er den gleichen Namen wie die Struktur besitzt.
– Syntax:
struct <Strukturname>
{
<Strukturname>(){}
}
– Es existieren Initialisierungslisten, falls x,y,z Membervariablen
sind, so ist ein Konstruktor mit Initialisierungsliste gegeben durch:
〈Strukturname〉( int a, int b, int c):x(a),y(b),z(c) {...}
– Was passiert im Speicher? Wie muss man mit dem Heap umgehen ?
• Destruktor
– Man kann ein Objekt bei der Freigabe auf die Freigabe ”vorbereiten“, dies realisiert eine spezielle Methode – der Destruktor.
– Ein Destruktor sollte immer aufgerufen werden, sobald ein Objekt dieser Struktur freigegeben wird ( Wann passiert das ? ).
– Der Destruktor ist dadurch gekennzeichnet, dass er den gleichen
Namen wie die Struktur besitzt und mit einer Tilde beginnt.
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
108
– Syntax:
struct <Strukturname>
{
~<Strukturname>(){}
}
– Was passiert im Speicher ? Wie muss man mit dem Heap umgehen?
• Kopierkonstruktor
– Man kann einem Konstruktor ein Objekt der gleichen Struktur
übergeben um eine Kopie anzufertigen, also ein neues Objekt
mit gleichem Inhalt.
– Syntax:
struct <Strukturname>
{
<Strukturname>( <Strukturname>& <zu_kopieren>)
{...}
}
• Default (Standard) Konstruktor und Destruktor legen alle MemberObjekte auf dem Stack an und geben genau diese frei. Der Standardkopierkonstruktor Legt alle Objekte auf dem Stack an und kopiert
alle Stackobjekte. Was kann bzw. muss hier ein Problem darstellen?
Wann muss man selbst Konstruktoren, Destruktoren und Kopierkonstruktor bereitstellen?
4.5.5 Objektorientierte Speicherverwaltung: new und de-
lete
• Speicherverwaltung von C ignoriert Konstruktoren / Destruktoren !
Warum ist das ein Problem ? =⇒ Beispiel
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
109
• new
– Syntax:
〈Typ〉* 〈Name〉= new 〈Typ〉[〈Anzahl〉];
– new legt ein im Speicher konsekutives Feld mit den jeweiligen
Membervariablen an
– dabei wird für jedes Objekt der Konstruktor aufgerufen
• delete
– Syntax:
delete 〈Name〉
delete[] 〈Name〉
– wo besteht der Unterschied zwischen diesen Anweisungen ?
– delete ruft einen oder mehrere Destruktoren auf und gibt danach
den Speicher eines oder mehrerer Objekte frei.
• Verwendung: Fast immer, um genau festzulegen, was bei Anlage und
Freigabe eines Objektes genau passiert.
• Vorlesungsbeispiele Konstruktor / Destruktor; dynamischer Speicher
4.5.6 Überladen von Operatoren
• In C++ können viele Operatoren überladen werden.
nicht überladbar:
::
.*
.
?:
• Operatoren können für alle Objekte (insbesondere Klassen) überladen werden.
• Verwendung: Zur signifikanten Vereinfachung der Programmierung
• Syntax: 〈Typ〉 operator 〈Operator〉 (〈Eingangsargumente〉);
• Was muss bei den Eingangsargumenten beachtet werden, wenn diese
nicht l - Werte enthalten (const)?
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
110
• Wann werden konstante Methoden notwendig ? Syntax:
〈Typ〉 operator 〈Operator〉 (〈Eingangsargumente〉)const;
〈Typ〉 〈Name〉 (〈Eingangsargumente〉)const;
• Wo liegen die Unterschiede bei der Implementierung als Strukturmethode und als freie Funktion ?
• Was ist this ?
• Typkonvertierung
– ein-argumentige Konstruktoren =⇒ erlaubt implizite Typkonvertierung bei Initialisierung
〈Strukturname〉 (const 〈Typ〉 & x ){}
– Zuweisungsoperator =⇒ erlaubt implizite Typkonvertierung bei
Zuweisungen
〈Strukturname〉& operator= (const 〈Typ〉 & x ){return *this;}
– Typkonvertierungsoperator =⇒ erlaubt implizite Typkonvertierung
operator 〈Typ〉 (){ return 〈Typ〉(); }
4.5.7 Klassen
• Strukturen und Klassen sind das gleiche, bis auf die Zugriffsbereiche
– public: (Standard in Strukturen)
– private: (Standard in Klassen)
– protected: (Erklärung später, siehe Vererbung)
• Was sollte dem öffentlichen und was dem privaten Zugriffsbereich
zugeordnet werden ?
• Ersetzt man bei einer Strukturdefinition das Schlüsselwort struct mit
class wird daraus eine Klassendefinition, man beachte die Zugriffsbereiche !
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
111
4.5.8 Freunde
• Sollen Funktionen auf private Variablen oder Methoden einer Klasse
zugreifen können, so muss sie sich mit der Klasse anfreunden.
• Syntax:
– In Klassendeklaration (Klasse A):
friend 〈Funktionsdeklaration〉
friend class 〈Klassenname der Klasse B〉;
=⇒ auch Klassen können sich anfreunden !
– Außerhalb der Klassendeklaration:
Funktions- und Klassendefinition wie gewohnt
=⇒ nun kann allerdings innerhalb der Funktion bzw. Klasse B
auf private Variablen und Funktionen von Objekten der Klasse
A zugegriffen werden !
• Siehe Vorlesungsbeispiel.
4.6
Vererbung
• Klassen können wiederverwendet werden, in dem von ihnen neue
Klassen abgeleitet werden
• abgeleitete Klassen erhalten automatisch alle Member der Basisklasse
• öffentlich abgeleitete Klassen sind Zuweisungskompatibel zur Basisklasse (Typkonvertierung möglich)
• abgeleitete Klassen können als Spezialisierung einer Basisklasse betrachtet werden
• Member die sowohl in der Basisklasse als auch in einer abgeleiteten Klasse vorkommen überdecken in der abgeleiteten Klasse das
Basisklassenelement
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
112
• Zugriffsbereiche:
– private nur die Basisklasse hat Zugriff auf ihre private Member
– protected nur die Basisklasse und von ihr abgeleitete Klassen
haben Zugriff auf protected Member
– public jeder und alles hat von außen Zugriff auf public Member
• Syntax:
class Basis
{
private:
int a;
public:
int b;
Basis():a(0),b(0),c(0){}
protected:
int c;
};
class Ableitung : public Basis
{
protected:
int d;
public:
int e;
};
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
113
• Mehrfachvererbung ist möglich ! Syntax:
class Ableitung_2 : public Basis, public Ableitung
{
protected:
int f;
public:
int g;
};
• Siehe Vorlesungsbeispiel
4.6.1 Ändern der Zugreifbarkeit
• Wurde beim Klassendesign nicht vorausschauend geplant können
Zugriffsbereichsänderungen notwendig werden
• Syntax:
class Ableitung : public Basis
{
protected:
int d;
using Basis::b;
public:
int e;
using Basis::c;
};
4.6.2 Konstruktoren und Zuweisung
• Konstruktoren werden nacheinander von der Basisklasse hin zur abgeleiteten Klasse aufgerufen (beliebig viele Klassen können dazwischen stehen)
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
114
• Destruktoren werden von der abgeleiteten Klasse hin zur Basisklasse
aufgerufen.
• Konstruktor einer abgeleiteten Klasse:
–
class Ableitung : public Basis
{
protected:
int d;
public:
int e;
Ableitung() : Basis(), d(0), e(0) {}
};
– Der aufzurufende Basisklassenkonstruktor darf nur in der Initialisiererliste angegeben werden. Ein Aufruf des Basisklassenkonstruktors im Konstruktor-Körper der abgeleiteten Klasse ist nicht
möglich.
Grund nach C++ standard (Konstruktion abgeleiteter Objekte):
1. Allokation des Speichers für alle Membervariablen (Basisklassen und abgeleitete Klasse)
2. Aufruf der Basisklassenkonstruktoren um die Basisklassenteile des Objektes zu initialisieren
3. Initialisieren der Membervariablen der abgeleiteten Klasse entsprechend des Konstruktorinitialisierers (Initialisiererliste);
4. Ausführen des Funktionskörpers des Konstruktors der abgeleiteten Klasse
– Man kann also an Stelle 3 Einfluss auf Punkt 2 nehmen, der
Funktionskörper wird aber immer nach allen Basisklassenkonstruktoren aufgerufen.
• Kopierkonstruktoren werden nicht automatisch vererbt !
• Syntax für Kopierkonstruktor einer abgeleiteten Klasse:
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
115
class Ableitung : public Basis
{
protected:
int d;
public:
int e;
Ableitung() : Basis(), d(0), e(0) {}
Ableitung( const Ableitung &copy ) : Basis(copy)
{ d = 0; e = 0;}
};
• Zuweisungsoperator:
class Ableitung : public Basis
{
protected:
int d;
public:
int e;
Ableitung() : Basis(), d(0), e(0) {}
Ableitung( const Ableitung &copy ) : Basis(copy){}
Ableitung& operator=(const Ableitung& in)
{
if( this != &in )
{
Basis::operator=(in);
d = in.d;
e = in.e;
}
return *this;
}
};
• Siehe Vorlesungsbeispiel array / vector / matrix
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
4.7
116
Templates
• Templates dienen der Umsetzung parametrischer Polymorphie im
Bereich der generischen Programmierung
• Also der Verwendung gleicher Algorithmen auf verschiedenen Datentypen / Objekten unter Nutzung eines Parameters um den Datentyp
zu spezifizieren.
• Templates verkleinern den Objekt-Code nicht, nach der Kompilieren
liegen also lediglich die benötigten Spezialisierungen vor.
• =⇒ Templates verringern den Programmieraufwand, nicht jedoch
den Kompilierungs- / Rechenaufwand !
• Bemerkung: liegt zur Kompilierzeit im Bereich des ”object codes“ (
Quellcode der in eine object Datei Kompiliert wird), kein Grund vor
eine spezielle Spezialisierung umzusetzen, so wird diese tatsächlich
auch nicht umgesetzt (typischer Linker-Fehler). Zusätzliche Spezialisierungen müssen explizit erzwungen werden.
4.7.1 Funktionstemplates
• Definition eines Templates:
template <typename 〈Name〉> 〈Fkt-./Klassendeklaration〉
• Semantik:
Nach obiger Definition kann das Template verwendet werden als
wäre es ein Datentyp, Beispiel:
template <typename unb_Datentyp>
unb_Datentyp sum(unb_Datentyp a, unb_Datentyp b)
{
return a+b;
}
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
117
• Bei Verwendung eines Templates kommt es zur automatischen Typdeduktion. Kann dies fehlschlagen ? Kann eine Spezialisierung erzwungen werden ? =⇒ Vorlesungsbeispiel
• Was für Eigenschaften muss der obige Datentyp unb_Datentyp mindestens besitzen, damit eine Spezialisierung durchgeführt werden
kann ?
• Explizite Spezialisierung, für einzelne Datentypen können individuelle Spezialisierungen angegeben werden
template <>
double sum(double a, double b)
{
return a+b;
}
Eine solche explizite Spezialisierung muss vor der ersten Instantiierung angegeben werden !
• Templateparameterlisten können mit Komma getrennt deklariert werden:
template <typename 〈Name_1〉, typename 〈Name_2〉>...
• Templateparameter können default Typen erhalten.
4.7.2 Klassentemplates
• Bei Instantiierung von Objekten einer Klasse ist die explizite Angabe
der zu verwendenden Datentypen notwendig. Warum ?
• Syntax:
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
118
template <〈Templateparameterliste〉>
class 〈Klassenname〉
{
...
};
• Memberdeklaration /-definition
– Bei Methodendefinitionen außerhalb der Templateklasse muss
die Templateparameterliste erneut angegeben werden und die
Klasse muss mit diesen Parametern spezialisiert werden:
template <〈Templateparameterliste〉>
〈Rückgabetyp〉 〈Klassenname〉<〈Spezialisierung〉>
::〈Methodenname〉(〈Eingangsparameter〉)
{
...
}
• Templatemember
– Innerhalb einer Templateklasse können weiter Templatemethoden verwendet werden.
– Bei der Definition von Templatemethoden einer Templateklasse
außerhalb der Klasse wird als erstes die Klassentemplateparameterliste und dann die Methodentemplateparameterliste angegeben.
template <〈Klassentemplateparameterliste〉>
template <〈Methodentemplateparameterliste〉>
〈Rückgabetyp〉 〈Klassenname〉<〈Spezialisierung〉>
::〈Methodenname〉(〈Eingangsparameter〉)
{
...
}
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
119
• siehe Vorlesungsbeispiel
4.7.3 variadische Templates
• variadische Templates geben die Möglichkeit Funktionen und Klassen mit Variabler Anzahl generischer Datentypen zu Deklarieren.
• Insbesondere Klassen können nun von einer variablen Anzahl generischer Klassen abgeleitet werden.
• Syntax zur Definition eines Variadischen Templates:
– template <typename... 〈Parameter_Name〉>
– template <typename 〈Name〉, typename... 〈Parameter〉>
– template <typename 〈Name〉= int, typename... 〈Parameter〉>
• Anzahl der Templateparameter kann über sizeof... ermittelt werden:
unsigned short int size = sizeof...(〈Argument_Name〉);
• typename... 〈Parameter_Name〉 ist ein template parameter pack
• Unter Verwendung, z.B. in variadischen Funktionen ist
〈Parameter_Name〉... 〈Variablen_Name〉
ein function parameter pack
• Über variadische Templates wird typischerweise rekursiv iteriert.
• Compileroptimierung optimiert diese Rekursion häufig vollständig
weg, oftmals bleiben ”unrolled“ loops die gar keinen Funktionsaufruf
enthalten
• Funktionstemplates
– einfaches Funktionstemplate mit rekursiver Abarbeitung / Terminierung:
template<typename T> void var_temp_funktion(T 〈Name〉){...}
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
120
template<typename T, typename... parameter_types >
void var_temp_funktion(T para_1, parameter_types... parameter )
{
...
var_temp_funktion(parameter...);
}
– Es ist möglich beliebig viele Parameter aus dem parameter-pack
in einer Stufe der Rekursion zu ”extrahieren“.
– Was passiert aber, falls die gegebenen Parameter kein ganzes
Vielfaches der in einer Stufe zu extrahierenden Parameter sind
? Was kann man hier machen ?
– siehe Vorlesungsbeispiele Summe / paarweiser Vergleich
• Ausblick:
– variadische Templates können auch in Intialisierungslisten eines
Konstruktors oder als Basisklassenliste verwendet werden...
– variadische Vererbung
template <typename... Basisklassen> class Klassenname : public Basisklassen...
{
public:
Klassenname (Basisklassen&&... basisklassen) :
Basisklassen(basisklassen)... {}
};
– && ist der ”Auspackoperator“ – unpack operator
– dieser kann auch genutzt werden, um beispielsweise eine Funktion ”parallel“ über alle parameter eines Variadischen Templates
auszuführen
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
4.7.4 Standardbibliotheken C
C89/C90
assert.h
ctype.h
errno.h
float.h
limits.h
locale.h
math.h
setjmp.h
signal.h
stdarg.h
stddef.h
stdio.h
stdlib.h
string.h
time.h
Assertions
Tests auf bestimmte Zeichentypen
Codes von Systemfehlern
Angaben zu den Wertbereichen von Gleitkommazahlen
Angaben zu Beschränkungen des verwendeten Systems
Einstellungen des Gebietsschemas
mathematische Funktionen
erweiterte Sprungfunktionen
Signalbehandlung
Argumentbehandlung für variadische Funktionen
zusätzliche Typdefinitionen
Ein- und Ausgabe
vermischte Standardfunktionen, u.a. Speicherverwaltung
Zeichenkettenoperationen
Datum und Uhrzeit
iso646.h
wchar.h
wctype.h
Neu in C95 (auch: „NA1“)
alternative Schreibweisen für logische und bitweise Operatoren
Unterstützung für Unicode-Zeichen
wie ctype.h, für Unicode-Zeichen
Neu in C99
complex.h
fenv.h
inttypes.h
stdbool.h
stdint.h
tgmath.h
Komplexe Zahlen
Einstellungen für das Rechnen mit Gleitkommazahlen
Konvertierungs- und Formatierungsfunktionen für
erweiterte Ganzzahltypen
Unterstützung für Boolesche Variablen
plattformunabhängige Definition von Ganzzahltypen
typgenerische Makros für mathematische Funktionen
Neu in C11
stdalign.h
Makros für die Speicherausrichtung von Objekten
stdatomic.h
Typen und Makros für atomare Operationen zwischen Threads
stdnoreturn.h Definition des Noreturn-Makros
threads.h
Unterstützung für Threads, Mutexes und Monitore
uchar.h
Unterstützung für UTF-16- und UTF-32-kodierte Unicode-Zeichen
121
Dr. Stefan Brechtken ♠ Wissenschaftliches Rechnen – Grundlagen ♣ 2014/2015
4.7.5
122
Standardbibliotheken C++
Ein kleiner Ausschnitt der Standardbibliotheken bzw. der STL (standard
template library)
• Zeichenkettenverarbeitung: Bibliothek <string>
• Ein- Ausgabe: Bibliothek <iostream>
• Zeit: <chrono>
• Zufallszahlen: <random>
• Komplexe zahlen: <complex>
• Grenzen der verwendeten Maschinengenauigkeit: <limits>
• Datenfelder, dynamische Größe, schneller Zugriff: <vector>
• Datenfelder, feste Länge, schneller Zugriff: <array>
• Datenfelder, schnelles einfügen / entfernen von Elementen: <list>
• Datenfelder, schnelle FIFO verarbeitung: <queue>
• schnelle!! numerische Operationen auf Datenfeldern: <valarray>
• abstrakte Tupel: <tuple>, <utility>
• Suchbäume (z.Bsp. mit schwacher Ordnung): <set>
• LIFO Stack: <stack>
• ”häufig“ verwendete Algorithmen: <algorithm>
• multi-threading: <atomic>, <condition_variable>, <future>,
<mutex>, <thread>