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: stefan.brechtken@tu-ilmenau.de 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 © ) : 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 © ) : 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>