0% fanden dieses Dokument nützlich (0 Abstimmungen)
0 Ansichten332 Seiten

pro2

Das Dokument ist ein Inhaltsverzeichnis für die Veranstaltung 'Programmieren II für Studierende der Mathematik' an der Ludwig-Maximilians-Universität München im Wintersemester 2025/26. Es listet verschiedene Skripte und Themen auf, die in der Veranstaltung behandelt werden, darunter typographische Konventionen, formale Sprachen, Klassen, Ein-/Ausgabe in C++, Datentypen und viele weitere Programmierkonzepte. Einige Abschnitte sind speziell für die Version der Veranstaltung relevant, die 6 ECTS-Punkte umfasst.

Hochgeladen von

d3for0f
Copyright
© All Rights Reserved
Wir nehmen die Rechte an Inhalten ernst. Wenn Sie vermuten, dass dies Ihr Inhalt ist, beanspruchen Sie ihn hier.
Verfügbare Formate
Als PDF, TXT herunterladen oder online auf Scribd lesen
0% fanden dieses Dokument nützlich (0 Abstimmungen)
0 Ansichten332 Seiten

pro2

Das Dokument ist ein Inhaltsverzeichnis für die Veranstaltung 'Programmieren II für Studierende der Mathematik' an der Ludwig-Maximilians-Universität München im Wintersemester 2025/26. Es listet verschiedene Skripte und Themen auf, die in der Veranstaltung behandelt werden, darunter typographische Konventionen, formale Sprachen, Klassen, Ein-/Ausgabe in C++, Datentypen und viele weitere Programmierkonzepte. Einige Abschnitte sind speziell für die Version der Veranstaltung relevant, die 6 ECTS-Punkte umfasst.

Hochgeladen von

d3for0f
Copyright
© All Rights Reserved
Wir nehmen die Rechte an Inhalten ernst. Wenn Sie vermuten, dass dies Ihr Inhalt ist, beanspruchen Sie ihn hier.
Verfügbare Formate
Als PDF, TXT herunterladen oder online auf Scribd lesen

Ludwig-Maximilians-Universität München WiSe 2025/26

Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik

Sektionen, die mit Sternchen (*) markiert sind, sind nur für die Version der Veranstaltung für 6 ECTS relevant.

Skript 00a
Typographische Konventionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Version der Veranstaltung für 6 ECTS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Skript 00b
Formale Sprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Extended Backus-Naur Form (EBNF) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
EBNFs für C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Skript 00c
Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Skript 00d
Ein-/Ausgabe in C++ (<iostream>, <iomanip>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Vordefinierte Ströme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Formatierte Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Formatierte Eingabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Zeichenketten (<string>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Ein-/Ausgabe-Ströme (<iostream>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Buffering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Unformatierte Ein-/Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Stromzustand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Ein-/Ausgabe auf Dateien (<fstream>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Ein-/Ausgabe auf Strings (<sstream>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Skript 01
Datentypen für komplexe Zahlen in der Standardbibliothek (complex) . . . . . . . . . . . . . . . . . 1
Überladung von Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Klassenkomponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Konstante Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Konstruktoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Destruktoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Zuweisungsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Statische Klassenkomponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Zugriffsattribut protected* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Vereinbarung von Klassenkomponenten als explicitly defaulted* . . . . . . . . . . . . . . . . . . . . 16
Deleted Definitions* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Abgeleitete Klassen* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Unterobjekte/Konstruktoren für abgeleitete Klassen* . . . . . . . . . . . . . . . . . . . . . . . 18
Überschattung von Namen von Komponenten abgeleiteter Klassen* . . . . . . . . . . . . . . . 19
Implizite Typkonvertierungen von Zeigern bzw. Referenzen auf Objekte abgeleiteter Klassen* 20
Virtuelle Basisklassen* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Virtuelle Methoden* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Object Slicing* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Rein virtuelle Methoden* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

Seite 1 von 3
Inhaltsverzeichnis Programmieren II für Studierende der Mathematik — WiSe 2025/26

Virtuelle Destruktoren* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Template Deklarationen* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Instanziierung von Templates* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Template Spezialisierung* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Skript 02
This space intentionally left blank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Präprozessor-Direktiven* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
#define* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
#ifdef, #ifndef, etc.* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
#include* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Header-Dateien* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Double inclusion* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Separate Compilation* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Objekt-Dateien* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Linker* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Deklarationen* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Speicherklassen* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Bindungswirkung von Namen (linkage)* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Skript 03
This space intentionally left blank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Arrays in STL (array)* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Namensräume* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Skript 04
Parametervoreinstellungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Überladene Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Auswahl überladener Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Benutzerdefinierte Konvertierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Überladen von Operatoren (Forts.) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Überladen von Operatoren als Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Inkrement- und Dekrementoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Überladen des Funktionsaufrufs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Funktionsobjekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
STL-Listen (list) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
STL-Mengen natürlicher Zahlen (bitset) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Buildsysteme* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Abhängigkeitsgraph* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Make* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Ninja* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Meson* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Meson WrapDB* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Unit testing* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
GoogleTest* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Skript 05
Typsynonyme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Typsynonyme mit typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Iteratoren und weitere Funktionen für vector . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Typ-Platzhalter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Bereichsbasierte for-Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

Seite 2 von 3
Inhaltsverzeichnis Programmieren II für Studierende der Mathematik — WiSe 2025/26

Arrays in STL (array) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6


Verzeigerte Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
STL-Paare (pair aus <utility>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Generische STL-Mengen (set) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Endliche Abbildungen in der STL (map) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Exceptions in der Standardlibrary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Vordefinierte Typen für Exceptions (stdexcept) . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Exceptions bei Speicherreservierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Bereichsüberprüfter Elementen-Zugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Exceptions bei Ein-/Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Funktions-try-Blöcke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Auf Numerik optimierte Vektoren (valarray)* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Indexmengen (slice etc.) & Teilvektoren (slice_array etc.)* . . . . . . . . . . . . . . . . . . 24
Matrix-Datentypen mit valarray* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Skript 06
Funktionstypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Funktionszeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Vereinbarungssyntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Funktionstypen als Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Funktionsobjekte in der Standardlibrary (aus <functional>) . . . . . . . . . . . . . . . . . . . . . . 3
Generische Funktionsobjekte (function) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Partielle Funktionsanwendung (bind) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Funktionsobjekte für Methoden (mem_fn) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Lambda-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Beispiel: Auswertung arithmetischer Ausdrücke mit Funktionen . . . . . . . . . . . . . . . . . . . . 11
Skript 07
This space intentionally left blank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Skript 08
Zeiger mit impliziter Speicherfreigabe (unique_ptr, <memory>) . . . . . . . . . . . . . . . . . . . . . 1
Assertions (<cassert>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

Seite 3 von 3
Skript 00a Programmieren II für Studierende der Mathematik — WiSe 2025/26

Typographische Konventionen

Wenn ein Begriff gerade zum ersten mal definiert wird, wird er kursiv gesetzt.
Textfragmente welche exakt so in z.B. Programmcode vorkommen können bezeichnen wir als verbatim. Sie
werden in breitengleicher Schrift gesetzt (monospace).
Platzhalter und Variablen im mathematischen Sinne werden konventionell in einer sog. mathematischen
Schriftart gesetzt, z.B. 𝑥.
Sie können auch in verbatim Textfragmenten vorkommen.
Größere Blöcke von verbatim Text werden in umrandeten Kästen gesetzt, z.B.:

Beispieltext

Derartige verbatim Blöcke können einen Bezeichner tragen, z.B.:

Bezeichner

Beispieltext 2
Zweite Zeile

Bezeichner anderer verbatim Blöcke dürfen wiederrum innerhalb von verbatim Blöcken vorkommen. Dies
versteht sich so, dass an der Stelle des Bezeichners innerhalb des Blocks, der Inhalt des referenzierten Blocks
einzusetzen ist.
Z.B. verstehen sich die folgenden zwei verbatim Blöcke so, dass sie den selben Inhalt haben:

Ein Beispieltext:
Beispieltext 2
Zweite Zeile

Ein Beispieltext:

Bezeichner

Gdw. der Bezeichner eines Blocks in breitengleicher Schrift gesetzt ist, wird der Inhalt des Textblocks au-
tomatisch extrahiert und neben dem Skript ebenfalls zum Download angeboten. Der Text des Bezeichners
entspricht dann dem Namen der Datei als der der Inhalt heruntergeladen werden kann.

[Link]

Ein weiterer Beispieltext:

Bezeichner

Verbatim Blöcke, welche in irgendeinem Sinne fehlerhaft sind, z.B. also Beispielprogramme, die sich so nicht
erfolgreich verwenden lassen, werden rot hinterlegt:

Fehlerhafter Beispieltext

Seite 1 von 2
Skript 00a Programmieren II für Studierende der Mathematik — WiSe 2025/26

Wenn Zeilen innerhalb von verbatim Blöcken zu lang sind und daher ein Zeilenumbruch eingefügt werden
muss, wird dies mit ↩→ auf der nächsten Zeile markiert. Diese Zeilenumbrüche dienen nur der Darstellung
von Verbatim Blöcken im Text und sind insb. in den extrahierten Inhalten der Textblöcke nicht enthalten:

langer_text.txt

Wort1 Wort2 Wort3 Wort4 Wort5 Wort6 Wort7 Wort8 Wort9 Wort10 Wort11 Wort12 Wort13 Wort14
↩→ Wort15

Das Tablatorzeichen ('\t') wird in verbatim Blöcken als ⇆ dargestellt:

[Link]

Ein Beispieltext:

⇆ Bezeichner

Wenn es sich bei einem verbatim Block um die Ausgabe eines Kommandozeilenprogramms handelt, so wird
Text, den das Programm ausgibt gewöhnlich gesetzt und Text, den der Benutzer eingibt grün hinterlegt gesetzt:

Marco
Polo

Version der Veranstaltung für 6 ECTS

Sektionen, die mit Sternchen (*) markiert sind, sind nur für die Version der Veranstaltung für 6 ECTS relevant.

Seite 2 von 2
Skript 00b Programmieren II für Studierende der Mathematik — WiSe 2025/26

Formale Sprachen

Sei Σ eine endliche, nicht-leere Menge. Wir bezeichnen Σ als ein Alphabet und die Elemente als Symbole. Σ𝑛

bezeichnet alle endlichen Folgen der Länge 𝑛 von Symbolen aus Σ und Σ∗ := 𝑖 ∈N0 Σ𝑖 die Menge von Worten
von Symbolen aus Σ, d.h. endliche Folgen beliebiger Länge. Eine Teilmenge 𝐴 ⊆ Σ∗ bezeichnen wir als Sprache.
Wir definieren mit 𝐴, 𝐵 Sprachen über Σ:

• 𝜀 das eindeutige Wort der Länge 0, das leere Wort


• {𝜀} := Σ0
• ◦ : Σ∗ × Σ∗ → Σ∗ die gewöhnliche Konkatenation von Worten1
• 𝑤 𝑛 := 𝑤 ◦ 𝑤 ◦ . . . ◦ 𝑤 (n-mal) die n-fache Konkatenation für 𝑤 ∈ 𝐴 (𝑤 0 = 𝜀)
• 𝐴 := Σ∗ − 𝐴
• 𝑤𝐴 := {𝑤 ◦ 𝑤 ′ | 𝑤 ′ ∈ 𝐴}, 𝐴𝑤 := {𝑤 ′ ◦ 𝑤 | 𝑤 ′ ∈ 𝐴}

• 𝐴 ◦ 𝐵 := 𝑤 ∈𝐴 𝑤𝐵
• 𝐴𝑛 := 𝐴 ◦ 𝐴 ◦ . . . ◦ 𝐴 (n-mal), 𝐴0 = {𝜀}, 𝐴1 = 𝐴
• 𝐴? := 𝐴0 ∪ 𝐴

• 𝐴∗ := 𝑛∈N0 𝐴𝑛

• 𝐴+ := 𝑛∈N,𝑛≥1 𝐴𝑛

Extended Backus-Naur Form (EBNF)

Sprachen stets nur mit Mengenschreibweise und den bisher eingeführten Operationen zu beschreiben wäre
sehr umständlich. Wir beschreiben daher die Grammatik 𝐺 einer Sprache als Extended Backus-Naur Form2 .
Eine Grammatik 𝐺 besteht hierbei aus dem Alphabet Σ, einer davon disjunkten Menge von Non-Terminalen
(auch Variablen) 𝑉 , einer Menge von Produktionen 𝑃 der Form „⟨𝑣⟩ → rechte Seite“ und einem speziell ausge-
zeichneten Startsymbol 𝑆 ∈ 𝑉 . Für jede Variable 𝑣 ∈ 𝑉 gibt es o.b.d.A. genau eine Produktion.
Für 𝑣, 𝑟 1, 𝑟 2 ∈ 𝑉 Non-Terminale, 𝑤 ∈ Σ∗ eine Folge von Terminalen und 𝑐 1, . . . , 𝑐𝑛 ∈ Σ alphanumerisch
aufeinanderfolgende Zeichen (z.B. A bis Z oder 0 bis 9) bezeichnen wir mit 𝐿(𝑣) die von 𝑣 induzierte Sprache
nach folgenden Regeln:

⟨𝑣⟩ → “𝑤” 𝐿(𝑣) = {𝑤 }


⟨𝑣⟩ → /[𝑐 1 -𝑐𝑛 ]/ 𝐿(𝑣) = {𝑐 1, . . . , 𝑐𝑛 }
⟨𝑣⟩ → [𝑟 1 ] 𝐿(𝑣) = 𝐿(𝑟 1 ) ?
⟨𝑣⟩ → {𝑟 1 } 𝐿(𝑣) = 𝐿(𝑟 1 ) ∗
⟨𝑣⟩ → {𝑟 1 }+ 𝐿(𝑣) = 𝐿(𝑟 1 ) +
⟨𝑣⟩ → 𝑟 1 𝑟 2 𝐿(𝑣) = 𝐿(𝑟 1 ) ◦ 𝐿(𝑟 2 )
⟨𝑣⟩ → 𝑟 1 | 𝑟 2 𝐿(𝑣) = 𝐿(𝑟 1 ) ∪ 𝐿(𝑟 2 )

Die durch die Grammatik 𝐺 beschriebene Sprache ist dann 𝐿(𝐺) := 𝐿(𝑆).

1 (Σ∗ , ◦)
ist ein Monoid, der freie Monoid über Σ
2 Im Allgemeinen lässt sich nicht jede Sprache durch eine Grammatik beschreiben. EBNFs beschreiben wiederrum eine Teilmenge
aller Grammatiken, die kontextfreien Grammatiken.

Seite 1 von 2
Skript 00b Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel (Arithmetische Ausdrücke mit EBNF). Wir geben eine EBNF an um korrekt geklammerte Arithmeti-
sche Ausdrücke (+, ·) mit natürlichen Zahlen zu beschreiben, Startsymbol ist ⟨Ausdruck⟩:
⟨Faktor⟩ → {/[0-9]/}+
| “(” ⟨Ausdruck⟩ “)”
⟨Produkt⟩ → ⟨Faktor⟩ {“*” ⟨Faktor⟩}
⟨Ausdruck⟩ → ⟨Produkt⟩ {“+” ⟨Produkt⟩}

EBNFs für C++

Wir geben in dieser Vorlesung oft EBNFs für die Syntax der Programmiersprache C++ an. In diesen Fällen
versteht sich die Syntax angewandt nicht auf einzelne Buchstaben als Alphabet sondern sog. Tokens. Bevor
der C++-Compiler den Code syntaktisch analysiert, trennt er ihn zunächst auf in eine Folge von Tokens. Als
Trenner zwischen Tokens fungiert i.W. ein Übergang zwischen alphanumerischen (also Buchstaben und Zif-
fern) und nicht-alphanumerischen Symbolen. Nicht-druckbare Zeichen (Leerzeichen, Zeilenumbrüche, etc.)
sind i.W. ohne Bedeutung außer, dass sie geeignet sind zwei Tokens voneinander zu trennen.
Zusätzlich werden Eingabedateien noch bevor sie vom Compiler als Übersetzungseinheit syntaktisch analy-
siert werden vom Präprozessor verarbeitet. Der Präprozessor ersetzt Direktiven, welche stets mit # beginnen,
durch andere Textabschnitte. Die genaue Ersetzung hängt natürlich von der Direktive ab.
Kommentare werden vor dem Zerlegen der Eingabe in tokens durch ein Leerzeichen ersetzt. Ein Kommentar
kann mit “//” eingeleitet werden und erstreckt sich von dort bis zum Ende der Zeile. Alternativ können
Kommentare mit “/*” eingeleitet werden, sich auch über mehrere Zeilen erstrecken und mit “*/” beendet
werden.

Seite 2 von 2
Skript 00c Programmieren II für Studierende der Mathematik — WiSe 2025/26

Klassen

⟨Klasse⟩ → “class” [⟨Name⟩] “{” ⟨Komponente⟩ “};”


⟨Komponente⟩ → [⟨Zugriff⟩ “:”] {⟨Komponente⟩}
| ⟨DeklAng⟩ ⟨AttrKomp⟩ {“,” ⟨AttrKomp⟩} “;”
| ⟨Funktionsdefinition⟩
⟨AttrKomp⟩ → ⟨Deklarator⟩ [“=” ⟨InitialAusdruck⟩]
⟨Zugriff⟩ → “private” | “public”
⟨DeklAng⟩ → {⟨Typ⟩ | “friend”}
Klassen sind zusammengesetze Datentypen. Komponenten von Klassen sind über ihren Namen ansprechbar.
Sie können sowohl in externen Bibliotheken, jedoch auch in eigenen Programmen vereinbart werden.
Als Komponenten von Klassen können sowohl Attribute (nonstatic data members), wie auch Methoden (non-
static member functions) vereinbart werden. Klassen werden verwendet indem Objekte der Klasse instanziiert
werden. Jedes Objekt der Klasse hat genug Platz für jeweils seine eigene Kopien aller Attribute. Methoden
werden auf Objekten aufgerufen.
Bestimmte Attribute regeln welche Teile des Programs Zugriff auf einzelne Komponenten haben. Standard-
mässig kann nur auf als public markierte Komponenten von außerhalb der Klasse direkt zugegriffen werden.
Eine Klassenvereinbarung kann Vereinbarungen von befreundete Funktionen enthalten (friend), welche Zu-
griff auf alle Klassenkomponenten haben. Befreundete Funktionen sind keine Methoden und, obwohl sie in-
nerhalb der Klassenvereinbarung vereinbart werden können, keine Klassenkomponenten.
Eine Vereinbarung einer Klasse mit dem Schlüsselwort struct statt class ist auch möglich. In diesem Fall
kann standardmäßig auf sämtliche Komponenten der Klasse auch von außerhalb der Klasse direkt zugegriffen
werden.

class 𝐶 { . . . }; Klasse vereinbaren (nur außerhalb von Funktionen)


𝐶𝑐 Variable von Typ 𝐶 vereinbaren; 𝑐 ist Objekt der Klasse 𝐶
Verwendet zur Initialisierung von 𝑐 Standardkonstruktor
𝐶 𝑐 (init1 , init2 , …) Variable von Typ 𝐶 vereinbaren; 𝑐 initialisieren mit gegebenen
Konstruktorargumenten
Klammern dürfen nicht leer sein
𝐶 𝑐 {init1 , init2 , …} direct-list-initialization; Var. von Typ 𝐶 vereinbaren; 𝑐 init. mit
gegebenen Konstruktorargumenten
𝐶 {} Konstruiere temporäres Objekt von Typ 𝐶 mit Standardkonstruktor;
Ausdruck nimmt temporäres Objekt als Wert an
𝐶 {init1 , init2 , …} Konstruiere temporäres Objekt von Typ 𝐶
𝑐 .name Ausdruck mit Wert des aktuellen Werts des Attributs name des
Objekts 𝑐
𝑐 . 𝑓 (. . .) Aufruf der Klassenmethode 𝑓 auf das Objekt 𝑐
𝑐𝑝 ->name (*𝑐𝑝 ).name

𝑐𝑝 -> 𝑓 (. . .) (*𝑐𝑝 ). 𝑓 (. . .)

Konstruktoren verhalten sich in ihrer Vereinbarung syntaktisch ähnlich als wären sie Komponentenfunktio-
nen mit Namen identisch zu dem der Klasse. Im Gegensatz zu (Komponenten-)Funktionen kennen Konstruk-
toren keinerlei Ergebnistyp; nicht einmal void. Es darf daher auch kein Ergebnistyp als Teil der Vereinbarung
angegeben werden.

Seite 1 von 3
Skript 00c Programmieren II für Studierende der Mathematik — WiSe 2025/26

Ihr Aufruf erfolgt wie oben angegeben mit eigener Syntax verschieden davon wie Funktionen aufgerufen
werden.
Zwischen Parameterliste und Funktionskörper eines Konstruktors kann eine Liste von Komponenteninitiali-
sierungen (member initializer list) eingeschoben sein, die statt der Standardinitialiserungen (default member
initializer) wie sie direkt in der Klasse vereinbart wurden, verwendet werden sollen.
Beispiel (Komplexe Zahlen — rudimentär). Im Folgenden vereinbaren wir eine Klasse zur Approximation kom-
plexer Zahlen 𝑧 = 𝑥 + 𝑖𝑦 durch zwei Werte vom Typ double um die kartesischen Koordination abzubilden.

Klasse

class Complex {
private:
double x, y;

public:
Complex(double Re = 0, double Im = 0) : x(Re), y(Im) {}

double real() { return x; }


double imag() { return y; }

friend Complex conj(Complex z) {


z.y = -z.y;

return z;
}
};

Im folgenden Hauptprogramm konstruieren wir mehrere Werte vom Typ Complex; sowohl konstant wie
auch durch Einlesen von der Standardeingabe. Es werden zusätzlich einige Komponenten- und befreundete
Funktionen aufgerufen.

class_demo.cpp

#include <iostream>

using namespace std;

Klasse

int main() {
Complex z0, z1{4.0}, z2{1.0, 2.0}, z3 = conj(z2);

double x4, y4;


cout << ”Re z4, Im z4: ”;
cin >> x4 >> y4;
Complex z4{x4, y4};

cout << ”Re z0 = ” << [Link]() << ” Im z0 = ” << [Link]() << endl;
cout << ”Re z1 = ” << [Link]() << ” Im z1 = ” << [Link]() << endl;
cout << ”Re z2 = ” << [Link]() << ” Im z2 = ” << [Link]() << endl;
cout << ”Re z3 = ” << [Link]() << ” Im z3 = ” << [Link]() << endl;
cout << ”Re z4 = ” << [Link]() << ” Im z4 = ” << [Link]() << endl;

return 0;

Seite 2 von 3
Skript 00c Programmieren II für Studierende der Mathematik — WiSe 2025/26

Re z4, Im z4: 3 4
Re z0 = 0 Im z0 = 0
Re z1 = 4 Im z1 = 0
Re z2 = 1 Im z2 = 2
Re z3 = 1 Im z3 = -2
Re z4 = 3 Im z4 = 4

Seite 3 von 3
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

Ein-/Ausgabe in C++ (<iostream>, <iomanip>)

Im Folgenden bezeichne 𝑖 einen Eingabestrom (z.B. cin) und 𝑜 einen Ausgabestrom (z.B. cout, cerr).

Vordefinierte Ströme

Die Header-Datei <iostream> definiert insb. die folgenden Konstanten:

cout Ausgabe auf die Konsole


cin Eingabe von der Konsole
cerr Ausgabe auf die Konsole, per Konvention für Fehlermeldungen
Die Ausgabe auf cerr erfolgt logisch getrennt von der Ausgabe auf cout. Das Betriebssystem stellt
Möglichkeiten um die beiden Ausgabeströme separat zu behandeln (z.B. die Ausgabe eines der Ströme
zu unterdrücken).

Formatierte Ausgabe

Die Ausgabe auf einen Strom erfolgt durch geeignet überladene shift-Operatoren <<, wie folgt:

𝑜 << arg1 << arg2 << . . .

Als Argumente können Werte aller Typen fungieren, für die der shift-Operator geeignet überladen ist. Insb.
die eingebauten Typen int, double, char, … und alle eigenen Klassen/Typen bei denen dies der Fall ist.
Zusätzlich können die folgenden sog. IO-Manipulatoren (Header-Datei <iomanip>) als Argument verwendet
werden um das Verhalten danach folgender Ausgaben zu verändern:

setw(𝑛 ) Setze1 minimale Feldbreite auf 𝑛 (Voreinst.: 0), Aufüllen nach Bündigkeit mit
Füllzeichen
setprecision(𝑛 ) Wenn fixed oder scientific, setze Ziffern nach Dezimalpunkt; wenn
defaultfloat, setze max. signifikante Stellen (Voreinst.: 6)

setfill(𝑐 ) Setze Füllzeichen auf 𝑐, vgl. Feldbreite (Voreinst.: Leerzeichen)


left right Links- bzw. Rechts- bzw. nur Vorzeichen links- und Zahl rechtsbündig, vgl.
internal Feldbreite (Voreinst.: rechtsbündig)
showpos noshowpos Positives Vorzeichen ausgeben bzw. nicht ausgeben (Voreinst.: nicht ausgeben)
fixed scientific Festpunkt-, Exponentialformat oder automatische Auswahl bei Gleitpunktzahlen
defaultfloat (Voreinst.: auto. Auswahl)
dec oct hex Dezimal-, Oktal- oder Hexadezimale Ausgabe bei ganzen Zahlen (Voreinst.: dezimal)
showbase Präfix für Zahlenbasis (0 für oktal bzw. 0x/0X für hexadezimal) ausgeben bzw. nicht
noshowbase ausgeben (Voreinst.: nicht ausgeben)
boolalpha Ausgabe von bool-Werten als Zeichenkette bzw. numerisch (Voreinst: numerisch)
noboolalpha

endl Zeilenumbruch
1 Wirkt sich i.d.R. nur auf die nächste Ausgabe aus

Seite 1 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

flush Ausgabepuffer leeren

Formatierte Eingabe

Das Lesen von Eingabe aus einem Strom erfolgt ebenfalls durch geeignet überladene shift-Operatoren. In
Analogie zur Richtung, in die die Information fließt für die Eingabe durch >>, wie folgt:

𝑖 >> arg1 >> arg2 >> . . .

Auch hierfür können (Referenzen auf) Variablen aller Typen als Argumente fungieren, für die der shift-
Operator geeignet überladen ist. Insb. wieder die eingebauten Typen int, double, char, … und alle eigenen
Klassen/Typen bei denen dies der Fall ist.
Folgende Manipulatoren stehen zur Verfügung:

skipws noskipws Nicht-druckbare Zeichen (insb. Leerzeichen) am aktuellen Stand überlesen, bis zur
nächsten Eingabe, oder nicht (Voreinst.: überlesen)
dec oct hex Dezimale, Oktale, bzw. Hexadezimale Eingabe bei ganzen Zahlen (Voreinst.: dezimal)

Zeichenketten (<string>)

C behandelt Zeichenketten als Werte vom Typ „Array von char“ unspezifizierter Länge. Es wird das Null-
Byte \0 als Marker innerhalb des arrays verwendet um das logische Ende der Zeichenkette zu markieren.
Fehlende oder inkorrekt gesetzte Null-Bytes in C-Zeichenketten sind eine häufige Quelle von Fehlern und
Sicherheitsproblemen in diverser Software.
⟨StringLiteral⟩ → “"” {⟨Zeichen⟩} “"”
String-Literale der Form ⟨StringLiteral⟩ sind vom Typ „Array von 𝑛 const char“ mit 𝑛 passend und sind
automatisch stets korrekt \0-terminierte C-Zeichenketten.
Die Verwendung von C-Zeichenketten ist auch in C++ an vielen Stellen möglich. Es ist jedoch, insb. wegen
der häufigen Problematik bzgl. Null-Bytes, stark davon abzuraten.
Analog zu STL-Vektoren als Ersatz für C-Arrays bietet die C++-Standardlibrary in der Header-Datei <string>
ebenfalls einen string Typ, der einen geeigneten Buffer von char und die zugehörige Länge (vom Typ
string::size_type) intern verwaltet. Ein Terminierungszeichen wie \0 ist dann nicht mehr nötig, was eine
häufige Fehlerquelle systematisch behebt.
C-Zeichenketten können implizit konvertiert werden zu Werten vom Typ string. Insb. können Variablen
vom Typ string als String-Literale zugewiesen werden, bzw. sie mit diesen initialisiert werden.
Im Folgenden bezeichnen 𝑠 und 𝑡 Zeichenketten vom Typ string, 𝑠𝐶 eine korrekt \0-terminierte C-
Zeichenkette vom Typ char[], 𝑐, 𝑐 0, . . . , 𝑐𝑛−1 Zeichen vom Typ char, 𝑖 und 𝑛 ganze Zahlen vom Typ
string::size_type und 𝑖 und 𝑜 einen Ein- bzw. Ausgabestrom. Folgende Operationen stehen zur Verfü-
gung:

string 𝑠 Vereinbart Variable 𝑠 vom Typ string leer (Länge 0)

Seite 2 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

string 𝑠 = 𝑡 string 𝑠 (𝑡 ) Vereinbart Variable 𝑠 vom Typ string mit Inhalt als Kopie dessen
von 𝑡
string 𝑠 (𝑡 , 𝑖 ) Vereinbart Variable 𝑠 vom Typ string mit Inhalt als Kopie dessen
von 𝑡 ab Index 𝑖 bis Ende
string 𝑠 (𝑡 , 𝑖 , 𝑛 ) Vereinbart Variable 𝑠 vom Typ string mit Inhalt als Kopie dessen
von 𝑡 ab Index 𝑖 bis 𝑖 + 𝑛 − 1 (Länge maximal 𝑛)
string 𝑠 = 𝑠𝐶 string 𝑠 (𝑠𝐶 ) Vereinbart Variable 𝑠 vom Typ string mit Inhalt als Kopie dessen
string 𝑠 (𝑠𝐶 , 𝑛 ) von 𝑠𝐶 bzw. den ersten 𝑛 Zeichen von 𝑠𝐶
string 𝑠 (𝑛 , 𝑐 ) Vereinbart Variable 𝑠 vom Typ string mit Inhalt 𝑛 viele Kopien des
Zeichen 𝑐
string 𝑠 = {𝑐 0 , . . ., 𝑐𝑛−1 } Vereinbart Variable 𝑠 vom Typ string mit Länge 𝑛 und Elementen
string 𝑠 {𝑐 0 , . . ., 𝑐𝑛−1 } Kopien von 𝑐 0, . . . , 𝑐𝑛−1
𝑠 [𝑖 ] Wert vom Typ char des Zeichen an Index 𝑖 von string 𝑠
𝑠 .at(𝑖 ) Analog zu 𝑠 [𝑖 ], löst jedoch Ausnahme (exception) aus, falls 𝑖
außerhalb des Bereichs von validen Indizes
𝑠 .front() 𝑠 .back() Liefert Referenz auf erstes bzw. letztes Zeichen von 𝑠 falls vorhanden,
sonst undefiniertes Verhalten
𝑠 =𝑡 𝑠 = 𝑠𝐶 𝑠 =𝑐 Setzt Inhalt von 𝑠 auf den von Kopie von 𝑡 bzw. den von 𝑠𝐶 bzw.
Länge 1 und Inhalt 𝑐
𝑠 +𝑡 𝑠 + 𝑠𝐶 𝑠 +𝑐 Nimmt neuen String als Wert an mit Kopie von 𝑠 konkateniert mit 𝑡
bzw. 𝑠𝐶 bzw. 𝑐, wie oben
𝑠 += 𝑡 𝑠 += 𝑠𝐶 𝑠 += 𝑐 Setzt 𝑠 auf Kopie von 𝑠 konkateniert mit 𝑡 bzw. 𝑠𝐶 bzw. 𝑐, wie oben
𝑠 == 𝑡 𝑠 != 𝑡 𝑠 >𝑡 𝑠 <𝑡 Vergleiche 𝑠 und 𝑡 lexikographisch, nimmt Wert vom Typ bool an
𝑠 >= 𝑡 𝑠 <= 𝑡
𝑠 == 𝑠𝐶 𝑠 != 𝑠𝐶 𝑠 > 𝑠𝐶 𝑠 < 𝑠𝐶 Vergleiche 𝑠 und 𝑠𝐶 lexikographisch, nimmt Wert vom Typ bool an
𝑠 >= 𝑠𝐶 𝑠 <= 𝑠𝐶
𝑠𝐶 == 𝑡 𝑠𝐶 != 𝑡 𝑠𝐶 > 𝑡 𝑠𝐶 < 𝑡 Vergleiche 𝑠𝐶 und 𝑡 lexikographisch, nimmt Wert vom Typ bool an
𝑠𝐶 >= 𝑡 𝑠𝐶 <= 𝑡
𝑠 .size() 𝑠 .length() Nimmt Länge von 𝑠 als Wert vom Typ string::size_type an
𝑠 .resize(𝑛 ) 𝑠 .resize(𝑛 , 𝑐 ) Setzt Länge von 𝑠 auf 𝑛, zusätzliche Elemente werden initialisiert als
Kopie von '\0' bzw. als Kopie von 𝑐
𝑠 .empty() Liefert als Wert vom Typ bool ob 𝑠 der leere String2 ist
𝑠 .substr(𝑖 ) 𝑠 .substr(𝑖 , 𝑛 ) Nimmt neuen string als Wert an mit Inhalt Kopie von 𝑠 ab Index 𝑖,
optional mit maximaler Länge 𝑛
𝑠 .find(𝑡 ) 𝑠 .find(𝑠𝐶 ) Sucht erstes von Vorkommen von 𝑡, 𝑠𝐶 , bzw. 𝑐 in 𝑠, optional erst ab
𝑠 .find(𝑐 ) 𝑠 .find(𝑡 , 𝑖 ) Index 𝑖, nimmt index vom Typ string::size_type an, wenn
𝑠 .find(𝑠𝐶 , 𝑖 ) 𝑠 .find(𝑐 , 𝑖 ) gefunden, sonst string::npos
𝑠 .rfind(𝑡 ) 𝑠 .rfind(𝑠𝐶 ) Analog zu .find(. . .), liefert jedoch letztes Vorkommen in 𝑠
𝑠 .rfind(𝑐 ) 𝑠 .rfind(𝑡 , 𝑖 )
𝑠 .rfind(𝑠𝐶 , 𝑖 ) 𝑠 .rfind(𝑐 , 𝑖 )

2 D.h. ein String der Länge 0

Seite 3 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

𝑠 .insert(𝑖 , 𝑡 ) 𝑠 .insert(𝑖 , 𝑠𝐶 ) Fügt 𝑡 bzw. 𝑠𝐶 bzw. 𝑛 viele Kopien von 𝑐 in 𝑠 ein ab Index 𝑖, 𝑠 wird
𝑠 .insert(𝑖 , 𝑛 , 𝑐 ) hierdurch verlängert um Länge von 𝑡 bzw. Länge von 𝑠𝐶 bzw. 𝑛
𝑠 .erase() 𝑠 .erase(𝑖 ) Leert 𝑠 bzw. entfernt Endstück ab Zeichen mit Index 𝑖 bzw. 𝑛 viele
𝑠 .erase(𝑖 , 𝑛 ) Zeichen ab Index 𝑖, verkürzt 𝑠 entsprechend
𝑠 .push_back(𝑐 ) Verlängert 𝑠 um 1, neues Element wird initialisiert als Kopie von 𝑡
𝑠 .pop_back() Verkürzt 𝑠 um eins, löscht letztes Element
𝑠 .clear() Ersetzt Inhalt von 𝑠 durch leeren String
𝑠 .replace(𝑖 , 𝑛 , 𝑡 ) Entfernt 𝑛 Zeichen ab Index 𝑖 in 𝑠 und fügt an ihrer Stelle den Inhalt
𝑠 .replace(𝑖 , 𝑛 , 𝑠𝐶 ) von 𝑡 bzw. 𝑠𝐶 ein, verlängert bzw. verkürzt 𝑠 um bis zu Länge von 𝑡
bzw. 𝑠𝐶 abzüglich 𝑛
𝑜 << 𝑠 𝑖 >> 𝑠 Aus- bzw. Eingabe, Länge von 𝑠 wird geeignet angepasst, Eingabe
beachtet skipws bzw. noskipws und liest dann bis zum ersten
nicht-druckbaren Zeichen (insb. Leerzeichen)
getline(𝑖 , 𝑠) Liest Zeile von 𝑖, entfernt Zeilenumbruch, speichert verbleibende
Zeichenkette in 𝑠, nimmt Referenz auf 𝑖 als Wert an

Beispiel. Wir verwenden verschiedene Möglichkeiten strings von der Standardeingabe einzulesen um den
unterschiedlichen Umgang mit Zwischenraumzeichen zu demonstrieren.

string_io.cpp

#include <iostream>
#include <string>
#include <sstream>

using namespace std;

int main() {
char m, newl;
cout << ”Methode? ”;
cin >> noskipws >> m >> newl;

string s;
if (m == 'a')
cin >> skipws >> s;
else if (m == 'b')
cin >> noskipws >> s;
else
getline(cin, s);

cout << ”»” << s << ”«” << endl;

return 0;
}

Methode? a
wort1 wort2 wort3
»wort1«

Seite 4 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

Methode? b
wort1 wort2 wort3
»«

Methode? c
wort1 wort2 wort3
» wort1 wort2 wort3 «

Ein-/Ausgabe-Ströme (<iostream>)

Im Folgenden bezeichne 𝑖 einen Eingabestrom (z.B. cin) und 𝑜 einen Ausgabestrom (z.B. cout, cerr).

Buffering

Ein- und Ausgabe von bzw. in Ströme geschieht i.A. nicht sofort. Stattdessen wird eine gewisse Menge an
Ein- bzw. Ausgabe zunächst angesammelt um sie dann in einem größeren Block auf einmal verarbeiten zu
können—es hat performance-Vorteile größere Blöcke auf einmal zu verarbeiten statt z.B. jedes Zeichen einzeln.
Dieses Verhalten nennt man Buffering (zu deutsch auch Pufferung).
Ströme sind in C++ i.d.R. buffered3 . Bei Ausgabeströmen kann, durch Anwenden des IO-Manipulators
unitbuf4 , der Buffer automatisch nach jeder Ausgabeoperation geleert werden (analog zur Verwendung des
IO-Manipulators flush nach jeder Operation).
Bzgl. des buffering haben die vordefinierten Ströme folgende Eigenschaften:

cin, cout Zu Start des Programms implementierungsabhängig buffered; i.d.R. buffered gdw. das Programm
mit einer interaktiven Konsole verbunden ist
Eingabeoperationen auf cin leeren automatisch vorher den buffer von cout.
cerr Ab Start des Programms ist unitbuf aktiv
Ausgabeoperationen auf cerr leeren automatisch vorher den buffer von cout.
clog Identisch zu cerr, jedoch buffering implementierungsabhängig wie bei cin und cout

Synchronisation der vordefinierten Ströme mit dem Betriebssystem Die soeben beschriebenen Ei-
genschaften bzgl. buffering zeigen, bis auf das gegenseitige Leeren der jeweiligen buffer bei Ein- bzw. Ausga-
beoperationen, keinen Effekt, da die Ströme cin, cout, cerr und clog zu Start des Programms synchronisiert 5
sind. Bei derart synchronisierten Strömen wird das C++-interne buffering vollständig umgangen und Ein-
bzw. Ausgabeoperationen kommunizieren jedes mal direkt mit dem Betriebssystem.

3 Die Größe des buffers ist implementierungsabhängig, z.B. 8 192 Zeichen


4 IO-Manipulatoren unitbuf und nounitbuf aus Header-Datei <ios>
5 Kontrolliert durch Funktion ostream::sync_with_stdio(𝑏 ) mit 𝑏 vom Typ bool aus Header-Datei <ostream>; sollte nur verwen-
det werden zu Start des Programms vor erster Ein- bzw. Ausgabe

Seite 5 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

Zusätzliches buffering durch das Betriebssystem Unabhängig vom buffering der Strom-Objekte selbst
kann das Betriebssystem die tatsächliche Ein- bzw. Ausgabe auf der Konsole bzw. in Dateien zusätzlich noch
anders buffern. I.d.R. werden die Ströme cin und cout, wenn sie mit der Konsole verbunden sind, vom Betriebs-
system line-buffered (zeilenweise gepuffert). D.h. sobald Ausgabe das Betriebssystem erreicht (i.d.R. sofort, da
die Ströme standardmäßig synchronisiert sind) wird diese angesammelt, bis zum nächsten Vorkommen eines
Zeilenumbruchs (\n) und dann die ganze Zeile auf einmal ausgegeben. Sind cin und cout nicht mit einer
Konsole verbunden und synchronisiert, so wird vom Betriebssystem gepuffert. Die Größe des buffers hängt
in diesem Fall natürlich vom Betriebssystem ab, z.B. 4 096 Zeichen.
cerr und clog werden vom Betriebssystem i.d.R. nicht gepuffert.

Unformatierte Ein-/Ausgabe

Mit den folgenden Funktionen kann zeichenweise von einem Eingabestrom 𝑖 gelesen bzw. auf einen Ausga-
bestrom 𝑜 geschrieben werden. Im folgenden sei 𝑣 eine Variable vom Typ char, 𝑐 ein Wert vom Typ char, 𝑛
ein Wert von Ganzzahltyp6 , 𝑠𝐶 eine C-Zeichenkette vom Typ char[] mit Länge7 ≥ 𝑛, 𝑝 ein Wert von vorzei-
chenlosem Ganzzahltyp8 und 𝑠 ′ eine Variable vom Typ string.

𝑖 .get() Liest ein einzelnes Zeichen von 𝑖 und liefert es als int. Liefert Wert identisch
mit Konstante EOF im Fehlerfall
𝑖 .get(𝑣 ) Liest ein einzelnes Zeichen von 𝑖 und speichert es auf der Referenz 𝑐. Liefert
eine Referenz auf 𝑖 als Rückgabewert
𝑖 .putback(𝑐 ) Stellt 𝑐 vorne an buffer, sodass nächstes gelesenes Zeichen 𝑐 sein wird
𝑖 .unget() Setzt cursor-Position im Strom ein Zeichen zurück. Liefert eine Referenz auf 𝑖
als Rückgabewert
𝑖 .peek() Liefert analog zu 𝑖 .get() nächstes Zeichen, verändert jedoch cursor-Position
im Strom nicht
𝑖 .read(𝑠𝐶 , 𝑛 ) Liest höchstens 𝑛 Zeichen von 𝑖 und speichert diese in 𝑐𝑠. Liefert eine Referenz
auf 𝑖 als Rückgabewert
𝑖 .gcount() Liefert Anzahl von Zeichen, die von letzter unformatierter Eingabefunktion
erfolgreich gelesen wurden
𝑠 .tellg() Liefert aktuelle cursor-Position im Ein- bzw. Ausgabestrom 𝑠 als
ios::pos_type

𝑠 .seekg(𝑝 ) Setzt aktuelle cursor-Position im Ein- bzw. Ausgabestrom 𝑠 auf Wert von 𝑝
(ebenfalls ios::pos_type)
𝑜 .put(𝑐 ) Gibt 𝑐 aus auf 𝑜. Liefert eine Referenz auf 𝑜 als Rückgabewert
getline(𝑖 , 𝑠 ′) Liest Zeile von 𝑖, entfernt Zeilenumbruch, speichert verbleibende Zeichenkette
in 𝑠 ′ , nimmt Referenz auf 𝑖 als Wert an

Beispiel 1 (Lesen bis zur ersten Leerzeile). Wir lesen aus einem Eingabestrom (hier cin) einen i.A. aus mehre-
ren Zeilen bestehenden Text ein. Die Eingabe kann vom Benutzer beendet werden, indem eine leere Zeile
eingegeben wird.

6Wird implizit konvertiert in den vorzeichenbehafteten Ganzzahldatentyp streamsize aus <ios> (das Inkludieren dieser Header-
Datei ist hierfür nicht notwendig)
7 D.h. für ein 𝑛 ′ ≥ 𝑛 sollte 𝑠 [𝑛 ′ ] ein Null-Byte \0 sein
𝐶
8Wird implizit konvertiert in den vorzeichenlosen Ganzzahldatentyp 𝑇 ::pos_type für 𝑇 der Typ des Stroms

Seite 6 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

string gesamt;
for (string zeile; getline(cin, zeile); ) {
if ([Link]()) break;
gesamt += zeile + '\n';
}

Beispiel 2 (Lesen der gesamten Eingabe). Wir lesen aus einem Eingabestrom (hier cin) die gesamte Eingabe
ein. Findet die Eingabe auf der Konsole statt (also, wie hier, mit cin), so kann die Eingabe vom Benutzer
mit der Tastenkombination ctrl + D beendet werden. I.d.R wird die Tastenkombination nur am Anfang
einer Zeile erkannt.

string gesamt;
for (char c; [Link](c); )
gesamt += c;

Stromzustand

Ein- und Ausgabeströme haben jeweils einen internen Zustand. Der Typ des Zustands ios::iostate ist ein
implementierungsabhängiger Bitmaskentyp. Bzgl. der Binäroperatoren verhalten sich Bitmaskentypen wie
vorzeichenlose Ganzzahldatentypen. Es werden die Bitmasken ios::badbit, ios::eofbit und ios::failbit
bereitgestellt um vermöge geeigneter bitweiser Arithmetik auf die einzelnen Felder zuzugreifen. Zudem ist
der Wert ios::goodbit definiert. Es lässt sich der Wert 0 vom Typ int konvertieren nach ios::iostate und
liefert dann ios::goodbit.
Unter einer Bitmaske verstehen wir hierbei einen Wert (i.d.R. von vorzeichenlosem Ganzzahldatentyp),
hier vom Typ ios::iostate, in deren Binärdarstellung genau ein bit gesetzt ist. Wenn wir nun z.B. einen
Wert 𝑣 vom Typ ios::iostate mit & verknüpfen mit einer der Konstanten ios::badbit, ios::eofbit bzw.
ios::failbit, so ist das Ergebnis verschieden von Null, gdw. das entsprechende bit in 𝑣 gesetzt ist.

Die Bedeutung der durch diese Bitmasken zugänglichen bits erschließt sich aus der Beschreibung der folgen-
den Methoden—für 𝑠 ein Strom:

𝑠 .rdstate() Liefert Zustand vom Typ ios::iostate


𝑠 .setstate(𝑧 ′ ) Mit 𝑧 aktueller Zustand, setze Zustand auf 𝑧 | 𝑧 ′
𝑠 .clear() 𝑠 .clear(𝑧 ) Setze Zustand auf ios::goodbit (also 0) bzw. 𝑧
𝑠 .good() Liefert true, falls keines der Ausnahme-bits gesetzt ist – die nächste Aktion
könnte gelingen
𝑠 .eof() Liefert true, wenn 𝑠 keine weitere Eingabe gelesen werden kann/keine
Ausgabe mehr erfolgen kann, da Ende des Stroms erreicht ist (d.h. das
ios::eofbit ist gesetzt)

𝑠 .fail() Liefert true, falls ein Fehler bzgl. 𝑠 aufgetreten ist (d.h. das ios::failbit
oder das ios::badbit sind gesetzt)
𝑠 .bad() Liefert true, falls ein hinreichend schwerer Fehler bzgl. 𝑠 aufgetreten ist,
dass Datenverlust aufgetreten sein könnte (d.h. das ios::badbit ist gesetzt)

Um zu prüfen, ob ein Strom 𝑠 in einem guten Zustand ist, empfiehlt sich if (!𝑠 .fail()) …oder, äquivalent,
if (𝑠 ) ….

Seite 7 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

Anmerkung. In Beispiel 1 und Beispiel 2 werden Referenzen auf den Eingabestrom cin jeweils verwendet als
Kondition einer Schleife. Hierbei wird der Strom cin jeweils implizit konvertiert nach bool. Der resultie-
rende Wert ist hierbei jeweils der gleiche wie ![Link]().
Das Endergebnis ist, dass die Schleife jeweils abgebrochen wird, sobald das erste Mal ein Lesevorgang
fehlgeschlagen ist. Im Regelfall liegt das daran, dass keine weitere Eingabe zur Verfügung steht.
Beispiel. Wir präsentieren Umstände in denen die Bits im Stromzustand gesetzt werden könnten:

ios::eofbit Wenn eine Einleseoperation auf einem Strom durchgeführt wird, also z.B. cin >> . . ., jedoch
keine Eingabe mehr zur Verfügung steht (z.B. wurde das Terminalfenster geschlossen), so liefert
danach [Link]() den Wert true.
ios::failbit Wenn eine Einleseoperation auf einem Strom fehlschlägt, also z.B. mit 𝑛 Name einer Varia-
ble vom Typ int, bei Ausführung der Anweisung cin >> 𝑛 ; sich die zur Verfügung stehende Eingabe
nicht interpretieren lässt als ein Wert vom Typ int (z.B. also die nächsten Zeichen der Eingabe abc
sind), so liefert danach [Link]() den Wert true.
ios::badbit Wenn eine Ausgabeoperation, z.B. cout << . . . auf einem Strom fehlschlägt (z.B. wurde das
Terminalfenster geschlossen), so liefert danach [Link]() den Wert true.

Ein-/Ausgabe auf Dateien (<fstream>)

Dateien können als Strom geöffnet werden, indem ein Objekt vom Typ ifstream (Eingabe) bzw. ofstream
(Ausgabe) konstruiert wird. Die Typen ifstream und ofstream stammen aus der Header-Datei <fstream>.
Die Konstruktoren akzeptieren jeweils einen Dateipfad (string oder char[]) als erstes Argument und optio-
nal einen Modus als zweites Argument.
Stromobjekte können auch ohne Konstruktorargumente mit dem Standardkonstruktor initialisiert werden. In
diesem Fall wird nicht sofort eine Datei geöffnet.
Der Modus ist ein numerischer Wert vom Typ ios::mode. Es stehen die folgenden bitmasken zur Verfügung
um Modi zu konstruieren bzw. zu modifizieren:

ios::in Datei zum Lesen öffnen


ios::out Datei zum Schreiben öffnen
ios::app Beim Schreiben hinten an Datei anhängen
ios::ate Nach erfolgreichem Öffnen cursor am Ende der Datei positionieren
ios::trunc Inhalt der Datei beim Öffnen löschen
ios::binary Datei im Binärmodus öffnen; verhindert automatische Übersetzung bestimmter
Kontrollzeichen9 auf bestimmten Betriebssystemen

Es sind jedoch nicht alle Kombinationen dieser Bitmasken zulässig. Bei Versuch einen unzulässigen Modus
zum Öffnen einer Datei zu verwenden, schlägt das Öffnen fehl.
Die Werte, die von den Masken ios::in, ios::binary und ios::ate abgedeckt werden, dürfen frei mitein-
ander und mit den folgenden zulässigen Kombinationen der anderen Bestandteile kombiniert werden:

9 I.W.
beläuft es sich darauf, dass Zeilenumbrüche dem Programm stets als \n präsentiert werden. Insb. auf Windows werden sie im
Text-Modus (also ohne aktvies ios::binary) jedoch transparent als \r\n in Dateien erwartet und auch so wieder zurückgeschrie-
ben.

Seite 8 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

out trunc app Bedeutung

1 ∗ 0 Inhalt der Datei beim Öffnen löschen


∗ 0 1 Beim Schreiben hinten an Datei anhängen

Im Folgenden bezeichne 𝑓 einen Dateinamen vom Typ string, 𝑚 einen Zugriffsmodus vom Typ ios::mode,
𝑖 einen Eingabe-, 𝑜 einen Ausgabe-, 𝑏 einen Ein-/Ausgabestrom und 𝑠 einen beliebigen Strom. Es stehen fol-
gende Operationen zur Verfügung:

ifstream 𝑖 { 𝑓 } Vereinbare Variable 𝑖 vom Typ ifstream, öffne dabei 𝑓 zum Lesen mit Modus ios::in
ifstream 𝑖 { 𝑓 , 𝑚} Vereinbare Variable 𝑖 vom Typ ifstream, öffne dabei 𝑓 zum Lesen mit Modus
ios::in | 𝑚

ofstream 𝑜 {𝑓 } Vereinbare Variable 𝑜 vom Typ ofstream, öffne dabei 𝑓 zum Schreiben mit Modus
ios::out

ofstream 𝑜 { 𝑓 , 𝑚} Vereinbare Variable 𝑜 vom Typ ofstream, öffne dabei 𝑓 zum Schreiben mit Modus
ios::out | 𝑚

fstream 𝑏{𝑓 } Vereinbare Variable 𝑏 vom Typ fstream, öffne dabei 𝑓 zum Schreiben und Lesen mit
Modus ios::out | ios:in
fstream 𝑏 { 𝑓 , 𝑚} Vereinbare Variable 𝑏 vom Typ fstream, öffne dabei 𝑓 zum Schreiben mit Modus 𝑚
ifstream 𝑠 Vereinbare Variable 𝑠 vom Typ ifstream bzw. ofstream bzw. fstream ohne dabei
ofstream 𝑠 Datei zu öffnen
fstream 𝑠
𝑠 .open( 𝑓 ) Öffne 𝑓 , Modus entsprechend dem Typ von 𝑠 (vgl. oben)
𝑠 .open( 𝑓 , 𝑚 ) Öffne 𝑓 , Modus entsprechend dem Typ von 𝑠 und 𝑚 (vgl. oben)
𝑠 .close() Schließe Strom
𝑠 .is_open() Prüfe ob Strom aktuell geöffnet

Beispiel. Wir implementieren ein Programm, das den Benutzer nach zwei Dateinamen fragt und den Inhalt
der ersten Datei in die Zweite kopiert. Die zweite Datei wird automatisch angelegt, wenn sie noch nicht
existiert.

[Link]

#include <string>
#include <iostream>
#include <fstream>
#include <cerrno>
#include <cstring>

using namespace std;

int main() {
string s;

cout << ”Eingabedatei: ”; cin >> s;


ifstream in{s, ios::binary};

Seite 9 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

if (!in) { cerr << strerror(errno) << endl; return 1; }

cout << ”Ausgabedatei: ”; cin >> s;


ofstream out{s, ios::binary};
if (!out) { cerr << strerror(errno) << endl; return 1; }

char c;
while ([Link](c))
[Link](c);
}

Beispiel. Wir implementieren ein Programm, das den Benutzer nach einem Dateinamen fragt und die Anzahl
von Zeichen (inkl. Leerzeichen etc.) im Inhalt der Datei zählt und ausgibt.

[Link]

#include <string>
#include <iostream>
#include <fstream>
#include <cerrno>
#include <cstring>

using namespace std;

int main() {
string s;

cout << ”Eingabedatei: ”; cin >> s;


ifstream in{s};
if (!in) { cerr << strerror(errno) << endl; return 1; }

unsigned int count = 0;


char c;
in >> noskipws;
while (in >> c) count++;

cout << s << ”: ” << count << endl;


}

Ein-/Ausgabe auf Strings (<sstream>)

Es können auch Ströme erzeugt werden, die, ohne unterliegende Datei oder Ähnliches, nur auf buffern vom
Typ string agieren.
Hierfür liefert die Header-Datei <sstream> geeignete Typen:
Im Folgenden bezeichne 𝑠 einen string, 𝑚 einen Zugriffsmodus vom Typ ios::mode, 𝑖 ein Eingabe-, 𝑜 ein
Ausgabe-, 𝑏 einen Ein-/Ausgabestrom und 𝑎 einen beliebigen Strom. Es stehen folgende Operationen zur
Verfügung:

istringstream 𝑖 Vereinbare Variable 𝑖 vom Typ istringstream, Buffer von 𝑖 initial leer,
Modus des Stroms ios::in

Seite 10 von 11
Skript 00d Programmieren II für Studierende der Mathematik — WiSe 2025/26

istringstream 𝑖 {𝑠 } Vereinbare Variable 𝑖 vom Typ istringstream, Buffer von 𝑖 initial Kopie von
𝑠, Modus des Stroms ios::in
istringstream 𝑖 {𝑠 , 𝑚} Vereinbare Variable 𝑖 vom Typ istringstream, Buffer von 𝑖 initial Kopie von
𝑠, Modus des Stroms ios::in | 𝑚
ostringstream 𝑜 Vereinbare Variable 𝑖 vom Typ ostringstream, Buffer von 𝑜 initial leer,
Modus des Stroms ios::out
ostringstream 𝑜 {𝑠 } Vereinbare Variable 𝑖 vom Typ ostringstream, Buffer von 𝑜 initial Kopie
von 𝑠, Modus des Stroms ios::out
ostringstream 𝑜 {𝑠 , 𝑚 } Vereinbare Variable 𝑖 vom Typ ostringstream, Buffer von 𝑜 initial Kopie
von 𝑠, Modus des Stroms ios::out | 𝑚
stringstream 𝑏 Vereinbare Variable 𝑖 vom Typ stringstream, Buffer von 𝑏 initial leer,
Modus des Stroms ios::in | ios::out
stringstream 𝑏 {𝑠 } Vereinbare Variable 𝑖 vom Typ stringstream, Buffer von 𝑏 initial Kopie von
𝑠, Modus des Stroms ios::in | ios::out
stringstream 𝑏 {𝑠 , 𝑚 } Vereinbare Variable 𝑖 vom Typ stringstream, Buffer von 𝑏 initial Kopie von
𝑠, Modus des Stroms 𝑚
𝑎 .str() Liefert Kopie des aktuellen Bufferinhalts
𝑎 .str(𝑠 ) Setze aktuellen Bufferinhalt auf Kopie von 𝑠

Beispiel. Wir lesen eine Zahl vom Typ int aus einem Stringliteral ein und geben sie hexadezimal aus.

demo_sstream.cpp

#include <iostream>
#include <string>
#include <sstream>

using namespace std;

int main() {
int i;
istringstream ein{” 123abc ”};
ein >> i;
cout << ”i: ” << i << endl;

string s;
ostringstream aus;
aus << hex << i;
cout << ”s: ” << [Link]() << endl;

return 0;
}

i: 123
s: 7b

Seite 11 von 11
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Datentypen für komplexe Zahlen in der Standardbibliothek (complex)

Die Datentypen complex<float>, complex<double> und complex<long double> sind spezialisiert vordefi-
niert.
Im Folgenden stehen 𝑥, 𝑦, 𝑟 und 𝜑 für Variablen vom Typ 𝑇 und 𝑧 und 𝑤 für Variablen vom Typ complex<𝑇 >.
𝑇 ist hierbei ein numerischer Datentyp (float, double, long double).

complex<𝑇 > 𝑧 Vereinbart 𝑧 mit Wert 0


complex<𝑇 > 𝑧 {𝑥 } Vereinbart 𝑧 mit Wert 𝑥 + 0𝑖
complex<𝑇 > 𝑧 {𝑥 , 𝑦 } Vereinbart 𝑧 mit Wert 𝑥 + 𝑖𝑦
𝑧 +𝑤 Ausdruck mit Wert 𝑧 + 𝑤
- * / Weitere arithmetische Grundoperatoren (- auch unär)
𝑧 = ... Zuweisung
𝑧 += 𝑤 Weist 𝑧 den Wert 𝑧 + 𝑤 zu; nimmt als Ausdruck den neuen Wert von
𝑧 an
-= *= /= Weitere arithmetische Zuweisungsoperatoren
𝑧 == 𝑤 true wenn 𝑧 = 𝑤, sonst false
!= Negierte Version von ==
cin >> 𝑧 Einlesen von 𝑧 von der Standardeingabe in Format 𝑥, (𝑥 ) oder (𝑥 , 𝑦 )
mit 𝑥 und 𝑦 Zeichenketten, die als 𝑇 eingelesen werden können
cout << 𝑧 Ausgabe von 𝑧 auf Standardausgabe im Format (𝑥 , 𝑦 ) mit 𝑥 und 𝑦
Zeichenketten
𝑧 .real() 𝑧 .imag() Re 𝑧, Im 𝑧
real(𝑧 ) imag(𝑧 ) conj(𝑧 ) Re 𝑧, Im 𝑧, 𝑧¯
abs(𝑧 ) norm(𝑧 ) |𝑧|, |𝑧| 2
arg(𝑧 ) atan2(Im 𝑧, Re 𝑧)
polar(𝑟 , 𝜑 ) 𝑟𝑒 𝑖𝜑
sin(𝑧 ) cos(𝑧 ) tan(𝑧 ) Trigonometrische Funktionen
sinh(𝑧 ) cosh(𝑧 ) tanh(𝑧 ) Hyperbolische Funktionen
exp(𝑧 ) exp(𝑧)
log(𝑧 ) log(𝑧), Hauptzweig des Log., für 𝑧 ∈ (−∞, 0) wird ln |𝑧| + 𝜋𝑖 geliefert
ln 𝑧
log10(𝑧 ) ln 10 , ln wie oben
pow(𝑧 , 𝑤 ) exp(𝑤 · log(𝑧))

sqrt(𝑧 ) 𝑧, Hauptzweig der Quadratwurzel

Beispiel (Ein-/Ausgabe von komplexen Zahlen der Standardbibliothek). Wir lesen einen Wert vom Typ
complex<double> von der Standardeingabe ein, deklarieren einen weiteren, berechnen zwei abgeleitete
Werte und geben diese aus.

Seite 1 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

complex_demo.cpp

#include <iostream>
#include <iomanip>
#include <complex>
#include <cmath>
#include <limits>

using namespace std;

int main()
{
complex<double> z, i{0.0, 1.0};

cout << ”(Re,Im): ”;


cin >> z;

cout << fixed << setprecision(numeric_limits<double>::digits10);


cout << ”sqrt(z) = ” << sqrt(z) << endl
<< ”sqrt(1.0+i) = ” << sqrt(1.0 + i) << endl;

return 0;
}

(Re,Im): (2,3)
sqrt(z) = (1.674149228035540,0.895977476129838)
sqrt(1.0+i) = (1.098684113467810,0.455089860562227)

Überladung von Operatoren

Ein beliebiger1 Operator ◦ kann als Funktion operator◦ definiert werden. Binäre Ausdrücke der Form 𝑥◦𝑦
werden dann übersetzt in einen Aufruf operator◦(𝑥 , 𝑦 ).
Analog können auch unäre Operatoren definiert werden.
Die operator◦-Funktion darf auch Methode einer Klasse sein. In diesem Fall reduziert sich die Parameterliste
entsprechend.
Vorrang und Syntax derart definierter Operatoren entspricht denen der in die Sprache eingebauten.
Bekanntestes Beispiel ist die Ein- und Ausgabe vermöge der arithmetischen Shiftoperatoren >> und <<, wie
sie auch in der Standardbibliothek bereitgestellt wird.
Beispiel (Ein- und Ausgabe vermöge Shiftoperatoren für komplexe Zahlen). Wir vereinbaren eine rudimentäre
Klasse zur Approximation komplexer Zahlen in kartesischen Koordinaten durch Werte vom Typ double.
In operator>> lesen wir die Trennzeichen im Eingabeformat manuell ein und prüfen erst danach, ob sie
sind, wie erwartet. Falls nicht setzen wir die Fehlerkomponente im gegebenen Eingabestrom.

1 mit wenigen Ausnahmen

Seite 2 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Klasse

class Complex {
private:
double x, y;

public:
Complex (double Re = 0, double Im = 0) : x(Re), y(Im) {}

friend Complex operator+ (Complex z1, Complex z2) {


return Complex{ z1.x+z2.x, z1.y+z2.y };
}

friend ostream& operator<< (ostream& stream, Complex z) {


return stream << ”(” << z.x << ”,” << z.y << ”)”;
}

friend istream& operator>> (istream& stream, Complex& z) {


char c1, c2, c3;
double x, y;

stream >> c1 >> x >> c2 >> y >> c3;


if (c1 != '(' || c2 != ',' || c3 != ')')
[Link](ios::failbit);

z = Complex{x, y};

return stream;
}
};

operators_demo.cpp

#include <iostream>

using namespace std;

Klasse

int main()
{
Complex z1, z2;

cout << ”z1 z2: ”;


cin >> z1 >> z2;

cout << ”z1+z2 = ” << z1+z2 << endl;

return 0;
}

z1 z2: (1,2) (3,4)


z1+z2 = (4,6)

Seite 3 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Klassenkomponenten

Gültigkeitsbereich Der syntaktische Gültigkeitsbereich der Namen von Klassenkomponenten erstreckt


sich zunächst über die Vereinbarung der gesamten Klasse, insb. also auch vor dem Ort an dem die Kompo-
nente selbst vereinbart wird. Zusätzlich schließt der syntaktische Gültigkeitsbereich alle Definitionen anderer
Klassenkomponenten innerhalb der selben Übersetzungseinheit ein, auch wenn diese außerhalb der Klassen-
vereinbarung stehen.
Außerhalb des syntaktischen Gültigkeitsbereichs eines Komponentennamens 𝑘 der Klasse 𝐶, jedoch innerhalb
des syntaktischen Gültigkeitsbereichs von 𝐶, steht 𝐶 ::𝑘 zur Verfügung um die Klassenkomponente anzu-
sprechen. Insbesondere Definitionen von Klassenkomponenten außerhalb der Deklaration der Klasse müssen
daher mit den derart qualifizierten Namen erfolgen.
Der syntaktische Gültigkeitsbereich des Namens der Klasse selbst folgt den Regeln einer gewöhnlichen De-
klaration, erstreckt sich also vom Punkt der Vereinbarung bis zum Ende der Übersetzungseinheit.

Separat definierte Klassenkomponenten

class Complex {
private:
double x, y;

public:
Complex(double Re = 0, double Im = 0);

double real();
double imag();

friend Complex conj(Complex);


};

Complex::Complex(double Re, double Im) : x(Re), y(Im) {}

double Complex::real() { return x; }


double Complex::imag() { return y; }

Complex conj(Complex z) {
z.y = -z.y;

return z;
}

Zugriffsattribute Zusätzlich zum syntaktischen Gültigkeitsbereich der Namen der Klassenkomponenten


gibt es auch Zugriffsbeschränkungen anhand des Zugriffsattributs, insb. private und public. Komponenten,
die bei Vereinbarung mit private markiert wurden, stehen nur innerhalb anderer Klassenkomponenten und
befreundeten Funktionen und Klassen (friend) zur Verfügung. Als public deklarierte Klassenkomponenten
stehen überall zur Verfügung.
Für mit dem Schlüsselwort class deklarierte Klassen ist private die Voreinstellung, wenn nichts Anderes
angegeben wird.

Seite 4 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

this

In Komponentenfunktionen der Klasse 𝐶 steht der Name this als Zeiger vom Typ 𝐶 * auf das aktuelle Objekt
zur Verfügung.
this zeigt innerhalb des Körpers einer Methode auf das Objekt, auf dem die Methode aufgerufen wurde. D.h.
für 𝐶 eine Klasse, 𝑐 ein Objekt (d.h. Inhalt einer Variable) vom Typ 𝐶 und 𝑚 einer Methode von 𝐶, wird beim
Aufruf 𝑐 .𝑚 (. . .) im Körper der Methode von 𝑚 der Name this zeigen auf 𝑐.
Innerhalb der Komponenten einer Klasse können die Namen 𝑛 anderer Klassenkomponenten aufgefasst wer-
den als verkürzte Notation äquivalent zu this->𝑛 bzw. (*this).𝑛.
Beispiel. Im folgenden Programm wird eine Methode square definiert, die bei Aufruf die komplexe Zahl qua-
driert und, unter Verwendung von this, eine Kopie des derart angepassten Objektes auch als Rückgabewert
zurück gibt.

this_demo.cpp

#include <iostream>

using namespace std;

class Complex {
private:
double x, y;

public:
Complex(double Re = 0, double Im = 0) : x(Re), y(Im) {}

double real() { return x; }


double imag() { return y; }

Complex square() {
double x2 = x*x - y*y, y2 = 2*x*y;
x = x2; y = y2;
return *this;
}

friend ostream& operator<<(ostream& out, Complex z) {


return out << ”(” << [Link]() << ”,” << [Link]() << ”)”;
}
};

int main() {
Complex z{1, 1};

cout << ”z davor: ” << z << endl;


cout << ”z quadriert: ” << [Link]() << endl;
cout << ”z danach: ” << z << endl;

return 0;
}

z davor: (1,1)
z quadriert: (0,2)
z danach: (0,2)

Seite 5 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Konstante Methoden

Methoden können als const markiert werden. In diesem Fall hat für eine Klasse 𝐶 this den Typ const 𝐶 *
innerhalb der Methode. Dies hat zur Folge, dass der Compiler sicherstellt, dass die Methode keine Änderungen
an den Attributen des Objekts vornimmt.
Ist ein Objekt einer Klasse const, so können auch nur konstante Methoden auf das Objekt aufgerufen werden.
Beispiel. Wir definieren eine rudimentäre Klasse zur Darstellung komplexer Zahlen mit zwei konstanten Me-
thoden für Real- bzw. Imaginärteil.

const_method_demo.cpp

#include <iostream>

using namespace std;

class Complex {
private:
double x, y;

public:
Complex(double Re = 0, double Im = 0) : x(Re), y(Im) {}

double real() const { return x; }


double imag() const { return y; }
};

int main() {
Complex z1{1.0, 0.0};
const Complex z2{2.0, 3.0};

cout << ”Re z1 = ” << [Link]() << ” Im z1 = ” << [Link]() << endl;
cout << ”Re z2 = ” << [Link]() << ” Im z2 = ” << [Link]() << endl;

return 0;
}

Re z1 = 1 Im z1 = 0
Re z2 = 2 Im z2 = 3

Konstruktoren

Konstruktoren verhalten sich in ihrer Vereinbarung syntaktisch ähnlich als wären sie Komponentenfunktio-
nen mit Namen identisch zu dem der Klasse. Im Gegensatz zu (Komponenten-)Funktionen kennen Konstruk-
toren keinerlei Ergebnistyp; nicht einmal void. Es darf daher auch kein Ergebnistyp als Teil der Vereinbarung
angegeben werden.

Standardkonstruktoren Falls innerhalb einer Klasse kein Konstruktor vereinbart ist, wird der Standard-
konstruktor mit Zugriffsattribut public automatisch erzeugt. Hierbei werden alle Attribute des Objekts eben-
falls mit ihrem Standardkonstruktor initialisiert bzw. bei eingebauten Typen garnicht initialisiert.

Seite 6 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Konstruktorinitialisierungsliste In Konstruktordefinitionen kann angegeben werden, wie die Attribute


der Klasse initialisert werden sollen, noch bevor der Code des Konstruktors ausgeführt wird. Die Initiali-
sierung erfolgt in der Reihenfolge der Vereinbarungen der Komponenten innerhalb der Klasse. Nicht in der
Konstruktorinitialisierungsliste aufgeführte Attribute werden initialisiert wie durch den Standardkonstruk-
tor.
⟨Kons'init'liste⟩ → “:” ⟨Init'⟩ {“,” ⟨Init'⟩}
⟨Init'⟩ → ⟨Name⟩ “(” [⟨Ausdruck⟩ {“,” ⟨Ausdruck⟩}] “)”
Beispiel. Im Folgenden werden mehrere Klassen mit verschiedenen Arten von Konstruktoren definiert:

Konstruktoren

class Complex {
private:
double x, y;

public:
Complex(double Re = 0, double Im = 0) : x(Re), y(Im) {}

friend ostream& operator<<(ostream& out, Complex z) {


return out << ”(” << z.x << ”,” << z.y << ”)”;
}
};

class Polynom {
private:
vector<double> a; // Koeffizienten

public:
Polynom(int n): a(n+1) { a[n] = 1; } // Monom x^n

int grad() const { return [Link]() - 1; }


};

class Vektor {
private:
double* ap; // Zeiger auf/C-Array von doubles
int len; // Länge

public:
Vektor(): ap(nullptr), len(0) {} // Leerer Vektor
Vektor(int n, double x = 0): len(n) { // n Kopien von x
ap = new double[n];
for (int i = 0; i < n; i++) ap[i] = x;
}

friend ostream& operator<<(ostream& out, Vektor v) {


for (int i = 0; i < [Link]; i++) {
if (i > 0) out << ”, ”;
out << [Link][i];
}
return out;
}
};

Seite 7 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

constructors_demo.cpp

#include <iostream>
#include <vector>

using namespace std;

Konstruktoren

int main() {
Polynom p{2};
cout << [Link]() << endl;

Complex c{1.0, 2.0};


cout << c << endl;

Vektor v{3, 1.0};


cout << v << endl;
}

2
(1,2)
1, 1, 1

Kopierkonstruktoren Der Kopierkonstruktor einer Klasse 𝐶 dient dazu neue Objekte der Klasse zu initia-
lisieren indem eine Kopie gemacht wird eines anderen Objektes der selben Klasse. Es handelt sich hierbei um
einen Konstruktor mit einzigem Referenzparameter 𝐶 & bzw. const 𝐶 & insb. also nicht 𝐶 bzw. const 𝐶.
Falls nicht explizit vereinbart wird der Kopierkonstruktor automatisch mit Zugriffsattribut public erzeugt.
Hierbei werden alle Attribute wiederum durch Aufruf ihrer Kopierkonstruktoren (oder, bei eingebauten Da-
tentypen, durch direkte Zuweisung) kopiert.
Der Kopierkonstruktor kommt bei der Übergabe eines Objekts als Parameter an eine Funktion und bei der
Rückgabe von Objekten durch Funktionen zum Einsatz. Er wird nicht für Zuweisungen verwendet (vgl. Zu-
weisungsoperatoren).
Beispiel. Wir vereinbaren eine rudimentäre Klasse für Vektoren von doubles inkl. Kopierkonstruktor. Wir
definieren eine Funktion f, die ein Vektor-Objekt als Parameter akzeptiert. Beim Aufruf von f aus main wird
eine Kopie eines Vektor-Objekts erzeugt. Hierfür wird der von uns definierte Kopierkonstruktor verwendet.
Dass es sich wirklich um eine Kopie handelt demonstrieren wir dadurch, dass wir innerhalb von f den
Inhalt des Objekts anpassen und zum Vergleich sowohl danach, wie auch im Hauptprogramm ausgeben.

vector_copy_demo.cpp

#include <iostream>

using namespace std;

class Vektor {
private:
double* ap;
int len;

Seite 8 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

public:
Vektor(): ap(nullptr), len(0) {} // Leerer Vektor
Vektor(int n, double x = 0): len(n) { // n Kopien von x
ap = new double[n];
for (int i = 0; i < n; i++) ap[i] = x;
}

Vektor(Vektor& v): len([Link]) {


ap = new double[len];
for (int i = 0; i < len; i++) ap[i] = [Link][i];
}

friend ostream& operator<<(ostream& out, Vektor v) {


for (int i = 0; i < [Link]; i++) {
if (i > 0) out << ”, ”;
out << [Link][i];
}
return out;
}
double& operator[](int i) {
return ap[i];
}
};

void f(Vektor v) { // Vektor als Parameter um Kopie zu provozieren


v[1] = 2;
cout << v << endl;
}

int main() {
Vektor v{3, 1};

f(v);

cout << v << endl;

return 0;
}

1, 2, 1
1, 1, 1

Destruktoren

Für eine Klasse 𝐶 trägt der zugehörige Destruktor den Namen ~𝐶. Er wird analog zu Konstruktoren innerhalb
der Klasse und ohne jeden Rückgabetyp vereinbart.
Beispiel. Wir definieren einen Destruktor für eine rudimentäre Klasse für Vektoren von doubles (wie oben).

Destruktor

~Vektor() {
delete[] ap;
}

Seite 9 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Aufgabe des Destruktors ist es etwaigen Speicher der mit dem Objekt assoziiert ist (z.B. Attribute für die im
Konstruktor mit new Speicher zugewiesen wurde) freizugeben. Der Destruktor wird automatisch aufgerufen,
wenn die Lebensdauer des Objektes endet. Bei Variablen also wenn der syntaktische Gültigkeitsbereich der
Variable (der Block, der die Deklaration enthält) verlassen wird. Bei temporären Objekten, die im Rahmen
eines Ausdrucks konstruiert wurden, direkt nachdem der enthaltende Ausdruck ausgewertet wurde.
Ein zweiter Aufruf des Destruktors, egal ob dieser explizit oder automatisch erfolgt, ist nicht zulässig.
Der Destruktor einer Klasse kann mithilfe des delete-Operators explizit aufgerufen werden. I.d.R. ist dies nur
für Objekte nötig für die vorher vermöge new Speicher explizit zugewiesen wurde.
Falls kein Destruktor explizit definiert wurde, wird automatisch ein leerer Destruktor erzeugt.
Wird der Destruktor aufgerufen (egal ob explizit definiert oder automatisch erzeugt), wird nach Ende der
Ausführung des Codes des Destruktors automatisch der jeweilige Destruktor für jedes Attribut aufgerufen.
Dies geschieht in umgekehrter Reihenfolge der Deklaration der Attribute innerhalb der Klasse.
Beispiel. Wir definieren eine rudimentäre Klasse für Vektoren von doubles ohne Destruktor (wie oben). Wir
demonstrieren, dass hierdurch ein Speicherleck auftreten kann – es wird Speicher zugewiesen jedoch vor
Ende des Programms nie freigegeben. Programme mit Speicherlecks verursachen Probleme dadurch, dass
sie bei langer Laufzeit i.d.R. unbegrenzt viel Speicher zugewiesen und somit alle dem System zur Verfügung
stehenden Ressourcen verbrauchen.

vector_leak_demo.cpp

#include <iostream>

using namespace std;

class Vektor {
private:
double* ap; // Zeiger auf/C-Array von doubles
int len; // Länge

public:
Vektor(): ap(nullptr), len(0) {} // Leerer Vektor
Vektor(int n, double x = 0): len(n) { // n Kopien von x
ap = new double[n];
for (int i = 0; i < n; i++) ap[i] = x;
}

/* Auskommentiert

Destruktor
*/

double sum() {
double s = 0;
for (int i = 0; i < len; i++) s += ap[i];
return s;
}
};

int main() {
Vektor a(1);
cout << [Link]() << endl;
}

Seite 10 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

==…== Memcheck, a memory error detector


==…== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==…== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==…== Command: vector_leak_demo
==…==
==…==
==…== HEAP SUMMARY:
==…== in use at exit: 8 bytes in 1 blocks
==…== total heap usage: 3 allocs, 2 frees, 74,760 bytes allocated
==…==
==…== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1
==…== at 0x48487AB: operator new[](unsigned long) (in …)
==…== by 0x40145A: Vektor::Vektor(int, double) (vector_leak_demo.cpp:13)
==…== by 0x4011CA: main (vector_leak_demo.cpp:31)
==…==
==…== LEAK SUMMARY:
==…== definitely lost: 8 bytes in 1 blocks
==…== indirectly lost: 0 bytes in 0 blocks
==…== possibly lost: 0 bytes in 0 blocks
==…== still reachable: 0 bytes in 0 blocks
==…== suppressed: 0 bytes in 0 blocks
==…==
==…== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Zuweisungsoperatoren

Der Zuweisungsoperator = kann nur durch eine Methode überladen werden (nicht durch eine befreundete
Funktion). Der Operator für eine Klasse 𝐶 muss genau einen Parameter vom Typ 𝐶, 𝐶 & oder const 𝐶 & akzep-
tieren.
Falls kein Zuweisungsoperator explizit überladen wird, wird automatisch einer erzeugt, der Zuweisung der
Attribute komponentenweise vornimmt. Hierfür wird ggf. wiederum der Zuweisungsoperator der Attribute
aufgerufen.
Zuweisungen dürfen nicht mit Aufrufen des Kopierkonstruktors verwechselt werden, obwohl Initialiserungen
vermöge des Kopierkonstruktors ebenfalls ein Gleichheitszeichen = enthalten.
Beispiel. Diverse Anweisungen, manche mit Zuweisungen oder Kopierkonstruktoraufrufen:

Complex a{4.0, 5.0} Initialisierung vermöge Konstruktor


Complex b{a} Initialisierung vermöge Kopierkonstruktor
Complex c = a Initialisierung vermöge Kopierkonstruktor
Complex d = Complex{1.0, 2.0} Initialisierung vermöge Kopierkonstruktor
Complex e, f Initialisierung vermöge Standardkonstruktor
e = c Zuweisung mit Zuweisungsoperator
f = Complex{3.0, 5.0} Zuweisung mit Zuweisungsoperator

Wesentlicher Unterschied zwischen Kopierkonstruktor und Zuweisungsoperator ist, dass der Zuweisungsope-
rator auf bereits initialisierte Objekte angewandt wird. Beim Kopierkonstruktor ist nur der Parameter bereits
initialisiert.

Seite 11 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel. Wir definieren einen Zuweisungsoperator für eine rudimentäre Klasse für Vektoren von doubles
(vgl. oben)

Zuweisungsoperator für Vektor

// Rückgabewert als Referenz um Kopie zu vermeiden


Vektor& operator=(const Vektor& v) {
delete[] ap; // this ist bereits initialisiert; Speicher muss daher zunächst freigegeben
↩→ werden

len = [Link];
ap = new double[len];
for (int i = 0; i < len; i++) ap[i] = [Link][i];

return *this;
}

Mit dieser Definition von Zuweisungsoperator und dem Kopierkonstruktor, wie oben, hat die Klasse
Vektor Wertesemantik. D.h., dass bei Zuweisungen und nicht-Referenz Parameterübergaben stets Kopien
angelegt werden.
Dies entspricht auch dem Verhalten der STL-Behälterklassen wie z.B. vector<T>.
Wünschenswert ist dies um unbeabsichtigte Änderungen an den Werten durch z.B. temporäre Änderungen
innerhalb aufgerufener Funktionen weitgehend auszuschließen.

Statische Klassenkomponenten

Klassenkomponenten können als static deklariert werden.

Statische Datenkomponenten Bei statischen Datenkomponenten muss die Definition außerhalb erfolgen
und eindeutig sein.
Statische Datenkomponenten einer Klasse sind nicht, wie Attribute, direkt mit den Objekten der Klasse ver-
knüpft. Die Klasse fungiert in diesem Sinne eher als Namensraum (namespace) um Werte semantisch mit ihr
zu assoziieren. Statische Datenkomponenten sind insofern nicht in Objekten der Klasse enthalten und dort
auch nicht mit dem Punkt- . oder Pfeiloperator -> ansprechbar. Auch nicht in this innerhalb einer Klassen-
komponente.
Beispiel. Wir vereinbaren eine Klasse zur Modellierung von Kreisen mit einer statischen Datenkomponente
für die Kreiszahl 𝜋.

circle_demo.cpp

#include <iostream>
#include <cmath>

using namespace std;

class Kreis {
public:
static const double pi;

private:
double r;

Seite 12 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

public:
Kreis(double r_ = 1): r(r_) {}
double radius() { return r; }
double umfang() { return 2*r*pi; }
friend double flaeche(Kreis k) { return pi*k.r*k.r; }
};

const double Kreis::pi = acos(-1.0);

int main() {
Kreis k{1.0};
cout << ”Radius: ” << [Link]() << endl
<< ”Umfang: ” << [Link]() << endl
<< ”Flaeche: ” << flaeche(k) << endl;

return 0;
}

Radius: 1
Umfang: 6.28319
Flaeche: 3.14159

Statische Komponentenfunktionen Bei statischen Komponentenfunktionen darf, im Gegensatz zu stati-


schen Datenkomponenten, die Definition auch innerhalb der Vereinbarung der Klasse erfolgen.
Statische Komponentenfunktionen verhalten sich i.W. gleich zu befreundeten Funktionen, nur können sie
außerhalb der Klasse nicht mit Ihrem Namen 𝑓 aufgerufen werden, sondern als 𝐶 :: 𝑓 . Auch hier fungiert die
Klasse also als Namensraum.
Beispiel. Wir vereinbaren die Klasse zur Modellierung von Kreisen erneut, vereinbaren diesmal jedoch die
Funktion flaeche als statisch statt befreundet.
Zudem vereinbaren wir pi als constexpr. Die Vereinbarung als constexpr erlaubt es bei statischen Klas-
senkomponenten die Definition innerhalb der Klasse vorzunehmen.
Generell führt die Vereinbarung als constexpr dazu, dass die Auswertung bereits beim Kompiliervorgang
geschieht und das Resultat als Konstante in die Binärdatei eingebunden wird. Dies vermeidet prinzipiell
unnötige Auswertungen zur Laufzeit und führt dadurch zu schnelleren Programmen.

circle_static_demo.cpp

#include <iostream>
#include <cmath>

using namespace std;

class Kreis {
public:
static constexpr double pi = acos(-1.0);

private:
double r;

public:

Seite 13 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Kreis(double r_ = 1): r(r_) {}


double radius() { return r; }
double umfang() { return 2*r*pi; }
static double flaeche(Kreis k) { return pi*k.r*k.r; }
};

int main() {
Kreis k{0.5};
cout << ”Radius: ” << [Link]() << endl
<< ”Umfang: ” << [Link]() << endl
<< ”Flaeche: ” << Kreis::flaeche(k) << endl;

return 0;
}

Radius: 0.5
Umfang: 3.14159
Flaeche: 0.785398

Beispiel (Vergleich der funktionsartigen Klassenkomponenten). Im Folgenden hat eine Klasse C eine Methode
m, eine konstante Methode mc, eine befreundete Funktion f und eine statische Komponentenfunktion s.
Zudem wird eine Funktion a außerhalb der Klasse C vereinbart.

function_class_components.cpp

#include <iostream>

using namespace std;

class C {
private:
int x = 1;

public:
int y = 3;

void m() { cout << ”x: ” << (x += 1) << endl; }


void mc() const { cout << ”x: ” << x << endl; }

friend void f(C c) { cout << ”c.x: ” << c.x << endl; }
static void s(C c) { cout << ”c.x: ” << c.x << endl; }
};

void a(C c) { cout << ”c.y: ” << c.y << endl; }

int main() {
C c;

c.m();
f(c);
C::s(c);
a(c);

const C cc{c};
[Link]();
}

Seite 14 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

x: 2
c.x: 2
c.x: 2
c.y: 3
x: 2

Die folgende Tabelle listet die oben gezeigten verschiedenen Sorten von „funktionsartigen Klassenkompo-
nenten“ (und die Funktion a ohne Klassenzusammenhang) erneut auf:

Klassenkomponente? Vereinbarung Aufruf

Methode ✓ void m() { … } c.m()

Konstante Methode ✓ void mc() const { … } [Link]()

Befreundete Funktion friend void f(C c) { … } f(c)

Statische Komponentenfunktion ✓ static void s(C c) { … } C::s(c)

Funktion void a(C c) { … } a(c)

Seite 15 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Zugriffsattribut protected*

Analog zu public und private lassen sich die Komponenten von Klassen auch mit protected als Sichtbarkeit
markieren.
Abseits von Auswirkungen bzgl. abgeleiteter Klassen verhält sich eine als protected markierte Klassenkom-
penente identisch als wäre sie mit private markiert.

Vereinbarung von Klassenkomponenten als explicitly defaulted*

Bei der Definition einiger Klassenkomponenten kann statt eines Körpers = default angegeben werden. Es
wird dann die ansonsten implizite Standardimplementierung für diese Klassenkomponente verwendet.
Dies ist natürlich nur bei Klassenkomponenten möglich, die ansonsten einen implizite Standardimplemen-
tierung hätten. Dies sind der Standardkonstruktor, der Kopierkonstruktor, der Zuweisungsoperator (sowohl
kopierend wie auch verschiebend), der Verschiebekonstruktor und der Destruktor.
Beispiel (Explizite Standardimplementierung). Wir definieren den Standardkonstruktor der Klasse A als expli-
citly defaulted.

explicitly_defaulted_demo.cpp

#include <iostream>

using namespace std;

class A {
public:
A() = default;
void f() { cout << ”A” << endl; }
};

int main() {
A a{}; a.f();
}

Deleted Definitions*

Funktionen und Methoden können als deleted definiert werden. Hierfür wird statt eines Körpers = delete
angegeben.
Programme in denen eine Funktion bzw. eine Methode verwendet wird, die als deleted definiert wurde, werden
vom Compiler nicht akzeptiert.
Beispiel (Gelöschter Kopierkonstruktor). Wir vereinbaren eine Klasse A, jedoch den Kopierkonstruktor von A
als deleted. Im Programm wird der Kopierkonstruktor zur Initialisierung der Variable a2 jedoch verwendet,
sodass sich das Programm nicht übersetzen lässt.

Seite 16 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

deleted_copy_demo.cpp

#include <iostream>

using namespace std;

class A {
A(const A&) = delete;
};

int main() {
A a1{};
A a2{a1};
}

deleted_copy_demo.cpp: In function ‘int main()’:


deleted_copy_demo.cpp:11:10: error: use of deleted function ‘A::A(const A&)’
11 | A a2{a1};
| ^
deleted_copy_demo.cpp:6:3: note: declared here
6 | A(const A&) = delete;
| ^

Abgeleitete Klassen*

⟨Basis⟩ → “:” ⟨BasisSpec⟩ {“,” ⟨BasisSpec⟩}


⟨BasisSpec⟩ → [⟨AccessSpec⟩] [“virtual”] ⟨ClassName⟩
⟨AccessSpec⟩ → “private” | “protected” | “public”
Durch Einschub der Basisklassenangabe ⟨Basis⟩ in die Definition einer Klasse (nach Schlüsselwort class und
Namen der Klasse jedoch vor der öffnenden geschweiften Klammer) wird die neu definierte Klasse erweitert
durch die Komponenten der angegebenen Basis-Klasse(n). Man sagt die neu Klasse erbt die Komponenten der
Basisklasse (Vererbung).
Die Sichtbarkeit der geerbten Klassenkompenenten bestimmt sich nach der Sichtbarkeit der jeweiligen
Komponente der Basisklasse und der Sichtbarkeitsangabe in ⟨AccessSpec⟩. Voreinstellung bei Weglassen der
⟨AccessSpec⟩ ist private.
Die resultierende Sichtbarkeit ist das „Minimum“ aus ⟨AccessSpec⟩ und Sichtbarkeit der Komponente der
Basisklasse, wie folgt:

Sichtbarkeit in Basisklasse
Abgeleitet als …
private protected public

private — private private

protected — protected protected

public — protected public

Komponenten der abgeleiteten Klasse können auf geerbte Komponenten, die in der Basisklasse als private
markiert wurden, abgesehen von einer Markierung als friend, nie zugreifen.

Seite 17 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel (Einfache Ableitung). Die abgeleitete Klasse B erbt die Komponente x von ihrer Basisklasse A.

simple_derived_class_demo.cpp

#include <iostream>

using namespace std;

class A {
public:
int x;
};
class B : public A {};

int main() {
B b;
b.x = 7;
cout << b.x << endl;
}

Unterobjekte/Konstruktoren für abgeleitete Klassen*

Objekte abgeleiteter Klassen enthalten vollständige Unterobjekte aller ihrer Basisklassen. Die Namen von ge-
erbten Komponenten referenzieren bei Verwendung die jeweiligen Komponenten des passenden Unterobjekts.
Bei Konstruktion eines Objektes einer abgeleiteten Klasse müssen auch die jeweiligen Unterobjekte konstru-
iert werden. Dies geschieht jeweils durch Aufruf eines der Konstruktoren der Basisklasse.
Die Konstruktoren aller Basisklassen werden, zur Initialisierung der Komponenten der Unterobjekte, aus-
geführt bevor nicht-geerbte Komponenten initialisiert werden (in der gleichen Reihenfolge in der sie in
⟨BasisSpec⟩ angegeben sind).
Durch die Initialisierungsliste der Konstruktoren der abgeleiteten Klasse können den Konstruktoren der Ba-
sisklassen, die verwendet werden um die Unterobjekte zu initialisieren, jeweils Parameter übergeben werden.
Kommt eine Basisklasse in der Initialisierungsliste einer der Konstruktoren der abgeleiteten Klasse nicht vor,
so wird dort stattdessen der Standardkonstruktor der Basisklasse verwendet.
Beispiel (Initialisierung bei abgeleiteten Klassen). Die abgeleitete Klasse B erbt die Komponente x von ihrer
Basisklasse A. Der Konstruktor mit einem Parameter der Klasse B verwendet zur Initialisierung des Unter-
objekts der Klasse A des anzulegenden Objektes der Klasse B den Konstruktor mit einem Parameter der
Klasse A und übergibt dem Konstruktor einen aus seinem eigenen Parameter berechneten Wert.

init_derived_class_demo.cpp

#include <iostream>

using namespace std;

class A {
public:
int x;

A() : x(7) {}

Seite 18 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

A(int x_) : x(x_) {} };


class B : public A {
public:
B() {}
B(int x_) : A(x_ / 2) {} };

int main() {
B b1{}; cout << b1.x << ” ”;
B b2{7}; cout << b2.x << endl;
}

7 3

Die „ist-Unterobjekt-von“ Relation bzgl. eines Objektes einer (mehrfach) abgeleiteten Klasse lässt sich visua-
lisieren als directed acyclic graph (DAG).
Beispiel. Wir visualisieren die Struktur eines Objektes einer mehrfach abgeleiteten Klasse C. Die mehreren
Vorkommen von A entsprechen der Tatsache, dass C mehrere Unterobjekte der Klasse A enthält.

A A
class A { public: int x; };
class B1 : public A {};
B1 B2 class B2 : public A {};
class C : public B1, public B2 {};
C

(a) DAG für C (b) Zugehörige Klassendeklarationen

Überschattung von Namen von Komponenten abgeleiteter Klassen*

In abgeleiteten Klassen können Komponenten mit Namen vereinbart werden, die identisch sind zu jenen ge-
erbter Komponenten. Die Komponenten der abgeleiteten Klasse haben syntaktisch Vorrang vor den geerbten
Komponenten; die geerbten Komponenten werden überschattet. Soll dennoch eine Komponente eines Unter-
objekts referenziert werden, lässt sich dies durch Verwendung des scope resolution operators (::) angewandt
auf den Komponentennamen erreichen.
Beispiel (Überschattung in abgeleiteten Klassen). Die abgeleitete Klasse B erbt die Komponente x von ihrer
Basisklasse A, hat jedoch selbst zusätzlich auch eine Komponente namens x.

shadowing_derived_class_demo.cpp

#include <iostream>

using namespace std;

class A { public: int x; };


class B : public A {
public: int x;
};

int main() {
B b;
b.x = 7; b.A::x = 3;
cout << b.x << ” ” << b.A::x

Seite 19 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

<< endl;
}

7 3

Bei mehrfacher Vererbung kann es vorkommen, dass ein Objekt mehrere Unterobjekte enthält die jeweils
gleichnamige Komponenten enthalten. Verwendete Komponentennamen müssen eindeutig zugeordnet wer-
den können. Hierfür wird die Komponente mit dem kürzesten Pfad im „ist-Unterobjekt-von“ DAG vorgezogen.
Der scope resolution operator (::) kann verwendet werden um einen Präfix des Pfads vorzugeben bis Eindeu-
tigkeit erreicht ist.
Beispiel. Wir referenzieren die Komponente x aus zwei unterschiedlichen Unterobjekten der Klasse A in einem
Objekt der abgeleiteten Klasse C indem als hinreichender (für Eindeutigkeit) Präfix des Pfads durch den
DAG jeweils B1 bzw. B2 angegeben wird.

attribute_multiple_derived_class_demo.cpp

#include <iostream>

using namespace std;

class A { public: int x; };


class B1 : public A {};
class B2 : public A {};
class C : public B1, public B2 {};

int main() {
C c;
c.B1::x = 3; c.B2::x = 7;
cout << c.B1::x << ” ” << c.B2::x
<< endl;
}

3 7

Implizite Typkonvertierungen von Zeigern bzw. Referenzen auf Objekte abgeleiteter


Klassen*

Für 𝐴 eine Basisklasse von 𝐵 kann ein Wert vom Typ „Zeiger auf 𝐵“ stets implizit konvertiert werden zu einem
Wert vom Typ „Zeiger auf 𝐴“. Analog auch für Referenzen.
Die Indirektion über Zeiger bzw. Referenzen ist notwendig, a priori ist keine implizite Typumwandlung der
Objekte selbst möglich.
Beispiel. Wir wandeln einen Wert vom Typ „Referenz auf 𝐵“ implizit um in einen Wert vom Typ „Referenz
auf 𝐴“.

typeconversion_derived_class_demo.cpp

#include <iostream>

Seite 20 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

using namespace std;

class A { public: int x; };


class B : public A {};

int main() {
B b;
b.x = 7;
A& a = b;
cout << a.x << endl;
}

Virtuelle Basisklassen*

Für alle Vorkommen einer Klasse 𝐴 im DAG eines Objektes 𝑐, bei denen die letzte Kante zu 𝐴 mit virtual
markiert wurde, enthält 𝑐 nur genau ein Unterobjekt der Klasse 𝐴. Gibt es weitere Vorkommen der Klasse
𝐴, bei denen die letzte Kante zu 𝐴 nicht mit virtual markiert wurde, so enthält das Objekt noch weitere
Unterobjekte der Klasse 𝐴.
Beispiel. Wir visualisieren die Struktur eines Objektes einer mehrfach abgeleiteten Klasse C. Da B1 und B2
jeweils virtual abgeleitet sind von A, enthalten Objekte der Klasse C nur jeweils ein Unterobjekt der
Klasse A.

Virtuell abgeleitete Klasse


A
class A { public: int x; };
class B1 : public virtual A {};
B1 B2 class B2 : public virtual A {};
class C : public B1, public B2 {};
C

(a) DAG für C (b) Zugehörige Klassendeklarationen

virtual_derived_class_demo.cpp

#include <iostream>

using namespace std;

Virtuell abgeleitete Klasse

int main() {
C c;
c.x = 7;
cout << c.x << endl;
}

Seite 21 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Virtuelle Methoden*

Gewöhnlicherweise wird anhand des Typs eines Objekts ausgewählt welche Methode ausgeführt wird, wenn
sie anhand ihres Namens auf ein Objekt angewandt wird. Dies gilt auch im Falle von überschatteten Metho-
dennamen bei abgeleiteten Klassen und bei Aufruf durch einen Zeiger bzw. durch eine Referenz.
Ist jedoch eine Methode in ihrer Deklaration als virtual markiert, so sind zunächst automatisch alle sie über-
schattenden Methoden ebenfalls virtual. Unter als virtual markierten Methoden erfolgt die Auswahl nicht
anhand des Typs, sondern anhand dessen welcher Konstruktor zuletzt auf das konkrete Objekt angewandt
wurde. Es wird dann die Methode der Klasse gewählt dessen Konstruktor zuletzt auf das Objekt ausgeführt
wurde.
Beispiel (Polymorphe Objekte). Wir rufen die überschattete, virtual Methode f auf ein Objekt der von A
abgeleiteten Klasse B auf. Es wird hierbei erwartungsgemäß die Methode f aus B gewählt, genauso wie
wenn f nicht virtual wäre. Es wird dann eine Referenz auf das Objekt b der Klasse B implizit umgewandelt
in einen Wert ap vom Typ „Referenz auf A“. Wäre f nicht virtual würde ein Aufruf von f durch ap die
Implementierung von f aus A wählen. Da f jedoch virtual ist, wird stattdessen die Implementierung aus
B gewählt, da das Objekt das ap referenziert mit einem Konstruktor der Klasse B initialisiert wurde.

virtual_fun_demo.cpp

#include <iostream>

using namespace std;

class A {
public:
virtual char f() { return 'A'; }
};
class B : public A {
public:
virtual char f() { return 'B'; }
};

int main() {
B b; A& ap = b;
cout << b.f() << ap.f() << endl;
}

BB

Attribute override und final*

Methoden für die in einer der (transitiven) Basisklassen eine Methode mit selben Namen und Parameterty-
pliste existiert, welche virtual ist, sind automatisch ebenfalls virtual.
Methoden-Deklarationen, die mit override markiert sind, müssen eine gleichnamige Methode aus einer der
Basisklassen überschatten. Ansonsten wird das Programm vom Compiler nicht akzeptiert.
Methoden-Deklarationen, die mit final markiert sind, dürfen von keiner gleichnamigen Methode in einer
ihrer abgeleiteten Klassen überschattet werden. Ansonsten wird das Programm ebenfalls nicht vom compiler
akzeptiert.

Seite 22 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel. Wir überschatten die Methode f in der von A abgeleiteten Klasse B markiert sowohl als override,
wie auch final.

override_final_demo.cpp

#include <iostream>

using namespace std;

class A {
public:
virtual char f() { return 'A'; }
};
class B : public A {
public:
char f() override final { return 'B'; }
};

int main() {
B b; A& ap = b;
cout << b.f() << ap.f() << endl;
}

BB

Object Slicing*

Per Definition nehmen die impliziten Standarddefinitionen des Kopierkonstruktors und des Zuweisungsope-
rators ein Objekt der jeweiligen Klasse als Referenzparameter. Es können daher Referenzen auf Objekte abge-
leiteter Klassen implizit konvertiert werden in Referenzen auf ihre Basisklassen bei Verwendung des Kopier-
konstruktors bzw. des Zuweisungsoperators.
Da sich der Kopierkonstruktor bzw. Zuweisungsoperator jedoch nur mit den Attributen der jeweiligen Klasse
befasst können Attribute hierbei „verloren gehen“. Man spricht von object slicing.
Beispiel (Konvertierung bei Zuweisungsoperator). Es wird für die Zuweisung der Variable a durch die Referenz
ap auf a mit einer Kopie von b der Zuweisungsoperator von A verwendet (die Auswahl des Zuweisungs-
operators geschieht anhand des Typs von ap).

assign_conversion_demo.cpp

#include <iostream>

using namespace std;

class A { public:
virtual char f() { return 'A'; }
};
class B : public A { public:
virtual char f() { return 'B'; }
};
int main() {
B b; A a; A& ap = a; ap = b;
cout << b.f() << ap.f() << endl;

Seite 23 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

BA

Beispiel (Konvertierung bei Kopierkonstruktor). Es wird für die Initialisierung der Variable a der Kopierkon-
struktor von A verwendet.

copy_conversion_demo.cpp

#include <iostream>

using namespace std;

class A { public:
virtual char f() { return 'A'; }
};
class B : public A { public:
virtual char f() { return 'B'; }
};
int main() {
B b; A a = b;
cout << b.f() << a.f() << endl;
}

BA

Beispiel (Verletzung einer Invariante durch object slicing). Durch object slicing beim Zuweisen von b1 durch
die Referenz ap vom Typ „Referenz auf A“ werden nur die zu A gehörigen Attribute „über“ den aktu-
ellen Wert von b1 kopiert. Die in b1 durchaus und auch weiterhin vorhandenen zusätzlichen Attribute
(approx_x) werden nicht verändert und werden dadurch inkonsistent mit denen aus A (x).

violated_invariant_assignment.cpp

#include <iostream>

using namespace std;

class A {
protected:
double x;

public:
A(double x_) : x(x_) {}
};

class B : public A {
private:
int approx_x;

public:
B(double x_) : A(x_), approx_x(x_) {}

friend ostream& operator<<(ostream& stream, const B& b) {

Seite 24 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

return stream << b.x << ” =~ ” << b.approx_x;


}
};

int main() {
B b1{3.0}, b2{7.0};
A& ap = b1;
ap = b2;

cout << b1 << endl;


}

7 =~ 3

Rein virtuelle Methoden*

Bei der Definition einer virtual Methode 𝑓 innerhalb einer Klasse 𝐴 kann statt eines Körpers = 0 angegeben
werden. Es ist dann nicht möglich Objekte zu initialisieren von 𝐴 oder von 𝐴 abgeleiteten Klassen, sofern
diese 𝑓 nicht überschatten. Werte vom Typ „Zeiger auf 𝐴“ bzw. „Referenz auf 𝐴“ bleiben jedoch möglich.
Man nennt 𝑓 eine rein virtuelle Methode und 𝐴 eine abstract base class (ABC).
Beispiel (Rein virtuelle Methode). Wir vereinbaren die Methode f der Klasse A als rein virtuell.

pure_virtual_demo.cpp

#include <iostream>

using namespace std;

class A { public:
virtual char f() = 0;
};
class B : public A { public:
virtual char f() { return 'B'; }
};

int main() {
B b; cout << b.f() << endl;
}

Virtuelle Destruktoren*

Die Auswahl des Destruktors bei Aufruf für ein Objekt, sowohl implizit wie auch explizit mit dem delete-
Operator, erfolgt nach dem selben Verfahren wie der Aufruf einer Methode. Destruktoren können also, wie
Methoden, virtual sein.
Beispiel (Vermeidung eines Speicherlecks durch virtuelle Destruktoren). Wir vereinbaren die Basisklasse A
mit virtual Destruktor. Als Implementierung wählen wir die, sonst implizite, Standardimplementierung.

Seite 25 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

In der von A abgeleiteten Klasse B fügen wir ein Attribut b vom Typ „Zeiger auf double“ hinzu. Im Kon-
struktor von B wird Speicher für b zugewiesen und im Destruktor wieder freigegeben.
Im Hauptprogramm wird ein Objekt der Klasse B erzeugt, jedoch nur ein unique_ptr<A> darauf gespei-
chert. Zum Ende der Lebensdauer der Variable ap (d.h. am Ende des Hauptprogramms) wird automatisch
der Destruktor der Klasse unique_ptr auf ap angewandt, insbesondere dadurch delete aufgerufen auf
das Objekt, auf das ap zeigt. Da das Objekt auf das ap zeigt initialisiert wurde mit einem Konstruktor
aus B, wird korrekt der virtual Destruktor der Klasse B gewählt, sodass der zugewiesene Speicher auch
ordnungsgemäß wieder freigegeben wird.

virtual_destructor_demo.cpp

#include <iostream>
#include <memory>

using namespace std;

class A {
public:
virtual ~A() = default;
virtual void f() = 0;
};
class B : public A {
private:
double* b;

public:
static constexpr int l = 100;
B() {
b = new double[l];
for (int i = 0; i < l; i++)
b[i] = i;
}
~B() { delete[] b; }

void f() {
for (int i = 0; i < l; i++)
cout << b[i] << endl;
}
};

int main() {
unique_ptr<A> ap{new B{}};
ap->f();
}

==…== Memcheck, a memory error detector


==…== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==…== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==…== Command: virtual_destructor_demo
==…==
==…==
==…== HEAP SUMMARY:
==…== in use at exit: 0 bytes in 0 blocks
==…== total heap usage: 4 allocs, 4 frees, 78,640 bytes allocated
==…==

Seite 26 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

==…== All heap blocks were freed -- no leaks are possible


==…==
==…== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Template Deklarationen*

⟨Templ'Dekl'⟩ → “template” “<” [⟨Templ'Param's⟩] “>” ⟨Dekl'⟩


⟨Templ'Param's⟩ → ⟨Templ'Param'⟩ {“,” ⟨Templ'Param'⟩}
⟨Templ'Param'⟩ → ⟨Templ'Typ'Param⟩ | ⟨Param'Dekl'⟩
⟨Templ'Typ'Param'⟩ → (“class” | “typename”) [⟨Ident⟩] [“=” ⟨Typ⟩]
| “template” “<” [⟨Templ'Param's⟩] “>” “class” [⟨Ident⟩] [“=” ⟨Id'Expr'⟩]
Das Schlüsselwort template deklariert eine parametrisierte, nicht-abgeschlossene Familie von „Objekten“.
An Stelle von ⟨Dekl’⟩ kann die Deklaration einer Funktion, eines Typaliases, einer Variable oder einer Klasse
stehen. Die Familie ist parametrisiert anhand der in ⟨Templ’Param’s⟩ angegebenen Parameter. Die einzelnen
Parameter ⟨Param’Dekl’⟩ sind hierbei entweder von der selben Form wie Funktionsparameter, ⟨Param’Dekl’⟩
oder Typparameter ⟨Templ’TypParam’⟩. Als Typparameter kann auch wiederum ein template mit Parametern
gefordert werden.
Ob in ⟨Templ’Typ’Param’⟩ class oder typename verwendet wird macht keinen Unterschied. class ist jedoch
aus Konsistenzgründen vorzuziehen.

Instanziierung von Templates*

⟨Templ'Inst'⟩ → ⟨Templ'Name⟩ “<” [⟨Templ'Arg'⟩ {“,” ⟨Templ'Arg'⟩}] “>”


⟨Templ'Arg'⟩ → ⟨Expr⟩ | ⟨Type⟩
Bevor ein konkretes „Objekt“ aus einer mit template eingeführten Familie ⟨Templ’Inst’⟩ verwendet werden
kann, muss es instanziiert werden. Es werden hierbei konkrete Argumente für die Parameter eingesetzt und
alle Erwähnungen der Parameter in der Deklaration entsprechend ersetzt.
⟨Expl'Templ'⟩ → “template” ⟨Inst'Dekl'⟩
Templates können vor Ihrer Verwendung explizit instanziiert werden. Hierfür wird ⟨Expl’Templ’⟩ der Deklara-
tion des Objektes das Schlüsselwort template vorgestellt. ⟨Inst’Dekl’⟩ darf hierbei keine Definition enthalten
und es wird für den Namen des „Objekts“ ⟨Templ’Inst’⟩ eingesetzt.
Beispiel. Wir instanziieren ein Mitglied des Templates array explizit.

instantiate_array_demo.cpp

#include <array>

using namespace std;

template class std::array<double, 3>;

int main() {
return 0;
}

I.d.R. wird auf die explizite Instanziierung von Templates verzichtet. Bei erster Erwähnung einer Instanz
⟨Templ’Inst’⟩ wird das template automatisch passend instanziiert.

Seite 27 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel. Wir vereinbaren ein Funktionstemplate für 𝑝-Normen auf arrays beliebiger Länge. Im Hauptpro-
gramm verwenden wir das Funktionstemplate. Es wird für jeden Satz von Parametern jeweils einmalig
eine Funktion instanziiert.

array_pnorm.cpp

#include <iostream>
#include <array>
#include <cstddef>
#include <cmath>

using namespace std;

template<class T = double, unsigned int p = 2, size_t dim>


T pNorm(const array<T, dim>& v) {
T x = 0;
for (const T& y: v)
x += pow(y, p);
return pow(x, static_cast<T>(1) / p);
}

int main() {
array<double, 3> a1{1, 2, 3};
cout << pNorm<double, 2, 3>(a1) << endl;

array<double, 5> a2{5, 4, 3, 2, 1};


cout << pNorm<double, 3>(a2) << endl;
}

3.74166
6.0822

Beispiel. Wir vereinbaren ein Variablentemplate pi_v für Darstellungen der Kreiszahl 𝜋 als Wert eines belie-
bigen Typs in den sich ein Wert vom Typ long double implizit konvertieren lässt.

template_pi.cpp

#include <iostream>
#include <iomanip>
#include <limits>

using namespace std;

template<class T>
constexpr T pi_v = static_cast<T>(3.141592653589793238462643383279502884L);

int main() {
cout
<< pi_v<int> << endl
<< setprecision(numeric_limits<float>::digits10 + 1)
<< pi_v<float> << endl
<< setprecision(numeric_limits<double>::digits10 + 1)
<< pi_v<double> << endl
<< setprecision(numeric_limits<long double>::digits10 + 1)
<< pi_v<long double> << endl;
}

Seite 28 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

3
3.141593
3.141592653589793
3.141592653589793239

Beispiel. Wir vereinbaren einen parametrisierten Typalias downset<𝑇 > für Mengen von Werten eines beliebi-
gen Typs 𝑇 . Wir übergeben hierbei an set aus STL einen weiteren Template-Parameter um zu erreichen,
dass die Werte in umgekehrter Reihenfolge gespeichert werden.

template_downset.cpp

#include <set>
#include <iostream>
#include <functional>

using namespace std;

template<class Key>
using downset = set<Key, greater<Key>>;

int main() {
downset<int> s;
[Link](3); [Link](1); [Link](2);

cout << ”s:”;


for (const int& x: s)
cout << ” ” << x;
cout << endl;
}

s: 3 2 1

Beispiel. Wir vereinbaren einen parametrisierten Typalias ptr<𝑇 > für den Typ „Zeiger auf 𝑇 “.

template_ptr.cpp

#include <iostream>

using namespace std;

template<class T>
using ptr = T*;

ptr<int> x = nullptr;

int main() {
int y = 7;
x = &y;

cout << *x << endl;


}

Seite 29 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel. Wir implementieren eine eigene Klasse Vektor in Analogie zum STL-Datentyp vector. Wie vector
ist Vektor ein über den Elementtyp parametrisiertes Template.

poly_vector.cpp

#include <iostream>

using namespace std;

template<class T>
class Vektor {
private:
T* ap;
int len;

public:
Vektor(int n = 0, const T& x = T{}): ap(nullptr), len(n) {
if (!len) return;
ap = new T[n];
for (int i = 0; i < len; i++)
ap[i] = T{x};
}
~Vektor() { if (ap) delete[] ap; }
Vektor& operator=(const Vektor& b) = delete;

class iterator {
friend class Vektor;

private:
Vektor* v;
int pos;

iterator(Vektor* v_, int pos_ = 0)


: v(v_), pos(pos_) {}

public:
T& operator*() { return v->ap[pos]; }
iterator& operator++() {
if (pos < v->len) pos++;
return *this;
}
bool operator!=(const iterator& other) const {
return v != other.v || pos != [Link];
}
};

iterator begin() { return iterator{this}; }


iterator end() { return iterator{this, len}; }
};

int main() {
Vektor<int> v{4};
cout << ”v: ”;
unsigned int count = 0;
for (Vektor<int>::iterator it = [Link](); it != [Link](); ++it) {
*it = count++; cout << *it << ” ”;
}
cout << endl;

Seite 30 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

v: 0 1 2 3

Template Spezialisierung*

⟨Templ'Spec'⟩ → “template” “<” [⟨Templ'Param's⟩] “>” ⟨Dekl'⟩


Templates können spezialisiert werden für konkrete Argumente. ⟨Dekl’⟩ ist hierbei eine Definition für eine
Instanz eines bereits bekannten templates. Der Zweck ist i.d.R. effizientere Implementierungen für gewisse
Spezialfälle bereitstellen zu können. Wenn ⟨Templ’Spec’⟩ wiederum einen nichtleeren Satz von Parametern
akzeptiert spricht man von partieller Spezialisierung. Nur templates von Klassen und Variablen können par-
tiell spezialisiert werden.
Beispiel (Spezialisierte 2-Norm). Wir spezialisieren die Implementierung von pNorm für 𝑝 = 2.

array_2norm.cpp

#include <iostream>
#include <array>
#include <cstddef>
#include <cmath>

using namespace std;

template<class T = double, unsigned int p = 2, size_t dim>


T pNorm(const array<T, dim>& v) {
T x = 0;
for (const T& y: v)
x += pow(y, p);
return pow(x, static_cast<T>(1) / p);
}

template<>
double pNorm<double, 2, 3>(const array<double, 3>& v) {
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}

int main() {
array<double, 3> a1{1, 2, 3};
cout << pNorm<double, 2, 3>(a1) << endl;

array<double, 5> a2{5, 4, 3, 2, 1};


cout << pNorm<double, 3>(a2) << endl;
}

3.74166
6.0822

Beispiel. Wir implementieren pNorm als Template von Funktionsobjekten um es partiell spezialisieren zu kön-
nen.

Seite 31 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

partial_pnorm.cpp

#include <iostream>
#include <iomanip>
#include <limits>
#include <array>
#include <vector>
#include <cstddef>
#include <cmath>

using namespace std;

template<class Vec, unsigned int p = 2>


class PNorm{
private:
using X = typename Vec::value_type;
public:
X operator()(const Vec& v) {
X x = 0;
for (const X& y: v)
x += pow(y, p);
return pow(x, static_cast<X>(1) / p);
}
};

template<class Vec>
class PNorm<Vec, 2>{
private:
using X = typename Vec::value_type;
public:
X operator()(const Vec& v) {
X x = 0;
for (const X& y: v)
x += y * y;
return sqrt(x);
}
};

int main() {
vector<double> c{5, 6};
array<float, 2> a{3, 4};

cout
<< setprecision(numeric_limits<double>::digits10 + 1)
<< PNorm<vector<double>>{}(c) << endl
<< PNorm<array<float, 2>, 3>{}(a) << endl;
}

7.810249675906654
4.497941493988037

Substitution Failure is Not An Error (SFINAE)*

Wenn, im Falle eines Templates mit partiellen Spezialisierungen, das Einsetzen der verbleibenden Argumen-
te in der Spezialisierung scheitert, ist dies kein Kompilierfehler. Es wird stattdessen stillschweigend auf die

Seite 32 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Verwendung dieser Spezialisierung verzichtet und i.A. die nächste, weniger gut passende, Spezialisierung in
Betracht gezogen. Dieses Verhalten wird gelegentlich explizit ausgenutzt um partielle Spezialisierungen ein-
zuschränken auf nur bestimmte Argumente.
Die Standardlibrary stellt hierfür in der Header-Datei type_traits einige Typprädikate und Werkzeuge bereit.
Instanzen des Templates std::enable_if<𝐵 , 𝑇 > sind Klassen, die einen Typalias type als Klassenkompo-
nente mit Wert 𝑇 enthalten, g.d.w. 𝐵 den Wert true hat. enable_if ließe sich z.B. implementieren, wie folgt:

Implementierung enable_if

template<bool B, class T = void> class enable_if {};

template<class T> class enable_if<true, T> {


public:
using type = T;
};

template<bool B, class T = void>


using enable_if_t = typename enable_if<B, T>::type;

Instanzen von Typprädikaten wie z.B. is_integral<𝑇 > sind Klassen, die einen statische Datenkomponente
value vom Typ bool (stets mit Wert true) enthalten, g.d.W. der Typ 𝑇 bestimmte, vom Prädikat abhängi-
ge, Einschränkungen erfüllt. Eine stark vereinfachte Implementierung von is_integral in der, statt eine
Einschränkung zu prüfen, jeder das Prädikat erfüllende Typ eine eigene Instanz benötigen würde, könnte
aussehen, wie folgt:

Implementierung is_integral

template<class T> class is_integral {};

template<> class is_integral<int> {


public:
static constexpr bool value = true;
};

Beispiel. Wir verwenden SFINAE um Spezialisierungen des Konstruktors einer Klasse Approx einzuschränken
sodass die eine Spezialisierung nur für Parameter ganzzahligen Typs und die andere nur für Parameter von
Gleitpunktzahlentyp gewählt werden.

sfinae_demo.cpp

#include <type_traits>
#include <iostream>
#include <cmath>

using namespace std;

class Approx {
public:
int x;

template<class Integral, enable_if_t<is_integral<Integral>::value, bool> = true>


Approx(Integral i): x(i) {}

template<class Floating, enable_if_t<is_floating_point<Floating>::value, bool> = true>

Seite 33 von 34
Skript 01 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Approx(Floating d): x(round(d)) {}


};

int main() {
Approx a1{3}, a2{5.7};

cout << a1.x << ” ” << a2.x << endl;


}

3 6

Seite 34 von 34
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

This space intentionally left blank

Seite 1 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Präprozessor-Direktiven*

⟨Pre'Dir⟩ → “#define” ⟨ident⟩ [“(” ⟨ident⟩ {“,” ⟨ident⟩} “)”] ⟨text⟩


| “#ifdef” ⟨ident⟩ | “#ifndef” ⟨ident⟩ | “#else” | “#endif”
| “#include” (“"” ⟨file⟩ “"” | “<” ⟨ident⟩ “>”)
Bevor eine Übersetzungseinheit (i.d.R. eine .cpp-Datei) kompiliert wird, wird der Präprozessor auf dem Text
der Übersetzungseinheit ausgeführt. Der Präprozessor nimmt textuelle Ersetzungen anhand von Direktiven
der Form ⟨Pre’Dir⟩ vor.

#define*

Die Direktive #define führt ein token ⟨ident⟩ ein. Im Folgenden wird dann jedes Vorkommen des eingeführ-
ten tokens durch ⟨text⟩ ersetzt. Das token zählt im Folgenden als definiert, auch wenn ⟨text⟩ leer ist.
Das eingeführte token kann parametrisiert werden. Es werden dann bei Ersetzung eines Vorkommen des
eingeführten tokens mit den gegebenen Argumenten in ⟨text⟩ die Argumente eingesetzt wo immer die ⟨ident⟩
der Parameter darin vorkommen.
Es handelt sich hierbei um eine Ersetzung auf rein textuellem Niveau. Insb. Bindungsstärken von Operatoren
werden, mit manchmal unintuitiven Folgen, nicht berücksichtigt.
Beispiel. Wir definieren die tokens SEVENTEEN und PLUS. PLUS ist hierbei parametrisiert über zwei Parameter
a und b.

define_demo.cpp

#include <iostream>
using namespace std;

#define SEVENTEEN (14 + 3)


#define PLUS(a, b) (a + b)

int main() {
cout << SEVENTEEN << ” ”
<< PLUS(3, 14)
<< endl;
}

17 17

#ifdef, #ifndef, etc.*

Die Direktive #ifdef bzw. #ifndef fügt den Text zwischen der Zeile die die Direktive enthält und dem darauf
folgenden #endif bzw. #else ein, g.d.w. das angegebene ⟨ident⟩ definiert ist, bzw. nicht definiert ist. Auf ein
etwaiges #else dürfen bis #endif weitere Zeilen Text folgen. Diese werden stattdessen eingefügt, falls die
Bedingung bzgl. Definiertheit nicht zutrifft.
Beispiel. Wir definieren das token SEVENTEEN und fügen in das Hauptprogramm eine Zeile Code ein wenn
SEVENTEEN definiert ist und stattdessen eine andere Zeile, wenn nicht.

Seite 2 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

ifdef_demo.cpp

#include <iostream>
using namespace std;

#define SEVENTEEN 17

int main() {
#ifdef SEVENTEEN
cout << SEVENTEEN << endl;
#else
cout << 18 << endl;
#endif
}

17

#include*

Die Direktive #include findet eine Datei im lokalen System anhand von ⟨file⟩ bzw. ⟨ident⟩ und ersetzt die
Direktive durch den Inhalt jener Datei. Die Methode mit der der Compiler die Datei findet ist im Standard nicht
festgelegt. I.d.R. sucht jedoch die Variante mit " im gleichen Verzeichnes wie die zu übersetzende .cpp-Datei
und die Variante mit <, > i.d.R. in systemweiten Standardverzeichnissen.
Beispiel. Wir binden die Datei iostream aus einem nicht näher spezifizierten systemweiten Standardverzeich-
nis ein.

[Link]

#include <iostream>

using namespace std;

int main() {
cout << ”Hello, World!” << endl;
}

Hello, World!

Header-Dateien*

Oft werden die Deklarationen von „Objekten“ (d.h. Funktionen, Klassen, globalen Variablen, etc.) ausgela-
gert in spezielle Header-Dateien, die i.d.R. nur Deklarationen enthalten und die Dateiendung .h tragen. Diese
Header-Dateien werden dann unter Verwendung der Präprozessor-Direktive #include in i.A. mehrere Über-
setzungseinheiten (i.d.R. .cpp-Dateien) eingebunden.

Seite 3 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Double inclusion*

Es kann leicht passieren, dass eine Header-Datei mehrfach in eine Übersetzungseinheit eingebunden wird. Es
ist jedoch i.A. nicht zulässig beliebige „Objekte“ (insb. Klassen) mehrfach zu deklarieren bzw. zu definieren.
Beispiel. Die Übersetzungseinheit [Link] bindet grandparent.h mittelbar doppelt ein. Die Übersetzung
schlägt daher fehl.

grandparent.h

class Klasse {
public: int attribut;
};

parent.h

#include ”grandparent.h”

[Link]

#include ”grandparent.h”
#include ”parent.h”

int main() {}

In file included from parent.h:1,


from [Link]:
grandparent.h:1:7: error: redefinition of ‘class Klasse’
1 | class Klasse {
| ^~~~~~
In file included from [Link]:
grandparent.h:1:7: note: previous definition of ‘class Klasse’
1 | class Klasse {
| ^~~~~~

Include guards*

Zur Vermeidung des Problems der double inclusion hat sich historisch die Verwendung von include guards
durchgesetzt. Es wird hierfür für jede Header-Datei ein token definiert und der gesamte Text der Header-Datei
nur dann vom Präprozessor eingesetzt, wenn das jeweilige token noch nicht definiert ist. Nachteil hierbei ist
dass es dem Programmierer überlassen bleibt ein hinreichend eindeutiges token für jede Header-Datei zu
wählen.
Beispiel. Wir vermeiden das Problem der double inclusion durch Verwendung von include guards.

grandparent_guard.h

#ifndef GRANDPARENT_GUARD_H
#define GRANDPARENT_GUARD_H

class Klasse {
public: int attribut;

Seite 4 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

};
#endif

parent_guard.h

#include ”grandparent_guard.h”

child_guard.cpp

#include ”grandparent_guard.h”
#include ”parent_guard.h”

int main() {}

#pragma once*

Bei den #pragma Präprozessor-Direktiven handelt es sich um Compiler-spezifische Erweiterungen des Präpro-
zessors. Der Standard definiert die Wirkung von #pragma-Direktiven nicht.
I.W. alle Compiler unterstützen #pragma once. Die Wirkung ist analog zu include guards; Header-Dateien,
die die Direktive enthalten, werden in jeder Übersetzungseinheit maximal einmal eingebunden.
Der Vorteil gegenüber include guards liegt in der Beseitigung einiger Fehlerquellen wie der sonst benötigten
#endif Direktive und der Wahl eines hinreichend eindeutigen tokens.

Beispiel. Wir vermeiden das Problem der double inclusion durch Verwendung von #pragma once.

grandparent_pragma.h

#pragma once

class Klasse {
public: int attribut;
};

parent_pragma.h

#include ”grandparent_pragma.h”

child_pragma.cpp

#include ”grandparent_pragma.h”
#include ”parent_pragma.h”

int main() {}

Seite 5 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Separate Compilation*

Objekt-Dateien*

Das Kompilieren einer Übersetzungseinheit gliedert sich in mehrere Phasen. Zunächst wendet der Präprozes-
sor textuelle Ersetzungen an, dann wird der resultierende Text als C++-Code kompiliert zu einer Objekt-Datei
und dann die resultierende Objekt-Datei zu einer ausführbaren Binärdatei weiterverarbeitet. Durch Überge-
ben des Kommandozeilenparameters -c an g++ (z.B. also g++ -c [Link]) kann das Weiterverarbeiten in
eine ausführbare Binärdatei unterdrückt werden. Es wird dann eine Objekt-Datei mit dem gleichen Namen
wie die übersetzte .cpp-Datei, jedoch mit Dateiendung .o produziert (z.B. also hello.o).
Die produzierte Objekt-Datei enthält kompilierten Maschinencode und einige Metadaten bzgl. insb. Funktio-
nen und globalen Variablen. Es korrespondiert ein Symbol der Objekt-Datei zu jedem „Objekt“, d.h. insb. jeder
Funktion und globalen Variablen.

Linker*

Der Linker wird, bei Aufruf ohne den Kommandozeilenparameter -c, vom Befehl g++ automatisch ausgeführt.
Er verknüpft Erwähnungen von Symbolen in Objekt-Dateien mit den Implementierungen selbiger, i.A. auch
über mehrere Objekt-Dateien, und produziert eine ausführbare Binärdatei. Werden Symbole erwähnt, die
sich in keiner der angegebenen Objekt-Dateien finden lässt, so scheitert die Ausführung des Linkers mit einer
Fehlermeldung.
Beispiel. Wir kompilieren eine leere Datei. Die Fehlermeldung die wir dabei vom Linker ld erhalten besagt,
dass das Symbol main zwar erwähnt wurde (es wird zum Erstellen einer ausführbaren Binärdatei natürlich
zwingend benötigt), jedoch nicht aufzufinden ist.

…/bin/ld: …/lib/crt1.o: in function `_start':


(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status

Auf eine Liste von Objekt-Dateien 𝑙 1, . . . , 𝑙𝑛 lässt sich der Linker aufrufen als g++ 𝑙 1 . . . 𝑙𝑛 . Die Reihenfolge in
der die Objekt-Dateien als Kommandozeilenparameter angegeben werden ist i.A. relevant. Ein oft zielführen-
der Ansatz ist es, dass Objekt-Dateien, die ein Symbol 𝑠 verwenden, im Befehl vorkommen müssen vor der
Objekt-Datei die 𝑠 definiert.
Beim Linken von C++-Code der Symbole verwendet, die in anderen Übersetzungseinheiten implementiert
sind, ist eine Implementierung des Symbols in Form einer Objekt-Datei zwar notwendig, aber i.A. nicht hin-
reichend. Es wird i.d.R. auch die Deklaration des „Objekts“ benötigt. Hierfür wird die Deklaration i.d.R. in einer
Header-Datei ausgelagert und vermöge #include in den C++-Code eingebunden, der das Symbol verwendet.
Von der Verwendung von Anweisungen wie using namespace in Header-Dateien ist abzuraten, da hierduch al-
le Symbole aus dem verwendeten namespace auch in Übersetzungseinheiten sichtbar würden, die die Header-
Datei bloß mit #include einbinden. Man spricht bei Verletzung dieses Prinzips von namespace pollution.
Beispiel (Programmbibliothek). Wir demonstrieren die konventionelle Struktur einer Programmbibliothek in-
dem wir eine Funktion greet zur Ausgabe eines strings auf der Standardausgabe separat implementieren
von ihrer Verwendung in den Übersetzungseinheiten [Link] und [Link].

Seite 6 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

greet.h

#include <string>

void greet(std::string greeting);

[Link]

#include <iostream>
#include <string>
#include ”greet.h”

using namespace std;

void greet(string greeting) {


cout << greeting << endl;
}

[Link]

#include <string>

using namespace std;

void greet(string greeting);

int main() {
greet(”Hello, World!”);
}

[Link]

#include ”greet.h”

int main() {
greet(”Hello!”);
}

Die Übersetzung von [Link] (und analog für [Link]) kann erfolgen, wie folgt:

g++ -c [Link]
g++ -c [Link]
g++ hello1.o greet.o
./[Link]

Die durch die Übersetzung von [Link] erzeugte ausführbare Binärdatei erzeugt bei Ausführung die
folgende Ausgabe:

Hello, World!

[Link] produziert bei Ausführung:

Seite 7 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Hello!

Deklarationen*

Deklarationen vereinbaren Namen in der aktuellen Übersetzungseinheit. I.A. werden vereinbarte Namen nicht
gleich mit einer Bedeutung versehen, d.h. bei Variablen ein initialer Wert bzw. bei Funktionen und Methoden
ein Körper. Wird ein initialer Wert (auch implizit) bzw. ein Körper angegeben so ist diese Deklaration auch
eine Definition. Nur Definitionen führen dazu, dass bei Ausführung des Programms Speicher zugewiesen wird
und eine Initialisierung z.B. einer Variable durchgeführt wird, wenn nötig.
In C++ gilt die One Definition Rule; es darf jede Funktion, Klasse, Variable, etc. maximal einmal definiert wer-
den. Davon nicht beeinträchtigt ist die Möglichkeit die selbe Funktion, Klasse, Variable, etc. mehrfach zu
deklarieren (wobei die Regeln wann eine Deklaration einer Variable als Definition fungiert nicht trivial sind).
Zusätzlich muss jedes Programm (insb. nicht jede Übersetzungseinheit, sondern erst das aus Objektdateien zu-
sammengefügte Programm) eine Definition enthalten für jede potentiell verwendete Funktion, Klasse, Variable,
etc.
Mit seiner Deklaration beginnt der syntaktische Gültigkeitsbereich (das scope) eines Namens. In den meisten
Fällen erstreckt sich der syntaktische Gültigkeitsbereich eines Namens ab seiner Deklaration bis zum Ende
des Blocks, der die Deklaration enthält (also bis zur „nächsten“ schließenden geschweiften Klammer). Für
Parameter von Funktionen und Methoden ist der syntaktische Gültigkeitsbereich zunächst stets der gesamet
Körper der Funktion bzw. Methode.
Wenn in verschachtelten Blöcken (z.B. bei Schleifen innerhalb von Funktionen) ein Name erneut deklariert
wird, so handelt es sich hierbei um einen anderen Namen. Der syntaktische Gültigkeitsbereich des „äußeren“
Namens erstreckt sich dann nicht über den syntaktischen Gültigkeitsbereich des „inneren“ Namens. Man
spricht hierbei von Überschattung.
Das Überschatten von Namen ist nicht per se ein Fehler, jedoch oft verwirrend und daher i.A. zu vermeiden.

Speicherklassen*

Die Speicherklasse einer Variablen bestimmt insb. wann Speicher für diese Variable zugewiesen wird, wann er
wieder freigegeben wird, an welcher Stelle im Arbeitsspeicher (auf dem heap oder auf dem stack) der Speicher
zugewiesen wird und wann die Initialisierung der Variable (bzw. des ihr zugewiesenen Speichers) erfolgt.
Die Speicherklasse einer Variable wird bei Ihrer Deklaration durch die Verwendung eines entsprechenden
Schlüsselworts angegeben. Wird keine Speicherklasse explizit angegeben, so hat die Variable Speicherklasse
automatisch.

automatisch Speicher wird zugewiesen wenn die Ausführung des Programms die Definition durchläuft. Der
Speicher wird auf dem stack zugewiesen. Der Speicher wird freigegeben sobald die Ausführung des
Programms das Ende des syntaktischen Gültigkeitsbereiches des Variablennamens erreicht.
statisch Wird eine Variable unter Verwendung des Schlüsselwortes static vereinbart so wird ihr Speicher
am Start des Programms zugewiesen. Die Initialisierung dieses Speichers (also der Variable) geschieht
jedoch erst sobald die Ausführung des Programms die Definition das erste mal durchläuft. Durchläuft die
Ausführung des Programms die Definition erneut, so hat dies keinerlei Auswirkung. Der zugewiesene
Speicher wird erst am Ende des Programms wieder freigegeben.

Seite 8 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

dynamisch Wird ein Wert unter Verwendung des new-Operators erzeugt, so hat dieser die Speicherklasse
dynamisch. Es wird bei Auswertung des new-Operators Speicher auf dem heap zugewiesen und die
Freigabe dieses Speichers muss manuell durch Verwendung des delete-Operators erfolgen.
Von der Verwendung des new-Operators sollte abgesehen werden, stattdessen sollte das pattern Resource
Acquisition Is Initialization (RAII) angewandt werden.

Beispiel. Wir implementieren eine Funkion strtok, die eine C-Zeichenkette durchläuft und mit jedem Auf-
ruf der Funktion die nächste durch eine gegebene Menge von Trennzeichen abgetrennte Teilzeichenkette
zurückgibt. Es wird hierfür eine Variable mit Speicherklasse statisch verwendet, welche sich die „aktuelle“
Position in der Zeichenkette zwischen Ausführungen der Funktion „merkt“.

[Link]

#include <iostream>
#include <iomanip>

using namespace std;

bool is_delim(char c, const char* del) {


for (int i = 0; del[i] != '\0'; i++)
if (del[i] == c)
return true;
return false;
}
int strspn(const char* str, const char* del) {
int res = 0;
while (str[res] != '\0')
if (is_delim(str[res], del))
res++;
else
return res;
return res;
}
int strcspn(const char* str, const char* del) {
int res = 0;
while (str[res] != '\0')
if (!is_delim(str[res], del))
res++;
else
return res;
return res;
}

char* strtok(char* str, const char* del) {


static char* buffer;
if (str)
buffer = str;

buffer += strspn(buffer, del);

if (*buffer == '\0')
return nullptr;

char* tokenBegin = buffer;

buffer += strcspn(buffer, del);


if (*buffer != '\0')

Seite 9 von 10
Skript 02 Programmieren II für Studierende der Mathematik — WiSe 2025/26

*(buffer++) = '\0';

return tokenBegin;
}

int main() {
char input[] = ”one + two * (three - four)!”;
const char delim[] = ”! +- (*)”;
for (char* token = strtok(input, delim); token; token = strtok(nullptr, delim))
cout << '”' << token << '”' << endl;
}

Bindungswirkung von Namen (linkage)*

Beim Zusammenfügen mehrerer Übersetzungseinheiten zu einem Programm können verwendete Namen sich
entweder auf ein Objekt innerhalb der selben Übersetzungseinheit beziehen oder auf einen Namen der über
Übersetzungseinheiten hinweg zugänglich sein soll.
Es wird daher die Bindungswirkung von Namen im Programm unterschieden, wobei diese sowohl von der
Verwendung bestimmter Schlüsselwörter abhängt, sowie von der Position der Deklaration.
Mit dem Schlüsselwort extern deklarierte Namen haben stets externe Bindung.
Variablen mit Speicherklasse statisch haben stets interne Bindung.
Als const bzw. constexpr deklarierte Variablen haben per Voreinstellung interne Bindung, können jedch bei
Verwendung des Schlüsselworts extern stattdessen externe Bindung haben.
Deklarationen im global scope (d.h. außerhalb sämtlicher Funktionen, Klassen und anderweitiger Blöcke) ha-
ben exteren Bindung.
Namen von Klassenkomponenten haben die selbe Bindungswirkung wie der Name der Klasse.
Alle Namen auf denen keine dieser Regel zutrifft haben keine Bindungswirkung.

Keine Bindungswirkung Der Name verweist nur innerhalb seines syntaktischen Gültigkeitsbereichs auf
das gleiche sprachliche Objekt (d.h. die gleiche Funktion, Klasse, Variable, etc.)
Interne Bindung Der Name verweist innerhalb der ganzen Übersetzungseinheit auf das gleiche Objekt
Externe Bindung Der Name verweist innerhalb des ganzen Programms auf das gleiche Objekt

Seite 10 von 10
Skript 03 Programmieren II für Studierende der Mathematik — WiSe 2025/26

This space intentionally left blank

Seite 1 von 4
Skript 03 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Arrays in STL (array)*

Objekte vom Datentyp array<𝑇 , 𝑛 > speichern 𝑛 viele Elemente vom Typ 𝑇 . Es handelt sich im Wesentlichen
um dünne wrapper um eingebaute C-Arrays vom Typ 𝑇 [𝑛 ].
Der Vorteil gegenüber der Verwendung von C-Arrays liegt in der, im Gegensatz zu C-Arrays, mit anderen
container-Datentypen aus der STL einheitlichen Programmierschnittstelle in Form der angebotenen Metho-
den und Attribute. Zusätzlich ist die Länge fester Bestandteil der jeweiligen Instanz des Typs array<𝑇 , 𝑛 >
und wird somit, anders als bei C-Arrays, an allen Stellen im Programm überprüft. Insb. kann es nicht pas-
sieren, dass einer Funktion, die ein array bestimmter Länge erwartet, ein array anderer Länge übergeben
wird.
Im Folgenden bezeichnet 𝑇 einen Datentyp, 𝑛 einen „hinreichend konstanten“ Ausdruck vom Typ size_t,
𝑘 < 𝑛 einen Wert vom Typ size_t, 𝑡 0, . . . , 𝑡𝑛−1 Werte vom Typ 𝑇 und 𝑎, 𝑎 ′ Variablen vom Typ array<𝑇 , 𝑛 >
Zudem bezeichnet 𝑖 einen Iterator über 𝑎.

array<𝑇 , 𝑛> 𝑎 Vereinbart Variable 𝑎 vom Typ array<𝑇 , 𝑛 > und allen
Elementen initialisiert mit Standardkonstruktor
array<𝑇 , 𝑛 > 𝑎 {𝑡 0 , . . ., 𝑡𝑘 −1 } Vereinbart Variable 𝑎 vom Typ array<𝑇 , 𝑛 > mit gegebenen
Präfix von Elementen und allen verbleibenden Elementen
initialisiert mit Standardkonstruktor
𝑎 .at(𝑘 ) Liefert Referenz auf Element mit Index 𝑘, wirft exception falls
𝑘 kein valider Index
𝑎 [𝑘 ] Liefert Referenz auf Element mit Index 𝑘, undefiniertes
Verhalten falls 𝑘 kein valider Index
𝑎 .front() 𝑎 .back() Liefert Referenz auf erstes bzw. letztes Element von 𝑎 falls
vorhanden, sonst undefiniertes Verhalten
𝑎 == 𝑎 ′ 𝑎 != 𝑎 ′ 𝑎 > 𝑎′ 𝑎 < 𝑎′ Vergleicht 𝑎 und 𝑎 ′ lexikographisch
𝑎 <= 𝑎 ′ 𝑎 >= 𝑎 ′
𝑎 .size() Liefert 𝑛
𝑎 .empty() Liefert true g.d.w. 𝑎 leer ist (d.h. 𝑛 = 0)
array<𝑇 , 𝑛 >::iterator 𝑖 Vereinbart Variable 𝑖 als Vorwärts-, Konstanten-, Rückwärts-
array<𝑇 , 𝑛 >::const_iterator 𝑖 bzw. Rückwärts-Konstanten-Iterator
array<𝑇 , 𝑛 >::reverse_iterator 𝑖
array<𝑇 , 𝑛 >::const_reverse_iterator 𝑖

𝑎 .begin() 𝑎 .end() 𝑎 .rbegin() Liefert Vorwärts-Iterator über 𝑎 zum ersten bzw. nach dem
𝑎 .rend() letzten Element bzw. Rückwarts-Iterator über 𝑎 zum letzten
++𝑖 𝑖 ++ --𝑖 𝑖 -- Verschiebt Iterator 𝑖 auf nächste bzw. vorherige Position in
Durchlaufrichtung
𝑖 + 𝑘 𝑘 + 𝑖 𝑖 - 𝑘 Liefert Iterator 𝑖 verschoben um 𝑘 in Durchlaufrichtung
𝑖 += 𝑘 𝑖 -= 𝑘 Verschiebt Iterator 𝑖 um 𝑘 in Durchlaufrichtung
*𝑖 Referenz zu Wert an aktueller Position von 𝑖
𝑖 [𝑘 ] *(𝑖 + 𝑘)
𝑖 ->𝑐 *(𝑖 ).𝑐

Seite 2 von 4
Skript 03 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel. Wir initialisieren und sortieren einen array der Länge drei mit Elementen vom Typ double.

array_sort.cpp

#include <iostream>
#include <array>
#include <algorithm>

using namespace std;

int main() {
array<double, 3> a1{3, 2, 1};

sort([Link](), [Link]());

cout << ”a1[0..”


<< [Link]() - 1
<< ”]:”;
for (const double& x: a1)
cout << ” ” << x;
cout << endl;
}

a1[0..2]: 1 2 3

Namensräume*

Zur Verwaltung von Namen mit externer Bindung und zur Vermeidung von Namenskollisionen kennt C++
Namensräume. Bei einem Namensraum handelt es sich um eine Sammlung von Deklarationen unter einen ge-
meinsamen Präfix. Als Präfix fungiert hierbei der Name des Namensraumes abgetrennt vom Namen innerhalb
des Namensraums mit dem scope resolution operator :: (z.B. std::string).
Die Namen aus einem Namensraum können mithilfe einer using namespace-Deklaration auch unqualifiziert
sichtbar (d.h. ansprechbar auch ohne Verwendung des scope resolution operator) gemacht werden (z.B. using
namespace std).

Namensräume können verschachtelt werden. Es wird hierfür auf der linken Seite des scope resolution opera-
tors ein wiederrum qualifizierter Name verwendet, z.B. std::numeric_limits<double>::epsilon.
Neue Namensräume können deklariert werden, wie folgt:
[“inline”] “namespace” [⟨Name⟩] “{” {⟨Deklaration⟩} “}”
Wird ein Namensraum als inline deklariert, so werden die enthaltenen Namen zusätzlich auch an der Stelle
der Deklaration des Namensraums bekannt gemacht (ähnlich zu using namespace).
Namensraum-Deklarationen ohne ⟨Name⟩ agieren wie Namensraum-Deklarationen mit einem, nicht näher
spezifizierten, eindeutigem Namen pro Übersetzungseinheit. Zusätzlich verhalten sich Deklarationen unbe-
nannter Namensräume stets so als wären sie inline. Unbenante Namensräume haben konsequenterweise
stets interne Bindung.
Namensräume im global scope (und somit auch die darin deklarierten Namen) haben externe Bindung.
Namensräumen kann ein zusätzlicher (i.d.R. kürzerer) Name gegeben werden durch Deklarationen eines
namespace alias, wie folgt:

Seite 3 von 4
Skript 03 Programmieren II für Studierende der Mathematik — WiSe 2025/26

“namespace” ⟨Name⟩ “=” ⟨Name⟩ “;”

Seite 4 von 4
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Parametervoreinstellungen

Bei der Vereinbarung von Funktionen kann ein Endstück der Parameterliste mit Voreinstellungen versehen
werden. D.h. den Paramatern werden, abgetrennt jeweils durch =, in der Parameterliste Werte zugewiesen.
Dies bewirkt, dass beim späteren Aufruf der Funktion jene Parameter, die Voreinstellungen haben, optional
weggelassen werden dürfen. In diesem Fall wird für die weggelassenen Parameter automatisch die Voreinstel-
lung eingesetzt.
Beispiel. Wir definieren eine Funktion strtoint die einen string von Ziffern umwandelt in eine Zahl vom
Typ int. Die Funktion nimmt optional einen Parameter für die Basis der Zahl (z.B. Dezimal oder Hexade-
zimal). Die Basis 10, d.h. Dezimal, ist für den basis-Parameter voreingestellt.

[Link]

#include <iostream>
#include <iomanip>
#include <cctype>

using namespace std;

int strtoint(string s, unsigned int basis = 10) {


if ([Link]() <= 0 || basis < 2 || basis >= 36)
return -1;

int zahl = 0;
for (unsigned int i = 0; i < [Link](); i++) {
unsigned int ziffer;

if (isdigit(s[i]))
ziffer = s[i] - '0';
else if (islower(s[i]))
ziffer = s[i] - 'a' + 10;
else if (isupper(s[i]))
ziffer = s[i] - 'A' + 10;
else
return -1;

if (ziffer < 0 || ziffer >= basis)


return -1;

zahl = basis*zahl + ziffer;


}

return zahl;
}

int main() {
string s;
cout << ”s: ”;
cin >> s;

cout << ”Wert in dezimal: ” << strtoint(s) << endl


<< ”Wert in hexadezimal: ” << strtoint(s, 16) << endl;

return 0;
}

Seite 1 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

s: 42
Wert in dezimal: 42
Wert in hexadezimal: 66

Andere Parameter der Funktion stehen im zugewiesenen Ausdruck einer Parametervoreinstellung nicht zur
Verfügung.
Bei überladenen Operatoren (z.B. operator+) dürfen Parametervoreinstellungen nicht verwendet werden.

Überladene Funktionen

In C++ dürfen mehrere Funktionen den selben Namen tragen. Sie müssen sich dann jedoch in der Anzahl
und/oder den Typen ihrer Parameter unterscheiden.
Beispiel. Wir überladen eine Funktion f mit f(int, int) und f(double, double).

overloading_demo.cpp

#include <iostream>

using namespace std;

int f(int a, int b) {


return a + b;
}

int f(double x, double y) {


return x * y;
}

int main() {
cout << f(1, 2) << endl;
cout << f(1.0, 2.0) << endl;
}

3
2

Auswahl überladener Funktionen

Bei Aufruf einer Funktion wird aus den gleichnamigen Funktionen, in deren syntaktischen Gültigkeitsbereich
der Aufruf liegt, eine konkrete Implementierung ausgewählt. Hierbei werden alle Parameter des Funktionsauf-
rufs implizit Konvertiert zu den Typen von Parametern, die die gewählte konkrete Implementierung erwartet.
Es wird stets die Implementierung gewählt für die die „besten“ impliziten Konvertierungen gewählt werden
können. Diese muss eindeutig sein.
Implizite Konvertierungen bestehen aus folgen von einzelnen Konvertierungen. Es werden keine impliziten
Konvertierungen in Betracht gezogen, die unnötige Schritte enthalten oder in denen mehr als eine benutzer-
definierte Konvertierung vorkommt.
Die „Güte“ einer impliziten Konvertierung ergibt sich grob aus der „Güte“ ihrer einzelnen Konvertierungen.
Einzelne Konvertierungen in absteigender „Güte“:

Seite 2 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

• – Exakte Übereinstimmung
– Triviale Konvertierungen (𝑡 ↔ 𝑡 &, 𝑡 [] ↔ 𝑡 *)
• Triviale Konvertierung mit const (𝑡 * → const 𝑡 *, 𝑡 & → const 𝑡 &)
• Numerische Zahlbereichserweiterung (float → double → long double, bool → int)
• Standard-Konvertierung (numerisch, Zeiger, bool)
• „Benutzerdefinierte“ Konvertierung (Konstruktoren mit einem Argument, Typumwandlungsoperato-
ren)

Beispiel. abs und pow liegen in der Standardlibrary überladen vor:

• – float abs(float)
– double abs(double)
– long double abs(long double)
– int abs(int)
– long int abs(long int)
– double abs(const complex<double>&)
– …
• – float pow(float, float)
– float pow(float, int)
– long double pow(long double, long double)
– long double pow(long double, long int)
– complex<double> pow(const complex<double>&, int)
– complex<double> pow(const complex<double>&, const double&)
– complex<double> pow(const complex<double>&, const complex<double>&)
– complex<double> pow(const double&, const complex<double>&)
– …

Benutzerdefinierte Konvertierungen

Es kann für Klassen definiert dass Werte anderer Typen in Objekte der Klasse implizit konvertiertbar sein
sollen. Hierfür wird ein Konstruktor akzeptiert der genau einen Parameter des Typs, von dem konvertiert
werden soll, als Argument akzeptiert.
Umgekehrt kann definiert werden, dass Objekte der Klasse impliziert konvertierbar sein sollen in Werte ande-
rer Typen (auch eingebauter Typen wie z.B. int). Hierfür wird spezielle Syntax verwendet, ein Typumwand-
lungsoperator für die Klasse definiert.

Typumwandlungen mittels Konstruktoren

Wann immer ein Konstruktor für eine Klasse existiert der sich mit genau einem Argument aufrufen lässt, auch
unter Berücksichtigung von Parametervoreinstellungen, wird dieser als benutzerdefinierte Konvertierung für
implizierte Konvertierung in Betracht gezogen.
Beispiel. Wir deuten eine eigene Vektor-Klasse an. Wir vereinbaren einen Konstruktor, der eine Angabe der
Länge und eine Vorlage für die Elemente eines neuen Vektors als Parameter akzeptiert. Dies führt, evtl.
unintuitiv, dazu dass Längen von Vektoren (Werte vom Typ int) implizit konvertiert werden zu Vektoren.

Seite 3 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

class Vektor {
Vektor (int n, double x = 0);
}

int main() {
Vektor v(3);
v = 2;
// [Link]=(2)
// [Link]=(Vektor(2))
}

Soll dies nicht geschehen kann der Konstruktor mit explicit dekoriert werden. Dies wird in der Standard-
bibliothek häufig verwendet, z.B. die Konstruktoren der Klasse vector<T>.

Typumwandlungen mittels Operatorfunktionen

Insb. Konvertierungen von Objekten einer Klasse in eingebaute Datentypen (z.B. int) lassen sich nicht durch
Konstruktoren mit einem Argument ausdrücken. Es gibt daher, in Anlehnung an Operatorüberladung, spezi-
elle Syntax um derartige Konvertierungen zu definieren. Für einen Typ 𝑇 , in den Objekte der Klasse konver-
tierbar sein sollen, wird operator 𝑇 () ohne Rückgabetyp (auch nicht void) und ohne Parameter vereinbart.
Bei 𝑇 darf es sich um einen pointer- oder Referenztyp handeln (* bzw. &).
Beispiel. Wir definieren (rudimentär) eine eigene Klasse Vektor und implementieren die Umwandlung von
und zu vector<double> mittels Operatorfunktion bzw. Konstruktor mit einem Argument.

vector_conversion.cpp

#include <iostream>
#include <vector>
#include <string>

using namespace std;

class Vektor {
private:
double* ap;
int len;

public:
Vektor() : ap(0), len(0) {}
Vektor(const vector<double>& v) : len([Link]()) {
ap = new double[len];
for (int i = 0; i < len; i++) ap[i] = v[i];
}

~Vektor() {
delete[] ap;
}

double& operator[](int i) {
return ap[i];
}

operator vector<double>() {
vector<double> v(len);

Seite 4 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

for (int i = 0; i < len; i++) v[i] = ap[i];


return v;
}

void ausgeben(string n) {
for (int i = 0; i < len; i++)
cout << n << ”[” << i << ”]: ” << ap[i] << ” ”;
cout << endl;
}
};

int main() {
vector<double> b(4, 2.0);
Vektor a = b;
[Link](”a”);

a[2] = 3;
vector<double> c = a;
for (unsigned int i = 0; i < [Link](); i++)
cout << ”c[” << i << ”]: ” << c[i] << ” ”;
cout << endl;

return 0;
}

a[0]: 2 a[1]: 2 a[2]: 2 a[3]: 2


c[0]: 2 c[1]: 2 c[2]: 3 c[3]: 2

In der Standardbibliothek ist für Ströme ein Typumwandlungsoperator nach bool definiert. Für einen Strom
𝑠 liefert die Umwandlung in einen bool das selbe Ergebnis wie !𝑠 .fail().

Überladen von Operatoren (Forts.)

Beispiel. Wir überladen einige arithmetische Operatoren einer eigenen Klasse zur Modellierung von Vektoren
aus R3 . Den Operator ^ verwenden wir zur Darstellung des Kreuzprodukts.

vector3d_arith.cpp

#include <iostream>

using namespace std;

class Vektor3D {
public:
double x, y, z;
Vektor3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {}
};

Vektor3D operator+(const Vektor3D& a, const Vektor3D& b) {


Vektor3D c;
c.x = a.x + b.x;
c.y = a.y + b.y;
c.z = a.z + b.z;

Seite 5 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

return c;
}

Vektor3D& operator+=(Vektor3D& a, const Vektor3D& b) {


a.x += b.x;
a.y += b.y;
a.z += b.z;
return a;
}

Vektor3D& operator*=(Vektor3D& a, double lambda) {


a.x *= lambda;
a.y *= lambda;
a.z *= lambda;
return a;
}

Vektor3D operator*(const Vektor3D& a, double lambda) {


Vektor3D b{a};
return b *= lambda;
}

Vektor3D operator*(double lambda, const Vektor3D& a) {


return a * lambda;
}

Vektor3D operator^(const Vektor3D& a, const Vektor3D& b) {


Vektor3D c;
c.x = a.y*b.z - a.z*b.y;
c.y = a.z*b.x - a.x*b.z;
c.z = a.x*b.y - a.y*b.x;
return c;
}

ostream& operator<<(ostream& stream, const Vektor3D& a) {


return stream << ”(” << a.x << ”,” << a.y << ”,” << a.z << ”)”;
}

istream& operator>>(istream& stream, Vektor3D& a) {


char c1, c2, c3, c4;
stream >> c1 >> a.x >> c2 >> a.y >> c3 >> a.z >> c4;
if (c1 != '(' || c2 != ',' || c3 != ',' || c4 != ')')
[Link](ios::failbit);
return stream;
}

int main() {
Vektor3D a, b;
cout << ”a: ”; cin >> a;
cout << ”b: ”; cin >> b;

Vektor3D c = a ^ b;
cout << ”c = ” << c << endl;

c += (a *= 2) ^ b;
cout << ”a = ” << a << endl
<< ”b = ” << b << endl
<< ”c = ” << c << endl;

Seite 6 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

return 0;
}

a: (1,2,3)
b: (3,2,1)
c = (-4,8,-4)
a = (2,4,6)
b = (3,2,1)
c = (-12,24,-12)

Überladen von Operatoren als Methoden

Ein Operator ◦ kann auch als Methode überladen werden. Binäre Ausdrücke der Form 𝑥◦𝑦 werden übersetzt
in den Aufruf 𝑥 .operator◦(𝑦 ) und unäre Ausdrücke der Form ◦𝑥 in 𝑥 .operator◦().
Die Operatoren [], (), = und -> können nur als Methoden überladen werden.
Wird zwischen nicht als Methoden überladenen Operatoren und als Methoden überladenen Operatoren aus-
gewählt (vgl. Auswahl überladener Funktionen) so werden die Implementierungen, bei denen der Operator
als Methode überladen wurde, behandelt als hätten sie einen zusätzlichen ersten Parameter vom Typ der Klas-
se. Bei nicht-konstanten Methoden (kein const) wird für eine Klasse 𝑇 agiert als gäbe es einen zusätzlichen
ersten Parameter vom Typ 𝑇 &; bei konstanten Methoden (mit const) stattdessen const 𝑇 &.
Beispiel. Wir definieren und implementieren Überladungen für den [] Operator für eine eigene Klasse zur
Modellierung von Vektoren aus R3 , sodass der Zugriff auf die Komponenten eines Vektors 𝑥 geschrieben
werden kann als 𝑥 [1], 𝑥 [2] bzw. 𝑥 [3].
Um sowohl schreibenden Zugriff auf die Komponenten von nicht-const Objekten, wie auch lesenden Zu-
griff auf const Objekte zu ermöglichen definieren wir sowohl eine nicht-const, wie auch eine const Vari-
ante von operator[]. Es muss zudem auf den Typ des Rückgabewerts der nicht-const Variante geachtet
werden.

class Vektor3D {
public:
double x, y, z;

double operator[] (int i) const {


switch(i) {
case 1: return x;
case 2: return y;
case 3: return z;
}
}
double& operator[] (int i) {
switch(i) {
case 1: return x;
case 2: return y;
case 3: return z;
}
}
};

Seite 7 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel. Wir definieren eine eigene Vektor-Klasse die zusätzlich unterstützt beim Aufruf des Konstruktors
einen eigenen Indexbereich [𝑚, 𝑛) ⊂ N anzugeben.

[Link]

#include <iostream>
#include <string>

using namespace std;

class IVektor {
private:
int m, n;
double* ap;

public:
IVektor(int n_ = 0, int m_ = 0, double x = 0.0) : m(m_), n(n_) {
ap = new double[n - m];
for (int i = m; i < n; i++)
ap[i - m] = x;
}
IVektor(const IVektor& v) : m(v.m), n(v.n) {
ap = new double[n - m];
for (int i = m; i < n; i++)
ap[i - m] = [Link][i - m];
}
~IVektor() {
delete[] ap;
}

IVektor& operator=(const IVektor& v) {


delete[] ap;

m = v.m; n = v.n;
ap = new double[n - m];
for (int i = m; i < n; i++)
ap[i - m] = [Link][i - m];
return *this;
}

double operator[](int i) const {


return ap[i - m];
}
double& operator[](int i) {
return ap[i - m];
}

void ausgeben(string name) {


for (int i = m; i < n; i++)
cout << name << ”[” << i << ”]=” << ap[i - m] << ” ”;
cout << endl;
}
};

int main() {
IVektor a{3, 1}, b{4}, c{4, 1, 5.0}, d{c}, e;
a[2] = b[2] = c[2] = 1.0;

Seite 8 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

[Link](”a”);
[Link](”b”);
[Link](”c”);
[Link](”d”);
[Link](”e”);

e = c;
[Link](”e”);

return 0;
}

a[1]=0 a[2]=1
b[0]=0 b[1]=0 b[2]=1 b[3]=0
c[1]=5 c[2]=1 c[3]=5
d[1]=5 d[2]=5 d[3]=5

e[1]=5 e[2]=1 e[3]=5

Vergleichsoperatoren

Um die Vergleichsoperatorn (<, >, <=, >=, ==, !=) zu überladen bietet die Standardlibrary in <utility> und
namespace rel_ops vordefinierte Implementierungen, sodass es bei Verwendung genügt nur < und == zu
überladen.
Beispiel. Wir definieren auf einer (rudimentären) eigenen Vektor-Klasse die lexikographische Ordnung.

vector_ordering.cpp

#include <iostream>
#include <utility>
#include <algorithm>

using namespace std;


using namespace rel_ops;

class Vektor {
private:
double* ap;
int len;

public:
Vektor(int len_ = 0, double x = 0.0) : len(len_) {
ap = new double[len];
for (int i = 0; i < len; i++) ap[i] = x;
}
~Vektor() {
delete[] ap;
}
double& operator[](int i) {
return ap[i];
}
double operator[](int i) const {
return ap[i];

Seite 9 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

friend bool operator== (const Vektor& v1, const Vektor& v2) {


if ([Link] != [Link]) return false;
for (int i = 0; i < [Link]; i++)
if (v1[i] != v2[i])
return false;
return true;
}

friend bool operator< (const Vektor& v1, const Vektor& v2) {


for (int i = 0; i < min([Link], [Link]); i++) {
if (v1[i] > v2[i])
return false;
else if (v1[i] < v2[i])
return true;
}
return [Link] < [Link];
}
};

int main() {
Vektor v1{2, 1.0}, v2{3, 1.0};

cout << boolalpha;

cout << ”v1 < v2: ” << (v1 < v2) << endl
<< ”v1 > v2: ” << (v1 > v2) << endl
<< ”v1 == v2: ” << (v1 == v2) << endl
<< ”v1 != v2: ” << (v1 != v2) << endl;
cout << endl;

v1[1] = 2.0;
cout << ”v1 < v2: ” << (v1 < v2) << endl
<< ”v1 > v2: ” << (v1 > v2) << endl
<< ”v1 == v2: ” << (v1 == v2) << endl
<< ”v1 != v2: ” << (v1 != v2) << endl;
cout << endl;

Vektor v3{3, 1.0};


cout << ”v2 <= v3: ” << (v2 <= v3) << endl
<< ”v2 >= v3: ” << (v2 >= v3) << endl
<< ”v2 == v3: ” << (v2 == v3) << endl
<< ”v2 != v3: ” << (v2 != v3) << endl;

return 0;
}

v1 < v2: true


v1 > v2: false
v1 == v2: false
v1 != v2: true

v1 < v2: false


v1 > v2: true
v1 == v2: false
v1 != v2: true

Seite 10 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

v2 <= v3: true


v2 >= v3: true
v2 == v3: true
v2 != v3: false

Inkrement- und Dekrementoperatoren

Auf die Inkrement- und Dekrementoperatoren ++ und -- können, sowohl in ihrer Präfix- wie auch Postfix-
Form, überladen werden. Um Präfix- von Postfix-Variante unterscheiden zu können erhalten die Postfix-
Varianten der Operatoren künstlich einen zusätzlichen Parameter vom Typ int. Beim Aufruf erhält dieser
stets den Wert 0.
Beispiel. Wir definieren Präfix und Postfix-Varianten der Inkrement- und Dekrementoperatoren für eine ei-
gene Klasse Complex.

complex_increment.cpp

#include <iostream>

using namespace std;

class Complex {
private:
double re, im;

public:
Complex (double re_ = 0, double im_ = 0) : re(re_), im(im_) {}

double real() const { return re; }


double imag() const { return im; }

friend Complex& operator++(Complex& z) {


[Link]++;
return z;
}
friend Complex operator++(Complex& z, int ignored) {
return Complex{[Link]++, [Link]};
}

friend Complex& operator--(Complex& z) {


[Link]--;
return z;
}
friend Complex operator--(Complex& z, int ignored) {
return Complex{[Link]--, [Link]};
}
};

int main() {
Complex z{2.0, 3.0};

cout << (z++).real() << endl


<< [Link]() << endl
<< (++z).real() << endl

Seite 11 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

<< [Link]() << endl;


cout << endl;

cout << (z--).real() << endl


<< [Link]() << endl
<< (--z).real() << endl
<< [Link]() << endl;

return 0;
}

2
3
4
4

4
3
2
2

Überladen des Funktionsaufrufs

Für ein Objekt 𝑥 vom Typ𝑇 können durch Definition und Implementierung einer Methode operator()(𝑎 1 , 𝑎 2 , . . .)
in 𝑇 Funktionsaufrufe der Form 𝑥 (𝑎 1 , 𝑎 2 , . . .) ermöglicht werden.
Beispiel. Wir überladen den Funktionsaufruf für eine Klasse Polynom zur Modellierung von Polynomen über
R, sodass für ein Objekt 𝑝 der Klasse Polynom der Aufruf 𝑝 (𝑥 ) den Wert des zugehörigen Polynoms an der
Stelle 𝑥 liefert.

polynom_op.cpp

#include <iostream>
#include <vector>

using namespace std;

class Polynom {
private:
vector<double> coeff;

public:
Polynom(const vector<double>& v) : coeff(v) {}

double operator()(double x) {
double s = 0, xpot = 1;
for (vector<double>::size_type i = 0; i < [Link](); i++) {
s += coeff[i] * xpot;
xpot *= x;
}
return s;
}
};

Seite 12 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

int main() {
Polynom p{vector<double>{1, 2, 3}};

cout << ”p(x) = ” << p(2) << endl;

return 0;
}

p(x) = 17

Beispiel. Wir definieren eine eigene Klasse Matrix zur Modellierung von 𝑚 ×𝑛-Matrizen über R. Die Elemente
der Matrix werden innerhalb des Objekts in einer listenartigen Datenstrukter valarray gespeichert. Es
werden hierfür die Zeilen der Matrix einfach konkateniert.
Wir überladen behelfsweise die Funktionsauswertung für Objekte vom Typ Matrix um Zugriff zu erhalten
auf die Elemente der Matrix anhand ihrer Zeile und Spalte.

matrix_op.cpp

#include <iostream>
#include <iomanip>
#include <valarray>

using namespace std;

class Matrix {
private:
int m, n; // Dimension
valarray<double> a; // Elemente

public:
Matrix (int m_ = 0, int n_ = 0, double x = 0)
: m(m_), n(n_), a(x, m_ * n_) {}

double operator()(int i, int j) const {


return a[i*n + j];
}
double& operator()(int i, int j) {
return a[i*n + j];
}
};

int main() {
const int m = 4, n = 3;

Matrix a{m, n};


for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
a(i, j) = i*n + j;

for (int i = 0; i < m; i++) {


for (int j = 0; j < n; j++) {
cout << setw(4) << a(i, j);
}
cout << endl;

Seite 13 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

cout << endl;

const Matrix b{1, 1, 42};


cout << b(0, 0) << endl;
}

0 1 2
3 4 5
6 7 8
9 10 11

42

Funktionsobjekte

Objekte einer Klasse für die der Funktionsaufruf überladen wurde, werden auch als Funktionsobjekte bezeich-
net. Die Bezeichnung wird vor Allem dann verwendet wenn der Hauptzweck der Klasse darin besteht dass
die Objekte funktionsartige Aufrufe bereitstellen.
Besonders nützlich sind Funktionsobjekte zur Darstellung von Funktionen deren Wert von zusätzlichen Para-
metern abhängt, die nicht bei jeder Auswertung angegeben sollen. Insb. ist die Manipulation der Parameter der
Funktionsobjekte (z.B. eine algebraische Struktur wie Addition/Multiplikation auf den Objekten anzubieten)
oft Zweck der Modellierung als Funktionsobjekt.
Beispiel. Wir definieren eine Klasse NormV zur Darstellung von Normalverteilungen. Eine Normalverteilung
ist durch ihren Mittelwert und ihre Standardabweichung bereits vollständig spezifiziert. Die Funktionsaus-
wertung eines Objekts der Klasse NormV liefert den Wert der Wahrscheinlichkeitsdichtefunktion an der
gegebenen Stelle.
Es wäre durchaus denkbar Additionsoperatoren für die Klasse zu implementieren. Der Effekt könnte in
diesem Fall äquivalent dazu sein die Wahrscheinlichkeitsdichtefunktionen der gegebenen Objekte erst
konventionell zu addieren und dann zu normieren, da die Familie von Normalverteilungen unter dieser
Addition abgeschlossen ist.

normv_op.cpp

#include <iostream>
#include <iomanip>
#include <cmath>

using namespace std;

class NormV {
private:
double mw, stdabw;

public:
NormV (double mw_ = 0, double stdabw_ = 1)
: mw(mw_), stdabw(stdabw_) {}

double operator()(double x) const {

Seite 14 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

return M_2_SQRTPI / (2 * M_SQRT2 * stdabw)


* exp(-((x - mw) * (x - mw)) / (2*stdabw*stdabw));
}
};

int main() {
NormV n{0, 2};
for (double x = -6; x <= 6.001; x += 12. / 18) {
double fx = n(x);
cout << setw(5) << fixed << setprecision(3) << fx << ' ';
for (int k = 1; k < fx * 200; k++) cout << '#';
cout << endl;
}

cout << endl;

cout << defaultfloat << setprecision(6)


<< NormV{}(-0.5) << endl;
}

0.002
0.006 #
0.013 ##
0.027 #####
0.050 #########
0.082 ################
0.121 ########################
0.160 ###############################
0.189 #####################################
0.199 #######################################
0.189 #####################################
0.160 ###############################
0.121 ########################
0.082 ################
0.050 #########
0.027 #####
0.013 ##
0.006 #
0.002

0.352065

STL-Listen (list)

Objekte vom Datentyp list<𝑇 > speichern beliebig viele Elemente vom Typ 𝑇 als doppelt verkettete Liste. D.h.
jedes Element speichert sowohl einen Zeiger auf das nächste, wie auch das vorherige Elemente in der Liste.
Das ermöglicht es manche Operationen effizienter zu implementieren und vor Allem bidirektionale Iteratoren
anbieten zu können.
Einfach verkettete Listen existieren in der STL ebenfalls unter dem Namen forward_list.
Im Gegensatz zu vector erlaubt list nicht den wahlfreien Zugriff auf Elemente gegeben einen numerischen
Index. Insb. implementieren deswegen Iteratoren über Werte vom Typ list einige Operationen nicht (z.B.
𝑖 -𝑖 ′ für Iteratoren 𝑖 und 𝑖 ′ ).

Seite 15 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Im Folgenden bezeichnet 𝑇 einen Datentyp, 𝑡, 𝑡 0, . . . , 𝑡𝑛−1 Werte vom Typ 𝑇 , 𝑛 eine natürliche Zahl vom Typ
list<𝑇 >::size_type, 𝑙, 𝑙 ′ Variablen vom Typ list<𝑇 >, 𝑝 der Name einer Funktion vom Typ bool 𝑝 (𝑇 𝑡 )
und 𝑐, 𝑒 der Name von Funktionen vom Typ bool 𝑐 (const 𝑇 & 𝑡 0 , const 𝑇 & 𝑡 1 ). Zudem bezeichnen 𝑖, 𝑖 ′
Iteratoren über 𝑙 und 𝑗, 𝑗 ′ Iteratoren über 𝑙 ′ .

list<𝑇 > 𝑙 Vereinbart Variable 𝑙 vom Typ list<𝑇 > leer (Länge 0)
list<𝑇 > 𝑙 (𝑛 ) Vereinbart Variable 𝑙 vom Typ list<𝑇 > mit Länge 𝑛 und allen
Elementen initialisiert mit Standardkonstruktor
list<𝑇 > 𝑙 (𝑛 , 𝑡) Vereinbart Variable 𝑙 vom Typ list<𝑇 > mit Länge 𝑛 und allen
Elementen initialisiert als Kopien von 𝑡
list<𝑇 > 𝑙 (𝑙 ′ ) Vereinbart Variable 𝑙 vom Typ list<𝑇 > als Kopie von 𝑙 ′
list<𝑇 > 𝑙 {𝑡 0 , . . ., 𝑡𝑛−1 } Vereinbart Variable 𝑙 vom Typ list<𝑇 > mit Elementen 𝑡 0, . . . , 𝑡𝑛−1
𝑙 .front() 𝑙 .back() Liefert erstes bzw. letztes Element von 𝑙 falls vorhanden, sonst
undefiniertes Verhalten
𝑙 == 𝑙 ′ 𝑙 != 𝑙 ′ 𝑙 > 𝑙′ 𝑙 < 𝑙′ Vergleicht 𝑙 und 𝑙 ′ lexikographisch
𝑙 <= 𝑙 ′ 𝑙 >= 𝑙 ′
𝑙 .size() Liefert Anzahl der Elemente in 𝑙
𝑙 .resize(𝑛 ) 𝑙 .resize(𝑛 , 𝑡 ) Ändert Anzahl der Elemente in 𝑙, ggf. Initialiserung neuer Elemente
mit Standardkonstruktor bzw. als Kopie von 𝑡
𝑙 .push_front(𝑡 ) 𝑙 .push_back(𝑡 ) Fügt Kopie von 𝑡 am Anfang bzw. Ende der Liste 𝑙 ein
𝑙 .pop_front() 𝑙 .pop_back() Löscht erstes bzw. letztes Element von 𝑙
𝑙 .clear() Löscht alle Elemente aus 𝑙
𝑙 .empty() Liefert true g.d.w. 𝑙 leer ist
𝑙 .remove(𝑡 ) 𝑙 .remove_if(𝑝 ) Löscht alle Elemente aus 𝑙 die unter operator== äquivalent sind zu
𝑡 bzw. für die 𝑝 true liefert
𝑙 .sort() 𝑙 .sort(𝑐 ) Sortiert die Elemente in 𝑙 aufsteigend nach operator< bzw. 𝑐. Die
Sortierung ist stabil, äquivalente Elemente behalten ihre
Reihenfolge relativ zueinander.
𝑙 .merge(𝑙 ′ ) 𝑙 .merge(𝑙 ′ , 𝑐 ) Sortiert, sofern 𝑙 und 𝑙 ′ bereits sortiert, 𝑙 ′ unter Erhalt der
Sortierung bzgl. operator< bzw. 𝑐 in 𝑙 ein. 𝑙 ′ ist danach leer.
𝑙 .reverse() Kehrt Reihenfolge der Elemente in 𝑙 um
𝑙 .unique() 𝑙 .unique(𝑒 ) Entfernt aus 𝑙 alle direkt aufeinanderfolgenden Elemente die
äquivalent sind bzgl. operator== bzw. 𝑒 außer dem jeweils Ersten
list<𝑇 >::iterator 𝑖 Vereinbart Variable 𝑖 als Vorwärts-, Konstanten-, Rückwärts- bzw.
list<𝑇 >::const_iterator 𝑖 Rückwärts-Konstanten-Iterator
list<𝑇 >::reverse_iterator 𝑖
list<𝑇 >::const_reverse_iterator 𝑖
𝑙 .begin() 𝑙 .end() 𝑙 .rbegin() Liefert Vorwärts-Iterator über 𝑙 zum ersten bzw. nach dem letzten
𝑙 .rend() Element bzw. Rückwarts-Iterator über 𝑙 zum letzten bzw. vor dem
ersten Element
++𝑖 𝑖 ++ --𝑖 𝑖 -- Verschiebt Iterator 𝑖 auf nächste bzw. vorherige Position in
Durchlaufrichtung
*𝑖 Referenz zu Wert an aktueller Position von 𝑖

Seite 16 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

list<𝑇 > 𝑙 ′ (𝑖 , 𝑖 ′ ) Vereinbart Variable 𝑙 ′ vom Typ list<𝑇 > als Kopie der Elemente aus
𝑙 im Bereich [𝑖, 𝑖 ′ )
𝑙 .insert(𝑖 ) 𝑙 .insert(𝑖 , 𝑡 ) Fügt bevor der aktuellen Position von 𝑖 eine Komponente in 𝑙 ein
initialisiert mit Standardkonstruktor bzw. als Kopie von 𝑡
𝑙 ′ .insert( 𝑗 , 𝑖 , 𝑖 ′ ) Fügt bevor der aktuellen Position von 𝑖 in 𝑙 ′ Kopien ein der
Elemente aus 𝑙 im Bereich [𝑖, 𝑖 ′ )
𝑙 .erase(𝑖 ) 𝑙 .erase(𝑖 , 𝑖 ′ ) Löscht aus 𝑙 die Elemente an der aktuellen Position von 𝑖 bzw. im
Bereich [𝑖, 𝑖 ′ ). Liefert Iterator auf Element das direkt auf letztes nun
gelöschtes Element gefolgt hat.
𝑙 .splice(𝑖 , 𝑙 ′ ) Verschiebt Elemente aus 𝑙 ′ bzw. aus 𝑙 ′ ab aktueller Position von 𝑗
𝑙 .splice(𝑖 , 𝑙 ′ , 𝑗 ) bzw. aus 𝑙 ′ aus dem Bereich [ 𝑗, 𝑗 ′ ) vor aktuelle Position von 𝑖 in 𝑙.
𝑙 .splice(𝑖 , 𝑙 ′ , 𝑗 , 𝑗 ′ ) Derart verschobene Elemente sind hinterher nicht mehr vorhanden
in 𝑙 ′ .

Beispiel. Wir implementieren ein Hautprogramm das zunächst beliebig viele Zeilen von der Standardeingabe
einliest und sie dabei in einer Liste vom Typ list<string> speichert. Danach wird die Liste sortiert und
die Elemente der Liste, in nun lexikographisch sortierter Reihenfolge, wieder ausgegeben.

[Link]

#include <iostream>
#include <string>
#include <list>

using namespace std;

int main() {
list<string> buffer;

for (string line; getline(cin, line); )


buffer.push_back(line);

[Link]();

while (![Link]()) {
cout << [Link]() << endl;
buffer.pop_front();
}

return 0;
}

eins
zwei
drei
drei
eins
zwei

Beispiel. Wir implementieren eine Klasse Polynom zur Modellierung von Polynomen über R (approximiert
durch double) intern als verkettete Liste von Monomen, d.h. Ausdrücken der Form 𝑣 · 𝑥 𝑝 .

Seite 17 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

[Link]

#include <iostream>
#include <list>
#include <utility>

using namespace std;


using namespace rel_ops;

class Monom {
public:
int pot;
double v;

Monom(double v_ = 0, int pot_=1): pot(pot_), v(v_) {}

int grad() const { if (v != 0) return pot; else return -1; }

friend bool operator<(const Monom& m1, const Monom& m2) {


return [Link]() > [Link]();
}
friend bool operator==(const Monom& m1, const Monom& m2) {
return [Link]() == [Link]();
}

friend ostream& operator<<(ostream& stream, const Monom& m) {


return stream << showpos << m.v << ”x^”
<< noshowpos << [Link];
}
};

class Polynom {
private:
list<Monom> ml;

public:
Polynom() {}
Polynom(double v_, int pot_=1): ml({Monom{v_, pot_}}) {}

Polynom& operator+=(Polynom q) {
[Link]([Link]);
if ([Link]()) return *this;
list<Monom>::iterator curr = [Link](),
next = ++([Link]());
for (; next != [Link](); ++curr, ++next) {
if (curr->pot == next->pot) {
curr->v += next->v;
*next = Monom{};
}
}
[Link](Monom{});
return *this;
}

friend ostream& operator<<(ostream& stream, const Polynom& p) {


if ([Link]()) return stream << ”0”;
for (const auto& monom: [Link])
stream << monom;

Seite 18 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

return stream;
}
};

int main() {
Polynom p, q;
p += Polynom{4, 3}; p += Polynom{2, 2}; p += Polynom{1, 1};
q += Polynom{1, 3}; q += Polynom{-2, 2}; q += Polynom{1, 0};

cout << ”p+q: ” << (p+=q) << endl;

return 0;
}

p+q: +5x^3+1x^1+1x^0

STL-Mengen natürlicher Zahlen (bitset)

Ein Objekt vom Datentyp bitset<𝑁 > speichert, für beliebies aber festes 𝑁 ∈ N, eine Folge von 𝑁 Bits. Es
wird also jeder Zahl 𝑛 ∈ [0, 𝑁 ) ⊂ N jeweils ein Wert vom Typ bool zugeordnet.
Für Zwecke der Umwandlung zwischen bitset<𝑁 > und string und für Zwecke der arithmetischen shift-
Operatoren wird das bitset verstanden als Folge von bits mit den niedrigsten Indizes rechts.
Beispiel. Wir demonstrieren die Umwandlung zwischen bitset<8> und string.

bitset_order.cpp

#include <iostream>
#include <bitset>
#include <cstddef>

using namespace std;

int main() {
bitset<8> s(”11110000”);

for (size_t i = 0; i < 8; i++)


cout << ”s[” << i << ”]: ” << s[i] << endl;

return 0;
}

s[0]: 0
s[1]: 0
s[2]: 0
s[3]: 0
s[4]: 1
s[5]: 1
s[6]: 1
s[7]: 1

Seite 19 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Im Folgenden bezeichne 𝑁 eine „hinreichend konstante“ natürliche Zahl, 𝑠, 𝑠 ′ Variablen vom Datentyp
bitset<𝑁 >, 𝑢 eine vorzeichenlose ganze Zahl vom Typ unsigned long, 𝑡 eine C++-Zeichenkette (string)
bestehend nur aus den Zeichen 0 und 1, 𝑗, 𝑘 vorzeichenlose ganze Zahlen vom Typ size_t (aus <cstddef>),
𝑏 eine Variable vom Typ bool, 𝑜 ein Ausgabe- und 𝑖 ein Eingabestrom.

bitset<𝑁 > 𝑠 Vereinbart Variable 𝑠 vom Typ bitset<𝑁 > mit allen bits 0
bitset<𝑁 > 𝑠 {𝑢 } Vereinbart Variable 𝑠 vom Typ bitset<𝑁 > initialisiert anhand von
𝑢
bitset<𝑁 > 𝑠 {𝑡 } Vereinbart Variable 𝑠 vom Typ bitset<𝑁 > initialisiert anhand von
bitset<𝑁 > 𝑠 {𝑡 , 𝑗 } 𝑡 bzw. den Zeichen aus 𝑡 ab Index 𝑗 bzw. 𝑘 vielen Zeichen aus 𝑡 ab
bitset<𝑁 > 𝑠 {𝑡 , 𝑗 , 𝑘 } Index 𝑗
𝑠[𝑗 ] Liefert Referenz auf bit mit Index 𝑗 als bool-Wert
𝑠 .test( 𝑗 ) Liefert Wert von bit mit Index 𝑗 als bool
𝑠 .size() Liefert 𝑁
𝑠 .count() Liefert Anzahl von bits aus 𝑠 mit Wert true
𝑠 .any() 𝑠 .all() 𝑠 .none() Liefert bool-Wert ob mindestens eins bzw. alle bzw. keins der bits
aus 𝑠 den Wert true hat
𝑠 .set( 𝑗 , 𝑏 ) Setzt bit mit Index 𝑗 auf Wert 𝑏
𝑠 .set() 𝑠 .set( 𝑗 ) Setzt alle bits bzw. bit mit Index 𝑗 auf Wert 1
𝑠 .reset() 𝑠 .reset( 𝑗 ) Setzt alle bits bzw. bit mit Index 𝑗 auf Wert 0
𝑠 .flip() 𝑠 .flip( 𝑗 ) Negiert alle bits bzw. bit mit Index 𝑗 (0 ↦→ 1, 1 ↦→ 0)
𝑠 == 𝑠 ′ 𝑠 != 𝑠 ′ Vergleicht 𝑠 und 𝑠 ′ auf Gleichheit
~𝑠 𝑠 & 𝑠′ 𝑠 | 𝑠′ 𝑠 ^ 𝑠′ Bit-weise logische Operatoren
𝑠 << 𝑗 𝑠 >> 𝑗
𝑠 &= 𝑠 ′ 𝑠 |= 𝑠 ′ 𝑠 ^= 𝑠 ′ Bit-weise logische Zuweisungsoperatoren
𝑠 <<= 𝑗 𝑠 >>= 𝑗
𝑠 .to_ulong() Liefert einen Wert vom Typ unsigned long der dem Inhalt von 𝑠
entspricht
𝑠 .to_string() 𝑖 >> 𝑠 𝑜 << 𝑠 Konvertierung von Zeichenketten bestehend nur aus den Zeichen 0
und 1 zu bzw. von 𝑠 vom Typ bitset<𝑁 >

Beispiel (Sieb des Eratosthenes). Wir implementieren ein Hauptprogramm, dass das Sieb des Eratosthenes
verwendet um Primzahlen kleiner als 50 zu finden und auszugeben. Wir verwenden hierbei ein Objekt
vom Typ bitset<50> um für Zahlen kleiner 50 zu speichern ob diese nach aktuellem Stand prim sind oder
nicht.

primes_bitset.cpp

#include <bitset>
#include <iostream>

using namespace std;

const unsigned int n = 50;

Seite 20 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

int main() {
bitset<n> primes;
[Link]();
[Link](0); [Link](1);

for (unsigned int k = 2; k*k < n; k++)


if (primes[k])
for (unsigned int i = k; i*k < n; i++)
[Link](i*k);

for (unsigned int k = 0; k < n; k++)


if (primes[k])
cout << k << ” ”;
cout << endl;

return 0;
}

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

Seite 21 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Buildsysteme*

Bei einem Buildsystem handelt es sich um Software mit der Zielsetzung den Buildprozess von Software-
Projekten zu automatisieren. Speziell für C++ besteht der Buildprozess daraus Übersetzungseinheiten
zu Objektdateien zu kompilieren und die resultierenden Objektdateien zu ausführbaren Binärdateien zu
linken. Buildsysteme enthalten oft jedoch auch weitere Funktionalität. Z.B. zum Verwalten von externen
Abhängigkeiten (Programmbibliotheken), zum Generieren von Quellcode aus für die konkrete Problem-
stellung angebrachteren Spezifikationen/bereichsspezifischen Programmiersprachen (DSLs; z.B. Parser-
bzw. Lexer-Generatoren) oder das Zusammenfügen der kompilierten Build-Produkte zu installierbaren
Softwarepaketen.
Unter einen lowlevel Buildsystem verstehen wir Software, die sich primär mit dem Ausführen konkret spezifi-
zierter Schritte eines Buildprozesses beschäftigt. Beispiele von lowlevel Buildsystemen sind Make und Ninja.
Abgegrenzt davon verstehen wir unter einem highlevel Buildsystem Software, die zusätzlich auch umfangrei-
che Standardlösungen für oft auftretende Situationen bereitstellt und somit konzisere Spezifikationen ermög-
licht. Weitere Eigenschaft von highlevel Buildsystemen ist die automatische Bestimmung und Anpassung an
lokale Begebenheiten, wie die Verwaltung von bzw. das lokalisieren von Abhängigkeiten und adaptive Vorein-
stellungen für zu verwendene Compilerparameter. Beispiele von highlevel Buildsystemen sind Make1 , GNU
Autotools und Meson.
Beispiel (Programmbibliothek). Wir stellen eine kleines Software-Projekt zusammen, welches im Folgenden
als Beispiel wieder aufgegriffen wird.

ld.h

#pragma once

double ld(double);

[Link]

#include <cmath>
#include ”ld.h”

template<class T>
constexpr T ln2_v
= static_cast<T>(0.693147180559945309417232121458176568L);

double ld(double x) { return log(x) / ln2_v<double>; }

ld_main.cpp

#include <iostream>
#include <cerrno>
#include <cstring>

#include ”ld.h”

using namespace std;

1 Makestellt diverse eingebaute Standardregeln zur Verfügung um insb. C Software-Projekte mit einem Minimum an Konfiguration
bauen zu können. Die Existenz dieser Standardregeln ist wenig bekannt.

Seite 22 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

int main() {
double x;
cout << ”x: ”;
cin >> x;
errno = 0;
cout << ”ld(” << x << ”) = ” << ld(x) << endl;
if (errno) cout << strerror(errno) << endl;
}

x: 8
ld(8) = 3
x: 0
ld(0) = -inf
Numerical result out of range
x: -4
ld(-4) = -nan
Numerical argument out of domain

Abhängigkeitsgraph*

Buildsysteme agieren auf dem Abhängigkeitsgraph (dependency graph) des zu übersetzenden Projekts. Der
Abhängigkeitsgraph ist ein azyklischer gerichteter Graph auf den Quelldateien und Übersetzungsprodukten
(Objektdateien, ausführbare Binärdateien, etc.). Eine Datei 𝐴 ist Nachfolger von 𝐵 im Graph, wenn 𝐴 neu
erzeugt werden muss (z.B. kompiliert) wann immer 𝐵 geändert wird. Wir sagen 𝐴 gehe hervor aus 𝐵.
Beispiel (Abhängigkeitsgraph). Wenn wir die Übersetzungseinheit, die das Hauptprogramm enthält
([Link]), mit der Objektdatei für [Link] linken und die ausführbare Binärdatei main nennen, ergibt
sich für das kleine Software-Projekt der Abhängigkeitsgraph in Abbildung 1.

[Link]

ld_main.o ld.o

ld_main.cpp ld.h [Link]

Abbildung 1: Abhängigkeitsgraph für das kleine Software-Projekt

Für Zwecke der Verwendung in einem Buildsystem müssen die Kanten des Abhängigkeitsgraphen zusätzlich
annotiert werden mit Befehlen, die ausgeführt werden sollen, wann immer ein Vorgänger im Graph geändert
wurde. Üblicherweise vergleicht das Buildsystem bei jeder Ausführung die Modifikations-Zeitstempel aller
Dateien, die zu Knoten im Abhängigkeitsgraph korrespondieren, mit den Modifikations-Zeitstempeln der
jeweiligen direkten Nachfolger. Wenn vorgefunden wird, dass ein Nachfolger einen Vorgänger hat, der neuer
ist als er selbst, so wird der mit der entsprechenden Kante assoziierte Befehl ausgeführt.
Das gewünschte Verhalten ist, dass wann immer eine Datei im Projekt vom Benutzer verändert wird, ein dar-
auf folgender Aufruf des Buildsystems genau jene Buildprodukte neu erstellt, die von der Änderung betroffen

Seite 23 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

sind. Besonders im Rahmen großer Software-Projekte ist zudem die Parallelität der Ausführung der Build-
Befehle und die Geschwindigkeit mit der Abhängigkeitsgraph durchlaufen werden kann ausschlaggebend für
die Qualität des Buildsystems.

Make*

Wir betrachten Make als lowlevel-Buildsystem was der üblichen Verwendung in moderner Softwareentwick-
lung entspricht.
Bei Make liegt die Spezifikation des Abhängigkeitsgraphen und der assoziierten Befehle in Form einer Datei
vor. Name der Datei ist per Konvention Makefile.
Beispiel. Wir formulieren ein Makefile für das kleine Software-Projekt.

make_ld/Makefile

.PHONY: all
all: main

main: ld_main.o ld.o


⇆g++ -o $@ $^

%.o: %.cpp ld.h


⇆g++ -o $@ -c $<

Ein Makefile besteht aus Regeln.


Jede Regel beginnt mit dem Namen der Datei die bei Ausführung der Regel erzeugt wird. Im Abhängigkeitsgra-
phen entspricht das einer Familie von Kanten zu dem gelisteten Nachfolger. Es folgt nach einem Doppelpunkt
eine Leerzeichen-separierte Aufzählung der Abhängigkeiten der Regel. Im Abhängigkeitsgraphen entspricht
das einer Menge Vorgängern für die von jedem Vorgänger jeweils eine Kante ausgeht zum gelisteten Nach-
folger.
Es folgen beliebige viele mit einem Tabulator-Zeichen eingerückte Zeilen von Befehlen, die ausgeführt werden
sollen, wann immer die Regel angewandt wird. Im Abhängigkeitsgraphen entspricht das dem Folgen aller
durch die Regel spezifizierten Kanten gleichzeitig.
Innerhalb von Regeln können Variablen verwendet werden indem dem Namen der Variable ein Dollar-Zeichen
vorgestellt wird. Der Inhalt der Variable (stets eine Zeichenkette) wird dann bei Auswertung der Regel einge-
fügt. Automatische Variablen, die stets definiert sind, sind insb.:

• $@ für den Namen der Zieldatei (links vom Doppelpunkt), der für die Auswertung der Regel „verant-
wortlich“ ist
• $^ für die Namen aller Abhängigkeiten der Regel
• $< für den Namen der ersten Abhängigkeit der Regel

Regeln können als pattern definiert werden. Hierfür kann das Prozent-Zeichen % im Namen der erzeugten
Datei vorkommen. Die Regel wird dann immer in Betracht gezogen, wenn eine Datei erzeugt werden soll,
deren Name zum so definierten Muster passt, wobei % für beliebig viele Zeichen steht. Im der Liste von Ab-
hängigkeiten kann % ebenfalls vorkommen. Es wird dann jeweils die konkrete Zeichenkette eingesetzt für die
% bei Auswertung der Regel im Namen der zu erzeugenden Datei steht.

Die spezielle „Zieldatei“ .PHONY wird nicht tatsächlich erzeugt sondern vom Buildsystem betrachtet als wäre
sie stets älter als alle ihre Abhängigkeiten. Durch Angabe einer Zieldatei als Abhängigkeit von .PHONY kann
also erreicht werden, dass die Regel für diese Zieldatei immer ausgeführt wird, wann immer sie in Betracht

Seite 24 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

gezogen wird. Dies wird verwendet um Regeln zu definieren, die nicht wirklich Zieldateien entsprechen, son-
dern nur als Sammlung von Abhängigkeiten bzgl. Befehlen fungieren. Würde eine derartige Regel nicht als
phony deklariert und später eine Datei mit gleichem Namen angelegt, könnte dies zu unerwatetem Verhalten
führen.
Beim Aufruf von Make mit dem Befehl make ohne Parameter wird die Datei mit Namen Makefile betrachtet
und die erste Regel in dieser Datei (Regeln deren Name mit einem Punkt beginnt werden hierbei zunächst
ignoriert) ausgeführt. Natürlich werden hierfür erst die Abhängigkeiten dieser Regel (neu) gebaut, falls das
nötig ist.
Die Definition einer phony Regel namens all als erste Regel ist üblich und dient dazu, dass ein einfacher
Aufruf von make alle zum Software-Projekt gehörigen Build-Produkte erstellt.

Ninja*

Ninja ist explizit als lowlevel Buildsystem konzipiert mit der Zielsetzung die ausufernde und obig nur An-
satzweise und skizzenhaft aufgeführte Syntax und Funktionalität von Make auf einen minimalen Satz zu
reduzieren. Angedacht ist es dann die explizit weniger mächtigen Spezifikationen für ninja mit einem separa-
ten highlevel Buildsystem (hier später Meson) automatisch aus einer möglichts konzisen und idiomatischen
Spezifikation zu generieren.
Auf bei Ninja liegt die Spezifikation in Form einer Datei vor. Name der Datei ist per Konvention [Link].
Beispiel. Wir formulieren eine [Link]-Datei für das kleine Software-Projekt.

ninja_ld/[Link]

CPPFLAGS = -Wall -Werror


CXXFLAGS = -std=c++14

rule cpp_compiler
command = g++ $CPPFLAGS $CXXFLAGS -o $out -c $in
rule cpp_linker
command = g++ $CPPFLAGS $CXXFLAGS -o $out $in

build main: cpp_linker ld_main.o ld.o


build ld.o: cpp_compiler [Link] | ld.h
build ld_main.o: cpp_compiler ld_main.cpp | ld.h

[Link]-Dateien teilen sich i.d.R. in drei Sektionen: die Definition von Spezifikations-weiten Voreinstel-
lungen für einen Satz von Variablen, die Definition von _rule_s – Familien von Befehlen die durch Substituti-
on von Variablen aus gemeinsamen Vorlagen entstehen und schließlich die Spezifikation des Abhängigkeits-
graphs.
Die Substitution von Variablen funktioniert i.W. identisch mit Make durch Vorstellung eines Dollar-Zeichens
$.

Regeln beginnen mit dem Schlüsselwort rule gefolgt von einem Nutzer-gewähltem Bezeichner für die Regel.
Es folgt dann ein jeweils eingerückter Satz von Belegungen von Variablen mit Werten. Manche Variablen
tragen hierbei spezielle Bedeutung. Insb. gibt command den Befehl an, der bei Ausführung der Regel ausgeführt
werden soll.
Die Spezifikation des Abhängigkeitsgraphen geschieht in Form eines Satzes von build-Regeln. Jede build-
Regel beginnt mit dem Schlüsselwort build gefolgt von einer Leerzeichen-separierten Liste von Dateien, die
bei Ausführung der Regel erzeugt werden (die outputs). Es folgt ein Doppelpunkt und der Name einer Regel.

Seite 25 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Danach folgt eine Leerzeichen-separierte Liste von Abhängigkeiten. Optional können implizite Abhängigkei-
ten nach einem vertikalen Strich | angegeben werden.
Die outputs stehen der Regel in der Variable out zur Verfügung, die nicht-impliziten Abhängigkeiten in der
Variable in.

Automatische Erkennung von Präprozessor include-Direktiven*

gcc unterstützt spezielle Kommandozeilenparameter um beim Ausführen des Präprozessors zusätzlich eine
separate Datei anzulegen (Dateiendung per Konvention .d) in der festgehalten wird, welche anderen Dateien
zusätzlich betrachtet wurden. Insb. mit #include eingebundene Dateien tauchen dann namentlich in der .d-
Datei auf.
Ninja unterstützt es direkt nach Ausführung des compilers die dabei erzeugte .d-Datei auszuwerten. Nin-
ja speichert diese Information dann in einer versteckten Datei .ninja_deps. Bei zukünftigen Ausführungen
liest Ninja automatisch stets auch die .ninja_deps-Datei und fügt dort spezifiizerte implizite Abhängigkeiten
dem Abhängigkeitsgraph hinzu. So kann insgesamt erreicht werden, dass, auch ohne explizite Angabe wel-
che header-Dateien eine Übersetzungseinheit inkludiert, Ninja die Übersetzungseinheit neu kompiliert, wann
immer eine der inkludierten header-Dateien angepasst wird.
Beispiel. Wir übergeben an gcc in der Regel cpp_compiler geeignete Kommandozeilenparameter um eine
.d-Datei zu erzeugen. Wir instruieren zudem ninja (durch setzen der Variablen deps und depfile) die so
erzeugte .d-Datei auch auszuwerten. Wir können dann auf die explizite Angabe der header-Datei ld.h als
implizite Abhängigkeit von ld.o und ld_main.o verzichten.

ninja_auto_ld/[Link]

CPPFLAGS = -Wall -Werror


CXXFLAGS = -std=c++14

rule cpp_compiler
deps = gcc
depfile = $out.d
command = g++ $CPPFLAGS $CXXFLAGS -MD -MF $out.d -o $out -c $in
rule cpp_linker
command = g++ $CPPFLAGS $CXXFLAGS -o $out $in

build main: cpp_linker ld_main.o ld.o


build ld.o: cpp_compiler [Link]
build ld_main.o: cpp_compiler ld_main.cpp

Meson*

Meson ist ein highlevel Buildsystem. Durch Erkennung der lokalen Begebenheiten und Interpretation einer
wesentlich abstrakteren Spezifikation in einer eigenen (nicht-Turing-vollständigen2 ) Sprache eine Spezifika-
tion für Ninja erezugt. Die eigentliche Ausführung der Build-Befehle geschieht dann mit Ninja.
Name der Datei, die die Meson-Spezifikation für ein Projekt enthält, ist per Konvention [Link].
Ein Auszug aus der Grammatik der Spezifikationssprache von Meson:
⟨Identifier⟩ → /[a-zA-Z_]/ {/[a-zA-Z_0-9]/}
⟨Literal⟩ → /[1-9]/ {/[0-9]/}

2 [Link]/wiki/Turing-Vollständigkeit

Seite 26 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

| “'” ⟨String⟩ “'”


| “[” [⟨Expression⟩ {“,” ⟨Expression⟩}] “]”
⟨AssignStmt⟩ → ⟨Identifier⟩ “=” ⟨Expression⟩
⟨PostfixExpr⟩ → ⟨Literal⟩ | ⟨Identifier⟩
| ⟨PostfixExpr⟩ “[” ⟨Expression⟩ “]”
| ⟨FunExpr⟩
| ⟨Identifier⟩ “.” ⟨FunExpr⟩
⟨FunExpr⟩ → ⟨Identifier⟩ “(” [⟨PosArgs⟩] [⟨KWArgs⟩] “)”
⟨PosArgs⟩ → ⟨Expression⟩ {“,” ⟨Expression⟩}
⟨KWArgs⟩ → ⟨Identifier⟩ “:” ⟨Expression⟩ [“,” ⟨KWArgs⟩]
Hierbei sei eine ⟨Expression⟩ ein Ausdruck von ⟨PostfixExpr⟩ unter Verwendung der Operatoren -, +, *, /, %,
==, !=, >, <, >=, <=, in, not in, and, or und not mit konventionellen Bindungsstärken und runder Klammern.

Im Unterschied zu C++ kennt die Meson-Spezifikationssprache keine Vereinbarungen von Variablen. Neue
Variablen können ohne vorherige Vereinbarung einfach zugewiesen werden.
Für unsere Zwecke relevante in Meson eingebaute Funktionen (können also als ⟨Identifier⟩ verwendet werden
ohne vorher definiert zu werden) sind:

project Ein Aufruf der Funktion project ist in jeder Meson-Spezifikation zwingend erforderlich.
Positionale Parameter (⟨PosArgs⟩) sind ein string der als Name des Projekts als Ganzes verwendet wird
und dann beliebig viele weitere string-Parameter die die in diesem Projekt verwendete Programmier-
sprachen auflisten – für unsere Zwecke nur der string 'cpp'.
library Jeder Aufruf der Funktion library führt dazu, dass später eine Übersetzungseinheit kompiliert wird
in eine Objektdatei. Der Rückgabewert der Funktion enthält insbesondere die notwendigen Informatio-
nen um die so kompilierte Objektdatei wiederfinden zu können. Es genügt daher im Folgenden den
Rückgabewert eines Aufrufs in eine neue Variable zu speichern und den Wert dieser Variable dann an
andere eingebaute Funktionen geeignet zu übergeben um zu erreichen, dass beim linken einer ausführ-
baren Binärdatei die erzeugte Objektdatei mit gelinkt wird.
Positionale Parameter sind ein string der als Name der library verwendet wird und die Namen von be-
liebig vielen Quellcode-Dateien (bei uns i.d.R. Dateiendung .cpp)
Es werden insb. die selben Schlüsselwort-Parameter (⟨KWArgs⟩) unterstützt wie bei executable.
subproject Wertet die Meson-Spezifikation des als string-Parameter gegebenen Unterprojekt-Namens aus.
Es wird erwartet, dass das Unterprojekt sich in einem Ordner mit dem gleichen Namen wie der Inhalt
des string-Parameters unterhalb des Unterordners subprojects des Projekts befindet.
Es wird ein Wert zurückgegeben auf den sich insb. die folgende Methode anwenden lassen:

get_variable Nimmt als Parameter den Namen einer Variablen die in der Meson-Spezifikation des
Unterprojektes definiert wurde und liefert ihren Wert zurück.

test Fügt eine ausführbare Binärdatei der Menge von Tests des Projektes hinzu.
Positionale Parameter sind ein Name als string und der Rückgabewert eines Aufrufs von executable.
dependency Sucht auf betriebsysstemabhängige Weise nach der als string-Parameter übergebenen externen
Abhängigkeit und liefert eine Referenz darauf als Rückgabewert.
declare_dependency Liefert einen Wert zurück, der sich zum Übergeben an den dependencies-Parameter
von executable und library eignet. Relevante Schlüsselwort-Parameter sind:

link_with Nimmt einen einzelne oder ein Array von Rückgabewerten von Aufrufen der Funktion
library und sorgt dafür, dass bei Verwendung des Rückgabewertes von declare_dependency in
z.B. executable die gegebenen Werte behandelt werden als wären auch sie direkt mit link_with
an den Aufruf von executable übergeben worden.

Seite 27 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

include_directories Nimmt einen einzelnen oder ein Array von strings die Unterverzeichnisse
des Projekts bezeichnen. Bei Verwendung des Rückgabewertes von declare_dependency in
z.B. executabe werden dann alle hier angegeben Unterverzeichnisse geeignet an den compiler
übergeben, sodass deren Inhalt mit durchsucht wird, wann immer der Präprozessor bei Auflösung
einer #include-Direktive (mit spitzen Klammern) nach einer Datei sucht.

executable Jeder Aufruf der Funktion executable führt dazu, dass spëter eine ausführbare Binärdatei er-
zeugt wird. Positionale Parameter sind ein string der als Name der ausführbaren Binärdatei verwendet
wird (bei uns bisher oft [Link], üblicher sind jedoch sprechendere Namen) und die Namen von beliebig
vielen Quellcode-Dateien (.cpp) die die Übersetzungseinheit bilden aus der die ausführbare Binärdatei
kompiliert werden soll. Relevante Schlüsselwort-Parameter sind:

link_with Nimmt einen einzelne oder ein Array von Rückgabewerten von Aufrufen der Funktion
library

dependencies Nimmt einen einzelne oder ein Array von Rückgabewerten von Aufrufen der Funktio-
nen dependency oder declare_dependency

Beispiel. Wir formulieren eine [Link]-Datei für das kleine Software-Projekt.

meson_ld/[Link]

project('ld', 'cpp')
ld_lib = library('ld_lib', '[Link]')
executable('main', 'ld_main.cpp', link_with: ld_lib)

Meson platziert seine generierte [Link]-Datei und die build-Produkte in einem Unterverzeichnis des
Projektverzeichnisses. Meson bezeichnet dieses Unterverzeichnis als das builddir. Wir nennen es per Konven-
tion build.
Folgende meson-Befehle sind für unsere Zwecke relevant und können im Projektverzeichnis verwendet wer-
den:

meson setup build Wertet die [Link]-Datei initial aus und erstellt das builddir mit Namen build
ninja -C build Wertet die [Link]-Datei erneut aus falls sie sich verändert hat und aktualisiert die re-
sultierende [Link]-Datei. Danach werden auch die build-Produkte und alle ihre Abhängigkeiten
neu gebaut, soweit erforderlich. Build-Produkte entstehen hierbei insb. durch alle Aufrufe der einge-
bauten Funktionen executable und library.
meson test -C build Führt alle an Aufrufe der eingebauten Funktion test übergebenen ausführbaren Bi-
närdateien aus nachdem sie, soweit erforderlich, neu gebaut wurden.

Meson WrapDB*

Die Meson community verwaltet eine Sammlung von quelloffenen Projekten die entweder standardmäßig
selbst mit Meson kompiliert werden oder für die die Meson community eigene Meson-Spezifikationen ge-
schrieben hat. Aus dieser, WrapDB genannten, Sammlung können einfach von der Kommandozeile Projekte
als subproject des eigenen Projektes hinzugefügt werden. Man spricht von vendoring.
Vor der ersten Installation eines subprojects aus der WrapDB muss zunächst im Projektverzeichnis das Unter-
verzeichnis subprojects manuell angelegt werden.

Seite 28 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Danach kann durch einen Befehl der Form “meson” “wrap” “install” ⟨ProjektName⟩, also z.B. meson wrap
install gtest, das Projekt mit Namen ⟨ProjektName⟩ in der WrapDB im Internet nachgeschlagen und
unterhalb von subprojects heruntergeladen werden.
Danach kann es in der Meson-Spezifikation des eigenen Projektes mit subproject verwendet werden.

Unit testing*

Als etablierte Methode der Softwareentwicklung bezeichnet unit testing die konzeptionelle Aufteilung von
selbst entwickelten Programmen in units und das unabhängige Testen dieser units durch eigens dafür ge-
schrieben Tests.
Bei der Aufteilung in units sollte auf Unabhängigkeit und Testbarkeit geachtet werden. Es soll möglich sein
durch einige, wiederrum als Code formulierbare, Beispiele jeweils i.W. die gesamte Komplexität des Verhaltens
jeder dieser units abzudecken. Insb. muss es möglich sein units separat voneinander zu testen sodass fehlge-
schlagene Tests klar mit einer konkreten unit im Code assoziiert sind und die Fehlersuche daher erleichtern.
Es ist wichtig die unit tests oft auszuführen, z.B. bei jedem build, um stets einen Eindruck (oder sogar eine
quantifizierbare Metrik) von der Korrektheit des Programms zu haben.
Oft werden für das etablieren von unit tests in einem Projekt sog. unit testing frameworks verwendet. Es
handelt sich hierbei um Programmbibliotheken die die Formulierung und das Ausführen von unit tests er-
leichtern.

GoogleTest*

GoogleTest ist ein quelloffenes unit testing framework, dass in der Meson WrapDB zur Verfügung steht.
Kerninhalt von GoogleTest ist eine Sammlung von Präprozessor-Makros, die zum Ausdrucken von Erwartung
innerhalb eines Tests und zur Deklaration der Tests selbst dienen:

TEST Nimmt zwei Parameter; einen Namen für eine Test-Suite, d.h. Sammlungen von Tests für die gleiche
unit, und einen Namen für den konkreten Test, der deklariert werden soll. Beide Parameter sind C++-
identifier, insb. keine strings o.Ä..
Das Makro löst auf zu Text, der an Stelle einer Funktionsdeklaration treten kann.
TEST_F Nimmt zwei Parameter; einen Namen einer vorher definierten Klasse, die von testing::Test abge-
leitet ist und einen Namen für den konkreten Test, der deklariert werden soll.
Das Makro löst ebenfalls zu Text auf, der an Stelle einer Funktionsdeklaration treten kann.
Als Unterschied zu TEST führt die Verwendung von TEST_F statt zu der Definition einer Funktion zur
Definition einer Methode einer wiederrum vom ersten Parameter abgeleiteten Klasse. Für Ausführen
des Tests erstellt GoogleTest ein Objekt dieser Klasse mit dem Standardkonstruktor und ruft die Methode
auf diesem Objekt auf. Innerhalb des Körpers des Tests hat man so direkten Zugriff auf die Attribute und
Methoden die in der Klasse vereinbart wurden, deren Name als erster Parameter an TEST_F übergeben
wurde, sofern diese in abgeleiteten Klassen sichtbar sind (also mindestens protected).
GoogleTest bezeichnet das erstellte Objekt bzw. die Klasse als fixture.

Seite 29 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Assertions GoogleTest bietet zahlreiche Makros, um innerhalb von Tests Erwartungen auszudrücken. Die
unten aufgeführten Makros prüfen jeweils eine Eigenschaft ihrer Parameter und generieren bei Aus-
führung des Tests eine Fehlermeldung, falls die Eigenschaft nicht erfüllt ist. Es kann notwendig sein
die Parameter der Makros jeweils in (ansonsten redundante) runde Klammern zu fassen, da der Präpro-
zessor in den Argumenten enthaltene Kommas und schließende runde Klammern ansonsten evtl. fehl
interpretiert.

EXPECT_EQ(𝑣 1 , 𝑣2) EXPECT_NE(𝑣 1 , 𝑣2) Prüft, dass 𝑣 1 == 𝑣 2 bzw. 𝑣 1 != 𝑣 2 bzw. 𝑣 1 < 𝑣 2 bzw. 𝑣 1 <= 𝑣 2
EXPECT_LT(𝑣 1 , 𝑣 2 ) EXPECT_LE(𝑣 1 , 𝑣 2 ) bzw. 𝑣 1 > 𝑣 2 bzw. 𝑣 1 >= 𝑣 2
EXPECT_GT(𝑣 1 , 𝑣 2 ) EXPECT_GE(𝑣 1 , 𝑣 2 )

EXPECT_FLOAT_EQ(𝑣 1 , 𝑣2) Prüft, dass die gegebenen Werte als float bzw. double gleich
EXPECT_DOUBLE_EQ(𝑣 1 , 𝑣2) sind bis auf eine vordefinierte Genauigkeit (4 ULPs3 )
EXPECT_NEAR(𝑣 1 , 𝑣2, 𝜀 ) Prüft, dass |𝑣 1 − 𝑣 2 | ≤ 𝜀
EXPECT_NO_THROW(𝑒 ) Prüft, dass bei der Auswertung des Ausdrucks 𝑒 keine
exception geworfen wird

Beispiel. Innerhalb eines kleinen Software-Projekts implementieren wir eine Funktion add, die zwei Werte
vom Typ int addiert und versehen diese in einer separaten Übersetzungseinheit mit unit tests.
Wir bauen das Software-Projekt mit Meson und definieren in der Meson-Spezifikation einen test, sodass
wir diesen später bequem mit meson test -C build ausführen können.
In der Variable gtest_main_dep hinterlegt die Meson-Spezifikation des subproject gtest geeignete Quell-
dateien, sodass ein von GoogleTest mitgeliefertes Hauptprogramm Teil wird der Übersetzungsenheit für
die ausführbare Binärdatei gtest_add. Das Hauptprogramm durchsucht die Übersetzungseinheit nach al-
len Vorkommnissen der Makros TEST und TEST_F und führt die damit spezifizierten Tests aus.

gtest_add/add.h

#pragma once

int add(int a, int b);

gtest_add/[Link]

#include ”add.h”

int add(int a, int b) {


return a + b;
}

gtest_add/add_unittest.cpp

#include ”add.h”
#include <gtest/gtest.h>

namespace {
TEST(Add, Negative) {

3 [Link]/web/20240204145454/[Link]

umbers-2012-edition/

Seite 30 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

EXPECT_EQ(add(2, -2), 0);


}
TEST(Add, Four) {
EXPECT_EQ(add(2, 2), 4);
}
}

gtest_add/[Link]

#include ”add.h”
#include <iostream>

using namespace std;

int main() {
int a, b;
while (cin >> a >> b)
cout << add(a, b) << endl;
}

gtest_add/[Link]

project('add', 'cpp')

add_lib = library('add_lib', '[Link]')


executable('add', '[Link]', link_with : add_lib)

gtest = subproject('gtest')
test('gtest',
executable('gtest_add', 'add_unittest.cpp', '[Link]', dependencies :
↩→ gtest.get_variable('gtest_main_dep'))
)

Beispiel. Wir implementieren unit tests für die Klasse vector aus der STL unter Verwendung einer eigens
erstellten Klasse als test fixture.

gtest_vector/vector_unittest.cpp

#include <vector>
#include <algorithm>
#include <functional>
#include <gtest/gtest.h>

namespace {
using namespace std;

class VectorFixture : public testing::Test {


protected:
vector<int> vs;

VectorFixture(): vs({1, 4, 3, 2, 5}) {}


};

TEST_F(VectorFixture, DoubleReverse) {
vector<int> vs_orig(vs);

Seite 31 von 32
Skript 04 Programmieren II für Studierende der Mathematik — WiSe 2025/26

reverse([Link](), [Link]());
reverse([Link](), [Link]());
EXPECT_EQ(vs_orig, vs);
}
TEST_F(VectorFixture, SortReverse) {
sort([Link](), [Link](), greater<int>());

vector<int> vs_comm(vs);
sort(vs_comm.begin(), vs_comm.end(), less<int>());
reverse(vs_comm.begin(), vs_comm.end());

EXPECT_EQ(vs, vs_comm);
}
}

Seite 32 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Typsynonyme

⟨Typdefinition⟩ → “using” ⟨Name⟩ “=” ⟨Typ⟩ “;”


Mit dem Schlüsselwort using können neue Namen für bereits existente Typen vereinbart werden.
Beispiel (Einfache Typsynonyme).

using size_t = unsigned int;


using myvector = double[10];
using mypointer = double*;

Die Definition von Typsynonymen ist auch innerhalb von Klassen möglich. Zugriff auf so definierte Namen
folgt den selben Regeln wie der Zugriff auf Namen von Klassenkomponenten. D.h. innerhalb der Klasse steht
der Name unqualizifiert zur Verfügung, außerhalb davon qualifiziert mit dem Namen der Klasse. Für eine
Typsynonym 𝑡 innerhalb einer Klasse 𝐾 also als 𝐾 ::𝑡.
Zugriff auf Typsynonyme folgt den Sichtbarkeitseinschränkungen private bzw. public, wie gewöhnlich.
Beispiel. Wir definieren ein assoziiertes Typsynonym für Längen von Vektoren einer rudimentären eigenen
Klasse Vektor.

vector_types.cpp

#include <iostream>

using namespace std;

class Vektor {
public:
using size_type = unsigned int;

private:
double* ap;
size_type len;

public:
Vektor(size_type n = 0, double x = 0) : len(n) {
ap = new double[n];
for (size_type i = 0; i < n; i++) ap[i] = x;
}
~Vektor() { delete[] ap; }

double& operator[](size_type i) {
return ap[i];
}
size_type size() const {
return len;
}
};

int main() {
Vektor v{4};
for (Vektor::size_type i = 0; i < [Link](); i++)
v[i] = i;

cout << ”v: ”;

Seite 1 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

for (Vektor::size_type i = 0; i < [Link](); i++)


cout << v[i] << ” ”;
cout << endl;

return 0;
}

v: 0 1 2 3

Typsynonyme mit typedef

⟨Typdefinition⟩ → “typedef” ⟨Typ⟩ ⟨Deklaration⟩ “;”


Typsynonyme können auch mit einer älteren Schreibweise mit typedef definiert werden. Die Definition mit
using ist in aller Regel vorzuziehen.

Beispiel (Einfache Typsynonyme).

typedef unsigned int size_t;


typedef double myvector[10];
typedef double *mypointer;

Iteratoren

Iteratoren sind mit einer Behälterklasse assoziierte Werte, die eine Position in einem konkreten Objekt der
Behälterklasse modelliert.
Valide Iteratoren können mit operator* dereferenziert werden um Zugriff auf den aktuellen Wert an der
referenzierten Position des assoziierten Behälters zu erhalten. Iteratoren können invalidiert werden, wenn der
assoziierte Behälter verändert wird. In diesem Fall gibt es keine Garantien bzgl. dem Verhalten von operator*.

Iteratoren und weitere Funktionen für vector

Im Folgenden Bezeichnet 𝑣 einen Vektor vom Typ vector<𝑇 >, 𝑛 eine ganze Zahl, 𝑐 eine Klassenkomponente
der Klasse 𝑇 , 𝑡 ein Wert vom Typ 𝑇 , 𝑖, 𝑖 ′ Iteratoren über 𝑣 und 𝑖 1 , 𝑖 2 Iteratoren über einen anderen Vektor.

vector<𝑇 >::iterator 𝑖 Vereinbart Variable 𝑖 als Vorwärts-Iterator


vector<𝑇 >::const_iterator 𝑖 Vereinbart Variable 𝑖 als Vorwärts-Konstanteniterator
vector<𝑇 >::reverse_iterator 𝑖 Vereinbart Variable 𝑖 als Rückwärts-Iterator
vector<𝑇 >::const_reverse_iterator 𝑖 Vereinbart Variable 𝑖 als Rückwärts-Konstanteniterator
𝑣 .begin() Liefert Vorwärts-Iterator über 𝑣 zum Index 0
𝑣 .end() Liefert Vorwärts-Iterator zum Index 𝑣 .size(), eine Position
nach Ende des Vektors
𝑣 .rbegin() Liefert Rückwärts-Iterator über 𝑣 zum Index 𝑣 .size() - 1

Seite 2 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

𝑣 .rend() Liefert Rückwärts-Iterator zum Index -1, eine Position vor


Beginn des Vektors
++𝑖 𝑖 ++ Verschiebt Iterator 𝑖 auf nächste Position in Durchlaufrichtung
--𝑖 𝑖 -- Verschiebt Iterator 𝑖 auf vorherige Position in Durchlaufrichtung
𝑖 + 𝑛 𝑛 + 𝑖 𝑖 - 𝑛 Liefert Iterator 𝑖 verschoben um 𝑛 in Durchlaufrichtung
𝑖 += 𝑛 𝑖 -= 𝑛 Verschiebt Iterator 𝑖 um 𝑛 in Durchlaufrichtung
*𝑖 Referenz zu Wert an aktueller Position von 𝑖
𝑖 [𝑛 ] *(𝑖 + 𝑛)
𝑖 ->𝑐 *(𝑖 ).𝑐

𝑣 .insert(𝑖 , 𝑡 ) Fügt vor aktueller Position von 𝑖 Kopie von 𝑡 als neues Element
in 𝑣 ein. Liefert Iterator auf Position des eingefügten Elements
zurück.
𝑣 .insert(𝑖 , 𝑖 1 , 𝑖 2 ) Fügt vor aktueller Position von 𝑖 Kopien der Elemente im
Interval [𝑖 1, 𝑖 2 ) in 𝑣 ein. Liefert Iterator auf Position des ersten
eingefügten Elements zurück.
𝑣 .erase(𝑖 ) Löscht Element an aktueller Position von 𝑖 aus 𝑣. Liefert Iterator
auf Element das direkt auf nun gelöschtes Element gefolgt hat.
𝑣 .erase(𝑖 , 𝑖 ′ ) Löscht Elemente im Intervall [𝑖, 𝑖 ′ ) aus 𝑣. Liefert Iterator auf
Element das direkt auf letztes nun gelöschtes Element gefolgt
hat.

Beispiel. Wir definieren ein Hauptprogramm um eine Matrix und einen Vektor von der Standardeingabe ein-
zulesen, miteinander zu multplizieren und den Ergebnis-Vektor auszugeben.

matmul_w.cpp

#include <iostream>
#include <vector>

using namespace std;

int main() {
unsigned int m, n;
cout << ”m, n: ”; cin >> m >> n;

vector<double> b(n);
cout << ”b: ”;
for (vector<double>::iterator i = [Link](); i != [Link](); ++i)
cin >> *i;

vector<vector<double>> a(m, vector<double>(n));


for (unsigned int i = 0; i < m; i++) {
cout << ”a[” << i << ”][...]: ”;
for (unsigned int j = 0; j < n; j++)
cin >> a[i][j];
}

vector<double> c(m);

Seite 3 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

vector<double>::iterator cpos = [Link]();

for (vector<vector<double>>::iterator apos_i = [Link]();


apos_i != [Link]();
apos_i++, cpos++) {
vector<double>::iterator bpos = [Link]();
for (vector<double>::iterator apos_j = (*apos_i).begin();
apos_j != (*apos_i).end();
apos_j++, bpos++)
*cpos += *apos_j * *bpos;
}

cout << ”a*b: ”;


for (cpos = [Link](); cpos != [Link](); cpos++)
cout << *cpos << ” ”;
return 0;
}

m, n: 2 3
b: 1 2 3
a[0][...]: 4 5 6
a[1][...]: 7 8 9
a*b: 32 50

Beispiel. Wir definieren eine Funktion um die euklidische Norm eines Vektors zu berechnen und ein einfaches
Hauptprogramm um die Funktion zu testen.

vector_norm_expl.cpp

#include <vector>
#include <iostream>
#include <cmath>

using namespace std;

double Norm(const vector<double>& x) {


double s = 0;
for (vector<double>::const_iterator c = [Link](); c != [Link](); ++c)
s += (*c)*(*c);
return sqrt(s);
}

int main() {
vector<double> a{5, 1};
cout << ”Norm(a) = ” << Norm(a) << endl;
return 0;
}

Norm(a) = 5.09902

Seite 4 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Typ-Platzhalter

Das Schlüsselwort auto kann bei Deklaration von Variablen anstelle eines Typnamens verwendet werden und
gibt an, dass anhand der Initialisierung der Variable automatisch eine passende Belegung an Stelle von auto
abgeleitet werden soll.
Werden mehrere Variablen auf einmal vereinbart, so muss die abgeleitete Belegung von auto für alle Variablen
identisch sein.
Beispiel. Wir vereinbaren einige Variablen unter Verwendung von auto.

int main()
{
auto i = 1; // i: int
const auto k = 5; // k: const int
auto x = 1.0; // x: double

auto& j = i; // j: int&
auto& l = k; // l: const int&

const auto& n = 2; // n: const int&

auto p = 2, y = 2.0; // unzulaessig


}

Bereichsbasierte for-Schleifen

⟨RangeFor⟩ → “for” “(” ⟨VarDekl⟩ “:” ⟨Ausdruck⟩ “)” ⟨Anweisung⟩


Die folgenden Anweisungen sind i.W. äquivalent:

for (T i : v) { ... }

for (auto __pos = [Link](),


__end = [Link]();
__pos != __end;
++__pos) {
T i = *__pos;
...
}

Beispiel. Wir definieren unter Verwendung bereichsbasierter for-Schleifen ein Hauptprogramm um eine Ma-
trix und einen Vektor von der Standardeingabe einzulesen, miteinander zu multplizieren und den Ergebnis-
Vektor auszugeben.

matmul_range.cpp

#include <iostream>
#include <vector>

using namespace std;

Seite 5 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

int main() {
unsigned int m, n;
cout << ”m, n: ”; cin >> m >> n;

vector<double> b(n);
cout << ”b: ”;
for (auto& i: b)
cin >> i;

vector<vector<double>> a(m, vector<double>(n));


for (unsigned int i = 0; i < m; i++) {
cout << ”a[” << i << ”][...]: ”;
for (auto& j: a[i])
cin >> j;
}

vector<double> c(m);
vector<double>::iterator cpos = [Link]();

for (auto i: a) {
vector<double>::const_iterator bpos = [Link]();
for (auto j: i)
*cpos += j * *(bpos++);
cpos++;
}

cout << ”a*b: ”;


for (auto i: c)
cout << i << ” ”;
return 0;
}

m, n: 2 3
b: 1 2 3
a[0][...]: 4 5 6
a[1][...]: 7 8 9
a*b: 32 50

Arrays in STL (array)

Objekte vom Datentyp array<𝑇 , 𝑛 > speichern 𝑛 viele Elemente vom Typ 𝑇 . Es handelt sich im Wesentlichen
um dünne wrapper um eingebaute C-Arrays vom Typ 𝑇 [𝑛 ].
Der Vorteil gegenüber der Verwendung von C-Arrays liegt in der, im Gegensatz zu C-Arrays, mit anderen
container-Datentypen aus der STL einheitlichen Programmierschnittstelle in Form der angebotenen Metho-
den und Attribute. Zusätzlich ist die Länge fester Bestandteil der jeweiligen Instanz des Typs array<𝑇 , 𝑛 >
und wird somit, anders als bei C-Arrays, an allen Stellen im Programm überprüft. Insb. kann es nicht pas-
sieren, dass einer Funktion, die ein array bestimmter Länge erwartet, ein array anderer Länge übergeben
wird.
Im Folgenden bezeichnet 𝑇 einen Datentyp, 𝑛 einen „hinreichend konstanten“ Ausdruck vom Typ size_t,
𝑘 < 𝑛 einen Wert vom Typ size_t, 𝑡 0, . . . , 𝑡𝑛−1 Werte vom Typ 𝑇 und 𝑎, 𝑎 ′ Variablen vom Typ array<𝑇 , 𝑛 >
Zudem bezeichnet 𝑖 einen Iterator über 𝑎.

Seite 6 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

array<𝑇 , 𝑛> 𝑎 Vereinbart Variable 𝑎 vom Typ array<𝑇 , 𝑛 > und allen
Elementen initialisiert mit Standardkonstruktor
array<𝑇 , 𝑛 > 𝑎 {𝑡 0 , . . ., 𝑡𝑘 −1 } Vereinbart Variable 𝑎 vom Typ array<𝑇 , 𝑛 > mit gegebenen
Präfix von Elementen und allen verbleibenden Elementen
initialisiert mit Standardkonstruktor
𝑎 .at(𝑘 ) Liefert Referenz auf Element mit Index 𝑘, wirft exception falls
𝑘 kein valider Index
𝑎 [𝑘 ] Liefert Referenz auf Element mit Index 𝑘, undefiniertes
Verhalten falls 𝑘 kein valider Index
𝑎 .front() 𝑎 .back() Liefert Referenz auf erstes bzw. letztes Element von 𝑎 falls
vorhanden, sonst undefiniertes Verhalten
𝑎 == 𝑎 ′ 𝑎 != 𝑎 ′ 𝑎 > 𝑎′ 𝑎 < 𝑎′ Vergleicht 𝑎 und 𝑎 ′ lexikographisch
𝑎 <= 𝑎 ′ 𝑎 >= 𝑎 ′
𝑎 .size() Liefert 𝑛
𝑎 .empty() Liefert true g.d.w. 𝑎 leer ist (d.h. 𝑛 = 0)
array<𝑇 , 𝑛 >::iterator 𝑖 Vereinbart Variable 𝑖 als Vorwärts-, Konstanten-, Rückwärts-
array<𝑇 , 𝑛 >::const_iterator 𝑖 bzw. Rückwärts-Konstanten-Iterator
array<𝑇 , 𝑛 >::reverse_iterator 𝑖
array<𝑇 , 𝑛 >::const_reverse_iterator 𝑖

𝑎 .begin() 𝑎 .end() 𝑎 .rbegin() Liefert Vorwärts-Iterator über 𝑎 zum ersten bzw. nach dem
𝑎 .rend() letzten Element bzw. Rückwarts-Iterator über 𝑎 zum letzten
++𝑖 𝑖 ++ --𝑖 𝑖 -- Verschiebt Iterator 𝑖 auf nächste bzw. vorherige Position in
Durchlaufrichtung
𝑖 + 𝑘 𝑘 + 𝑖 𝑖 - 𝑘 Liefert Iterator 𝑖 verschoben um 𝑘 in Durchlaufrichtung
𝑖 += 𝑘 𝑖 -= 𝑘 Verschiebt Iterator 𝑖 um 𝑘 in Durchlaufrichtung
*𝑖 Referenz zu Wert an aktueller Position von 𝑖
𝑖 [𝑘 ] *(𝑖 + 𝑘)
𝑖 ->𝑐 *(𝑖 ).𝑐

Beispiel. Wir initialisieren einen array und verwenden den subscript-Operator ([]) und die Methode .size()
um die Elemente des array von der Standardeingabe einzulesen. Wir verwenden dann einen Iterator über
array um in einer bereichsbasierten for-Schleife die eingelesenen Elemente des array wieder auszugeben.

array_usage.cpp

#include <iostream>
#include <array>
#include <algorithm>

using namespace std;

int main() {
array<double, 3> a1;

Seite 7 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

cout << ”a1: ”;


for ( unsigned int i = 0;
i < [Link]();
i++)
cin >> a1[i];

for (const double& x: a1)


cout << ” ” << x;
cout << endl;
}

a1: 17 7 42
17 7 42

Verzeigerte Datenstrukturen

Bereits in C können records Zeiger auf Werte des eigenen Datentyps enthalten. Das ermöglicht den Aufbau
von graph-, baum- bzw. listenartigen Datenstrukturen wobei die Zeiger als gerichtete Kanten fungieren. In
C++ gibt es in der STL Datentypen für einige der wichtigeren Datenstrukturen dieser Art.
Wichtige Einschränkung ist, dass sowohl structs in C wie auch Klassen in C++ keine Werte ihres eigenen
Datentyps direkt als Felder bzw. Attribute enthalten können. Hinreichendes Argument ist, dass Datentypen
sowohl in C wie auch in C++ eine wohldefinierte und endliche Größe im Sinne des von ihnen benötigten
Speichers haben müssen. Die Formulierung der relevanten Fehlermeldung stützt sich darauf, dass die Defini-
tion eines structs bzw. einer Klasse erst vollständig ist, sobald die schließende rechte geschweifte Klammer }
erreicht ist. Sehr wohl erlaubt innerhalb von structs bzw. Klassen ist jedoch das Vorkommen von Zeigern auf
den eigenen Datentyp, insb. da diese ja eine wohldefinierte Größe haben (oft genau 8 byte).
Beispiel (Einfach verkettete Listen). Wir vereinbaren eine eigene Klasse zur Speicherung einer beliebig langen
Liste von double Werten. Hierbei identifizieren wir jedes Element der Liste mit einem Objekt der Klasse
Liste wobei jedes Objekt sowohl einen Wert vom Typ double als Attribut trägt, wie auch einen Zeiger
auf das direkt folgende nächste Element der Liste.

start lauf Null

17 7 42
Speicherlayout einer einfach verketteten Liste

Das Beispiel ist unvollständig. Es fehlt insbesondere der Destruktor; so wie dargestellt verursacht Verwen-
dung der Klasse ein Speicherleck.

[Link]

#include <iostream>

using namespace std;

class Liste {
public:

Seite 8 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

double v;

private:
Liste* next;

public:
explicit Liste(double v_ = 0): v(v_), next(nullptr) {}

static Liste* einlesen(string n) {


Liste *start = nullptr, *lauf = start;

cout << n << ”: ”;

double x;
while (cin >> x) {
Liste* elem = new Liste{x};
if (!start)
start = lauf = elem;
else {
lauf->next = elem;
lauf = elem;
}
if ([Link]() == '\n') break;
}

return start;
};

friend ostream& operator<<(ostream& stream, const Liste& l) {


for (const Liste *lauf = &l; lauf; lauf = lauf->next)
stream << lauf->v << ” ”;
return stream;
}
};

int main() {
Liste *liste = Liste::einlesen(”l”);
if (!liste)
return 2;

cout << ”l: ” << *liste << endl;


return 0;
}

l: 17 7 42 13
l: 17 7 42 13

STL-Paare (pair aus <utility>)

Im Folgenden bezeichnen 𝑇1 , 𝑇2 Datentypen, 𝑡 1 , 𝑡 2 Werte vom Typ 𝑇1 bzw. 𝑇2 und 𝑝 eine Variable vom Typ
pair<𝑇1 ,𝑇2 >.

Seite 9 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

pair<𝑇1 , 𝑇2 > 𝑝 Vereinbart Variable 𝑝 vom Typ pair<𝑇1 , 𝑇2 > mit beiden
pair<𝑇1 , 𝑇2 > 𝑝 {𝑡 1 , 𝑡 2 } Komponenten initialisert mit jeweiligem Standardkonstruktor bzw.
als Kopie von 𝑡 1 bzw. 𝑡 2
make_pair(𝑡 1 , 𝑡 2 ) Liefert (𝑡 1, 𝑡 2 ) als Wert vom Typ pair<𝑇1 , 𝑇2 >
𝑝 .first 𝑝 .second Zugriff auf Komponenten von 𝑝

Generische STL-Mengen (set)

Objekte vom Datentyp set<𝑇 > speichern beliebig viele Elemente vom Typ 𝑇 ohne Duplikate in einer geeigne-
ten Baumstruktur für effizienten Zugriff. Mengenelemente lassen sich nicht verändern, sondern nur einfügen
und löschen. Um die interne Baumstruktur bilden zu können muss operator< geeignet überladen sein für 𝑇 .
Im Folgenden bezeichnet 𝑇 einen Datentyp, 𝑡, 𝑡 0, . . . , 𝑡𝑛−1 Werte vom Typ 𝑇 und 𝑠, 𝑠 ′ Variablen vom Typ
set<𝑇 >. Zudem bezeichnen 𝑖, 𝑖 ′ Iteratoren über 𝑠.

set<𝑇 > 𝑠 set<𝑇 > 𝑠 {𝑡 0 , . . ., 𝑡𝑛−1 } Vereinbart Variable 𝑠 vom Typ set<𝑇 > leer bzw. mit Elementen
𝑡 0, . . . , 𝑡𝑛−1
𝑠 == 𝑠 ′ 𝑠 != 𝑠 ′ 𝑠 > 𝑠′ 𝑠 < 𝑠′ Vergleicht 𝑠 und 𝑠 ′ lexikographisch
𝑠 <= 𝑠 ′ 𝑠 >= 𝑠 ′
𝑠 .size() Liefert Anzahl der Elemente in 𝑠
𝑠 .clear() Löscht alle Elemente aus 𝑠
𝑠 .empty() Liefert true g.d.w. 𝑠 leer ist
𝑠 .count(𝑡 ) Liefert 1 falls 𝑠 Element enthält, dass äquivalent ist zu 𝑡, sonst 0
𝑠 .insert(𝑡 ) Fügt Kopie von 𝑡 ein in 𝑠. Liefert Paar (pair) aus Iterator auf
Position von 𝑡 in 𝑠 (bzw. Element äquivalent zu 𝑡) und Wert vom
Typ bool der angibt ob Einfügen stattgefunden hat (also kein
Element äquivalent zu 𝑡 bereits vorhanden war in 𝑠)
𝑠 .erase(𝑡 ) Löscht Element äquivalent zu 𝑡 aus 𝑠, falls vorhanden. Liefert
Anzahl gelöschter Elemente (0 oder 1)
set<𝑇 >::iterator 𝑖 Vereinbart Variable 𝑖 als Vorwärts-, bzw. Rückwärts-Iterator
set<𝑇 >::reverse_iterator 𝑖
𝑠 .begin() 𝑠 .end() 𝑠 .rbegin() Liefert Vorwärts-Iterator über 𝑠 zum ersten bzw. nach dem letzten
𝑠 .rend() Element bzw. Rückwarts-Iterator über 𝑠 zum letzten bzw. vor dem
ersten Element
𝑠 .find(𝑡 ) Liefert Iterator zur Position von Element äquivalent zu 𝑡 in 𝑠 falls
vorhanden, sonst 𝑠 .end()
𝑠 .lower_bound(𝑡 ) Liefert Iterator zu erstem Element das nicht kleiner bzw. größer ist
𝑠 .upper_bound(𝑡 ) als 𝑡
++𝑖 𝑖 ++ --𝑖 𝑖 -- Verschiebt Iterator 𝑖 auf nächste bzw. vorherige Position in
Durchlaufrichtung
*𝑖 Konstante Referenz zu Wert an aktueller Position von 𝑖
𝑠 ′ .insert(𝑖 , 𝑖 ′) Fügt Kopien der Elemente aus 𝑠 im Bereich [𝑖, 𝑖 ′ ) ein in 𝑠 ′

Seite 10 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

𝑠 .erase(𝑖 ) 𝑠 .erase(𝑖 , 𝑖 ′ ) Löscht Element an aktueller Position von 𝑖 bzw. im Bereich [𝑖, 𝑖 ′ )
aus 𝑠. Liefert Iterator auf Element das direkt auf letztes nun
gelöschtes Element gefolgt hat.

Beispiel (Sieb des Eratosthenes). Wir implementieren ein Hauptprogramm, dass das Sieb des Eratosthenes
verwendet um Primzahlen kleiner als 50 zu finden und auszugeben. Wir verwenden hierbei ein Objekt
vom Typ set<unsigned int> um Zahlen zu speichern für die wir bereits ausschließen konnten, dass diese
prim sind.

primes_set.cpp

#include <set>
#include <iostream>

using namespace std;

const unsigned int n = 50;

int main() {
set<unsigned int> nonprimes{0, 1};

for (unsigned int k = 2; k*k < n; k++)


if (![Link](k))
for (unsigned int i = k; i*k < n; i++)
[Link](i*k);

for (unsigned int k = 0; k < n; k++)


if (![Link](k))
cout << k << ” ”;
cout << endl;

return 0;
}

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

Endliche Abbildungen in der STL (map)

Objekte vom Datentyp map<𝐾 , 𝑉 > speichern beliebig viele Paare vom Typ pair<const 𝐾 , 𝑉 > ohne Duplikate
bzgl. der ersten Komponente der Paare (also keine Duplikate in Werten vom Typ 𝐾, Duplikate in den Werten
vom Typ 𝑉 sind erlaubt). Die Daten werden in einer geeigneten Baumstruktur für effizienten Zugriff anhand
der Indizes vom Typ 𝐾 gespeichert. Um die interne Baumstruktur bilden zu können muss operator< geeignet
überladen sein für 𝐾.
Semantisch modelliert ein Wert vom Typ map<𝐾 , 𝑉 > eine Abbildung 𝐾 ′ → 𝑉 mit 𝐾 ′ ⊆ 𝐾 endlich.
Im Folgenden bezeichnen 𝐾, 𝑉 Datentypen, 𝑚, 𝑚 ′ Variablen vom Typ map<𝐾 , 𝑉 >, 𝑘, 𝑘 0, . . . , 𝑘𝑛−1 Variablen
vom Typ 𝐾, 𝑣, 𝑣 0, . . . , 𝑣𝑛−1 Variablen vom Typ 𝑉 und 𝑝 eine Variable vom Typ pair<𝐾 , 𝑉 >. Zudem bezeichnen
𝑖, 𝑖 ′ Iteratoren über 𝑚.

Seite 11 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

map<𝐾 , 𝑉> 𝑚 Vereinbart Variable 𝑚 vom Typ map<𝐾 , 𝑉 > leer


map<𝐾 , 𝑉 > 𝑚 {{𝑘 0 ,𝑣 0 }, . . ., {𝑘𝑛−1 ,𝑣𝑛−1 }} Vereinbart Variable 𝑚 vom Typ map<𝐾 , 𝑉 > mit 𝑚 [𝑘𝑖 ]=𝑣𝑖 für
0≤𝑖 <𝑛
𝑚 [𝑘 ] Liefert Referenz auf Wert zum Index 𝑘. Falls 𝑘 noch nicht
existiert in 𝑚 wird der Wert zum Index 𝑘 zunächst mit
Standardkonstruktor initialisiert.
𝑚 == 𝑚 ′ 𝑚 != 𝑚 ′ 𝑚 > 𝑚 ′ Vergleicht 𝑚 und 𝑚 ′ lexikographisch
𝑚 < 𝑚 ′ 𝑚 <= 𝑚 ′ 𝑚 >= 𝑚 ′
𝑚 .size() Liefert Anzahl der Elemente in 𝑚
𝑚 .clear() Löscht alle Elemente aus 𝑚
𝑚 .empty() Liefert true g.d.w 𝑚 leer ist
𝑚 .count(𝑘 ) Liefert 1 falls 𝑘 Element zu einem Index enthält, der äquivalent
ist zu 𝑘, sonst 0
𝑚 .insert(𝑝 ) Fügt Index-Wert-Paar 𝑝 ein in 𝑚. Liefert Paar aus Iterator auf
Position von erster Komponente von 𝑝 in 𝑚 (bzw. Position mit
Index äquivalent zu erster Komponente von 𝑝) und Wert vom
Typ bool der angibt ob Einfügen stattgefunden hat (also kein
Index äquivalent zur ersten Komponente von 𝑝 bereits
vorhanden war in 𝑚)
𝑚 .erase(𝑘 ) Löscht Index-Wert-Paar aus 𝑚 mit Index äquivalent zu 𝑘, falls
vorhanden. Liefert Anzahl gelöschter Index-Wert-Paare (0 oder
1).

map<𝐾 , 𝑉 >::iterator 𝑖 Vereinbart Variable 𝑖 als Vorwärts-, Konstanten-, Rückwärts-


map<𝐾 , 𝑉 >::const_iterator 𝑖 bzw. Rückwärts-Konstanten-Iterator
map<𝐾 , 𝑉 >::reverse_iterator 𝑖
map<𝐾 , 𝑉 >::const_reverse_iterator 𝑖

𝑚 .begin() 𝑚 .end() 𝑚 .rbegin() Liefert Vorwärts-Iterator über 𝑚 zum ersten bzw. nach dem
𝑚 .rend() letzten Index-Wert-Paar bzw. Rückwarts-Iterator über 𝑚 zum
letzten bzw. vor dem ersten Index-Wert-Paar
++𝑖 𝑖 ++ --𝑖 𝑖 -- Verschiebt Iterator 𝑖 auf nächste bzw. vorherige Position in
Durchlaufrichtung
*𝑖 Referenz zu Index-Wert-Paar an aktueller Position von 𝑖
𝑖 ->first 𝑖 ->second Referenz zu Index bzw. Wert an aktueller Position von 𝑖
𝑚 .find(𝑘 ) Liefert Iterator zur Position mit Index äquivalent zu 𝑘 in 𝑚 falls
vorhanden, sonst 𝑚 .end()
𝑚 ′ .insert(𝑖 , 𝑖 ′ ) Fügt Kopien der Index-Werte-Paare aus 𝑚 im Bereich [𝑖, 𝑖 ′ ) ein
in 𝑚 ′
𝑚 .erase(𝑖 ) 𝑚 .erase(𝑖 , 𝑖 ′ ) Löscht Index-Wert-Paar an aktueller Position von 𝑖 bzw. im
Bereich [𝑖, 𝑖 ′ ) aus 𝑚. Liefert Iterator auf Index-Wert-Paar das
direkt auf letztes nun gelöschtes Index-Wert-Paar gefolgt hat.

Beispiel. Wir implementieren ein Hauptprogramm zum Einlesen von Paaren bestehend aus dem Namen ei-
nes Kursteilnehmers und einer Anzahl von erreichten Übungspunkten. Die Namen fungieren als Indizes

Seite 12 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

eines Objekts vom Typ map<string, int>, points. Zunächst wird points vorbelegt mit den Namen von
Teilnehmern, die aus einer Datei ([Link]) eingelesen werden. Im Anschluss wird vom Benutzer
immer wieder die Eingabe eines Namens und einer Punktezahl erwartet. Falls unter diesem Namen bereits
ein Eintrag in points existiert werden die Punkte addiert, ansonsten wird der Benutzer gefragt ob ein neu-
er Eintrag angelegt werden soll. Am Ende werden die eingegebenen Daten dann ausgegeben, wegen der
internen Struktur von map in nach Namen sortierter Reihenfolge.

input_exc.cpp

#include <map>
#include <string>
#include <iostream>
#include <iomanip>
#include <fstream>

using namespace std;

int main(int argc, char* argv[]) {


map<string, int> points;

ifstream names(argv[1]);
string s;
while (names >> s) points[s];

cout << ”Teilnehmer Punkte:” << endl;


int n;
while (cin >> s >> n) {
if ([Link](s) != [Link]())
points[s] += n;
else {
char c;
cout << ”Neuer Teilnehmer (j/N)? ”; cin >> c;
if (c == 'j') [Link](pair<string, int>{s, n});
}
}

for (pair<string, int> entry: points)


cout << setw(10) << [Link]
<< setw(3) << [Link]
<< endl;

return 0;
}

[Link]

Meyer
Mueller
Moser
Maier

Teilnehmer Punkte:
Moser 3
Mueller 4
Maurer 2

Seite 13 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Neuer Teilnehmer (j/N)? j


Moser 5
Maier 0
Maurer 2
Meyer 0
Moser 8
Mueller 4

Beispiel. Wir implementieren ein Hauptprogramm um die häufigsten Worte im Inhalt einer gegebenen Da-
tei zu bestimmen. Es werden zunächst alle (durch insb. Leerzeichen oder Zeilenumbrüche getrennten)
Worte aus der als Kommandozeilenparameter gegebenen Datei eingelesen und in einem Objekt vom Typ
map<string, int>, freq, mit der Häufigkeit ihres Vorkommens assoziiert. Durch die Verwendung von
operator[] wird, sollte das Wort in freq noch nicht vorkommen, den Eintrag für diesen Index automa-
tisch mit 0 initialisiert. Es werden dann die Index-Werte-Paare in einen Vektor kopiert und die Funkti-
on stable_sort aus <algorithm> verwendet um diesen Vektor zu sortieren. Hierbei wird eine eigene
Vergleichsfunktion für die Elemente des Vektors (vom Typ pair<string, int>) verwendet die nur den
zweiten Eintrag der Paare (hier die Häufigkeit des Vorkommens) betrachtet. Es werden dann die ersten 10
Einträge des derart sortierten Vektors ausgegeben.

[Link]

#include <map>
#include <string>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <algorithm>

using namespace std;

bool ordnung(const pair<string, int>& a,


const pair<string, int>& b) {
return [Link] > [Link];
}

int main(int argc, char* argv[]) {


map<string, int> freq;
int nwort = 0;

ifstream ein(argv[1]);
string wort;
while (ein >> wort) {
nwort++;
freq[wort]++;
}

vector<pair<string, int>> vektor([Link](), [Link]());


stable_sort([Link](), [Link](), ordnung);

int count = 0;
for (auto entry: vektor) {
cout << setw(10) << [Link]
<< setw(5) << [Link]
<< endl;
if (++count >= 10) break;

Seite 14 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

cout << endl << ”Wortzahl: ” << setw(8) << nwort << endl;

return 0;
}

Wir rufen das Programm auf mit dem Namen der folgenden Datei als Konsolenparameter.

heron_kurz.cpp

int main ( )
{
double a , b , c , s , F ;
cout << ”a b c: ” ;
cin >> a >> b >> c ;
s = ( a + b + c ) / 2.0 ;
F = sqrt ( s * ( s - a ) * ( s - b ) * ( s - c ) ) ;
cout << ”F = ” << F << endl ;
return 0 ;
}

; 7
( 6
) 6
s 6
b 5
, 4
<< 4
a 4
c 4
* 3

Wortzahl: 82

Exceptions

Exceptions (Ausnahmen) und ihre Behandlung ermöglichen es die weitere Auswertung von Programmteilen
abzubrechen und stattdessen Informationen über den aufgetretenen Fehler an eine andere Stelle im Programm
zu kommunizieren wo dann geeignet reagiert werden kann.
⟨ThrowAusd⟩ → “throw” [⟨Wert⟩] “;”
throw-Ausdrücke sind als Ausdruck vom Typ void. Wird ein throw Ausdruck ausgeführt, wird die Ausführung
des Programms an dieser Stelle abgebrochen. Man sagt der an throw übergebene Wert wird als exception
geworfen.
⟨TryBlock⟩ → “try” “{” {⟨Anweisung⟩} “}” {⟨Handler⟩}
⟨Handler⟩ → “catch” “(” ⟨ExceptionDekl⟩ “)” “{” {⟨Anweisung⟩} “}”
⟨ExceptionDekl⟩ → “...” | ⟨Typ⟩ [⟨Deklarator⟩]
Wird eine exception geworfen, so wird nach dem nächsten try-Block gesucht der assoziiert ist einem pas-
sendem catch-Block. Hierfür wird zunächst lokal gesucht (ob sich der throw-Ausdruck also direkt in einem
try-Block mit passendem catch-Block befindet) und danach etwaige Funktionsaufrufe „rückabgewickelt“. Es

Seite 15 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

wird also betrachtet von wo die aktuelle Funktion aufgerufen wurde und ob der Aufruf dort in einem try-
Block mit passendem catch-Block liegt. Falls nicht wird auf die gleiche Weise weiter gesucht bis, als erster
Einstiegspunkt in das Programm, in der Funktion main gesucht wird.
Für eine exception vom Typ𝑇 sind jene catch-Blöcke passend für die der in den runden Klammern angegebene
Typ 𝑇 ist (oder Varianten davon wie const 𝑇 oder 𝑇 &). Zusätzlich ist catch (...) immer passend. Bei
mehreren passenden catch-Blöcken die mit dem selben try-Block assoziiert sind, ist die Reihenfolge der
catch-Blöcke ausschlaggebend.

Der Ausdruck throw ohne Wert ist nur innerhalb eines catch-Blocks zulässig und wirft den ursprünglichen
Wert der vom catch-Block gefangen wurde erneut.
Wird eine exception zwar geworfen aber nie gefangen, so wird die spezielle Funktion std::terminate()
aufgerufen. Voreinstellung ist, dass std::terminate() die Ausführung des Programms abbricht.

Exceptions in der Standardlibrary

Alle Typen deren Werte als exceptions von Funktionen der Standardlibrary geworfen werden, implementieren
eine Methode .what() die eine \0-terminierten C-Zeichenkette (vom Typ const char*) zurückgibt, die den
aufgetretenen Fehler beschreibt.

Vordefinierte Typen für Exceptions (stdexcept)

In der Header-Datei <stdexcept> sind die folgenden Klassen vereinbart. Die Klassen implementieren jeweils
die Methode .what(), wie oben. Zudem haben sie jeweils einen Konstruktor, der eine string-Referenz als
einzigen Parameter akzeptiert. Der string wird kopiert und geeignet abgelegt, sodass .what(), angewandt
auf ein so erzeugtes Objekt, später seinen Inhalt zurück gibt.

Typ Semantik

logic_error Nicht näher festgelegter Logik-Fehler, d.h. eine Fehlerkondition, die auf einen
Programmierfehler hinweist (z.B. Programm hat festgestellt, dass eine
Invariante anderswo im Programm verletzt wurde)
runtime_error Nicht näher festgelegter Laufzeit-Fehler, d.h. eine Fehlerkondition, deren
Auftreten außerhalb der Kontrolle des Programms liegt (z.B. Datei, die geöffnet
werden sollte, existiert nicht)
invalid_argument Funktion wurde aufgerufen mit Argument, welches nicht akzeptabel ist
domain_error Eingabe einer Operation liegt außerhalb ihres Definitionsbereichs
length_error Es wurde versucht gegen eine implementierungsabhängige
Größenbeschränkung einer Datenstruktur zu verstoßen (z.B. Länge eines
neuen vector zu groß)
out_of_range Es wurde versucht auf ein Element einer Datenstruktur zuzugreifen, das nicht
existiert (z.B. Index bei vector-Zugriff zu groß)
range_error Ergebnis einer Operation ist in gewähltem Datentyp nicht darstellbar
overflow_error Ergebnis einer Operation ist (betragsmäßig) zu groß um in gewähltem
Datentyp (präzise) darstellbar zu sein

Seite 16 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

underflow_error Ergebnis einer Operation ist betragsmäßig zu klein um in gewähltem Datentyp


(präzise) darstellbar zu sein

Anmerkung. Obwohl es eine gewisse Überschneidung mit den Fehlerkonditionen gibt, die mit dem errno-
Mechanismus kommuniziert werden können, ersetzt keine der beiden Varianten die andere. Insb. kommu-
nizieren die mathematischen Standardfunktionen ihre Fehlerkonditionen mit dem errno-Mechanismus1
und nicht mit exceptions.

Exceptions bei Speicherreservierung

Die Header-Datei <new> enthält einen exception-Typ std::bad_alloc. Werte vom Typ bad_alloc werden als
exception geworfen, insb. wenn bei Aufruf des new-Operators nicht genug Speicher reserviert werden kann.
Beispiel. Wir lesen von der Standardeingabe eine natürliche Zahl ein und versuchen ein C-Array von double
dieser Länge zu reservieren. Wenn die Reservierung eine exception vom Typ bad_alloc wirft, wird diese
in einem catch-Block gefangen und eine Fehlermeldung ausgegeben.

dyn_bad_alloc.cpp

#include <iostream>
#include <new>
#include <cstddef>

using namespace std;

int main() {
size_t n;
cout << ”n: ”; cin >> n;

double* a;
try {
a = new double[n];

a[0] = 3.14;
cout << a[0] << endl;
} catch (const bad_alloc& e) {
cerr << ”Zu wenig Speicherplatz fuer a vorhanden: ”
<< [Link]() << endl;
return 1;
}

return 0;
}

Wir rufen das Programm auf und übergeben eine Länge für 𝑛, die definitiv zu groß ist.

n: 100000000000
Zu wenig Speicherplatz fuer a vorhanden: std::bad_alloc

1 Implementierungsabhängigwerden auf manchen Systemen Fehlerkonditionen bei mathematischen Standardfunktionen nicht mit
mit dem errno-Mechanismus kommuniziert. Es gibt einen Mechanismus von sog. Gleitpunktzahl-Exceptions, die stattdessen ver-
wendet werden können. Dieser hat jedoch mit dem hier beschriebenen Exceptions-Konzept nichts zu tun und hat stattdessen eher
Ähnlichkeit mit einem weiteren, eigenen errno-Mechanismus für Zwecke der Gleitpunktarithmetik.

Seite 17 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Bereichsüberprüfter Elementen-Zugriff

Die Klasse vector<𝑇 > implementiert eine Methode .at(𝑛 ) die eine Referenz auf das Element mit Index
𝑛 zurückgibt. Falls 𝑛 kein valider Index für den Vektor ist, wird stattdessen eine exception vom Typ
std::out_of_range (aus <stdexcept>) geworfen.

Beispiel. Wir lesen von der Standardeingabe eine natürliche Zahl 𝑛 ein und versuchen einen C++-Vektor von
double dieser Länge zu konstruieren. Wenn die Reservierung eine exception vom Typ bad_alloc wirft,
wird diese in einem catch-Block gefangen und eine Fehlermeldung ausgegeben. Nach Konstruktion des
C++-Vektor wird versucht auf die Komponente mit Index 𝑛 zuzugreifen. Dies sollte eine exception vom
Typ out_of_range werfen.

vector_exc.cpp

#include <iostream>
#include <new>
#include <vector>

using namespace std;

int main() {
vector<double>::size_type n;
cout << ”n: ”; cin >> n;

cout << ”Beginn” << endl;


try {
cout << ”Vor Vereinbarung” << endl;
vector<double> a(n);
cout << ”Nach Vereinbarung” << endl;
[Link](n) = 1;
cout << ”Nach Zugriff” << endl;
} catch (const bad_alloc& e) {
cerr << ”Zu wenig Speicherplatz fuer a vorhanden: ” << endl
<< ” ” << [Link]() << endl;
} catch (const out_of_range& e) {
cerr << ”Zugriff ausserhalb von Grenzen:” << endl
<< ” ” << [Link]() << endl;
} catch (...) {
cerr << ”Andere exception” << endl;
throw;
}
cout << ”Ende” << endl;

return 0;
}

Wir rufen das Programm auf und übergeben eine Länge für 𝑛, die definitiv zu groß ist.

n: 100000000000
Beginn
Vor Vereinbarung
Zu wenig Speicherplatz fuer a vorhanden:
std::bad_alloc
Ende

Seite 18 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

n: 10
Beginn
Vor Vereinbarung
Nach Vereinbarung
Zugriff ausserhalb von Grenzen:
vector::_M_range_check: __n (which is 10) >= this->size() (which is 10)
Ende

Exceptions bei Ein-/Ausgabe

Die Stromklassen (istream, ostream, fstream, …) implementieren eine Methode .exceptions(𝑚 ). Der Para-
meter 𝑚 ist vom Typ ios::iostate. Aufrufen der Methode .exceptions(𝑚 ) setzt eine interne Bitmaske des
Stromobjekts, auf dem sie aufgerufen wurde, auf den Wert vom 𝑚. Sobald im internen Zustand des Stromob-
jekts ein bit (ios::badbit, ios::eofbit, ios::failbit) gesetzt wird, das auch in 𝑚 gesetzt ist, so wird eine
exception vom Typ ios::failure geworfen.
Beispiel. Wir implementieren ein Hauptprogramm, dass eine ganze Zahl von der Standardeingabe ein- und
dann auf der Standardausgabe ausgibt. Wir aktivieren vorher das Werfen von exceptions wenn im Strom-
zustand von cin die bits ios::failbit oder ios::badbit gesetzt werden.

iostream_exc.cpp

#include <iostream>

using namespace std;

int main() {
[Link](ios::failbit|ios::badbit);

try {
int n;
cout << ”n: ”; cin >> n;
cout << ”n: ” << n << endl;
} catch (const ios::failure& e) {
cerr << ”I/O-exception: ”
<< [Link]() << endl;
}

return 0;
}

Wir führen das Programm aus und übergeben auf der Standardeingabe eine Zeichenkette, die nicht erfolg-
reich als int interpretiert werden kann. Es wird eine exception vom Typ ios::failure geworfen, da im
internen Zustand von cin das bit ios::failbit gesetzt wird, wenn das formattierte Einlesen eines Werts
fehlschlägt.

n: abc
I/O-exception: basic_ios::clear: iostream error

Seite 19 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Funktions-try-Blöcke

Es ist zulässig den Körper einer Funktion oder Methode durch einen try-Block (und folgende catch-Blöcke)
zu ersetzen.
Im Falle eines Konstruktors muss das Schlüsselwort try vor der Initialisierungsliste stehen.
Beispiel. Wir vereinbaren eine Klasse Rational zur modellieren von rationalen Zahlen. Wir vereinbaren die
main-Funktion mit Funktions-try-Block um im Programmverlauf etwaige auftrende exceptions zu verar-
beiten. Wir stellen sicher, dass an Stellen, an denen Fehler auftreten können, i.d.R. exceptions geworfen
werden, im Fehlerfall.

rational_exc.cpp

#include <iostream>
#include <stdexcept>
#include <sstream>

using namespace std;

class Rational {
private:
long p, q;

void normalisieren() {
long teiler = ggt(abs(p), abs(q));

if (teiler > 1) {
p /= teiler;
q /= teiler;
}
if (q < 0) {
p = -p;
q = -q;
}
if (p == 0)
q = 1;
}

static long ggt(long a, long b) {


if (a == 0 && b == 0)
throw domain_error(”ggt(0, 0)”);

long r;
do {
r = a % b;
a = b;
b = r;
} while (r != 0);
return r;
}

public:
Rational(long p_ = 0, long q_ = 1): p(p_), q(q_) {
if (q == 0) {
ostringstream os;
os << ”Unerlaubter Aufruf Rational(” << p << ”, ” << q << ”)”;

Seite 20 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

throw domain_error([Link]());
}
normalisieren();
}

friend ostream& operator<<(ostream& stream, const Rational& r) {


if (r.q == 1)
return stream << r.p;

return stream << r.p << '/' << r.q;


}
friend istream& operator>>(istream& stream, Rational& r) {
char c;
stream >> r.p >> c >> r.q;
if (c != '/' || r.q == 0) {
[Link](ios::failbit);
return stream;
}
[Link]();
return stream;
}

friend Rational operator/(const Rational& s, const Rational& t) {


if (t.p == 0) {
ostringstream os;
os << ”Unerlaubte Operation: ” << s << ” / ” << t;
throw domain_error([Link]());
}
return Rational(s.p*t.q, s.q*t.p);
}
};

int main() try {


[Link](ios::failbit|ios::badbit);

Rational r, s;
cout << ”p1/q1 p2/q2: ”;
cin >> r >> s;
cout << ”r/s = ” << r/s << endl;
return 0;
} catch (const domain_error& e) {
cerr << ”Bereichsfehler: ” << [Link]() << endl;
} catch (const ios::failure& e) {
cerr << ”I/O-Fehler: ” << [Link]() << endl;
} catch (...) {
cerr << ”Sonstige exception” << endl;
}

p1/q1 p2/q2: 1/2 3/4


r/s = 4/6

p1/q1 p2/q2: 1/0 3/4


I/O-Fehler: basic_ios::clear: iostream error

Seite 21 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

p1/q1 p2/q2: 1/2 0/4


r/s = Bereichsfehler: Unerlaubte Operation: 1/2 / 0

Seite 22 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Auf Numerik optimierte Vektoren (valarray)*

Objekte vom Datentyp valarray<𝑇 > speichen beliebig viele Elemente vom Typ 𝑇 dicht, d.h. als in einem
großen Speicherbereich direkt aufeinander folgend. Die Werte, die 𝑇 annehmen darf sind eingeschränkt auf
eingebaute Zahlentypen wie double, float, int, etc., Zeigertypen, complex und valarray<𝑇 ′ >.
valarray<𝑇 >s unterstützen arithmetische Operationen punktweise mit einem Skalar vom Typ 𝑇 und punkt-
weise mit einem weiteren valarray<𝑇 > gleicher Größe.
Zudem unterstützen valarrays das bilden von Teilvektoren mit Referenzsemantik, was sie insb. für die Ver-
wendung als Datentyp zu Darstellung von Matrizen attraktiv macht. Teilvektoren wären in diesem Kontext
z.B. Zeilen, Spalten und Untermatrizen.
Im Folgenden bezeichnet 𝑇 einen Datentyp, 𝑡, 𝑡 0, . . . , 𝑡𝑛−1 Werte vom Typ 𝑇 , 𝑛, 𝑖 natürliche Zahlen vom Typ
std::size_t, 𝑖 eine ganze Zahl vom Typ int, 𝑣, 𝑣 ′ Variablen vom Typ valarray<𝑇 >, 𝑓 ein Zeiger auf Funktion
vom Typ 𝑇 ′ (𝑇 )

valarray<𝑇 > 𝑣 Vereinbart Variable 𝑣 vom Typ valarray<𝑇 > leer (Länge 0)
valarray<𝑇 > 𝑣 (𝑛 ) Vereinbart Variable 𝑣 vom Typ valarray<𝑇 > mit Länge 𝑛
und allen Elementen initialisiert mit Standardkonstruktor
valarray<𝑇 > 𝑣 (𝑡 , 𝑛 ) Vereinbart Variable 𝑣 vom Typ valarray<𝑇 > mit Länge 𝑛
und allen Elementen initialisiert als Kopien von 𝑡 2
valarray<𝑇 > 𝑣 {𝑡 0 , . . ., 𝑡𝑛−1 } Vereinbart Variable 𝑣 vom Typ valarray<𝑇 > mit Elementen
𝑡 0, . . . , 𝑡𝑛−1
𝑣 [𝑖 ] Liefert Referenz auf Element mit Index 𝑖 in 𝑣
𝑣 == 𝑣 ′ 𝑣 != 𝑣 ′ 𝑣 > 𝑣 ′ 𝑣 < 𝑣 ′ Liefert Wert vom Typ valarray<bool> mit den Ergebnissen
𝑣 >= 𝑣 ′ 𝑣 <= 𝑣 ′ des komponentenweisen Vergleichs der Element von 𝑣 mit
𝑡 == 𝑣 𝑡 != 𝑣 𝑡 > 𝑣 𝑡 < 𝑣 𝑡 >= 𝑣 denen von 𝑣 ′ bzw. von 𝑡 jeweils mit den Elementen von 𝑣
𝑡 <= 𝑣
𝑣 == 𝑡 𝑣 != 𝑡 𝑣 > 𝑡 𝑣 < 𝑡 𝑣 >= 𝑡
𝑣 <= 𝑡
𝑣 + 𝑣′ 𝑣 - 𝑣′ 𝑣 * 𝑣′ 𝑣 / 𝑣′ 𝑣 % 𝑣′ Liefert valarray mit Ergebnissen der komponentenweisen
𝑣 & 𝑣′ 𝑣 | 𝑣 ′ 𝑣 ^ 𝑣 ′ 𝑣 << 𝑣 ′ Anwendung des arith. bzw. bitweisen bzw. logischen
𝑣 >> 𝑣 ′ 𝑣 && 𝑣 ′ 𝑣 || 𝑣 ′ 𝑣 += 𝑣 ′ Operators der Elemente von 𝑣 mit denen von 𝑣 ′ bzw. von 𝑡
𝑣 -= 𝑣 ′ 𝑣 *= 𝑣 ′ 𝑣 /= 𝑣 ′ 𝑣 %= 𝑣 ′ jeweils mit den Elementen von 𝑣
𝑣 &= 𝑣 ′ 𝑣 |= 𝑣 ′ 𝑣 ^= 𝑣 ′ 𝑣 <<= 𝑣 ′
𝑣 >>= 𝑣 ′
𝑡 + 𝑣 𝑡 - 𝑣 𝑡 * 𝑣 𝑡 / 𝑣 𝑡 % 𝑣
𝑡 & 𝑣 𝑡 | 𝑣 𝑡 ^ 𝑣 𝑡 << 𝑣 𝑡 >> 𝑣
𝑡 && 𝑣 𝑡 || 𝑣
𝑣 + 𝑡 𝑣 - 𝑡 𝑣 * 𝑡 𝑣 / 𝑡 𝑣 % 𝑡
𝑣 & 𝑡 𝑣 | 𝑡 𝑣 ^ 𝑡 𝑣 << 𝑡 𝑣 >> 𝑡
𝑣 && 𝑡 𝑣 || 𝑡 𝑣 += 𝑡 𝑣 -= 𝑡
𝑣 *= 𝑡 𝑣 /= 𝑡 𝑣 %= 𝑡 𝑣 &= 𝑡
𝑣 |= 𝑡 𝑣 ^= 𝑡 𝑣 <<= 𝑡 𝑣 >>= 𝑡

2 Die Reihenfolge der Parameter ist hier anders als bei anderen container-Datentypen

Seite 23 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

abs(𝑣 ) exp(𝑣 ) log(𝑣 ) log10(𝑣 ) Liefert valarray mit Ergebnissen der komponentenweisen
sqrt(𝑣 ) sin(𝑣 ) cos(𝑣 ) tan(𝑣 ) Anwendung der arith. Funktion auf die Elemente von 𝑣
asin(𝑣 ) acos(𝑣 ) atan(𝑣 ) sinh(𝑣 )
cosh(𝑣 ) tanh(𝑣 )

pow(𝑣 , 𝑣 ′ ) pow(𝑣 , 𝑡 ) pow(𝑡 , 𝑣 ) Liefert valarray mit Ergebnissen der komponentenweisen


atan2(𝑣 , 𝑣 ′ ) atan2(𝑣 , 𝑡 ) Anwendung der arith. Funktion auf die Elemente von 𝑣 und
atan2(𝑡 , 𝑣 ) die Elemente von 𝑣 ′ bzw. von 𝑡 jeweils mit den Elementen
von 𝑣
𝑣 .sum() 𝑣 .min() 𝑣 .max() Liefert Summe bzw. Minimum bzw. Maximum der Elemente
von 𝑣
𝑣 .shift(𝑖 ) Liefert valarray<𝑇 > mit Kopien der Elemente von 𝑣,
verschoben um 𝑖 Stellen; füllt von den Enden her mit 0 auf.
𝑣 .cshift(𝑖 ) Liefert valarray<𝑇 > mit Kopien der Elemente von 𝑣,
verschoben um 𝑖 Stellen; verschiebt zyklisch.
𝑣 .apply( 𝑓 ) Liefert valarray<𝑇 ′ > mit den Ergebnissen der
komponentenweisen Anwendung von 𝑓 auf die Elemente
von 𝑣
𝑣 .resize(𝑛 ) 𝑣 .resize(𝑛 , 𝑡 ) Ersetzt 𝑣 durch ein neues valarray der Länge 𝑛 mit allen
Elementen initialisiert mit Standardkonstruktor bzw. als
Kopie von 𝑡
begin(𝑣 ) end(𝑣 ) Liefert Iterator nicht näher spezifizierten Typs

Beispiel. Wir berechnen das Skalarprodukt eines Vektors von double dargestellt als valarray<double>.

valarray_scalar.cpp

#include <iostream>
#include <valarray>

using namespace std;

int main() {
valarray<double> a(5), b{1.0, 2.0, 3.0, 4.0, 5.0};

cout << ”a: ”;


for (double& x: a) cin >> x;

cout << ”skalar(a, b) = ” << (a*b).sum() << endl;


}

a: 5 4 3 2 1
skalar(a, b) = 35

Indexmengen (slice etc.) & Teilvektoren (slice_array etc.)*

Objekte der Klassen slice, gslice und valarray<bool> und valarray<size_t> können als Indexmengen
fungieren. Der Subscriptoperator [] ist für valarray geeignet überladen, sodass auch Indexmengen als Para-

Seite 24 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

meter fungieren können. Zurückgegeben wird dann ein Teilvektor von einem mit valarray verwandten Typ
(slice_array, gslice_array, mask_array bzw. indirect_array) der Referenzsemantik hat auf die Elemente
des valarray. D.h. Zuweisungsoperatoren mit einem Teilvektor auf der linken Seite verändern die im an den
Subscriptoperater übergebenen valarray gespeicherten Werte.
Zudem können Teilvektoren implizit konvertiert werden in Werte vom Typ valarray sodass z.B. auch die
Verwendung auf der rechten Seite von Zuweisungsoperatoren möglich ist. Es wird jedoch bei der Konvertie-
rung zu valarray jedes mal eine Kopie aller Elemente gemacht, was einen großen performance-Einfluß haben
kann.

Bitmasken (mask_array) & Indirekte Indizierung (indirect_array)*

Bei Verwendung eines valarray<bool>, einer Bitmaske, als Indexmenge muss der valarray<bool> von selber
Größe sein wie das valarray auf das der Subscriptoperator später angewandt wird. Der zurückgegebene Typ
mask_array enthält eine Referenz auf genau jene Werte bei denen am selben Index in der Indexmenge der
Wert true steht.
Bei Verwendung eines valarray<size_t> als Indexmenge enthält der zurückgegebene Typ Referenzen auf
genau jene Stellen im valarray, auf das der Subscriptoperator angewandt wurde, die im valarray<size_t>
stehen in der Reihenfolge in der sie in valarray<size_t> stehen. Das mehrfache Vorkommen des selben
Index in dem valarray<size_t> ist nicht zulässig.
Beispiel. Wir setzen mithilfe einer Bitmaske die vier Ecken einer Matrix (zeilenweise gespeichert in einem
valarray<double>) auf 0 und geben die Diagonale der resultierenden Matrix aus.

misc_slices.cpp

#include <iostream>
#include <iomanip>
#include <valarray>
#include <algorithm>

using namespace std;

void ausgabe_vektor(const valarray<double>& a) {


for (double x: a)
cout << setw(3) << x;
}
void ausgabe_matrix(const valarray<double>& a, size_t n) {
for (size_t k = 0; k < [Link](); k++) {
cout << setw(3) << a[k];
if ((k+1) % n == 0)
cout << endl;
}
}

int main() {
size_t m, n;
cout << ”m n: ”; cin >> m >> n;

valarray<double> a(m*n);

for (size_t k = 0; k < [Link](); k++) a[k] = k;

valarray<bool> bv(m*n);
bv = false;

Seite 25 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

bv[0] = bv[n - 1] = bv[n * (m-1)] = bv[m*n - 1] = true;


a[bv] = 0;
cout << ”a:” << endl;
ausgabe_matrix(a, n); cout << endl;

valarray<size_t> iv(min(m, n));


for (size_t i = 0; i < min(m, n); i++)
iv[i] = i*(n + 1);
cout << ”diag(a):”;
ausgabe_vektor(a[iv]); cout << endl;
}

m n: 4 3
a:
0 1 0
3 4 5
6 7 8
0 10 0

diag(a): 0 4 8

slice*

slice(𝑖 0 , 𝑛 , ℎ ) liefert ein Objekt vom Typ slice, das die Indexmenge (𝑖 0 + 𝑘ℎ)𝑘=0,...,𝑛−1 darstellt.
Die Klasse slice implementiert die Methoden start(), size() und stride() um die im Konstruktor gege-
benen Werte wieder auslesen zu können.
Beispiel. Für eine Matrix (𝑎𝑖 𝑗 )𝑖=0,...,𝑚−1 , die zeilenweise in einer Variable 𝑎 vom Typ valarray gespeichert ist,
𝑗=0,...,𝑛−1
d.h. a[𝑖 · 𝑛 + 𝑗 ] entspricht 𝑎𝑖 𝑗 :

𝒊-te Zeile Die Index-Folge (𝑖 · 𝑛 + 𝑗) 𝑗=0,...,𝑛−1 entspricht slice(𝑖 · 𝑛 , 𝑛 , 1).


𝒋-te Spalte Die Index-Folge (𝑖 · 𝑛 + 𝑗)𝑖=0,...,𝑛−1 entspricht slice( 𝑗 , 𝑚 , 𝑛 ).

Beispiel. Wir geben die 𝑖-te Zeile und die 𝑗-te Spalte einer Matrix aus. Danach addieren wir die 𝑙-te Zeile auf
die 𝑗-te Zeile und geben die resultierende Matrix aus.

[Link]

#include <iostream>
#include <iomanip>
#include <valarray>

using namespace std;

slice zeile(size_t i, size_t m, size_t n)


{ return slice(i * n, n, 1); }
slice spalte(size_t j, size_t m, size_t n)
{ return slice(j, m, n); }

void ausgabe_vektor(const valarray<double>& a) {


for (double x: a)
cout << setw(3) << x;

Seite 26 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

}
void ausgabe_matrix(const valarray<double>& a, size_t n) {
for (size_t k = 0; k < [Link](); k++) {
cout << setw(3) << a[k];
if ((k+1) % n == 0)
cout << endl;
}
}

int main() {
size_t i, j, l, m, n;
cout << ”m n: ”; cin >> m >> n;
cout << ”i j l: ”; cin >> i >> j >> l;

valarray<double> a(m*n);

for (size_t k = 0; k < [Link](); k++) a[k] = k;

cout << ”Zeile ” << i << ”: ”;


ausgabe_vektor(a[zeile(i, m, n)]); cout << endl;
cout << ”Spalte ” << j << ”: ”;
ausgabe_vektor(a[spalte(j, m, n)]); cout << endl;

a[zeile(i, m, n)] += a[zeile(l, m, n)];

cout << ”a:” << endl;


ausgabe_matrix(a, n); cout << endl;
}

m n: 3 4
i j l: 1 2 0
Zeile 1: 4 5 6 7
Spalte 2: 2 6 10
a:
0 1 2 3
4 6 8 10
8 9 10 11

gslice*

Bei gslice handelt es sich um die mehrdimensionale Verallgemeinerung von slice. Im Gegensatz zu slice
nimmt gslice als zweiten und dritten Parameter jeweils ein valarray<size_t> von Längen und Schrittwei-
ten.
Ein Aufruf von gslice(𝑖 0 , {𝑛 0 , 𝑛 1 , . . .}, {ℎ 0 , ℎ 1 , . . .}) liefert die Indexmenge

(𝑖 0 + 𝑘 0ℎ 0 + . . . + 𝑘𝐿−1ℎ𝐿−1 )𝑘𝐿−1 =0,...,𝑛𝐿−1


..
.
𝑘 0 =0,...,𝑛 0 −1

wobei 𝑘𝐿−1 „am schnellsten variiert“ und 𝑘 0 „am langsamsten“.


Anders ausgedrückt genügt gslice der folgenden Rekursionsbeziehung:

• gslice(𝑖 0 , {𝑛 }, {ℎ }) liefert: slice(𝑖 0 , 𝑛 , ℎ )

Seite 27 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

• gslice(𝑖 0 , {𝑛 0 , 𝑛 1 , . . .}, {ℎ 0 , ℎ 1 , . . .}) liefert:


gslice(𝑖 0 + 0 · ℎ 0 , {𝑛 1 , . . .}, {ℎ 1 , . . .}) ◦ gslice(𝑖 0 + 1 · ℎ 0 , {𝑛 1 , . . .}, {ℎ 1 , . . .}) ◦ . . . ◦
gslice(𝑖 0 + (𝑛 0 − 1) · ℎ 0 , {𝑛 1 , . . .}, {ℎ 1 , . . .})

wobei ◦ die Konkatenation von Folgen von Indizes bezeichne.


Beispiel. Wir wandeln ein gslice_array, das den oberen linken Teil einer 2 × 2-Blockmatrix referenziert,
implizit in ein valarray um und geben dieses aus.

[Link]

#include <iostream>
#include <iomanip>
#include <valarray>

using namespace std;

gslice submatrix(size_t mp, size_t np, size_t n)


{ return gslice(0, {mp, np}, {n, 1}); }

void ausgabe_matrix(const valarray<double>& a, size_t n) {


for (size_t k = 0; k < [Link](); k++) {
cout << setw(3) << a[k];
if ((k+1) % n == 0)
cout << endl;
}
}

int main() {
size_t m, n, mp, np;
cout << ”m n: ”; cin >> m >> n;
cout << ”mp np: ”; cin >> mp >> np;

valarray<double> a(m*n);
for (size_t k = 0; k < [Link](); k++) a[k] = k;

cout << ”a:” << endl;


ausgabe_matrix(a, n); cout << endl;

cout << ”sub a:” << endl;


ausgabe_matrix(a[submatrix(mp, np, n)], np); cout << endl;
}

m n: 3 4
mp np: 2 3
a:
0 1 2 3
4 5 6 7
8 9 10 11

sub a:
0 1 2
4 5 6

Seite 28 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Matrix-Datentypen mit valarray*

valarray ist zwar a priori kein Matrix-Datentyp, eignet sich aber sehr gut zur Implementierung eben dieser.
Beispiel. Wir vereinbaren einen eigenen Matrix-Datentyp der insb. den Zugriff auf einzelne Elemente durch
doppelte Anwendung des Subscriptoperators erlauben soll. D.h. für 𝐴 eine Matrix soll 𝐴[𝑖 ][ 𝑗 ] eine Refe-
renz auf den 𝑗-ten Eintrag der 𝑖-ten Zeile der Matrix 𝐴 liefern.
Es genügt hierfür nicht den vordefinierten Typ slice_array für Zeilen der Matrix zu verwenden, da dieser
nicht wiederum selbst den Subscriptoperator überlädt.
Es ist zudem keine gute Idee die Klasse matrix<T> von valarray<T> abzuleiten, da valarray<T> die Metho-
de resize (nicht virtuell) implementiert und diese dazu verwendet werden könnte einen bzgl. der Größe
des enthaltenen valarray und den beiden anderen Attributen m und n invaliden Zustand zu erreichen.
Wir beginnen mit den Deklarationen für den Matrix-Typ, die wir in eine separate Header-Datei auslagern:

matrix.h

#pragma once

#include <valarray>

template<class T> class matrix {


private:
std::valarray<T> v;
std::size_t m, n;

public:
matrix(std::valarray<T>&&, std::size_t, std::size_t);
matrix(std::size_t m_ = 0, std::size_t n_ = 0, T val_ = T());

size_t rows() const; size_t columns() const;

class matrix_slice : public std::slice {


friend class matrix;
private:
std::valarray<T>& v;

matrix_slice(std::valarray<T>& v_, std::size_t start, std::size_t size,


↩→ std::size_t stride);

public:
T& operator[](std::size_t i);
T operator[](std::size_t i) const;
operator std::valarray<T>() const;
};

matrix_slice operator[](std::size_t i);


matrix_slice row(std::size_t i);
matrix_slice column(std::size_t j);

operator std::valarray<T>() const;

T* begin();
T* end();
};

Die Definitionen für den Matrix-Typ:

Seite 29 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

[Link]

#include <valarray>
#include <stdexcept>

#include ”matrix.h”

using namespace std;

template<class T>
matrix<T>::matrix(std::valarray<T>&& v_, std::size_t m_, std::size_t n_)
: v(v_), m(m_), n(n_) {
if ([Link]() != m * n)
throw invalid_argument(”valarray of wrong size”);
}
template<class T>
matrix<T>::matrix(size_t m_, size_t n_, T val_)
: v(val_, m_ * n_),m(m_),n(n_) {}

template<class T>
size_t matrix<T>::rows() const { return m; }
template<class T>
size_t matrix<T>::columns() const { return n; }

template<class T>
matrix<T>::matrix_slice::matrix_slice
(valarray<T>& v_, size_t start, size_t size, size_t stride)
: slice(start, size, stride), v(v_) {}

template<class T>
T& matrix<T>::matrix_slice::operator[](size_t i)
{ return v[start() + i*stride()]; }
template<class T>
T matrix<T>::matrix_slice::operator[](size_t i) const
{ return v[start() + i*stride()]; }
template<class T>
matrix<T>::matrix_slice::operator valarray<T>() const {
valarray<T> v(size());
for (size_t i = 0; i < size(); i++)
v[i] = (*this)[i];
return v;
}

template<class T>
typename matrix<T>::matrix_slice
matrix<T>::operator[](size_t i) { return row(i); }
template<class T>
typename matrix<T>::matrix_slice matrix<T>::row(size_t i)
{ return matrix<T>::matrix_slice{v, i*n, n, 1}; }
template<class T>
typename matrix<T>::matrix_slice matrix<T>::column(size_t j)
{ return matrix<T>::matrix_slice{v, j, m, n}; }

template<class T>
matrix<T>::operator valarray<T>() const {
return v;
}

Seite 30 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

template<class T>
T* matrix<T>::begin()
{ return std::begin(v); }
template<class T>
T* matrix<T>::end()
{ return std::end(v); }

template class matrix<double>;

Wir können den so eingeführten Matrix-Typ verwenden um auf einzelne Elemente der Matrix (auch schrei-
bend) zuzugreifen:

matrix_basic.cpp

#include <iostream>

#include ”matrix.h”

using namespace std;

int main() {
size_t m, n, i, j;
cout << ”m n: ”; cin >> m >> n;
cout << ”i j: ”; cin >> i >> j;
matrix<double> a{m, n};
cout << ”Matrix a: ” << endl;
for (double& x: a) cin >> x;
cout << (a[i][j] = 13) << endl;
}

m n: 3 4
i j: 1 2
Matrix a:
1 2 3 4
5 6 7 8
9 10 11 12
13

Wir können die Multiplikation von Werten unseres Matrix-Typs (naiv) implementieren:

[Link]

#include <iostream>
#include <iomanip>

#include ”matrix.h”

using namespace std;

int main() {
size_t m, n;
cout << ”m n: ”; cin >> m >> n;
matrix<double> a{m, n};
cout << ”Matrix a: ” << endl;
for (double& x: a) cin >> x;

Seite 31 von 32
Skript 05 Programmieren II für Studierende der Mathematik — WiSe 2025/26

cout << endl;

size_t k, l;
cout << ”k l: ”; cin >> k >> l;
matrix<double> b{k, l};
cout << ”Matrix b: ” << endl;
for (double& x: b) cin >> x;
cout << endl;

matrix<double> c{m, l};


for (size_t i = 0; i < m; i++)
for (size_t j = 0; j < l; j++) {
valarray<double> as = [Link](i), bs = [Link](j);
c[i][j] = (as * bs).sum();
}

for (size_t i = 0; i < m; i++) {


for (size_t j = 0; j < l; j++)
cout << setw(3) << c[i][j];
cout << endl;
}
}

m n: 4 4
Matrix a:
3 2 1 4
1 0 2 3
3 2 1 2
3 2 1 4

k l: 4 4
Matrix b:
1 2 1 4
0 1 0 3
4 0 4 2
1 2 1 4

11 16 11 36
12 8 12 20
9 12 9 28
11 16 11 36

Seite 32 von 32
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Funktionstypen

⟨Fun'typ⟩ → ⟨Rückgabetyp⟩ “(” ⟨Parametertypliste⟩ “)”


Funktionen haben Typen. Syntaktisch besteht der Typ einer Funktion aus dem Rückgabetyp gefolgt von den
Typen der Parameter in runden Klammern.

Funktionszeiger

Analog wie sich für eingebaute Datentypen (int, double, etc.) bzw. den Namen von Klassen jeweils ein asso-
ziierter Zeigertyp ableiten lässt, ist das auch für Funktionstypen möglich.
Beispiel (Vereinbarung und Aufruf von Funktionszeigern). Wir vereinbaren ein Typsynonym für einen Funk-
tionstyp, den Typ von Funktionen mit einem Parameter vom Typ double und Rückgabetyp double.
Im Hauptprogramm vereinbaren wir eine Variable funptr vom Typ Zeiger auf Funktion mit einem Para-
meter vom Typ double und Rückgabetyp double und weisen ihr als Wert eine Adresse zu, die assoziiert
ist mit der Funktion sqrt aus cmath. Hierfür wird der Name cmath automatisch konvertiert in eine geeig-
nete Speicheradresse. Danach wird der Funktionsaufrufsoperator auf die Variable funptr angewandt was
die mit der in funptr enthaltenen Speicheradresse assoziierte Funktion mit den gegebenen Parametern
aufruft.

funtyp_demo.cpp

#include <cmath>
#include <iostream>

using namespace std;

using ArithFun = double(double);

int main() {
ArithFun* funptr = sqrt;

cout << funptr(2) << endl;


}

1.41421

Funktionsnamen werden automatisch umgewandelt in Funktionszeiger, wo benötigt.


Es kann der Funktionsaufrufsoperator auf Funktionszeiger-Werte angewandt werden. Es wird hierbei die mit
der Speicheradresse, die dargestellt wird vom Funktionszeiger-Wert, assoziierte Funktion mit den gegebenen
Parametern aufgerufen.

Vereinbarungssyntax

Die Vereinbarung von Namen erfolgt stets in der Form 𝑇 𝐷 mit 𝑇 einem Datentyp und 𝐷 dem Deklarator. Der
Deklarator enthält stets den zu vereinbarenden Namen.
Beispiel. Wir vereinbaren einen Namen x als Variable vom Typ double:

Seite 1 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

double x;

Der Deklarator kann auch eine Parameterliste bzw. eine Parametertypliste enthalten.
Beispiel. Wir vereinbaren einen Namen f als Funktion von einem Parameter vom Typ double und mit Rück-
gabetyp double:

double f(double);

Beispiel. Wir vereinbaren einen Namen f als Funktion von einem Parameter vom Typ double und mit Rück-
gabetyp Zeiger auf double:

double *f(double);

Wird der „Zeiger-auf“-Operator * bei einem Ausdruck wie zur Vereinbarung eines Namens als Funktion ange-
wandt auf den zu vereinbarenden Namen (statt z.B. auf den Rückgabetyp wie im Beispiel), so wird der Name
vereinbart als Variable von einem Typ der Form Zeiger auf Funktion.
Beispiel. Wir vereinbaren einen Namen fptr als Variable vom Typ Zeiger auf Funktion von einem Parameter
vom Typ double und mit Rückgabetyp double:

double (*f)(double);

Als Faustregel lässt sich sagen, dass der Typ mit dem ein Name innerhalbe einer Deklaration vereinbart wird,
von innen nach außen zu lesen ist.
Beispiel. Wir vereinbaren eine Funktion signal mit zwei Parametern vom Typ int und vom Typ Zeiger auf
Funktion von einem Parameter int ohne Rückgabewert. Die Funktion signal hat Rückgabetyp Zeiger auf
Funktion mit einem Parameter vom Typ int und ohne Rückgabewert.

void (*signal(int, void (*func)(int)))(int);

I.A. ist der „Umweg“ über ein Typsynonym der direkten Vereinbarungssyntax vorzuziehen.
Wird typedef statt using zur Vereinbarung eines Typsynonyms der Form Zeiger auf Funktion verwendet, so
muss hierfür die direkte Vereinbarungssyntax verwendet werden.

Funktionstypen als Parameter

Wird der Parameter einer Funktion mit einem Funktionstyp vereinbart (entweder unter Verwendung eines
Typsynonyms oder mit direkter Vereinbarungssyntax), so akzeptiert die Funktion als Parameter an dieser
Stelle stattdessen einen Parameter vom entsprechenden Typ der Form Zeiger auf Funktion.
Beispiel (Sehnentrapezregel mit Zeiger auf Funktion). Der erste Parameter der Funktion trapezoidal ist mit
Typ Funktion mit einem Parameter vom Typ double und Rückgabetyp double vereinbart. Dennoch ak-
zeptiert trapezoidal als ersten Parameter einen Wert vom Typ Zeiger auf Funktion mit einem Parameter
vom Typ double und Rückgabetyp double vereinbart.
Beim Aufruf von trapezoidal im Hautprogramm wird der Name der Funktion f automatisch umgewan-
delt in einen Wert von einem Typ der Form Zeiger auf Funktion. Der Wert ist eine Speicheradresse geeignet
assoziiert ist mit der Funktion f.

Seite 2 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Den Parameter der Funktion trapezoidal stattdessen als double (*g)(double) zu vereinbaren würde
sich genau identisch verhalten.

trapezoidal_fptr.cpp

#include <iostream>
#include <cmath>

using namespace std;

double trapezoidal(double g(double),


int n, double a = 0, double b = 1) {
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

double f(double x) {
x -= 0.25;
return exp(-x * x);
}

int main() {
for (int n = 1; n <= 512; n *= 2)
cout << trapezoidal(f, n) << endl;

return 0;
}

0.754598
0.847006
0.868203
0.873407
0.874702
0.875025
0.875106
0.875126
0.875131
0.875133

Funktionsobjekte in der Standardlibrary (aus <functional>)

Die Standardlibrary stellt diverse Werkzeuge zur Verfügung um mit (Repräsentationen von) Funktionen als
Werten arbeiten zu können.

Generische Funktionsobjekte (function)

function<𝑇 > ist eine generische Klasse für Funktionsobjekte deren Funktionsaufrufsoperatoren sich verhal-
ten wie eine Funktion vom Typ 𝑇 .

Seite 3 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Die Klasse function<𝑇 > hat einen Konstruktor der als einzigen Parameter einen beliebigen Wert akzeptiert
für den der Funktionsaufrufsoperator kompatibel überladen ist. Insb. also auch Werte vom Typ Zeiger auf
Funktion vom Typ 𝑇 . Der Konstruktor nimmt (da er mit nur einem Parameter aufgerufen werden kann) Teil
an der impliziten Typkonvertierung.
Für function<𝑇 > ist der Funktionsaufrufsoperator natürlich ebenfalls kompatibel zum Typ 𝑇 überladen.
Beispiel (Sehnentrapezregel mit generischen Funktionsobjekten). Wir vereinbaren die Funktion trapezoidal
mit einem Parameter vom Typ function<𝑇 > für 𝑇 der Typ von Funktionen mit einem Parameter vom Typ
double und Rückgabewert double, also double(double).

Beim ersten Aufruf von trapezoidal im Hauptprogramm wird der Name der Funktion f zunächst auto-
matisch umgewandelt in einen passenden Wert von einem Typ der Form Zeiger auf Funktion und dann
von dort weiter (durch implizite Verwendung des Konstruktors der Klasse function<𝑇 >) in ein Objekt der
Klasse function<𝑇 >.
Beim zweiten Aufruf wird das temporäre Objekt der Klasse NormV ebenfalls implizit konvertiert in ein
Objekt der Klasse function<𝑇 >. Da der Funktionsaufrufsoperator für die Klasse NormV kompatibel zu 𝑇
überladen ist und somit ein Objekt der Klasse NormV ein akzeptabler Parameterwert für den einen Parame-
ter des Konstruktors von function<𝑇 > wäre, können Objekte der Klasse NormV implizit konvertiert werden
zu Objekten der Klasse function<𝑇 >.

trapezoidal_stl.cpp

#include <iostream>
#include <cmath>
#include <functional>

using namespace std;

class NormV {
private:
double mw, stdabw;

public:
NormV (double mw_ = 0, double stdabw_ = 1)
: mw(mw_), stdabw(stdabw_) {}

double operator()(double x) const {


return M_2_SQRTPI / (2 * M_SQRT2 * stdabw)
* exp(-((x - mw) * (x - mw)) / (2*stdabw*stdabw));
}
};

double trapezoidal(function<double(double)> g,
int n, double a = 0, double b = 1) {
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

double f(double x) {
x -= 0.25;
return exp(-x * x);
}

Seite 4 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

int main() {
for (int n = 1; n <= 512; n *= 4)
cout << trapezoidal(f, n) << endl;

cout << endl;

for (int n = 1; n <= 512; n *= 4)


cout << trapezoidal(NormV{}, n) << endl;

return 0;
}

0.754598
0.868203
0.874702
0.875106
0.875131

0.320457
0.340082
0.341266
0.34134
0.341344

Partielle Funktionsanwendung (bind)

Die Standardlibrary stellt eine Funktion bind zur Verfügung. Die Rückgabewerte der Funktion sind nicht
näher spezifizierte Funktionsobjekte. Als ersten Parameter akzeptiert die Funktion ein beliebiges Funktions-
objekt. Wird der Funktionsaufrufparameter auf einen Rückgabewert der Funktion bind angewandt, so wird
der Funktionsaufrufsoperator des als ersten Parameter übergebenen Funktionsobjektes ausgewertet.
Die weiteren Parameter der Funktion bind nach dem Ersten beschreiben wie beim eventuellen Aufruf des
Funktionsaufrufsoperators auf dem Rückgabewert der Funktion bind der Funktionsaufruf des unterliegenden
Funktionsobjektes (dem ersten Parameter) erfolgen soll. Zulässig als Parameter sind zunächst beliebige Werte
(diese werden kopiert und die Kopie beim eventuellen Funktionsaufruf unverändert übergeben).
Zusätzlich enthält der Namensraum placeholders unterhalb von std spezielle Werte _1, _2, . . . die ebenfalls
zulässig sind als Parameter der Funktion bind. Beim eventuellen Funktionsaufruf eines Rückgabewerts der
Funktion bind werden die dabei angegebenen Parameter der Reihenfolge nach abgebildet auf die Platzhalter-
werte. Wo beim Aufruf der Funktion bind ein Platzhalter als Parameter stand, wird dann an das unterliegende
Funktionsobjekt der entsprechende Parameter des eventuellen Funktionsaufrufs des Rückgabewerts der Funk-
tion bind übergeben.
Beispiel (Ändern der Parameterreihenfolge einer Funktion). Wir rufen die Funktion bind auf mit dem Na-
men einer Funktion als ersten Parameter. Dieser wird automatisch umgewandelt in einen entsprechenden
Funktionszeiger.
Die weiteren Parameter sind hier zwei Platzhalter, jedoch in umgekehrter Reihenfolge. Beim Aufruf des
resultierenden Rückgabewertes (den wir der Variable rev_sub zuweisen) werden die dabei angegebenen
Parameter daher in umgekehrter Reihenfolge an die unterliegende Funktion do_sub übergeben.

Seite 5 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

flip_demo.cpp

#include <iostream>
#include <functional>

using namespace std;

double do_sub(double a, double b) {


return a - b;
}

int main() {
using namespace placeholders;
function<double(double, double)>
rev_sub = bind(do_sub, _2, _1);
cout << rev_sub(3, 1) << endl;
}

-2

Beispiel (Sehnentrapezregel mit partieller Funktionsanwendung). Wir übergeben an die Funktion


trapezoidal den Rückgabewert eines Aufrufs der Funktion bind.

trapezoidal_bind.cpp

#include <iostream>
#include <cmath>
#include <functional>

using namespace std;

double trapezoidal(function<double(double)> g,
int n, double a = 0, double b = 1) {
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

double f(double x, double offset=0) {


x += offset;
return exp(-x * x);
}

int main() {
using namespace placeholders;
function<double(double)> f_shifted
= bind(f, _1, -0.25);

for (int n = 1; n <= 512; n *= 2)


cout << trapezoidal(f_shifted, n) << endl;

return 0;
}

Seite 6 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

0.754598
0.847006
0.868203
0.873407
0.874702
0.875025
0.875106
0.875126
0.875131
0.875133

Referenzen (ref, cref)

Die Standardlibrary stellt zwei Funktion ref und cref zur Verfügung. Diese akzeptieren als Parameter jeweils
eine Referenz bzw. eine konstante Referenz. Ihre Rückgabewerte sind ebenfalls als Parameter der Funktion
bind zulässig und werden von dieser speziell behandelt. Bei Auswerten des Aufrufs der Funktion bind selbst
wird eine Kopie der an ref bzw. cref übergebenen Referenz im Rückgabewert der Funktion bind gespeichert.
Bei eventuellem Aufruf des Funktionsaufrufsoperators auf den Rückgabewert der Funktion bind werden dann
die kopierten Referenzen übergeben.
Beispiel (Partielle Funktionsanwendung mit Referenzparameter). Wir vereinbaren eine Funktion inc mit ei-
nem Referenzparameter. Im Hauptprogramm rufen wir die Funktion bind mit insb. einem Parameter dem
Rückgabewert eines Aufrufs der Funktion ref auf. Beim eventuellen Funktionsaufruf des Rückgabewertes
der Funktion bind wird dann eine Referenz auf die Variable i an die unterliegende Funktion inc übergeben.

inc_demo.cpp

#include <iostream>
#include <functional>

using namespace std;

void inc(int& n) { n++; }


int main() {
int i = 0;
function<void()>
do_inc = bind(inc, ref(i));

cout << i << endl;


do_inc();
cout << i << endl;
}

0
1

Funktionsobjekte für Methoden (mem_fn)

Die Standardlibrary stellt eine Funktion mem_fn zur Verfügung. Als Parameter erhält die Funktion einen Zeiger
auf die Methode einer Klasse. Für eine Klasse 𝑇 und eine Methode 𝑚 von 𝑇 ist die Syntax einen Zeiger auf 𝑚
zu konstruieren: &𝑇 ::𝑚.

Seite 7 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Als Rückgabewert liefert mem_fn ein Funktionsobjekt von nicht näher spezifiertem Typ mit erstem Parameter
eine Referenz (oder ein Zeiger) auf ein Objekt der Klasse 𝑇 . Die restlichen Parametern des zurückgegebenen
Funktionsobjektes sind identisch zu denen der Method 𝑚.
Wird der Funktionsaufrufsoperator auf einen Rückgabewert von mem_fn angewandt so wird die unterliegende
Methode auf dem als ersten Parameter gegebene Objekt aufgerufen.
Beispiel (Sehnentrapezregel mit Methode). Wir rufen die Funktion trapezoidal auf auf den Rückgabewert
eines Aufrufs der Funktion mem_fn. Die unterliegende Methode ist hierbei die Methode eval der Klasse
NormV.

Durch einen geeigneten Aufruf der Funktion bind erzeugen wir ein Funktionsobjekt dass bei Auswertung
des Funktionsaufrufsoperators die Methode eval auf das Objekt stdNorm anwendet.

trapezoidal_mem_fn.cpp

#include <iostream>
#include <cmath>
#include <functional>

using namespace std;

class NormV {
private:
double mw, stdabw;

public:
NormV (double mw_ = 0, double stdabw_ = 1)
: mw(mw_), stdabw(stdabw_) {}

double eval(double x) const {


return M_2_SQRTPI / (2 * M_SQRT2 * stdabw)
* exp(-((x - mw) * (x - mw)) / (2*stdabw*stdabw));
}
};

double trapezoidal(function<double(double)> g,
int n, double a = 0, double b = 1) {
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

int main() {
using namespace placeholders;
NormV stdNorm{};
function<double(double)> stdNormEval =
bind(mem_fn(&NormV::eval), ref(stdNorm), _1);

for (int n = 1; n <= 512; n *= 4)


cout << trapezoidal(stdNormEval, n) << endl;

return 0;
}

Seite 8 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

0.320457
0.340082
0.341266
0.34134
0.341344

Lambda-Ausdrücke

In Form von Lambda-Ausdrücken stellt C++ spezielle Syntax zur Verfügung um Funktionsobjekte in strikt
mächtigerer Weise zu erzeugen als die beschriebenen Werkzeuge.
⟨Lambda⟩ → “[” [⟨Capture⟩ {“,” ⟨Capture⟩}] “]” “(” ⟨Params⟩ “)” “->” ⟨Type⟩ “{” ⟨Body⟩ “}”
⟨Capture⟩ → [“&”] ⟨Variable⟩
| “this”
Syntaktisch besteht ein Lambda-Ausdruck aus einer Spezifikation der sog. captures, gefolgt von einer Parame-
terliste, dem Rückgabetyp und dann einem Körper, analog zu einer gewöhnlichen Funktion.
Der Wert des Lambda-Ausdrucks ist eine sog. closure. Ein Funktionsobjekt analog zu denen die z.B. die Funk-
tion bind zurückliefert.
Wird der Name einer Variable als capture erwähnt, so wird (ähnlich wie bei einem Aufruf von bind) stan-
dardmäßig eine Kopie des Wertes dieser Variable angelegt. Wir sagen der Lambda-Ausdruck überträgt die
erwähnte Variable in seinen Körper. Die Variable steht dann (zusätzlich qualifiziert als const) innerhalb des
Körpers des Lambdas zur Verfügung. Wird dem Namen der Variable im zugehörigen capture ein & vorne an-
gestellt, so wird statt einer Kopie eine Referenz angelegt. Wird statt eines Variablennamens das Schlüsselwort
this angegeben (was nur zulässig ist wenn der Lambda-Ausdruck innerhalb einer Methode steht) so steht in-
nerhalb des Körpers der Methode der implizite Parameter this zur Verfügung und zeigt auf das selbe Objekt
wie this an der Stelle an der der Ausdruck selbst steht.
Die Parameterliste spezifiziert welche Parameter und von welchem Typ die closure bei ihrem eventuellen
Aufruf akzeptiert.
Wird der Funktionsaufrufsoperator auf eine closure angewandt so wird der Körper des Lambda-Ausdrucks,
mit dem die closure erstellt wurde, ausgeführt. Es stehen dabei die Parameter aus der Parameterliste zur Verfü-
gung. Der Körper des Lambda-Ausdrucks kann, genauso wie eine Funktion, return-Anweisungen enthalten.
Die an die return-Anweisungen übergebenen Werte müssen implizit konvertierbar sein in den Typ der als
Rückgabetyp des Lambda-Ausdrucks spezifiziert wurde. Wurde statt einem Rückgabetyp void angegeben, so
akzeptieren die return-Anweisungen innerhalb des Körpers des Lambda-Ausdrucks keinen Rückgabewert.
Beispiel (Sehnentrapezregel mit Lambda-Ausdrücken). Wir rufen die Funktion trapezoidal auf eine mit ei-
nem Lambda-Ausdruck erzeugte closure.
Der zweite Lambda-Ausdruck übertragt das Funktionsobjekt f in Referenz in seinen Körper; würde nach
Erstellen des Funktionsobjekts g der in f gespeicherte Wert verändert, so hätte dies Auswirkungen auf
den eventuellen Aufruf von g. Der in der Variable off gespeicherte Wert wird in Kopie in den Körper des
zweiten Lambda-Ausdrucks übertragen. Würde der Wert von off nach Erstellung von g verändert, so hätte
dies keine Auswirkung beim eventuellen Aufruf von g.

trapezoidal_lambda.cpp

#include <iostream>
#include <cmath>

Seite 9 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

#include <functional>

using namespace std;

double trapezoidal(function<double(double)> g,
int n, double a = 0, double b = 1) {
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

int main() {
function<double(double)> f
= [](double x) -> double { return exp(-x * x); };

double off = -0.25;


function<double(double)> g
= [&f, off](double x) -> double { return f(x + off); };

for (int n = 1; n <= 512; n *= 2)


cout << trapezoidal(g, n) << endl;

return 0;
}

0.754598
0.847006
0.868203
0.873407
0.874702
0.875025
0.875106
0.875126
0.875131
0.875133

Beispiel. Wir übertragen eine Variable i in Referenz in den Körper eines Lambda-Ausdrucks. Beim eventuel-
len Aufrufs der closure, die der Lambda-Ausdruck erzeugt, wirken sich Änderungen an i im Körper des
Lambda-Ausdrucks auch außerhalb des Lambda-Ausdrucks aus.

inc_lambda_demo.cpp

#include <iostream>
#include <functional>

using namespace std;

int main() {
int i = 0;
function<void()>
do_inc = [&i]() -> void { i++; };

cout << i << endl;


do_inc();

Seite 10 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

cout << i << endl;


}

0
1

Beispiel: Auswertung arithmetischer Ausdrücke mit Funktionen

Wir erzeugen zu Programmstart eine endliche Abbildung funs die Namen von mathematischen Funktionen
assoziiert mit Funktionsobjekten zum Typ double(double). Die Verwendung von static_cast ist hier not-
wending da die arithmetischen Funktionen exp, log, etc. in <cmath> in hochgradig überladener Form imple-
mentiert sind sodass sonst die jeweilige implizite Konvertierung zu einem Objekt der Klasse function<. . .>
nicht eindeutig wäre.
Im Hauptprogramm lesen wir in einer Schleife immer wieder den Namen einer arithmetischen Funktion und
einen Wert vom Typ double ein. Wir wenden dann jeweils die entsprechende Funktion aus der endlichen
Abbildung funs an und behandeln den Fall, dass keine Funktion von diesem Namen in funs hinterlegt wurde,
als exception.

fun_eval.cpp

#include <iostream>
#include <map>
#include <functional>
#include <cmath>

using namespace std;

using ArithFun = double(double);

const map<string, function<ArithFun>> funs{


{”exp”, static_cast<ArithFun*>(exp)},
{”ln”, static_cast<ArithFun*>(log)},
{”log10”, [](double x) -> double { return log(x)/log(10); }},
{”cos”, static_cast<ArithFun*>(cos)},
{”sin”, static_cast<ArithFun*>(sin)},
{”arccos”, static_cast<ArithFun*>(acos)},
{”arcsin”, static_cast<ArithFun*>(asin)}
};

int main() {
while (true) {
string fun;
double val;
cout << ”> ”;
if (!(cin >> fun >> val))
break;

try {
cout << ([Link](fun))(val) << endl;
} catch (const out_of_range&) {
cout << ”Unknown function: ” << fun << endl;
}

Seite 11 von 12
Skript 06 Programmieren II für Studierende der Mathematik — WiSe 2025/26

}
}

> exp 1
2.71828
> log10 100
2
> arccos -1
3.14159
> sinh 0.25
Unknown function: sinh
>

Seite 12 von 12
Skript 07 Programmieren II für Studierende der Mathematik — WiSe 2025/26

This space intentionally left blank

Seite 1 von 1
Skript 08 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Zeiger mit impliziter Speicherfreigabe (unique_ptr, <memory>)

Objekte der Klasse unique_ptr<𝑇 >, für 𝑇 entweder ein beliebiger Typ oder ein Array-Typ der Form 𝑇 ′ []
(in diesem Fall ist das relevante template spezialisiert), enthalten intern einen Zeiger. Der Destruktor der
Klasse unique_ptr<𝑇 > ruft den delete-Operator auf den enthaltenen Zeiger auf. Der Konstruktor der Klasse
unique_ptr<𝑇 > akzeptiert einen Parameter vom Typ Zeiger auf 𝑇 bzw. 𝑇 falls 𝑇 ein Array-Typ, also der Form
𝑇 ′ [], ist. Der Konstruktor ist als explicit markiert, nimmt also nicht Teil an der impliziten Typkonvertierung.
Beispiel. Das Beispielprogramm weist mit dem new-Operator Speicher für einen Vektor von Werten
vom Typ int der Länge 1 000 auf dem heap zu. Der erzeugte Zeiger wird an den Konstruktor von
unique_ptr<int[]> übergeben. Das für C-Arrays spezialisierte Template von unique_ptr erlaubt
den Zugriff auf Array-Element mit dem subscript-Operator. Sobald die Variable v ihren syntaktischen
Gültigkeitsbereich verlässt (am Ende der main-Funktion) wird automatisch der Destrkutor auf das in v
enthaltene Objekt aufgerufen. Bei Ausführung des Destruktors wird der delete-Operator auf den enthal-
tenen Zeiger angewandt um den mit new zugewiesenen Speicher wieder freizugeben. Das Programm hat
kein Speicherleck.

unique_ptr_demo.cpp

#include <memory>

using namespace std;

int main() {
unique_ptr<int[]> v{new int[1000]};

for (int i = 0; i < 1000; i++)


v[i] = i;
}

Assertions (<cassert>)

Für einen Ausdruck 𝑏 vom Typ bool bricht das Präprozessor-Makro assert((𝑏 )) die Ausführung des Pro-
grammes sofort mit einer Fehlermeldung ab, wenn 𝑏 zu false auswertet. Wird an den compiler der Kom-
mandozeilenparameter -DNDEBUG bei Übersetzung des Programms übergeben, werden alle Vorkommnisse von
assert(. . .) durch i.W. Leerzeichen ersetzt, sodass sie im übersetzten Programm keinerlei Auswirkung haben.

Beispiel. Wir übersetzen ein einfaches Testprogramm mit einer assertion zu Beginn einmal ohne und einmal
mit dem Kommandozeilenparameter -DNDEBUG. Bei Übersetzung mit -DNDEBUG hat die assertion keinerlei
Auswirkung bei Ausführung des Programms.

[Link]

#include <iostream>
#include <cassert>

using namespace std;

int main() {
assert((1 == 2));
cout << ”output” << endl;
}

Seite 1 von 2
Skript 08 Programmieren II für Studierende der Mathematik — WiSe 2025/26

g++ [Link]; ./[Link]

assert: _copy/[Link]: int main(): Assertion `(1 == 2)' failed.

g++ -DNDEBUG [Link]; ./[Link]

output

Seite 2 von 2
Programmieren II für Studierende der Mathematik WiSe 2025/26

Organisatorisches

Organisatorisches

Website [Link]/course/W25/MI/Pro2
Zielgruppe Studierende des BSc (Wirtschafts-) Mathematik, ca. 5. Semester, 3 ECTS
Auch 6 ECTS für Studierende des BSc Mathematik PO2011 & PO2015
6 ECTS Aufgaben/Sektionen markiert mit *
Veranstaltung 2-Stündige Vorlesung alle zwei Wochen, Stoff für 3 ECTS
2-Stündige Übung abwechselnd zur Vorlesung und wöchentlich zu
weiterem Termin
Übung Bearbeitung des zweiwöchentlichen Übungsblattes im stud. PC-Pool
Prüfung 3 ECTS Schriftliche Klausur
Vstl. 1. oder 2. Semesterferien-Woche
Separate Anmeldung in Uni2work und lsf
Bescheide bzgl. Nachteilsausgleich bis spätestens 4 Wochen
vor Ende der Vorlesungszeit jedoch so bald wie möglich
6 ECTS Mündliche Prüfung
⇒ E-Mail mit Nachweis an mich, dass Studienordnung
studiert wird, die Prüfung für 6 ECTS vorsieht 1/2

Vorlesungs-Website

[Link]

2/2

Seite 1 von 1
Programmieren II für Studierende der Mathematik WiSe 2025/26

Inhalt

Inhalt

Zweiter Teil einer gründlichen Einführung in C++


Fokus auch auf Objektorientierung

1/1

Seite 1 von 1
Programmieren II für Studierende der Mathematik WiSe 2025/26

Operatorüberladung

Überladung von Operatoren

• Für ◦ beliebiger∗ Operator:


𝑥 ◦ 𝑦 wird übersetzt zu Aufruf von operator◦(𝑥 , 𝑦 )
• Unäre Operatoren analog
• operator◦ darf auch Methode sein; linkes Argument von Operator ist dann Objekt
auf dem Methode aufgerufen wird
• Vorrang und Syntax wie eingebaute Operatoren

∗ mit wenigen Ausnahmen 1/2

Beispiel: Komplexe Zahlen


operators_demo.cpp }
};
#include <iostream>
int main()
using namespace std; {
Complex z1, z2;
class Complex {
private: cout << ”z1 z2: ”;
double x, y; cin >> z1 >> z2;

public: cout << ”z1+z2 = ” << z1+z2 << endl;


Complex (double Re = 0, double Im = 0) : x(Re), y(Im) {}
return 0;
friend Complex operator+ (Complex z1, Complex z2) { }
return Complex{ z1.x+z2.x, z1.y+z2.y };
}
z1 z2: (1,2) (3,4)
friend ostream& operator<< (ostream& stream, Complex z) { z1+z2 = (4,6)
return stream << ”(” << z.x << ”,” << z.y << ”)”;
}

friend istream& operator>> (istream& stream, Complex& z) {


char c1, c2, c3;
double x, y;

stream >> c1 >> x >> c2 >> y >> c3;


if (c1 != '(' || c2 != ',' || c3 != ')')
[Link](ios::failbit);

z = Complex{x, y};

return stream;
2/2

Seite 1 von 1
Programmieren II für Studierende der Mathematik WiSe 2025/26

Klassenkomponenten

Klassenkomponenten (Wiederholung)

• Attribute
• Methoden und statische Komponentenfunktionen
• Konstruktoren
• Destruktoren
• Vereinb. befreundete Funktionen

Komponente 𝑘, Objekt 𝑜 ⇒ i.d.R. Zugriff durch 𝑜 .𝑘, Aufruf durch 𝑜 .𝑘 (. . .)

1 / 32

Gültigkeitsbereich von Namen von Klassenkomponenten

• Gesamte Vereinbarung der Klasse selbst


• Alle Klassenkomponenten
• Außerhalb davon: 𝐶 ::𝑘

Reihenfolge von Vereinbarungen von Methoden nicht relevant, Attribute i.A. schon

2 / 32

Seite 1 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Nachgestellte Definition von Klassenkomponenten


post_def.cpp Complex z0, z1{4.0}, z2{1.0, 2.0}, z3 = conj(z2);

#include <iostream> cout << ”Re z0 = ” << [Link]()


<< ” Im z0 = ” << [Link]() << endl;
using namespace std; cout << ”Re z1 = ” << [Link]()
<< ” Im z1 = ” << [Link]() << endl;
class Complex { cout << ”Re z2 = ” << [Link]()
private: << ” Im z2 = ” << [Link]() << endl;
double re, im; cout << ”Re z3 = ” << [Link]()
<< ” Im z3 = ” << [Link]() << endl;
public:
Complex(double=0, double=0); return 0;
}
double real();
double imag();
Re z0 = 0 Im z0 = 0
friend Complex conj(Complex); Re z1 = 4 Im z1 = 0
}; Re z2 = 1 Im z2 = 2
Re z3 = 1 Im z3 = -2
Complex::Complex(double re_, double im_)
: re(re_), im(im_) {}

double Complex::real() { return re; }


double Complex::imag() { return im; }

Complex conj(Complex z) {
[Link] = -[Link];

return z;
}

int main() {
3 / 32

Gültigkeitsbereich von Namen von Klassen

• Ab Vereinbarung bis Ende der Übersetzungseinheit


• Reihenfolge von Klassen i.A. wichtig

4 / 32

Seite 2 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Nachgestellte Definition von Klassenkomponenten (fehlerhaft)

post_def_f.cpp post_def_f.cpp:1:1: error: ‘Complex’ does not name a type


1 | Complex::Complex(double re_, double im_)
Complex::Complex(double re_, double im_) | ^~~~~~~
: re(re_), im(im_) {}

class Complex {
private:
double re, im;

public:
Complex(double=0, double=0);
};

5 / 32

this

Methode zum Quadrieren


class Complex {
• this ist Zeiger, also Typ 𝐶 * Complex quadriere() {
• quadriere hat Seiteneffekt double re2 = re*re - im*im,
im2 = 2*re*im;
• quadriere gibt Kopie von neuem
re = re2; im = im2;
Zustand zurück return *this;
}
};

6 / 32

Seite 3 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Verwendung von this


return_this.cpp
return in;
#include <iostream> }
};
using namespace std;
int main() {
class Complex { Complex z;
private: cout << ”z: ”; cin >> z;
double re, im;
cout << ”z davor: ” << z << endl;
public: cout << ”z quadriert: ” << [Link]() << endl;
Complex (double re_=0, double im_=0) : re(re_), im(im_) {} cout << ”z danach: ” << z << endl;

Complex quadriere() { return 0;


double re2 = re*re - im*im, }
im2 = 2*re*im;
re = re2; im = im2;
return *this; z: (2.0, 4)
} z davor: (2,4)
z quadriert: (-12,16)
friend ostream& operator<<(ostream& out, Complex z) { z danach: (-12,16)
return out << ”(” << [Link] << ”,” << [Link] << ”)”;
}
friend istream& operator>>(istream& in, Complex& z) {
char c1, c2, c3;
double re_, im_;

in >> c1 >> re_ >> c2 >> im_ >> c3;


if (c1 != '(' || c2 != ',' || c3 != ')')
[Link](ios::failbit);

z = Complex(re_, im_);
7 / 32

Verwendung von this für Attributzugriff


attrs_this.cpp }

#include <iostream>
#include <cmath> Re z = 1 Im z = 2 |z| = 2.23607

using namespace std;

class Complex {
private:
double re, im;

public:
Complex (double re_=0, double im_=0) : re(re_), im(im_) {}

double real() { return (*this).re; }


double imag() { return this->im; }

double abs() {
return sqrt( (*this).real()*(*this).real() +
↩→ (this->imag())*(this->imag()) );
}
};

int main() {
Complex z{1.0, 2.0};

cout << ”Re z = ” << [Link]()


<< ” Im z = ” << [Link]()
<< ” |z| = ” << [Link]()
<< endl;

return 0;
8 / 32

Seite 4 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Zugriffsattribute

Klassenkomponenten können sein:


• private
• public

private nicht sichtbar außerhalb Klassenvereinbarung/Klassenkomponenten


private ist Voreinstellung

9 / 32

Fehlermeldung bei Verstoß gegen Zugriffsattribute

private_f.cpp private_f.cpp: In function ‘int main()’:


private_f.cpp:16:13: error: ‘double Complex::re’ is private
#include <iostream> ↩→ within this context
16 | cout << [Link] << endl;
using namespace std; | ^~
private_f.cpp:7:12: note: declared private here
class Complex { 7 | double re, im;
private: | ^~
double re, im;

public:
Complex (double re_=0, double im_=0) : re(re_), im(im_) {}
};

int main() {
Complex z{1, 2};

cout << [Link] << endl;

return 0;
}

10 / 32

Seite 5 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Konstante Methoden

• Dürfen Attribute nicht verändern Vereinbarung einer konst. Methode


• Auf konstante Objekte können nur class Complex {
konstante Methoden angewandt double real() const {
werden return re;
}
• this vom Typ const 𝐶 * };

11 / 32

Verwendung von konstanten Methoden


const_methods.cpp Re z1 = 1 Im z1 = 2
Re z2 = 2 Im z2 = 3
#include <iostream>

using namespace std;

class Complex {
private:
double re, im;

public:
Complex (double re_=0, double im_=0) : re(re_), im(im_) {}

double real() const {


return re;
}
double imag() const {
return im;
}
};

int main() {
Complex z1{1, 2};
const Complex z2{2, 3};

cout << ”Re z1 = ” << [Link]()


<< ” Im z1 = ” << [Link]() << endl;
cout << ”Re z2 = ” << [Link]()
<< ” Im z2 = ” << [Link]() << endl;

return 0;
}
12 / 32

Seite 6 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Verwendung von konstanten Methoden (fehlerhaft)


const_methods_f.cpp }

#include <iostream>
const_methods_f.cpp: In function ‘int main()’:
using namespace std; const_methods_f.cpp:27:35: error: passing ‘const Complex’ as
↩→ ‘this’ argument discards qualifiers [-fpermissive]
class Complex { 27 | cout << ”Re z2 = ” << [Link]()
private: | ~~~~~~~^~
double re, im; const_methods_f.cpp:12:12: note: in call to ‘double
↩→ Complex::real()’
public: 12 | double real() {
Complex (double re_=0, double im_=0) : re(re_), im(im_) {} | ^~~~
const_methods_f.cpp:28:35: error: passing ‘const Complex’ as
double real() { ↩→ ‘this’ argument discards qualifiers [-fpermissive]
re *= 2; 28 | << ” Im z2 = ” << [Link]() << endl;
return re; | ~~~~~~~^~
} const_methods_f.cpp:16:12: note: in call to ‘double
double imag() { ↩→ Complex::imag()’
return im; 16 | double imag() {
} | ^~~~
};

int main() {
Complex z1{1, 2};
const Complex z2{2, 3};

cout << ”Re z1 = ” << [Link]()


<< ” Im z1 = ” << [Link]() << endl;
cout << ”Re z2 = ” << [Link]()
<< ” Im z2 = ” << [Link]() << endl;

return 0;
13 / 32

Konstruktoren

• Syntaktisch wie Methoden


• Name ist identisch mit Name der
Klasse Konstruktor
• Kein Ergebnistyp, auch nicht void class Complex {
• Können nicht const sein Complex (double x, double y) {
re = x;
• Mehrere Konstruktoren pro Klasse
im = y;
möglich }
• Standardkonstruktor und };
Kopierkonstruktor im Zweifel
automatisch erzeugt

14 / 32

Seite 7 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Konstruktor-Initialisierungslisten

• Gibt an wie Attribute initialisert


werden sollen Konstruktor m. Initialisierungsliste
• Abgetrennt mit : direkt nach class Complex {
Parameterliste Complex (double x, double y)
: re(x), im(y) {}
• Nicht erwähnte Attribute ⇒ wie };
Standardkonstruktor

15 / 32

Beispiele für Konstruktoren


polynom_eval.cpp cout << ”a[0] ... a[” << n << ”]: ”;
for (int i = 0; i <= n; i++) cin >> v[i];
#include <iostream>
#include <vector> double x;
cout << ”x: ”; cin >> x;
using namespace std;
Polynom p{v};
class Polynom { cout << ”p(x) = ” << [Link](x) << endl;
private:
vector<double> coeff; return 0;
}
public:
Polynom() {}
Polynom(int n) : coeff(n + 1) { coeff[n] = 1; } n: 3
Polynom(const vector<double>& v) : coeff(v) {} a[0] ... a[3]: 0 1 2 3
x: 1.1
double eval(double x) { p(x) = 7.513
double s = 0, xpot = 1;
for (vector<double>::size_type i = 0; i < [Link]();
↩→ i++) { n: 0
s += coeff[i] * xpot; a[0] ... a[0]: 7
xpot *= x; x: 3
} p(x) = 7
return s;
}
};

int main() {
int n;
cout << ”n: ”; cin >> n;

vector<double> v(n + 1);


16 / 32

Seite 8 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Einschub: Initialisierung von Vektoren

Initialisierung eines Vektors auf gegebe-


ne Länge
vector<double> v(n + 1);
• Init. mit (𝑛 ) ⇒ Länge 𝑛, Elemente
mit Standardkonstruktor
Initialisierung eines Vektors mit gegebe-
• Init. mit {. . .} ⇒ Inhalt
nem Inhalt
vector<double> v1{7};
vector<double> v2{1, 2, 3, 4};

17 / 32

Standardkonstruktor

• Konstruktor ohne Parameter


• Automatisch erzeugt gdw. keine Konstruktoren vereinbart

18 / 32

Seite 9 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiele für Standardkonstruktoren


default_constr.cpp for (int i = 0; i < len; i++) ap[i] = x;
}
#include <vector> };

using namespace std; int main() {


Complex z1, z2{2}, z3{3, 4};
class Complex { const Complex i{0, 1};
private:
double re, im; Polynom p, q{5};
const Polynom cp, cq{5};
public:
Complex(double re_=0, double im_=0) : re(re_), im(im_) {} Vektor v, w{3};
}; const Vektor cv, cw{3};

class Polynom { return 0;


private: }
vector<double> a;

public:
Polynom() {}
Polynom(int n) : a(n + 1) { a[n] = 1; }
};

class Vektor {
private:
double* ap;
int len;

public:
Vektor() : ap(0), len(0) {}
Vektor(int n, double x=0) : len(n) {
ap = new double[n];
19 / 32

Automatische Erzeugung von Konstruktoren

default_constr_f.cpp default_constr_f.cpp: In function ‘int main()’:


default_constr_f.cpp:14:11: error: no matching function for
#include <vector> ↩→ call to ‘Complex::Complex()’
14 | Complex z1, z3{3, 4};
using namespace std; | ^~
default_constr_f.cpp:10:5: note: candidate:
class Complex { ↩→ ‘Complex::Complex(double, double)’
private: 10 | Complex(double re_, double im_) : re(re_), im(im_) {}
double re, im; | ^~~~~~~
default_constr_f.cpp:10:5: note: candidate expects 2
public: ↩→ arguments, 0 provided
Complex(double re_, double im_) : re(re_), im(im_) {} default_constr_f.cpp:5:7: note: candidate: ‘constexpr
}; ↩→ Complex::Complex(const Complex&)’
5 | class Complex {
int main() { | ^~~~~~~
Complex z1, z3{3, 4}; default_constr_f.cpp:5:7: note: candidate expects 1 argument,
const Complex i{0, 1}; ↩→ 0 provided
default_constr_f.cpp:5:7: note: candidate: ‘constexpr
return 0; ↩→ Complex::Complex(Complex&&)’
} default_constr_f.cpp:5:7: note: candidate expects 1 argument,
↩→ 0 provided

20 / 32

Seite 10 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Kopierkonstruktor

• Konstruktor der Referenz auf anderes Objekt der Klasse als einzigen Parameter
nimmt
• Parameter vom Typ
• 𝐶&
• const 𝐶 &
• Nicht 𝐶 oder const 𝐶
• Automatisch erzeugt, falls nicht vereinbart
• Implizit verwendet bei Parameterübergabe und Rückgabe aus Funktionen
• Keine Verwendung bei Zuweisungen (=)

21 / 32

Beispiele für Kopierkonstruktoren


copy_constr.cpp ap[i] = [Link][i];
}
#include <vector> };

using namespace std; int main() {


Polynom p, q{5}, r(q);
class Polynom { const Polynom cp, cq{5}, cr(q);
private:
vector<double> a; Vektor v, w{3}, u(w);
const Vektor cv, cw{3}, cu(w);
public:
Polynom() {} return 0;
Polynom(int n) : a(n + 1) { a[n] = 1; } }

Polynom(const Polynom& p) : a(p.a) {}


};

class Vektor {
private:
double* ap;
int len;

public:
Vektor() : ap(0), len(0) {}
Vektor(int n, double x=0) : len(n) {
ap = new double[n];
for (int i = 0; i < len; i++) ap[i] = x;
}

Vektor(const Vektor& a) : len([Link]) {


ap = new double[len];
for (int i = 0; i < len; i++)
22 / 32

Seite 11 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Destruktor

• Syntaktisch wie eine Methode, Name


~𝐶
• Kein Ergebnistyp, auch nicht void
• Automatisch erzeugt, falls nicht
vereinbart Destruktor
• Explizite Destruktoraufrufe möglich, ~Vektor() {
delete-Operator delete[] ap;
}
• Implizite Destruktoraufrufe die Norm
• Nach Auswertung des Ausdrucks
bei temporären Objekten
• Ende des Gültigkeitsbereichs bei
Variablen

23 / 32

Speicherleck
vector_leak_demo.cpp ==…== Memcheck, a memory error detector
==…== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward
#include <iostream> ↩→ et al.
==…== Using Valgrind-3.23.0 and LibVEX; rerun with -h for
using namespace std; ↩→ copyright info
==…== Command: vector_leak_demo
class Vektor { ==…==
private: ==…==
double* ap; ==…== HEAP SUMMARY:
int len; ==…== in use at exit: 8 bytes in 1 blocks
==…== total heap usage: 3 allocs, 2 frees, 74,760 bytes
public: ↩→ allocated
Vektor(int n, double x = 0): len(n) { ==…==
ap = new double[n]; ==…== 8 bytes in 1 blocks are definitely lost in loss record 1
for (int i = 0; i < n; i++) ap[i] = x; ↩→ of 1
} ==…== at 0x48487AB: operator new[](unsigned long) (in …)
==…== by 0x40145A: Vektor::Vektor(int, double)
double sum() { ↩→ (vector_leak_demo.cpp:12)
double s = 0; ==…== by 0x4011CA: main (vector_leak_demo.cpp:24)
for (int i = 0; i < len; i++) s += ap[i]; ==…==
return s; ==…== LEAK SUMMARY:
} ==…== definitely lost: 8 bytes in 1 blocks
}; ==…== indirectly lost: 0 bytes in 0 blocks
==…== possibly lost: 0 bytes in 0 blocks
int main() { ==…== still reachable: 0 bytes in 0 blocks
Vektor a(1); ==…== suppressed: 0 bytes in 0 blocks
cout << [Link]() << endl; ==…==
} ==…== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0
↩→ from 0)

24 / 32

Seite 12 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel für Destruktor


vector_destructor.cpp ==…== Memcheck, a memory error detector
==…== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward
#include <iostream> ↩→ et al.
==…== Using Valgrind-3.23.0 and LibVEX; rerun with -h for
using namespace std; ↩→ copyright info
==…== Command: vector_destructor
class Vektor { ==…==
private: ==…==
double* ap; ==…== HEAP SUMMARY:
int len; ==…== in use at exit: 0 bytes in 0 blocks
==…== total heap usage: 3 allocs, 3 frees, 74,760 bytes
public: ↩→ allocated
Vektor(int n, double x = 0): len(n) { ==…==
ap = new double[n]; ==…== All heap blocks were freed -- no leaks are possible
for (int i = 0; i < n; i++) ap[i] = x; ==…==
} ==…== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0
↩→ from 0)
~Vektor() {
delete[] ap;
}

double sum() {
double s = 0;
for (int i = 0; i < len; i++) s += ap[i];
return s;
}
};

int main() {
Vektor a(1);
cout << [Link]() << endl;
}
25 / 32

Zuweisungsoperator

• z1=z2 ⇒ [Link]=(z2)
• Nur als Methode
Überladener Zuweisungsoperator
• Automatisch erzeugt, falls nicht
class Complex {
vereinbart
Complex& operator=(Complex& z) {
• Wird nicht in Initialisierung re = [Link]; im = [Link];
verwendet, daher Zielobjekt immer return *this;
bereits initialisert }
};
• U.U. muss Speicher bei Zuweisung
freigegeben werden

26 / 32

Seite 13 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Zuweisungsoperator für Klasse Vektor


vector_assign.cpp friend ostream& operator<<(ostream& aus, const Vektor& v) {
for (int i = 0; i < [Link]; i++) {
#include <iostream> if (i != 0) aus << ” ”;
aus << [Link][i];
using namespace std; } return aus;
}
class Vektor {
private: double& at(int i) { return ap[i]; }
double* ap; };
int len;
int main() {
public: int n; cout << ”n: ”; cin >> n;
Vektor(int n = 0, double x = 0) : len(n) { Vektor a(n); cout << ”a[0] ... a[” << (n - 1) << ”]: ”;
ap = new double[n]; for (int i = 0; i < n; i++) cin >> [Link](i);
for (int i = 0; i < n; i++) ap[i] = x;
} Vektor b, c{a};
Vektor(const Vektor& a) : len([Link]) { b = a;
ap = new double[len];
for (int i = 0; i < len; i++) [Link](0) = [Link](n - 1) = 7;
ap[i] = [Link][i];
} cout << ”a: ” << a << ”, ” << ”b: ” << b << ”, ” << ”c: ” << c
~Vektor() { delete[] ap; } ↩→ << endl;
}
Vektor& operator=(const Vektor& b) {
delete[] ap;
len = [Link]; n: 4
ap = new double[len]; a[0] ... a[3]: 1 2 3 4
for (int i = 0; i < len; i++) a: 7 2 3 4, b: 1 2 3 7, c: 1 2 3 4
ap[i] = [Link][i];
return *this;
}

27 / 32

Bedeutung von =: Kopierkonstruktor oder Zuweisungsoperator

Complex a{4.0, 5.0} Konstruktor


Complex b{a} Kopierkonstruktor
Complex c = a
Complex d = Complex{1.0, 2.0}
Complex e, f Standardkonstruktor
e = c Zuweisungsoperator
f = Complex{3.0, 5.0}

28 / 32

Seite 14 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Statische Datenkomponenten

Kreis
• Deklaration innerhalb der Klasse class Kreis {
• Definition außerhalb der Klasse static const double pi;

• Zugriff/Definition mit :: double r;


• Besser als globale Variablen };

const double Kreis::pi = 3.14;

29 / 32

Initialisierung statischer Datenkomponenten als constexpr

kreis_constexpr.cpp

#include <cmath>
• constexpr wird zur Kompilierzeit
class Kreis {
ausgewertet
static constexpr double pi
• Definition statischer
= acos(-1);
Datenkomponenten innerhalb von
Klasse nur als constexpr double r;
};

int main() {}

30 / 32

Seite 15 von 16
Klassenkomponenten Programmieren II für Studierende der Mathematik — WiSe 2025/26

Statische Komponentenfunktionen

• Ähnlich zu befreundeten Funktionen


• static statt friend Statische Komponentenfunktion
• Kein this
static double flaeche(Kreis k) {
• Aufruf innerhalb der Klasse ohne return pi*k.r*k.r;
Qualifikation }

• Aufruf außerhalb der Klasse mit ::

31 / 32

Beispiel zur Verwendung einer statischen Komponentenfunktion


circle_static_this_demo.cpp << ”Zylindervol.: ” << [Link](2) << endl;

#include <iostream> return 0;


#include <cmath> }

using namespace std;


Radius: 0.5
class Kreis { Umfang: 3.14159
public: Flaeche: 0.785398
static constexpr double pi = acos(-1.0); Zylindervol.: 1.5708

private:
double r;

public:
Kreis(double r_ = 1): r(r_) {}
double radius() { return r; }
double umfang() { return 2*r*pi; }

static double flaeche(Kreis k) {


return pi*k.r*k.r;
}
double zylinder(double h) {
return h * flaeche(*this);
}
};

int main() {
Kreis k{0.5};
cout << ”Radius: ” << [Link]() << endl
<< ”Umfang: ” << [Link]() << endl
<< ”Flaeche: ” << Kreis::flaeche(k) << endl
32 / 32

Seite 16 von 16
Programmieren II für Studierende der Mathematik WiSe 2025/26

Abgeleitete Klassen*

Abgeleitete Klassen

⟨Basis⟩ → “:” ⟨BasisSpec⟩ {“,” ⟨BasisSpec⟩}


⟨BasisSpec⟩ → [⟨AccessSpec⟩] [“virtual”] ⟨ClassName⟩ Einfache Ableitung
⟨AccessSpec⟩ → “private” | “protected” | “public”
class A {
• Einschub von ⟨Basis⟩ nach Name in Vereinbarung
public:
einer Klasse int x;
• Vereinbarte Klasse ist abgeleitet von ihren };
Basis-Klassen class B : public A {};

• Abgeleitete Klasse erbt Komponenten von ihren int main() {


Basis-Klassen B b;
Ausnahmen: Konstruktor, Destruktor, operator= b.x = 7;
• Geerbte Komponenten höchstens so sichtbar wie cout << b.x << endl;
}
⟨AccessSpec⟩

1 / 20

Zugriffsattribut protected

• Zusätzliches Zugriffsattribut wie private und public


• Verwendbar in ⟨BasisSpec⟩ und Vereinbarung von Klassenkomponenten
• Verhalten i.W. wie private
• Erlaubt Zugriff in Komponenten abgeleiteter Klassen

2 / 20

Seite 1 von 10
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Konstruktoren für abgeleitete Klassen

Initialisierung bei abgeleiteten Klassen


• Vererbte Komponenten werden mit
class A { public:
Konstruktor der jeweiligen int x;
Basisklasse initialisiert A() : x(7) {}
• Ausführung Konstruktoren aller A(int x_) : x(x_) {} };
Basisklassen vor Initialiserungen class B : public A { public:
anderer Komponenten (in B() {}
B(int x_) : A(x_ / 2) {} };
Reihenfolge der ⟨BasisSpec⟩)
int main() {
• Konstruktoren der Basisklassen
B b1{}; cout << b1.x << ” ”;
können ausgewählt B b2{7}; cout << b2.x << endl;
werden/Parameter erhalten durch }
Erwähnung in Initialisierungsliste
7 3

3 / 20

Überschattung von Klassenkomponenten

Überschattung in abgeleiteten Klassen


class A { public: int x; };
• Namen von Komponenten in class B : public A {
abgeleiteten Klassen überschatten public: int x;
jene der Basisklassen };
• Objekte abgeleiteter Klassen
enthalten Unterobjekte der int main() {
B b;
Basisklassen mit allen Komponenten
b.x = 7; b.A::x = 3;
• Zugriff auf überschattete Namen mit
cout << b.x << ” ” << b.A::x
scope resolution operator (::) << endl;
angewandt auf Komponentennamen }

7 3

4 / 20

Seite 2 von 10
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Einschub: Visualisierung von Objekten abgeleiteter Klassen als DAG

A A

Mehrfach abgeleitete Klasse


B1 B2
class A { public: int x; };
class B1 : public A {};
C
class B2 : public A {};
class C : public B1, public B2 {};
Kanten entsprechen ist-Unterobjekt-von
Beziehung

5 / 20

Eindeutigkeit von Namen bei mehrfacher Ableitung

Verhalten mehrfach abgeleiteter Klasse


• Objekte abgeleiteter Klasse 𝐶 mit class A { public: int x; };
class B1 : public A {};
mehrfachem Vorkommen gleicher
class B2 : public A {};
Basisklasse 𝐴 in Ableitungs-DAG
class C : public B1, public B2 {};
enthalten mehrere Subobjekte von 𝐴
• C.x hier nicht zulässig int main() {
• Kürzester Pfad zu Komponente C c;
gleichen Namens wird vorgezogen c.B1::x = 3; c.B2::x = 7;
• Bei Gleichstand: Kompilerfehler cout << c.B1::x << ” ” << c.B2::x
• Sonst: Qualifikation mit :: bis Pfad << endl;
}
eindeutig

3 7

6 / 20

Seite 3 von 10
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Implizite Typkonvertierung von Objektzeigern/-referenzen

Implizite Typumwandlung abgeleiteter


Klassen
Sei 𝐴 Basisklasse von 𝐵.
class A { public: int x; };
• Implizite Typumwandlung möglich:
class B : public A {};
„Zeiger auf 𝐵“ → „Zeiger auf 𝐴“,
analog für Referenzen int main() {
• A priori keine implizite B b;
Typumwandlung zwischen Objekten b.x = 7;
A& a = b;
– Indirektion (Zeiger/Referenz) ist
cout << a.x << endl;
notwendig }

7 / 20

Motivation: Virtuelle Basisklassen

Semantische Bedeutung mehrere Vorkommnisse von A?


Mehrere unabhängige Weisen in denen C Anforderungen von A erfüllt,
A A
Verhalten von A implementiert.
Analogie: Motorsegler ist sowohl Segelschiff wie auch Motorschiff.
B1 B2

Oft jedoch nur eine Implementierung von A. C


Analogie: Motorsegler ist trotzdem nur auf eine Weise ein Schiff.

8 / 20

Seite 4 von 10
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Virtuelle Basisklassen

Virtuell abgeleitete Klasse


• Qualifikation virtual in
⟨BasisSpec⟩ class A { public: int x; };
• Für Objekte einer Klasse 𝐶, class B1 : public virtual A {};
A class B2 : public virtual A {};
alle Klassen 𝐴 mit letzter
class C
Kante virtual im Pfad von 𝐶
B1 B2 : public B1, public B2 {};
zu 𝐴: nur eine Kopie von 𝐴 in
Objekten von 𝐶 int main() {
• Wenn zusätzlich Pfade ohne C
C c;
virtual von 𝐶 zu 𝐴: c.x = 7;
zusätzliche Kopien cout << c.x << endl;
}

9 / 20

Exkurs: Implementierung virtueller Basisklassen

• Implementierungsdetails im Standard nicht vorgeschrieben


• Typumwandlung „Zeiger auf C“ → „Zeiger auf A“ soll möglichst geringe Kosten zur
Laufzeit verursachen (Rechenzeit, Speicher, …)
• Daher: Objekte von C enthalten ihre Subobjekte von A in exakt gleicher Form
(Speicherlayout) wie auch Objekte von A angeordnet sind
⇒ Typumwandlung allein durch Addition einer Konstante (Beginn des Subobjekts
im Speicher des Objekts)
• Problem: Wenn Subobjekte von B1 und B2 beide eigentlich A enthalten sollen, C
insgesamt aber nur ein A, dann Finden eines Speicherlayouts mit dieser Eigenschaft
unmöglich
• Daher: (Sub-)Objekte von B1, B2 enthalten Zahl (offset) wo Subobjekt von A jeweils
startet.
Objekte von C passen offsets in ihren Subobjekten an sodass sie zeigen auf selbes
Subobjekt von A
• Nachschlagen von Zeigern im Objekt selbst, statt anhand des Typs: virtual dispatch
10 / 20

Seite 5 von 10
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Virtuelle Methoden

Polymorphe Objekte
• Methoden können mit virtual class A { public:
qualifiziert werden virtual char f() { return 'A'; }
};
• Auswahl unter überschattenden
class B : public A { public:
Methoden nicht anhand von Typ, virtual char f() { return 'B'; }
sondern letztem auf Objekt };
angewandtem Konstruktor int main() {
(Funktionszeiger für jede virtuelle B b; A& ap = b;
Methode Teil des Objekts, werden im cout << b.f() << ap.f() << endl;
}
Konstruktor implizit gesetzt)

BB

11 / 20

override, final

override und final


class A { public:
virtual char f() { return 'A'; }
• Methode mit selbem Namen und };
Parametertypliste in Basisklasse class B : public A { public:
(transitiv) virtuell ⇒ Methode virtual char f() override final
{ return 'B'; }
implizit virtuell
};
• override: muss Methode aus
int main() {
Basisklasse überschatten B b; A& ap = b;
• final: darf nicht überschattet werden cout << b.f() << ap.f() << endl;
}

BB

12 / 20

Seite 6 von 10
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Fehlerquelle object slicing: Kopierkonstruktor, Zuweisungsoperator

Referenz als Parameter (Kopierkons., Zuw'operator) → implizite Typkonvertierung

Konvertierung bei Zuweisungsoperator Konvertierung bei Kopierkonstruktor


class A { public: class A { public:
virtual char f() { return 'A'; } virtual char f() { return 'A'; }
}; };
class B : public A { public: class B : public A { public:
virtual char f() { return 'B'; } virtual char f() { return 'B'; }
}; };
int main() { int main() {
B b; A a; A& ap = a; ap = b; B b; A a = b;
cout << b.f() << ap.f() << endl; cout << b.f() << a.f() << endl;
} }

BA BA

13 / 20

Fehlerquelle: Zuweisungsoperator
violated_invariant_assignment.cpp }

#include <iostream>
7 =~ 3
using namespace std;

class A {
protected:
double x;

public:
A(double x_) : x(x_) {}
};

class B : public A {
private:
int approx_x;

public:
B(double x_) : A(x_), approx_x(x_) {}

friend ostream& operator<<(ostream& stream, const B& b) {


return stream << b.x << ” =~ ” << b.approx_x;
}
};

int main() {
B b1{3.0}, b2{7.0};
A& ap = b1;
ap = b2;

cout << b1 << endl;


14 / 20

Seite 7 von 10
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Rein virtuelle Methoden

Rein virtuelle Methode


• Spezielle Syntax: = 0 statt Körper der
Methode class A { public:
• Keine Objekte von Klassen die rein virtual char f() = 0;
};
virtuelle Methode nicht überschatten class B : public A { public:
(auch nicht Parameter-, Ergebnistyp virtual char f() { return 'B'; }
von Funktionen/Methoden) };
• Zeiger und Referenzen jedoch erlaubt int main() {
(auch als Parameter-, Ergebnistyp) B b; cout << b.f() << endl;
• Klasse mit min. einer rein virtuellen }

Methode: abstract base class (ABC)


B

15 / 20

Einschub: Vereinbarung von Klassenkomponenten als explicitly defaulted

Explizite Standardimplementierung

• Sonst implizite class A { public:


A() = default;
Standardimplementierung von
void f() { cout << ”A” << endl; }
Kopierkonstruktor, };
Zuweisungsoperator, Destruktor, …
kann explizit gefordert werden int main() {
• Spezielle syntax: = default statt A a{}; a.f();
Körper der Methode }

16 / 20

Seite 8 von 10
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Einschub: Vereinbarung von Klassenkomponenten als deleted

Gelöschter Kopierkonstruktor
class A {
A(const A&) = delete;
• Sonst implizite };
Standardimplementierung von
Kopierkonstruktor, int main() {
Zuweisungsoperator, Destruktor, … A a1{};
A a2{a1};
kann explizit ausgeschlossen werden
}
• Spezielle syntax: = delete statt
Körper der Methode

… error: use of deleted function
↩→ ‘A::A(const A&)’

17 / 20

Motivation: virtuelle Destruktoren


Speicherleck durch falschen Destruktor ==…== Memcheck, a memory error detector
==…== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward
class A {}; ↩→ et al.
class B : public A { public: ==…== Using Valgrind-3.23.0 and LibVEX; rerun with -h for
double* b; ↩→ copyright info
B() { b = new double[100]; } ==…== Command: destructor_leak_demo
~B() { delete b; } ==…==
==…== Mismatched new/delete size value: 1
void f() { ==…== at 0x484AC21: operator delete(void*, unsigned long)
for (int i = 0; i < 100; i++) ↩→ (in …)
cout << b[i] << endl; ==…== by 0x40130A: main (destructor_leak_demo.cpp:24)
} ==…== Address 0x5427080 is 0 bytes inside a block of size 8
}; ↩→ alloc'd
==…== at 0x4846FBB: operator new(unsigned long) (in …)
int main() { ==…== by 0x4011BC: main (destructor_leak_demo.cpp:18)
B* bp = new B{}; ==…==
for (int i = 0; i < 100; i++) ==…==
bp->b[i] = i; ==…== HEAP SUMMARY:
bp->f(); ==…== in use at exit: 800 bytes in 1 blocks
==…== total heap usage: 4 allocs, 3 frees, 78,632 bytes
A* ap = bp; ↩→ allocated
delete ap; ==…==
} ==…== 800 bytes in 1 blocks are definitely lost in loss record 1
↩→ of 1
==…== at 0x48487AB: operator new[](unsigned long) (in …)
==…== by 0x401354: B::B() (destructor_leak_demo.cpp:8)
==…== by 0x4011F2: main (destructor_leak_demo.cpp:18)
==…==
==…== LEAK SUMMARY:
==…== definitely lost: 800 bytes in 1 blocks
==…== indirectly lost: 0 bytes in 0 blocks
==…== possibly lost: 0 bytes in 0 blocks
18 / 20

==…== still reachable: 0 bytes in 0 blocks


==…== suppressed: 0 bytes in 0 blocks
==…==
==…== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0
↩→ from 0)
==…==
==…== 1 errors in context 1 of 2:
==…== Mismatched new/delete size value: 1
==…== at 0x484AC21: operator delete(void*, unsigned long)
↩→ (in …)
==…== Seite 9 von 10
by 0x40130A: main (destructor_leak_demo.cpp:24)
==…== Address 0x5427080 is 0 bytes inside a block of size 8
↩→ alloc'd
==…== at 0x4846FBB: operator new(unsigned long) (in …)
==…== by 0x4011BC: main (destructor_leak_demo.cpp:18)
Abgeleitete Klassen* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Virtuelle Destruktoren

Aufruf von Destruktor mit delete-Operator (oder auch implizit) wählt Destruktor nach
selben Regeln wie Methode.
⇒ Destruktoren können virtual sein

19 / 20

Beispiel: virtuelle Destruktoren

Kein Speicherleck durch virtuellen Destruktor ==…== Memcheck, a memory error detector
==…== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward
class A { public: virtual ~A() = default; }; ↩→ et al.
class B : public A { public: ==…== Using Valgrind-3.23.0 and LibVEX; rerun with -h for
double* b; ↩→ copyright info
B() { b = new double[100]; } ==…== Command: virtual_destructor_demo
~B() { delete[] b; } ==…==
==…==
void f() { ==…== HEAP SUMMARY:
for (int i = 0; i < 100; i++) ==…== in use at exit: 0 bytes in 0 blocks
cout << b[i] << endl; ==…== total heap usage: 4 allocs, 4 frees, 78,640 bytes
} ↩→ allocated
}; ==…==
==…== All heap blocks were freed -- no leaks are possible
int main() { ==…==
B* bp = new B{}; ==…== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0
for (int i = 0; i < 100; i++) ↩→ from 0)
bp->b[i] = i;
bp->f();

A* ap = bp;
delete ap;
}

20 / 20

Seite 10 von 10
Programmieren II für Studierende der Mathematik WiSe 2025/26

Templates*

Template Deklarationen

⟨Templ'Dekl'⟩ → “template” “<” [⟨Templ'Param'⟩ {“,” ⟨Templ'Param'⟩}] “>” ⟨Dekl'⟩


⟨Templ'Param'⟩ → ⟨Templ'Typ'Param⟩ | ⟨Param'Dekl'⟩
⟨Templ'Typ'Param'⟩ → (“class” | “typename”) [⟨Ident⟩] [“=” ⟨Typ⟩]
| “template” “<” [⟨Templ'Param'⟩ {“,” ⟨Templ'Param'⟩}] “>” “class” [⟨Ident⟩] [“=” ⟨Id'Expr'⟩]
• template deklariert parametrisierte, nicht-abgeschlossene Familie von Objekten
(Funktionen, Typaliase, Variablen, Klassen) ⟨Dekl’⟩
• Parameter der Familie entweder wie Funktionsparameter (Werte) ⟨Param’Dekl’⟩ oder
Typparameter ⟨Templ’Typ’Param’⟩
• Typparameter dürfen wiederum als templates mit Parametern gefordert werden
• Ansatz: wann immer Objekt aus Familie verwendet wird, setze passende Parameter
ein in neue Kopie der Vorlage (engl. template)

1 / 13

Einschub: Arrays in STL (array)

Verwendung array
array<double, 3> a1{3, 2, 1};

• Datentyp array<𝑇 , 𝑛 > für beliebigen sort([Link](), [Link]());


Typ 𝑇 und Ganzzahl 𝑛
• Dünner wrapper um C-Array der cout << ”a1[0..”
<< [Link]() - 1
Form 𝑇 [𝑛 ]
<< ”]:”;
• Vorteil: Methoden, Operatoren,
for (const double& x: a1)
Iteratoren analog zu vector cout << ” ” << x;
cout << endl;

a1[0..2]: 1 2 3

2 / 13

Seite 1 von 7
Templates* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Funktionstemplate p-Norm

array_pnorm.cpp 3.74166
6.0822
#include <iostream>
#include <array>
#include <cstddef>
#include <cmath>

using namespace std;

template<class T = double, unsigned int p = 2, size_t dim>


T pNorm(const array<T, dim>& v) {
T x = 0;
for (const T& y: v)
x += pow(y, p);
return pow(x, static_cast<T>(1) / p);
}

int main() {
array<double, 3> a1{1, 2, 3};
cout << pNorm<double, 2, 3>(a1) << endl;

array<double, 5> a2{5, 4, 3, 2, 1};


cout << pNorm<double, 3>(a2) << endl;
}

3 / 13

(Explizite) Instanziierung von Templates

• Templates spezifizieren Familie von


Objekten instantiate_array_demo.cpp
• Bevor konkretes Objekt aus der
Familie verwendet werden kann, #include <array>
muss es instanziiert werden —
using namespace std;
Argumente werden eingesetzt in
Deklaration template class std::array<double, 3>;
• Explizite Instanziierung möglich
(siehe Beispiel) int main() {
• Gewöhnlicher: implizite return 0;
Instanziierung bei erster Verwendung }

in jeder Übersetzungseinheit

4 / 13

Seite 2 von 7
Templates* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Template Spezialisierung

⟨Templ'Spec'⟩ → “template” “<” [⟨Templ'Param'⟩ {“,” ⟨Templ'Param'⟩}] “>” ⟨Dekl'⟩


• Mit ⟨Dekl’⟩ Definition für Objekt aus bereits bekanntem Template
• Zweck: effizientere Implementierung
• Wenn Parameter vorhanden → partielle Spezialisierung
• Partielle Spezialisierung nur für Klassen und Variablen

5 / 13

Beispiel: spezialisierte 2-Norm


array_2norm.cpp 3.74166
6.0822
#include <iostream>
#include <array>
#include <cstddef>
#include <cmath>

using namespace std;

template<class T = double, unsigned int p = 2, size_t dim>


T pNorm(const array<T, dim>& v) {
T x = 0;
for (const T& y: v)
x += pow(y, p);
return pow(x, static_cast<T>(1) / p);
}

template<>
double pNorm<double, 2, 3>(const array<double, 3>& v) {
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}

int main() {
array<double, 3> a1{1, 2, 3};
cout << pNorm<double, 2, 3>(a1) << endl;

array<double, 5> a2{5, 4, 3, 2, 1};


cout << pNorm<double, 3>(a2) << endl;
}

6 / 13

Seite 3 von 7
Templates* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Substitution Failure is Not An Error (SFINAE)

• Situation: template mit partieller Spezialisierung, Einsetzen von Argumenten in


Spezialisierung scheitert (Klassenkomponenten fehlen, eine Typkonvertierung ist
nicht möglich, …)
• Kein Kompilierfehler
• Stattdessen: Streichen dieser Spezialisierung als Kandidat, i.A. Wahl einer Anderen
• Gelegentlich: explizites Ausnutzen hiervon um Spezialisierungen einzuschränken
auf nur bestimmte Argumente

7 / 13

std::enable_if

sfinae_narrow_demo.cpp Implementierung enable_if

#include <type_traits> template<bool B, class T = void>


#include <iostream> class enable_if {};
#include <cmath>
template<class T>
using namespace std; class enable_if<true, T> { public: using type = T; };

class Approx { template<bool B, class T = void>


public: using enable_if_t = typename enable_if<B, T>::type;
int x;

template<class Integral,
enable_if_t<is_integral<Integral>::value, bool> = true>
Implementierung is_integral
Approx(Integral i): x(i) {}

template<class T>
template<class Floating,
class is_integral {};
enable_if_t<is_floating_point<Floating>::value, bool> =
↩→ true>
template<>
Approx(Floating d): x(round(d)) {}
class is_integral<int> {
};
public: static constexpr bool value = true;
};
int main() {
Approx a1{3}, a2{5.7};

cout << a1.x << ” ” << a2.x << endl;


} 3 6

8 / 13

Seite 4 von 7
Templates* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Ausblick: Constraints & Concepts (C++20)

Constraint Angabe eines (hinreichend konstanten) require-Ausdrucks vom Typ bool


bei Definition eines templates mit Schlüsselwort requires → Template
nur instanziierbar wenn require-Ausdruck true
Concept Definition eines benannten require-Ausdrucks als template mit
Schlüsselwort concept → So definiertes concept verwendbar als
require-Ausdruck bei folgenden templates

template<class T>
concept Integral = std::is_integral<T>::value;
template<class T>
concept SignedIntegral = Integral<T> && std::is_signed<T>::value;

template<class T> requires SignedIntegral<T>


void f(T);

9 / 13

Beispiel: Variablentemplate pi_v

template_pi_narrow.cpp 3
3.141593
#include <iostream> 3.141592653589793
#include <iomanip> 3.141592653589793239
#include <limits>

using namespace std;

template<class T>
constexpr T pi_v
= static_cast<T>(3.141592653589793238462643383279502884L);

int main() {
cout
<< pi_v<int> << endl
<< setprecision(numeric_limits<float>::digits10 + 1)
<< pi_v<float> << endl
<< setprecision(numeric_limits<double>::digits10 + 1)
<< pi_v<double> << endl
<< setprecision(numeric_limits<long double>::digits10 + 1)
<< pi_v<long double> << endl;
}

10 / 13

Seite 5 von 7
Templates* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Typalias-templates

template_downset.cpp template_ptr.cpp

#include <set> #include <iostream>


#include <iostream>
#include <functional> using namespace std;

using namespace std; template<class T>


using ptr = T*;
template<class Key>
using downset = set<Key, greater<Key>>; ptr<int> x = nullptr;

int main() { int main() {


downset<int> s; int y = 7;
[Link](3); [Link](1); [Link](2); x = &y;

cout << ”s:”; cout << *x << endl;


for (const int& x: s) }
cout << ” ” << x;
cout << endl;
} 7

s: 3 2 1

11 / 13

Beispiel: Polymorphe Klasse Vektor


poly_vector.cpp public:
T& operator*() { return v->ap[pos]; }
#include <iostream> iterator& operator++() {
if (pos < v->len) pos++;
using namespace std; return *this;
}
template<class T> bool operator!=(const iterator& other) const {
class Vektor { return v != other.v || pos != [Link];
private: }
T* ap; };
int len;
iterator begin() { return iterator{this}; }
public: iterator end() { return iterator{this, len}; }
Vektor(int n = 0, const T& x = T{}): ap(nullptr), len(n) { };
if (!len) return;
ap = new T[n]; int main() {
for (int i = 0; i < len; i++) Vektor<int> v{4};
ap[i] = T{x}; cout << ”v: ”;
} unsigned int count = 0;
~Vektor() { if (ap) delete[] ap; } for (Vektor<int>::iterator it = [Link](); it != [Link]();
Vektor& operator=(const Vektor& b) = delete; ↩→ ++it) {
*it = count++; cout << *it << ” ”;
class iterator { }
friend class Vektor; cout << endl;
}
private:
Vektor* v;
int pos; v: 0 1 2 3

iterator(Vektor* v_, int pos_ = 0)


: v(v_), pos(pos_) {}

12 / 13

Seite 6 von 7
Templates* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Partiell spezialisierte p-Norm


partial_pnorm.cpp for (const X& y: v)
x += y * y;
#include <iostream> return sqrt(x);
#include <iomanip> }
#include <limits> };
#include <array>
#include <vector> int main() {
#include <cstddef> vector<double> c{5, 6};
#include <cmath> array<float, 2> a{3, 4};

using namespace std; cout


<< setprecision(numeric_limits<double>::digits10 + 1)
template<class Vec, unsigned int p = 2> << PNorm<vector<double>>{}(c) << endl
class PNorm{ << PNorm<array<float, 2>, 3>{}(a) << endl;
private: }
using X = typename Vec::value_type;
public:
X operator()(const Vec& v) { 7.810249675906654
X x = 0; 4.497941493988037
for (const X& y: v)
x += pow(y, p);
return pow(x, static_cast<X>(1) / p);
}
};

template<class Vec>
class PNorm<Vec, 2>{
private:
using X = typename Vec::value_type;
public:
X operator()(const Vec& v) {
X x = 0;
13 / 13

Seite 7 von 7
Programmieren II für Studierende der Mathematik WiSe 2025/26

Gültigkeit von Namen, Speicherklassen & Bindungswirkung*

Definition vs. Deklaration

• Deklaration führt Namen ein


• Manche Deklarationen sind Definitionen
• Definition weist Speicher zu und führt Initialisierung durch, wenn nötig

1/9

One Definition Rule (ODR)

• Jede Funktion, Klasse, Variable, … darf nur eine Definition haben


• Wiederholte Deklaration jedoch möglich
• Jedes Programm muss genau eine Definition enthalten für jede potentiell verwendete
Funktion, Klasse, Variable, …

2/9

Seite 1 von 5
Gültigkeit von Namen, Speicherklassen &Programmieren
Bindungswirkung*
II für Studierende der Mathematik — WiSe 2025/26

Gültigkeitsbereiche von Namen (scope)

• Durch Deklaration eingeführter Name verwendbar in seinem scope


(Gültigkeitsbereich)
• Faustregel: ab Deklaration bis Ende des enthaltenden Blocks (geschweifte Klammern)
• Funktionsparameter insb. im Körper der Funktion
• Überschattung: Deklaration des gleichen Namens in verschaltetem Block
• Überschattung erlaubt, jedoch oft verwirrend – lieber vermeiden

3/9

Speicherklassen (storage duration)

• Speicherklasse bestimmt: Wann Speicher zugewiesen? Wo? Wann wieder


freigegeben?
• Angabe bei Deklaration durch Schlüsselwörter
• Voreinstellung: automatisch; Speicher zuweisen und Initialisieren bei Ausführung
der Definition, auf stack, Freigabe am Ende des syn. Gültigkeisbereichs
• static: Speicher zuweisen am Start des Programms, Initialisierung bei erster
Ausführung der Definition, Freigabe am Ende des Programms
• dynamisch: Zuweisen mit new, auf dem heap, Freigeben mit delete

4/9

Seite 2 von 5
Gültigkeit von Namen, Speicherklassen &Programmieren
Bindungswirkung*
II für Studierende der Mathematik — WiSe 2025/26

Beispiel: strtok
[Link] if (*buffer == '\0')
return nullptr;
#include <iostream>
#include <iomanip> char* tokenBegin = buffer;

using namespace std; buffer += strcspn(buffer, del);


if (*buffer != '\0')
bool is_delim(char c, const char* del) { *(buffer++) = '\0';
for (int i = 0; del[i] != '\0'; i++)
if (del[i] == c) return tokenBegin;
return true; }
return false;
} int main() {
int strspn(const char* str, const char* del) { char input[] = ”one + two * (three - four)!”;
int res = 0; const char delim[] = ”! +- (*)”;
while (str[res] != '\0') for (char* token = strtok(input, delim); token; token =
if (is_delim(str[res], del)) ↩→ strtok(nullptr, delim))
res++; cout << '”' << token << '”' << endl;
else }
return res;
return res;
} ”one”
”two”
strcspn
”three”
”four”
char* strtok(char* str, const char* del) {
static char* buffer;
if (str)
buffer = str;

buffer += strspn(buffer, del);

5/9

Bindungswirkung von Namen (linkage)

• Relevant bei Zusammenfügen mehrerer Übersetzungseinheiten zu Programm


• Keine Bindungswirkung: Name verweist nur in Gültigkeitsbereich des Namens auf
Objekt (Funktion, Variable, Klasse, …)
• Interne Bindung: Name verweist innerhalb von Übersetzungseinheit auf gleiches
Objekt
• Externe Bindung: Name verweist innerhalb von Programm auf gleiches Objekt
• Schlüsselwort extern → Externe Bindung
• static → Interne Bindung
• Deklarationen in global scope (außerhalb von Funktionen, Blöcken, etc.) haben
externe Bindung
• const, constexpr → Voreinstellung interne Bindung
• Klasse vererbt Bindungswirkung an Komponenten

6/9

Seite 3 von 5
Gültigkeit von Namen, Speicherklassen &Programmieren
Bindungswirkung*
II für Studierende der Mathematik — WiSe 2025/26

Deklaration von namespaces

[“inline”] “namespace” [⟨Name⟩] “{” {⟨Deklaration⟩} “}”

• Fügt enthaltene Deklarationen zu Namensraum hinzu


• Zugriff dann mit scope resolution operator ::
• “inline” macht Namen auch im aktuellen scope bekannt (verschachtelte
namespaces)
• Namespaces (und enthaltene Deklarationen) haben externe Bindung

7/9

Unbenannte namespaces

• Namespace Deklaration ohne Name agiert wie eine mit eindeutigem Namen pro
Übersetzungseinheit
• Unbenannte namespaces (und enthaltene Deklarationen) haben interne Bindung
• Unbenannte namespaces verhalten sich als wären sie inline

8/9

Seite 4 von 5
Gültigkeit von Namen, Speicherklassen &Programmieren
Bindungswirkung*
II für Studierende der Mathematik — WiSe 2025/26

Namespace alias

“namespace” ⟨Name⟩ “=” ⟨Name⟩ “;”

• Führt weiteren Namen ein für bereits existenten namespace

9/9

Seite 5 von 5
Programmieren II für Studierende der Mathematik WiSe 2025/26

Separate Compilation*

Präprozessor

• Präprozessor nimmt textuelle Modifikationen an Übersetzungseinheit vor


• Ausführung automatisch und direkt vor Kompilation zu Binärdatei

1 / 11

#define

define_demo.cpp

#include <iostream>
“#define” ⟨ident⟩ [“(” ⟨ident⟩ {“,” ⟨ident⟩} “)”] ⟨text⟩ using namespace std;
• Definiert token, wird überall folgend ersetzt durch
#define SEVENTEEN (14 + 3)
gegebenen ⟨text⟩ #define PLUS(a, b) (a + b)
• Kann parametrisiert sein, Parameter werden
eingesetzt in ⟨text⟩ bei Ersetzung int main() {
cout << SEVENTEEN << ” ”
• Wirklich nur textuelle Ersetzung; insb. keine
<< PLUS(3, 14)
Rücksicht auf Operatorvorrang << endl;
}

17 17

2 / 11

Seite 1 von 6
Separate Compilation* Programmieren II für Studierende der Mathematik — WiSe 2025/26

#ifdef

ifdef_demo.cpp

#include <iostream>
“#ifdef” ⟨ident⟩ using namespace std;

“#ifndef” ⟨ident⟩
#define SEVENTEEN 17
“#else”
“#endif” int main() {
• Fügt Text zwischen #ifdef und #ifdef SEVENTEEN
#endif ein, gdW. ⟨ident⟩ mit #define cout << SEVENTEEN << endl;
#else
definiert wurde
cout << 18 << endl;
• #ifndef negiert Bedingung
#endif
}

17

3 / 11

#include

[Link]
“#include” (“"” ⟨file⟩ “"” | “<” ⟨ident⟩ “>”)
• Findet Datei im lokalen System #include <iostream>
anhand von ⟨file⟩ bzw. ⟨ident⟩
using namespace std;
• Ersetzt #include-Anweisung durch
Inhalt der Datei int main() {
• Variante mit " sucht i.d.R. im gleichen cout << ”Hello, World!” << endl;
Verzeichnis, Variante mit <, > i.d.R. in }
systemweiten Standardverzeichnissen
Hello, World!

4 / 11

Seite 2 von 6
Separate Compilation* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Objekt-Dateien

• Kompilieren einer Übersetzungseinheit liefert Objekt-Datei


• Bisher immer automatisch weiterverarbeitet zu ausführbarer Binärdatei
• g++ -c [Link] → hello.o
• Objektdatei enthält kompilierten Maschinencode und Metadaten bzgl. Funktionen
etc., die Symbole

5 / 11

Linker

• Linker fügt beliebig viele Objektdateien zusammen zu ausführbarer Binärdatei


• g++ hello.o → [Link]
• Sind benötigte Symbole in keiner der Objektdateien vorhanden → Linkerfehler

…/bin/ld: …/lib/crt1.o: in function `_start':


(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status

6 / 11

Seite 3 von 6
Separate Compilation* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Header-Dateien

• Motivation: selbe Funktion in mehreren Übersetzungseinheiten verwenden


• Kompilierte Instruktionen in Objekt-Datei notwendig, aber nicht hinreichend
• Insb. Typ, d.h. Deklaration wird auch benötigt
• Daher: Auslagern der Deklarationen in eigene Datei(en), Dateiendung .h
• Einbinden der Deklarationen mit #include, wo benötigt

7 / 11

Beispiel: Programmbibilothek
greet.h g++ -c [Link]
g++ -c [Link]
#include <string> g++ hello1.o greet.o
./[Link]
void greet(std::string greeting);

Hello, World!
[Link]

#include <iostream> [Link]


#include <string>
#include ”greet.h” #include ”greet.h”

using namespace std; int main() {


greet(”Hello!”);
void greet(string greeting) { }
cout << greeting << endl;
}
Hello!

[Link]

#include <string>

using namespace std;

void greet(string greeting);

int main() {
greet(”Hello, World!”);
}

8 / 11

Seite 4 von 6
Separate Compilation* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Double inclusion

grandparent.h [Link]

class Klasse { #include ”grandparent.h”


public: int attribut; #include ”parent.h”
};
int main() {}

In file included from parent.h:1,


from [Link]:
parent.h grandparent.h:1:7: error: redefinition of ‘class Klasse’
1 | class Klasse {
#include ”grandparent.h” | ^~~~~~
In file included from [Link]:
grandparent.h:1:7: note: previous definition of ‘class Klasse’
1 | class Klasse {
| ^~~~~~

9 / 11

Include guards

grandparent_guard.h parent_guard.h

#ifndef GRANDPARENT_GUARD_H #include ”grandparent_guard.h”


#define GRANDPARENT_GUARD_H

class Klasse { child_guard.cpp


public: int attribut;
}; #include ”grandparent_guard.h”
#endif #include ”parent_guard.h”

int main() {}

10 / 11

Seite 5 von 6
Separate Compilation* Programmieren II für Studierende der Mathematik — WiSe 2025/26

#pragma once

grandparent_pragma.h parent_pragma.h

#pragma once #include ”grandparent_pragma.h”

class Klasse {
public: int attribut; child_pragma.cpp
};
#include ”grandparent_pragma.h”
#include ”parent_pragma.h”

int main() {}

11 / 11

Seite 6 von 6
Programmieren II für Studierende der Mathematik WiSe 2025/26

Überladung & Operatoren

Parametervoreinstellungen

int strtoint(string s, int basis=10);


• Voreinstellung für Parameter ⇒
Parameter bei Aufruf optional strtoint(”123”);
strtoint(”123”, 16);
• Immer ganzes Endstück der
Parameter mit Voreinstellung
int strtoint(string s=””, int basis);
• Keine Erwähnung anderer Parameter
• Nicht für Operatoren double flaeche(double a, double b =
↩→ a);

1 / 16

Überladene Funktionen

• Mehrere Funktionen dürfen selben Namen tragen


• Einschränkung: unterschiedliche Listen von Parameter-Typen

2 / 16

Seite 1 von 8
Überladung & Operatoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Einfache überladene Funktion

overloading_demo.cpp 3
2
#include <iostream>

using namespace std;

int f(int a, int b) {


return a + b;
}

int f(double x, double y) {


return x * y;
}

int main() {
cout << f(1, 2) << endl;
cout << f(1.0, 2.0) << endl;
}

3 / 16

Auswahl überladener Funktionen

• Parameter des Aufrufs werden Einzelne Konvertierungen in absteigender


implizit konvertiert „Güte“:
• Implizite Konvertierung ist Folge • • Exakte Übereinstimmung
beliebig vieler einzelner • Triviale Konvertierung (𝑡 ↔ 𝑡 &, 𝑡 []
Konvertierungen ↔ 𝑡 *)
• Eindeutige „beste“ Konvertierung • Triviale Konvertierung mit const (𝑡 *
wird gewählt → const 𝑡 *, 𝑡 & → const 𝑡 &)
• Keine implizite Konvertierung mit: • Numerische Zahlbereichserweiterung
• unnötigen Schritten • Standard-Konvertierung (numerisch,
• mehr als einer Zeiger, bool)
„benutzerdefinierten“
• „Benutzerdefinierte“ Konvertierung
Konvertierung

4 / 16

Seite 2 von 8
Überladung & Operatoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Interaktion von Überladung mit Templates

tmpl_overload_f.cpp

• Aufruf kann erfordern Parameter #include <iostream>


eines Templates zu bestimmen #include <complex>

• Bestimmung der Parameter vor


using namespace std;
Auswahl überladener Funktion
• Daher keine implizite int main() {
Typumwandlung obwohl evtl. complex<double> i{0, 1.0};

erwartet
cout << (1 + i) << endl;
}

5 / 16

Benutzerdefinierte Konvertierungen

Benutzerdef. Konvertierungen
class Complex {
• Konstruktoren mit einem Argument Complex(double x_) : x(x_), y(0) {}

• Typumwandlungsoperatoren operator complex<double>() {


return complex<double>{x, y};
}
};

6 / 16

Seite 3 von 8
Überladung & Operatoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Typumwandlung mit Konstruktor

class Vektor {
• Konstruktor C(T t) nimmt Teil an Vektor (int n, double x = 0);
}
impliziter Typumwandlung
• Attribut explicit verhindert int main() {
Teilnahme Vektor v(3);
v = 2;
• Z.B. explicit vector(size_type
// [Link]=(2)
n) aus STL // [Link]=(Vektor(2))
}

7 / 16

Typumwandlung mit Operator

• Motivation: Typumwandlung Klasse


class Vektor {
C zu eingebautem Datentyp T bisher
private int len;
nicht darstellbar
• Spezielle Syntax: operator T() wie operator int() {
Methode return len;
}
• Expliziter Aufruf für c vom Typ C mit
}
static_cast<T>(c)

8 / 16

Seite 4 von 8
Überladung & Operatoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Überladung von Operatoren

• Definition analog Funktionen bzw. Methoden, spezielle Syntax


• Für Operator a◦b definiere: operator◦(𝑎 , 𝑏 ) oder 𝑎 .operator◦(𝑏 )
• Für Operator ◦a bzw. a◦: operator◦(𝑎 ) oder 𝑎 .operator◦()
• Nur als Methode: =, (), [], ->
• Garnicht: ., .*, ::, sizeof, ?:
• Principle Of Least Surprise
• Annotation operator◦ const (. . .) möglich
• const präferiert wo möglich

9 / 16

Vergleichsoperatoren

• Satz von Operatoren <, ==, !=, >, >=,


#include <utility>
<= für eigene Klassen oft nützlich
• Fehlerquelle: Inkonsistenz zwischen using namespace rel_ops;
Operatoren (z.B. 𝑎 < 𝑏 ⇔/ 𝑏 > a)
class Complex {
• Daher in <utility> und namespace double x, y;
rel_ops vordefinierte templates
Annahmen: friend bool operator<(Complex x,
• == Äquivalenzrelation ↩→ Complex y);

• < strenge Totalordnung auf // damit autom. auch: >, <=, >=
Äquiv'klassen bzgl. == };

10 / 16

Seite 5 von 8
Überladung & Operatoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Inkrementoperatoren

Inkrementoperatoren
class Complex {
public:
• ++𝑥 übersetzt zu: double x, y;
• operator++(𝑥 ) Complex(double x_, double y_) : x(x_), y(y_) {}
• 𝑥 .operator++()
Complex& operator++() {
• 𝑥 ++ übersetzt zu: x++;
• operator++(𝑥 , 0) return *this;
• 𝑥 .operator++(0) }
Complex operator++(int ignored) {
return Complex{x++, y};
}
};

11 / 16

Überladung des Funktionsaufrufs

𝑥 (𝑎 1 , 𝑎 2 , . . .) übersetzt zu 𝑥 .operator()(𝑎 1 , 𝑎 2 , . . .)

12 / 16

Seite 6 von 8
Überladung & Operatoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Überladener Funktionsaufruf: Polynom


polynom_op.cpp p(x) = 17

#include <iostream>
#include <vector>

using namespace std;

class Polynom {
private:
vector<double> coeff;

public:
Polynom(const vector<double>& v) : coeff(v) {}

double operator()(double x) {
double s = 0, xpot = 1;
for (vector<double>::size_type i = 0; i < [Link]();
↩→ i++) {
s += coeff[i] * xpot;
xpot *= x;
}
return s;
}
};

int main() {
Polynom p{vector<double>{1, 2, 3}};

cout << ”p(x) = ” << p(2) << endl;

return 0;
}
13 / 16

Überladener Funktionsaufruf: Matrix


matrix_op.cpp
for (int i = 0; i < m; i++) {
#include <iostream> for (int j = 0; j < n; j++) {
#include <iomanip> cout << setw(4) << a(i, j);
#include <valarray> }
cout << endl;
using namespace std; }

class Matrix { cout << endl;


private:
int m, n; // Dimension const Matrix b{1, 1, 42};
valarray<double> a; // Elemente cout << b(0, 0) << endl;
}
public:
Matrix (int m_ = 0, int n_ = 0, double x = 0)
: m(m_), n(n_), a(x, m_ * n_) {} 0 1 2
3 4 5
double operator()(int i, int j) const { 6 7 8
return a[i*n + j]; 9 10 11
}
double& operator()(int i, int j) { 42
return a[i*n + j];
}
};

int main() {
const int m = 4, n = 3;

Matrix a{m, n};


for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
a(i, j) = i*n + j;
14 / 16

Seite 7 von 8
Überladung & Operatoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Überladener Funktionsaufruf: Normalverteilung


normv_op.cpp
cout << defaultfloat << setprecision(6)
#include <iostream> << NormV{}(-0.5) << endl;
#include <iomanip> }
#include <cmath>

using namespace std; 0.002


0.006 #
class NormV { 0.013 ##
private: 0.027 #####
double mw, stdabw; 0.050 #########
0.082 ################
public: 0.121 ########################
NormV (double mw_ = 0, double stdabw_ = 1) 0.160 ###############################
: mw(mw_), stdabw(stdabw_) {} 0.189 #####################################
0.199 #######################################
double operator()(double x) const { 0.189 #####################################
return M_2_SQRTPI / (2 * M_SQRT2 * stdabw) 0.160 ###############################
* exp(-((x - mw) * (x - mw)) / (2*stdabw*stdabw)); 0.121 ########################
} 0.082 ################
}; 0.050 #########
0.027 #####
int main() { 0.013 ##
NormV n{0, 2}; 0.006 #
for (double x = -6; x <= 6.001; x += 12. / 18) { 0.002
double fx = n(x);
cout << setw(5) << fixed << setprecision(3) << fx << ' '; 0.352065
for (int k = 1; k < fx * 200; k++) cout << '#';
cout << endl;
}

cout << endl;


15 / 16

Funktionsobjekte

Funktionsobjekt ist Objekt einer Klasse, bei der:


• operator() überladen
• Aufruf von operator() „Primärzweck“ der Klasse

16 / 16

Seite 8 von 8
Programmieren II für Studierende der Mathematik WiSe 2025/26

Buildsysteme*

Buildsysteme

• Automatisieren Buildprozess von Software-Projekten


• Für C++ speziell: Kompilieren von Objektdateien, Linken zu executables/libraries
• Grobe Kategorien:
lowlevel Ausführen von build-Befehlen (wenn jeweilige Eingabe-Dateien sich
ändern)
Beispiele: Make, Ninja
highlevel Automatismen zum Bestimmen der lokalen Begebenheiten (Orte von
geteilten Bibliotheken, notwendige Compilerparameter, etc.)
Eingebaute Lösungen für übliche Problemstellungen
Generiert oft Spezifikation für ein lowlevel Buildsystem
Beispiele: Make, GNU Autotools, Meson

1/9

Beispiel: Programmbibliothek

ld.h ld_main.cpp

#pragma once #include <iostream>


#include <cerrno>
double ld(double); #include <cstring>

#include ”ld.h”

using namespace std;

int main() {
double x;
[Link] cout << ”x: ”;
cin >> x;
#include <cmath> errno = 0;
#include ”ld.h” cout << ”ld(” << x << ”) = ” << ld(x) << endl;
if (errno) cout << strerror(errno) << endl;
template<class T> }
constexpr T ln2_v
= static_cast<T>(0.693147180559945309417232121458176568L);
x: 8
double ld(double x) { return log(x) / ln2_v<double>; } ld(8) = 3
x: 0
ld(0) = -inf
Numerical result out of range
x: -4
ld(-4) = -nan
Numerical argument out of domain

2/9

Seite 1 von 5
Buildsysteme* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Konzept: dependency graph

• Dependency Graph ist DAG von Dateien


• „𝐴 Nachfolger von 𝐵“ entspricht Aussage, dass
𝐴 neu erzeugt werden muss, wann immer 𝐵 [Link]
geändert wird; 𝐴 geht hervor aus 𝐵
• Ansatz:
• Dependency Graph kodieren in Spezifikation
für Buildsystem
ld_main.o ld.o
• Jede Datei hat optional Befehl wie sie erzeugt
werden kann aus Vorgängern (sonst muss sie
bereits existieren)
• Buildsystem überprüft z.B.
Modifikations-Zeitstempel und führt Befehle ld_main.cpp ld.h [Link]
aus, wo Nachfolger jünger ist als einer der
Vorgänger

3/9

Make

• Spezifikation als Datei, Name per Konvention


Makefile
• ⇆ ist Tabulator, nicht Leerzeichen
• Aufruf als make für erstes Ziel make_ld/Makefile
• Eine Regel pro erzeugtem Satz Dateien (Ziel),
Eingabedateien (Abhängigkeiten) direkt nach .PHONY: all
all: main
Name erzeugter Datei, Befehle zur erzeugung
darunter eingerückt main: ld_main.o ld.o
• % steht für mind. eines aber beliebig viele ⇆g++ -o $@ $^
Zeichen
• $< ist erste Abhängigkeit, $@ ist Ziel, $^ ist alle %.o: %.cpp ld.h
Abhängigkeiten ⇆g++ -o $@ -c $<
• .PHONY ist Liste spezieller Regeln, die immer
ausgeführt werden ohne
Zeitstempel-Überprüfung
4/9

Seite 2 von 5
Buildsysteme* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Ninja

• Spezifikation als Datei, Name per


Konvention [Link] ninja_ld/[Link]
• Aufruf als ninja baut alle Outputs die CPPFLAGS = -Wall -Werror
nicht auch Abhängigkeit sind CXXFLAGS = -std=c++14
• rules spezifizieren Befehle zur
Verwendung in build-Regeln rule cpp_compiler
• rules tragen Variablen-Zuweisungen; command = g++ $CPPFLAGS $CXXFLAGS -o
↩→ $out -c $in
können in build-Regeln überschattet rule cpp_linker
werden command = g++ $CPPFLAGS $CXXFLAGS -o
• Kanten im DAG entsprechen ↩→ $out $in
build-Regeln
• build-Regeln spezifizieren build main: cpp_linker ld_main.o ld.o
build ld.o: cpp_compiler [Link] | ld.h
Abhängigkeiten (verfügbar in rule als
build ld_main.o: cpp_compiler
$in); zusätzliche Abhängigkeiten mit |
↩→ ld_main.cpp | ld.h
• Keine patterns wie bei make
5/9

Ninja/GCC: Automatische Erkennung von includes

ninja_auto_ld/[Link]

CPPFLAGS = -Wall -Werror


CXXFLAGS = -std=c++14

rule cpp_compiler
deps = gcc
depfile = $out.d
command = g++ $CPPFLAGS $CXXFLAGS -MD -MF $out.d -o $out -c $in
rule cpp_linker
command = g++ $CPPFLAGS $CXXFLAGS -o $out $in

build main: cpp_linker ld_main.o ld.o


build ld.o: cpp_compiler [Link]
build ld_main.o: cpp_compiler ld_main.cpp

6/9

Seite 3 von 5
Buildsysteme* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Meson

• Eigene Syntax für Build-Spezifikationen ([Link]); Python-artig


• Eingebaute Funktionen, insb.:
project Zwingend erforderlich; spezifiziert Eigenschaften des gesamten
Projekts, insb. name und (i.A. mehrere) Programmiersprache(n) die
unterstützt werden sollen
library Kompiliert Programmbibliothek zu Objektdatei; liefert meson-object
das verwendet werden kann insb. als Parameter anderer Funktionen
executable Kompiliert Übersetzungseinheit bis ausführbare Binärdatei; i.A. unter
linken mit vorher übersetzten libraries
• meson setup build initialisiert builddir; Übersetzen mit ninja -C build

meson_ld/[Link]

project('ld', 'cpp')
ld_lib = library('ld_lib', '[Link]')
executable('main', 'ld_main.cpp', link_with: ld_lib)
7/9

Einbinden von externen Abhängigkeiten

• In der Regel gute Idee vorgefertigte Lösungen für Probleme einzubinden statt alles
selbst zu entwickeln (Z.B. Datenstrukturen aus STL)
• Woher Objekt- und Header-Dateien der benötigten Libraries beziehen?
Vom Betriebssystem Bereits bekannt, z.B. iostream
Findet relevante Dateien auf Compiler-spezifische Weise
Version/Verfügbarkeit von Abhängigkeiten Betriebssystem-abhängig
Vendoring Einbinden von spezifischen Versionen von Abhängigkeiten direkt in
das Projekt
Historisch: copy & paste
Besser: Referenz (in einem lockfile) hinterlegen; vom Buildsystem
beschaffen lassen

8/9

Seite 4 von 5
Buildsysteme* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Meson: WrapDB

• Community unterhält Liste von Abhängigkeiten auf GitHub → die Meson WrapDB
• Erfordert Unterverzeichnis subprojects
• meson wrap install gtest installiert googletest unterhalb von subprojects
• Verwendung in [Link] mit subproject und get_variable

meson_wrapdb/[Link]

project('wrapdb', 'cpp')
gtest = subproject('gtest')
executable('testbin',
'[Link]',
dependencies : gtest.get_variable('gtest_main_dep')
)

9/9

Seite 5 von 5
Programmieren II für Studierende der Mathematik WiSe 2025/26

Unit tests mit Google Test*

Unit testing

• Konzeptionelles Aufteilen von geschriebenem Code in units (oft Funktionen bzw.


Methoden, gelegentlich auch alle Methoden einer ganzen Klasse)
• Formulieren von Beispielen mit bekannten Ergebnissen (oder zumindest
Eigenschaften) als Code
• Ausführen von Test-Code möglichst oft (z.B. bei jedem build) → liefert
Daten/Eindruck bzgl. Korrektheit des Codes

1/5

Unit testing frameworks

• Frameworks bieten Werkzeuge um formulieren und ausführen von unit tests


bequemer zu machen
• Hier speziell betrachtet: Google Test

2/5

Seite 1 von 3
Unit tests mit Google Test* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Addition
gtest_add/add.h gtest_add/[Link]

#pragma once #include ”add.h”


#include <iostream>
int add(int a, int b);
using namespace std;

gtest_add/[Link] int main() {


int a, b;
#include ”add.h” while (cin >> a >> b)
cout << add(a, b) << endl;
int add(int a, int b) { }
return a + b;
}
gtest_add/[Link]

gtest_add/add_unittest.cpp
project('add', 'cpp')

#include ”add.h” add_lib = library('add_lib', '[Link]')


#include <gtest/gtest.h>
executable('add', '[Link]', link_with : add_lib)

namespace {
gtest = subproject('gtest')
TEST(Add, Negative) {
test('gtest',
EXPECT_EQ(add(2, -2), 0);
executable('gtest_add', 'add_unittest.cpp', '[Link]',
}
↩→ dependencies : gtest.get_variable('gtest_main_dep'))
TEST(Add, Four) {
)
EXPECT_EQ(add(2, 2), 4);
}
}
meson test -C build

3/5

Test Fixtures

• Häufig mehrere Tests für selbe „Situation“ gewünscht; z.B. Eigenschaften einer
bestimmten Datenstruktur
• Ansatz von Google Test ist RAII
• Fixture ist Klasse, Konstruktor etabliert „Situation“, jeder Test kann automatisch
erzeugtes Objekt der Klasse verwenden
• Fixture Klassen müssen (public) Erben von testing::Test
• Implementierung von Google Test verwendet Klasse die wiederrum von Fixture
erbt; Konstruktor und relevante Attribute müssen mindestens protected sein

4/5

Seite 2 von 3
Unit tests mit Google Test* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Test Fixture


gtest_vector/vector_unittest.cpp }

#include <vector>
#include <algorithm>
#include <functional>
#include <gtest/gtest.h>

namespace {
using namespace std;

class VectorFixture : public testing::Test {


protected:
vector<int> vs;

VectorFixture(): vs({1, 4, 3, 2, 5}) {}


};

TEST_F(VectorFixture, DoubleReverse) {
vector<int> vs_orig(vs);
reverse([Link](), [Link]());
reverse([Link](), [Link]());
EXPECT_EQ(vs_orig, vs);
}
TEST_F(VectorFixture, SortReverse) {
sort([Link](), [Link](), greater<int>());

vector<int> vs_comm(vs);
sort(vs_comm.begin(), vs_comm.end(), less<int>());
reverse(vs_comm.begin(), vs_comm.end());

EXPECT_EQ(vs, vs_comm);
}
5/5

Seite 3 von 3
Programmieren II für Studierende der Mathematik WiSe 2025/26

Typsynonyme & Iteratoren

Typsynonyme mit using

using size_t = unsigned int;


• Eigene Namen für bereits existente
Typen using myvector = double[10];
• using 𝑡 ′ = 𝑡 ;
using mypointer = double*;

1 / 14

Klassen-assoziierte Typsynonyme

• Definition von Typsynonym innerhalb von Klasse möglich


• Steht innerhalb von Klasse unqualifiziert zur Verfügung
• Außerhalb von Klasse 𝐾 assoziiertes Typsynonym mit Name 𝑡 mit 𝐾 ::𝑡

2 / 14

Seite 1 von 7
Typsynonyme & Iteratoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Assoziierte Typsynonyme für Klasse Vektor


vector_types.cpp v[i] = i;

#include <iostream> cout << ”v: ”;


for (Vektor::size_type i = 0; i < [Link](); i++)
using namespace std; cout << v[i] << ” ”;
cout << endl;
class Vektor {
public: return 0;
using size_type = unsigned int; }

private:
double* ap; v: 0 1 2 3
size_type len;

public:
Vektor(size_type n = 0, double x = 0) : len(n) {
ap = new double[n];
for (size_type i = 0; i < n; i++) ap[i] = x;
}
~Vektor() { delete[] ap; }

double& operator[](size_type i) {
return ap[i];
}
size_type size() const {
return len;
}
};

int main() {
Vektor v{4};
for (Vektor::size_type i = 0; i < [Link](); i++)
3 / 14

Typsynonyme mit typedef

typedef unsigned int size_t;

• In neuem Code eher mit using


typedef double myvector[10];
• typedef 𝑡 𝑡 ′ ; ↔ using 𝑡 ′ = 𝑡 ;

typedef double *mypointer;

4 / 14

Seite 2 von 7
Typsynonyme & Iteratoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Iteratoren allgemein

• Iterator ist Wert von Typ assoziiert mit Behälter


• Modelliert Position eines Elements im Behälter
• Immer operator* geeignet (oft überladen) sodass Rückgabewert Element liefert
oder Referenz darauf
• Oft operator++ bzw. operator-- für nachste bzw. vorherige Position
• I.d.R. kein Konstruktor sondern Rückgabewert von Behälter-Methoden

5 / 14

Iteratoren für Klasse Vektor


vector_iterator.cpp iterator& operator++() {
if (pos < v->len) pos++;
#include <iostream> return *this;
}
using namespace std; bool operator!=(const iterator& other) const {
return v != other.v || pos != [Link];
class Vektor { }
private: };
double* ap;
unsigned int len; iterator begin() { return iterator{this}; }
iterator end() { return iterator{this, len}; }
public: };
Vektor(int n = 0, double x = 0) : len(n) {
ap = new double[n]; int main() {
for (int i = 0; i < n; i++) ap[i] = x; Vektor v{4};
}
~Vektor() { delete[] ap; } cout << ”v: ”;
unsigned int count = 0;
class iterator { for (Vektor::iterator it = [Link](); it != [Link](); ++it) {
friend class Vektor; *it = count++;
cout << *it << ” ”;
private: }
Vektor* v; cout << endl;
unsigned int pos; }

iterator(Vektor* v_, unsigned int pos_ = 0)


: v(v_), pos(pos_) {} v: 0 1 2 3

public:
double& operator*() {
return v->ap[pos];
}
6 / 14

Seite 3 von 7
Typsynonyme & Iteratoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Iteratoren über konstante Behälter

• I.d.R. separater Typ für Iteratoren über Konstanten (analog zu const Vektor* v
statt Vektor* v)
• Separate const-Methoden auf Behälter um Konstanteniteratoren zu erstellen
• Analog zu Zugriffsschutz durch pointer auf Konstante:
double arr[1]; const double* arrp = arr; arrp[0] = 17;

7 / 14

Konstanteniteratoren für Klasse Vektor


vector_const_iterator.cpp const_iterator& operator++() {
if (pos < v->len) pos++;
#include <iostream> return *this;
}
using namespace std; bool operator!=(const const_iterator& other) const
{ return v != other.v || pos != [Link]; }
class Vektor { };
private:
double* ap; const_iterator begin() const
unsigned int len; { return const_iterator{this}; }
const_iterator end() const
public: { return const_iterator{this, len}; }
Vektor(int n = 0, double x = 0) : len(n) { };
ap = new double[n];
for (int i = 0; i < n; i++) ap[i] = x; int main() {
} const Vektor v{2, 1.141};
~Vektor() { delete[] ap; }
cout << ”v: ”;
class const_iterator { for ( Vektor::const_iterator it = [Link]();
friend class Vektor; it != [Link]();
++it )
private: cout << *it << ” ”;
const Vektor* v; cout << endl;
unsigned int pos; }

const_iterator(const Vektor* v_, unsigned int pos_ = 0)


: v(v_), pos(pos_) {} v: 1.141 1.141

public:
const double& operator*() const
{ return v->ap[pos]; }

8 / 14

Seite 4 von 7
Typsynonyme & Iteratoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Iteratoren für STL vector

• Mit vector<T> v:
• vector<T>::iterator vi1 = [Link](), vi2 = [Link]();
• vector<T>::const_iterator vi1 = [Link](), vi2 = [Link]();
• vector<T>::reverse_iterator vi1 = [Link](), vi2 = [Link]();
• vector<T>::const_reverse_iterator vi1 = [Link](), vi2 = [Link]();
• Iteratoren für vector<T> sind random-access iterators, d.h. mit a und b Iteratoren, n
ganzzahling, m Komponente von T, t vom Typ T:
• a == b, a != b
• a < b, a <= b, a > b, a >= b
• *a und a->m
• *a = t
• a++, ++a, --a, a--
• b - a
• a + n, n + a, a += n, a - n, a -= n
• a[n]

9 / 14

Matrixmultiplikation mit Iteratoren


[Link] for (vector<double>::iterator apos_j = (*apos_i).begin();
apos_j != (*apos_i).end();
#include <iostream> apos_j++, bpos++)
#include <vector> *cpos += *apos_j * *bpos;
}
using namespace std;
cout << ”a*b: ”;
int main() { for (cpos = [Link](); cpos != [Link](); cpos++)
unsigned int m, n; cout << *cpos << ” ”;
cout << ”m, n: ”; cin >> m >> n; return 0;
}
vector<double> b(n);
cout << ”b: ”;
for (vector<double>::iterator i = [Link](); m, n: 2 3
i != [Link](); b: 1 2 3
++i) a[0][...]: 4 5 6
cin >> *i; a[1][...]: 7 8 9
a*b: 32 50
vector<vector<double>> a(m, vector<double>(n));
for (unsigned int i = 0; i < m; i++) {
cout << ”a[” << i << ”][...]: ”;
for (unsigned int j = 0; j < n; j++)
cin >> a[i][j];
}

vector<double> c(m);
vector<double>::iterator cpos = [Link]();

for (vector<vector<double>>::iterator apos_i = [Link]();


apos_i != [Link]();
apos_i++, cpos++) {
vector<double>::iterator bpos = [Link]();
10 / 14

Seite 5 von 7
Typsynonyme & Iteratoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Einschub: Typ-Platzhalter

int main()
{
auto i = 1; // i: int
const auto k = 5; // k: const int
• Deklaration und Initialisierung von auto x = 1.0; // x: double
Variable mit auto statt Typ
auto& j = i; // j: int&
• Typ wird anhand der Initialisierung auto& l = k; // l: const int&
automatisch bestimmt
const auto& n = 2; // n: const int&

auto p = 2, y = 2.0; // unzulaessig


}

11 / 14

Range-based for loops

Range-based for syntax


for (T i : v) { ... }

• Spezielle Syntax für for-Schleifen Äquivalentes konventionelles for


mit Iteratoren for (auto __pos = [Link](),
• T oft auto oder auto& __end = [Link]();
__pos != __end;
++__pos) {
T i = *__pos;
...
}

12 / 14

Seite 6 von 7
Typsynonyme & Iteratoren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Matrixmultiplikation mit Iteratoren II


matmul_range.cpp
cout << ”a*b: ”;
#include <iostream> for (auto i: c)
#include <vector> cout << i << ” ”;
return 0;
using namespace std; }

int main() {
unsigned int m, n; m, n: 2 3
cout << ”m, n: ”; cin >> m >> n; b: 1 2 3
a[0][...]: 4 5 6
vector<double> b(n); a[1][...]: 7 8 9
cout << ”b: ”; a*b: 32 50
for (auto& i: b)
cin >> i;

vector<vector<double>> a(m, vector<double>(n));


for (unsigned int i = 0; i < m; i++) {
cout << ”a[” << i << ”][...]: ”;
for (auto& j: a[i])
cin >> j;
}

vector<double> c(m);
vector<double>::iterator cpos = [Link]();

for (auto i: a) {
vector<double>::const_iterator bpos = [Link]();
for (auto j: i)
*cpos += j * *(bpos++);
cpos++;
}
13 / 14

Verwendung von Konstanteniteratoren

vector_norm.cpp Norm(a) = 5.09902

#include <vector>
#include <iostream>
#include <cmath>

using namespace std;

double Norm(const vector<double>& x) {


double s = 0;
for (const double& c: x)
s += c*c;
return sqrt(s);
}

int main() {
vector<double> a{5, 1};
cout << ”Norm(a) = ” << Norm(a) << endl;
return 0;
}

14 / 14

Seite 7 von 7
Programmieren II für Studierende der Mathematik WiSe 2025/26

Für Numerik optimierte Vektoren*

Numerische Vektoren in STL (valarray)

• Datentyp valarray<𝑇 > für stark eingeschränktes 𝑇 :


eingebaute Zahlentypen (float, double, …), Zeiger, complex (STL)
• Verschachtelte valarrays ebenfalls möglich
• Standardkonstruktor für Länge 0, Konstruktor mit Länge, Konstruktor mit Wert und
Länge (umgekehrte Reihenfolge wie bei vector), Initialisierungsliste mit Elementen
(wie bei vector)
• Arithmetische operatoren für valarrays selber Größe und mit Skalar, Überladungen
für exp, log, pow, sqrt, sin, cos, …
Jeweils punktweise
• Methode apply wendet Parameter-Funktion (Zeiger, nicht Funktionsobjekt)
punktweise an, liefert neues valarray
• Methoden sum, min, max, und cshift (zirkulärer shift)
• Methoden begin und end für range-based for loops; keine ordentlichen Iteratoren

1 / 15

Beispiel: Skalarprodukt mit valarray

valarray_scalar.cpp a: 5 4 3 2 1
skalar(a, b) = 35
#include <iostream>
#include <valarray>

using namespace std;

int main() {
valarray<double> a(5), b{1.0, 2.0, 3.0, 4.0, 5.0};

cout << ”a: ”;


for (double& x: a) cin >> x;

cout << ”skalar(a, b) = ” << (a*b).sum() << endl;


}

2 / 15

Seite 1 von 8
Für Numerik optimierte Vektoren* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Indexmengen

• valarray ist selbst kein Matrixtyp, effiziente Implementierung von Matrixtyp damit
jedoch möglich
• Hierfür: Klassen slice, gslice und valarray<bool>, valarray<size_t>
beschreiben Indexmengen
• Jeweils subscript-Operator [] überladen mit Indexmenge als Parameter, liefert
Teilvektor mit Referenz-Semantik
• valarray hat Konvertier-Konstruktoren für Teilvektoren, jedoch performance
Auswirkungen wegen Kopie
• valarray<bool> ist bitmaske, valarray<size_t> ist Vektor von Indizes

3 / 15

slice

slice(𝑖 0 , 𝑛 , ℎ ) liefert (𝑖 0 + 𝑘ℎ)𝑘=0,...,𝑛−1


Für Matrix (𝑎𝑖 𝑗 )𝑖=0,...,𝑚−1 gespeichert als a[𝑖 · 𝑛 + 𝑗 ]:
𝑗=0,...,𝑛−1

𝑖 -te Zeile Index-Folge (𝑖 · 𝑛 + 𝑗) 𝑗=0,...,𝑛−1 entspricht slice(𝑖 · 𝑛 , 𝑛 , 1)


𝑗 -te Spalte Index-Folge (𝑖 · 𝑛 + 𝑗)𝑖=0,...,𝑛−1 entspricht slice( 𝑗 , 𝑚 , 𝑛 )

4 / 15

Seite 2 von 8
Für Numerik optimierte Vektoren* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: slice
[Link] for (size_t k = 0; k < [Link](); k++) a[k] = k;

#include <iostream> cout << ”Zeile ” << i << ”: ”;


#include <iomanip> ausgabe_vektor(a[zeile(i, m, n)]); cout << endl;
#include <valarray> cout << ”Spalte ” << j << ”: ”;
ausgabe_vektor(a[spalte(j, m, n)]); cout << endl;
using namespace std;
a[zeile(i, m, n)] += a[zeile(l, m, n)];
slice zeile(size_t i, size_t m, size_t n)
{ return slice(i * n, n, 1); } cout << ”a:” << endl;
slice spalte(size_t j, size_t m, size_t n) ausgabe_matrix(a, n); cout << endl;
{ return slice(j, m, n); } }

void ausgabe_vektor(const valarray<double>& a) {


for (double x: a) m n: 3 4
cout << setw(3) << x; i j l: 1 2 0
} Zeile 1: 4 5 6 7
void ausgabe_matrix(const valarray<double>& a, size_t n) { Spalte 2: 2 6 10
for (size_t k = 0; k < [Link](); k++) { a:
cout << setw(3) << a[k]; 0 1 2 3
if ((k+1) % n == 0) 4 6 8 10
cout << endl; 8 9 10 11
}
}

int main() {
size_t i, j, l, m, n;
cout << ”m n: ”; cin >> m >> n;
cout << ”i j l: ”; cin >> i >> j >> l;

valarray<double> a(m*n);

5 / 15

gslice

Mehrdimensionale Verallgemeinerung von slice


start gleich, aber zwei beliebig (aber gleich) lange valarrays für size und stride

gslice(𝑖 0 , {𝑛 }, {ℎ }) liefert: slice(𝑖 0 , 𝑛 , ℎ )


gslice(𝑖 0 , {𝑛 0 , 𝑛 1 , . . .}, {ℎ 0 , ℎ 1 , . . .}) liefert:
gslice(𝑖 0 + 0 · ℎ 0 , {𝑛 1 , . . .}, {ℎ 1 , . . .}), gslice(𝑖 0 + 1 · ℎ 0 , {𝑛 1 , . . .}, {ℎ 1 , . . .}),
. . ., gslice(𝑖 0 + (𝑛 0 − 1) · ℎ 0 , {𝑛 1 , . . .}, {ℎ 1 , . . .})

6 / 15

Seite 3 von 8
Für Numerik optimierte Vektoren* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: gslice
[Link] }

#include <iostream>
#include <iomanip> m n: 3 4
#include <valarray> mp np: 2 3
a:
using namespace std; 0 1 2 3
4 5 6 7
gslice submatrix(size_t mp, size_t np, size_t n) 8 9 10 11
{ return gslice(0, {mp, np}, {n, 1}); }
sub a:
void ausgabe_matrix(const valarray<double>& a, size_t n) { 0 1 2
for (size_t k = 0; k < [Link](); k++) { 4 5 6
cout << setw(3) << a[k];
if ((k+1) % n == 0)
cout << endl;
}
}

int main() {
size_t m, n, mp, np;
cout << ”m n: ”; cin >> m >> n;
cout << ”mp np: ”; cin >> mp >> np;

valarray<double> a(m*n);
for (size_t k = 0; k < [Link](); k++) a[k] = k;

cout << ”a:” << endl;


ausgabe_matrix(a, n); cout << endl;

cout << ”sub a:” << endl;


ausgabe_matrix(a[submatrix(mp, np, n)], np); cout << endl;
7 / 15

Beispiel: Weitere Indexmengen


misc_slices.cpp a[bv] = 0;
cout << ”a:” << endl;
#include <iostream> ausgabe_matrix(a, n); cout << endl;
#include <iomanip>
#include <valarray> valarray<size_t> iv(min(m, n));
#include <algorithm> for (size_t i = 0; i < min(m, n); i++)
iv[i] = i*(n + 1);
using namespace std; cout << ”diag(a):”;
ausgabe_vektor(a[iv]); cout << endl;
void ausgabe_vektor(const valarray<double>& a) { }
for (double x: a)
cout << setw(3) << x;
} m n: 4 3
void ausgabe_matrix(const valarray<double>& a, size_t n) { a:
for (size_t k = 0; k < [Link](); k++) { 0 1 0
cout << setw(3) << a[k]; 3 4 5
if ((k+1) % n == 0) 6 7 8
cout << endl; 0 10 0
}
} diag(a): 0 4 8

int main() {
size_t m, n;
cout << ”m n: ”; cin >> m >> n;

valarray<double> a(m*n);

for (size_t k = 0; k < [Link](); k++) a[k] = k;

valarray<bool> bv(m*n);
bv = false;
bv[0] = bv[n - 1] = bv[n * (m-1)] = bv[m*n - 1] = true;
8 / 15

Seite 4 von 8
Für Numerik optimierte Vektoren* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Matrix-Datentyp, Deklarationen


matrix.h matrix_slice row(std::size_t i);
matrix_slice column(std::size_t j);
#pragma once
operator std::valarray<T>() const;
#include <valarray>
T* begin();
template<class T> class matrix { T* end();
private: };
std::valarray<T> v;
std::size_t m, n;

public:
matrix(std::valarray<T>&&, std::size_t, std::size_t);
matrix(std::size_t m_ = 0, std::size_t n_ = 0, T val_ = T());

size_t rows() const; size_t columns() const;

class matrix_slice : public std::slice {


friend class matrix;
private:
std::valarray<T>& v;

matrix_slice(std::valarray<T>& v_, std::size_t start,


↩→ std::size_t size, std::size_t stride);

public:
T& operator[](std::size_t i);
T operator[](std::size_t i) const;
operator std::valarray<T>() const;
};

matrix_slice operator[](std::size_t i);


9 / 15

Beispiel: Matrix-Datentyp, Implementierung


[Link] { return v[start() + i*stride()]; }
template<class T>
#include <valarray> T matrix<T>::matrix_slice::operator[](size_t i) const
#include <stdexcept> { return v[start() + i*stride()]; }
template<class T>
#include ”matrix.h” matrix<T>::matrix_slice::operator valarray<T>() const {
valarray<T> v(size());
using namespace std; for (size_t i = 0; i < size(); i++)
v[i] = (*this)[i];
template<class T> return v;
matrix<T>::matrix(std::valarray<T>&& v_, std::size_t m_, }
↩→ std::size_t n_)
: v(v_), m(m_), n(n_) { template<class T>
if ([Link]() != m * n) typename matrix<T>::matrix_slice
throw invalid_argument(”valarray of wrong size”); matrix<T>::operator[](size_t i) { return row(i); }
} template<class T>
template<class T> typename matrix<T>::matrix_slice matrix<T>::row(size_t i)
matrix<T>::matrix(size_t m_, size_t n_, T val_) { return matrix<T>::matrix_slice{v, i*n, n, 1}; }
: v(val_, m_ * n_),m(m_),n(n_) {} template<class T>
typename matrix<T>::matrix_slice matrix<T>::column(size_t j)
template<class T> { return matrix<T>::matrix_slice{v, j, m, n}; }
size_t matrix<T>::rows() const { return m; }
template<class T> template<class T>
size_t matrix<T>::columns() const { return n; } matrix<T>::operator valarray<T>() const {
return v;
template<class T> }
matrix<T>::matrix_slice::matrix_slice
(valarray<T>& v_, size_t start, size_t size, size_t stride) template<class T>
: slice(start, size, stride), v(v_) {} T* matrix<T>::begin()
{ return std::begin(v); }
template<class T> template<class T>
T& matrix<T>::matrix_slice::operator[](size_t i) T* matrix<T>::end()
10 / 15

{ return std::end(v); }

template class matrix<double>;

Seite 5 von 8
Für Numerik optimierte Vektoren* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Matrix-Datentyp, Demonstration

matrix_basic.cpp m n: 3 4
i j: 1 2
#include <iostream> Matrix a:
1 2 3 4
#include ”matrix.h” 5 6 7 8
9 10 11 12
using namespace std; 13

int main() {
size_t m, n, i, j;
cout << ”m n: ”; cin >> m >> n;
cout << ”i j: ”; cin >> i >> j;
matrix<double> a{m, n};
cout << ”Matrix a: ” << endl;
for (double& x: a) cin >> x;
cout << (a[i][j] = 13) << endl;
}

11 / 15

Beispiel: Matrix-Multiplikation
[Link] for (size_t j = 0; j < l; j++)
cout << setw(3) << c[i][j];
#include <iostream> cout << endl;
#include <iomanip> }
}
#include ”matrix.h”

using namespace std; m n: 4 4


Matrix a:
int main() { 3 2 1 4
size_t m, n; 1 0 2 3
cout << ”m n: ”; cin >> m >> n; 3 2 1 2
matrix<double> a{m, n}; 3 2 1 4
cout << ”Matrix a: ” << endl;
for (double& x: a) cin >> x; k l: 4 4
cout << endl; Matrix b:
1 2 1 4
size_t k, l; 0 1 0 3
cout << ”k l: ”; cin >> k >> l; 4 0 4 2
matrix<double> b{k, l}; 1 2 1 4
cout << ”Matrix b: ” << endl;
for (double& x: b) cin >> x; 11 16 11 36
cout << endl; 12 8 12 20
9 12 9 28
matrix<double> c{m, l}; 11 16 11 36
for (size_t i = 0; i < m; i++)
for (size_t j = 0; j < l; j++) {
valarray<double> as = [Link](i), bs = [Link](j);
c[i][j] = (as * bs).sum();
}

for (size_t i = 0; i < m; i++) {


12 / 15

Seite 6 von 8
Für Numerik optimierte Vektoren* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Strassen Algorithmus


[Link] m4 = matmul_v(m4, h, n / 2);
h = b[s22];
#include <valarray> valarray<T> m5 = a[s11]; m5 += a[s12];
#include <stdexcept> m5 = matmul_v(m5, h, n / 2);
h = b[s11]; h += b[s12];
#include ”matrix.h” valarray<T> m6 = a[s21]; m6 -= a[s11];
m6 = matmul_v(m6, h, n / 2);
using namespace std; h = b[s21]; h += b[s22];
valarray<T> m7 = a[s12]; m7 -= a[s22];
template<class T> m7 = matmul_v(m7, h, n / 2);
valarray<T>& matmul_v(valarray<T>& a, const valarray<T>& b,
↩→ size_t n) { a[s11] = m1 + m4 - m5 + m7;
if (n == 1) a[s12] = m3 + m5;
return a = a * b; a[s21] = m2 + m4;
a[s22] = m1 - m2 + m3 + m6;
gslice return a;
s11(0, {n / 2, n / 2}, {n, 1}), }
s12(n / 2, {n / 2, n / 2}, {n, 1}),
s21(n * n / 2, {n / 2, n / 2}, {n, 1}), matmul strassen
s22(n * n / 2 + n / 2, {n / 2, n / 2}, {n, 1});

valarray<T> h = b[s11]; h += b[s22];


valarray<T> m1 = a[s11]; m1 += a[s22];
m1 = matmul_v(m1, h, n / 2);
h = b[s11];
valarray<T> m2 = a[s21]; m2 += a[s22];
m2 = matmul_v(m2, h, n / 2);
h = b[s12]; h -= b[s22];
valarray<T> m3 = a[s11];
m3 = matmul_v(m3, h, n / 2);
h = b[s21]; h -= b[s11];
valarray<T> m4 = a[s22];
13 / 15

Beispiel: Strassen Algorithmus (Forts.)

matmul strassen strassen.h

bool power_of_two(size_t i) #pragma once


{ return i && !(i & (i - 1)); }
#include ”matrix.h”
template<class T>
matrix<T> matmul(matrix<T> a, matrix<T> b) { template<class T>
if ([Link]() != [Link]() || [Link]() != [Link]() || matrix<T> matmul(matrix<T> a, matrix<T> b);
↩→ [Link]() != [Link]())
throw invalid_argument(”matrices not square or not of same
↩→ dimension”);
if (!power_of_two([Link]()))
throw invalid_argument(”matrix dimension not power of
↩→ two”);

valarray<T> av = a;
av = matmul_v<T>(av, b, [Link]());
return matrix<T>{valarray<T>(av), [Link](), [Link]()};
}

template matrix<double> matmul<double>(matrix<double>,


↩→ matrix<double>);

14 / 15

Seite 7 von 8
Für Numerik optimierte Vektoren* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Strassen Algorithmus (Forts.)


matrixmul_strassen.cpp m n: 4 4
Matrix a:
#include <iostream> 3 2 1 4
#include <iomanip> 1 0 2 3
3 2 1 2
#include ”strassen.h” 3 2 1 4
#include ”matrix.h”
k l: 4 4
using namespace std; Matrix b:
1 2 1 4
int main() { 0 1 0 3
size_t m, n; 4 0 4 2
cout << ”m n: ”; cin >> m >> n; 1 2 1 4
matrix<double> a{m, n};
cout << ”Matrix a: ” << endl; 11 16 11 36
for (double& x: a) cin >> x; 12 8 12 20
cout << endl; 9 12 9 28
11 16 11 36
size_t k, l;
cout << ”k l: ”; cin >> k >> l;
matrix<double> b{k, l};
cout << ”Matrix b: ” << endl;
for (double& x: b) cin >> x;
cout << endl;

matrix<double> c = matmul(a, b);


for (size_t i = 0; i < m; i++) {
for (size_t j = 0; j < l; j++)
cout << setw(3) << c[i][j];
cout << endl;
}
}
15 / 15

Seite 8 von 8
Programmieren II für Studierende der Mathematik WiSe 2025/26

Container-Datentypen

Arrays in STL (array)

Verwendung array
array<double, 3> a1;

• Datentyp array<𝑇 , 𝑛 > für beliebigen


cout << ”a1: ”;
Typ 𝑇 und Ganzzahl 𝑛 for ( unsigned int i = 0;
• Dünner wrapper um C-Array der i < [Link]();
Form 𝑇 [𝑛 ] i++)
cin >> a1[i];
• Vorteile:
• Methoden, Operatoren, Iteratoren for (const double& x: a1)
analog zu vector cout << ” ” << x;
• 𝑛 zuverlässig überprüft cout << endl;

a1: 17 7 42
17 7 42

1 / 14

Beispiel: Arrays als Funktionsparameter

c_array_funlen.cpp stl_array_funlen.cpp

#include <iostream> #include <iostream>


#include <array>
using namespace std;
using namespace std;
void f(double vec[3]) {}
void f(array<double, 3> vec) {}
int main() {
double v[2] = {1, 2}; int main() {
array<double, 2> v{1, 2};
f(v);
} f(v);
}

stl_array_funlen.cpp: In function ‘int main()’:


stl_array_funlen.cpp:11:5: error: could not convert ‘v’ from
↩→ ‘array<[...],2>’ to ‘array<[...],3>’
11 | f(v);
| ^
| |
| array<[...],2>

2 / 14

Seite 1 von 8
Container-Datentypen Programmieren II für Studierende der Mathematik — WiSe 2025/26

Verkettete Listen
[Link]
return start;
#include <iostream> };

using namespace std; friend ostream& operator<<(ostream& stream, const Liste& l)


↩→ {
class Liste { for (const Liste *lauf = &l; lauf; lauf = lauf->next)
public: stream << lauf->v << ” ”;
double v; return stream;
}
private: };
Liste* next;
int main() {
public: Liste *liste = Liste::einlesen(”l”);
explicit Liste(double v_ = 0): v(v_), next(nullptr) {} if (!liste)
return 2;
static Liste* einlesen(string n) {
Liste *start = nullptr, *lauf = start; cout << ”l: ” << *liste << endl;
return 0;
cout << n << ”: ”; }

double x;
while (cin >> x) { l: 17 7 42 13
Liste* elem = new Liste{x}; l: 17 7 42 13
if (!start)
start = lauf = elem;
else {
lauf->next = elem;
lauf = elem;
}
if ([Link]() == '\n') break;
}
3 / 14

Speicherlayout verketteter Listen

Liste *start = nullptr,


*lauf = start;

double x;
while (cin >> x) {
start lauf Null Liste* elem = new Liste{x};
if (!start)
start = lauf = elem;
17 7 42 else {
lauf->next = elem;
lauf = elem;
}
if ([Link]() == '\n') break;
}

4 / 14

Seite 2 von 8
Container-Datentypen Programmieren II für Studierende der Mathematik — WiSe 2025/26

Verkettete Listen in STL (list)

• Datentyp list<𝑇 > für beliebiges 𝑇 𝑡1


• Doppelt verkettete Liste
• Einfügen/Löschen in konstanter Zeit 𝑡2
• Iteratoren bidirektional
𝑡3
• Kein wahlfreier Zugriff, insb. kein operator[]
• Verlängern/Verkürzen sowohl am Anfang wie auch am Ende: ..
𝑙 .push_front(𝑡 ), 𝑙 .pop_front(), 𝑙 .push_back(𝑡 ), 𝑙 .pop_back() .
• Div. weitere Operationen als Methoden:
insert, erase, sort, unique, merge, splice, reverse 𝑡𝑛

5 / 14

Polynome mit verketteten Listen

Monom

class Monom {
public:
int pot;
double v;

Monom(double v_ = 0, int pot_=1): pot(pot_), v(v_) {}

int grad() const { if (v != 0) return pot; else return -1; }

friend bool operator<(const Monom& m1, const Monom& m2) {


return [Link]() > [Link]();
}
friend bool operator==(const Monom& m1, const Monom& m2) {
return [Link]() == [Link]();
}

friend ostream& operator<<(ostream& stream, const Monom& m)


↩→ {
return stream << showpos << m.v << ”x^”
<< noshowpos << [Link];
}
};

6 / 14

Seite 3 von 8
Container-Datentypen Programmieren II für Studierende der Mathematik — WiSe 2025/26

Polynome mit verketteten Listen


[Link] return *this;
}
#include <iostream>
#include <list> friend ostream& operator<<(ostream& stream, const Polynom&
#include <utility> ↩→ p) {
if ([Link]()) return stream << ”0”;
using namespace std; for (const auto& monom: [Link])
using namespace rel_ops; stream << monom;
return stream;
Monom }
};

class Polynom { int main() {


private: Polynom p, q;
list<Monom> ml; p += Polynom{4, 3}; p += Polynom{2, 2}; p += Polynom{1, 1};
q += Polynom{1, 3}; q += Polynom{-2, 2}; q += Polynom{1, 0};
public:
Polynom() {} cout << ”p+q: ” << (p+=q) << endl;
Polynom(double v_, int pot_=1): ml({Monom{v_, pot_}}) {}
return 0;
Polynom& operator+=(Polynom q) { }
[Link]([Link]);
if ([Link]()) return *this;
list<Monom>::iterator curr = [Link](), p+q: +5x^3+1x^1+1x^0
next = ++([Link]());
for (; next != [Link](); ++curr, ++next) {
if (curr->pot == next->pot) {
curr->v += next->v;
*next = Monom{};
}
}
[Link](Monom{});
6 / 14

Paare (pair)

Paare
• Definiert in <utility> pair<char, int> p{'A', 7};
• Datentyp pair<𝐿 , 𝑅 > für beliebige 𝐿
cout << ”(”
und 𝑅 << [Link]
• Objekte speichern jeweils einen Wert << ”,”
von Typ 𝐿 und 𝑅 << [Link]
<< ”)” << endl;
• Zugriff als Attribute first, second

(A,7)

7 / 14

Seite 4 von 8
Container-Datentypen Programmieren II für Studierende der Mathematik — WiSe 2025/26

Generische Mengen in STL (set)

• Datentyp set<𝑇 > für beliebiges 𝑇


• Strikte Totalordnung auf 𝑇
• I.d.R. balancierter Binärbaum
• insert/erase in 𝑂 (log(𝑛))
• Elemente nicht modifizierbar
• Iteratoren bidirektional
• Suchen in 𝑂 (log(𝑛)) mit find, kein operator[]
• Mengenoperationen in <algorithm> einheitlich für alle Container-Datentypen

8 / 14

Sieb des Eratosthenes mit set

primes_set.cpp 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

#include <set>
#include <iostream>

using namespace std;

const unsigned int n = 50;

int main() {
set<unsigned int> nonprimes{0, 1};

for (unsigned int k = 2; k*k < n; k++)


if (![Link](k))
for (unsigned int i = k; i*k < n; i++)
[Link](i*k);

for (unsigned int k = 0; k < n; k++)


if (![Link](k))
cout << k << ” ”;
cout << endl;

return 0;
}

9 / 14

Seite 5 von 8
Container-Datentypen Programmieren II für Studierende der Mathematik — WiSe 2025/26

Mengen von natürlichen Zahlen in STL (bitset)

• Datentyp bitset<𝑁 > für beliebiges 𝑁 ∈ N


• I.d.R. Array von 64-bit Zahlen
• Menge von Werten aus [0, 𝑁 ) ⊂ N
• Kein Einfügen/Löschen, stattdessen 𝑁 viele Werte vom Typ bool
• Mitgliedschaft von Zahl 𝑛 < 𝑁 setzen mit set(𝑛 ) bzw. reset(𝑛 )
• Auslesen mit test(𝑛 ), operator[], all, any, none, count
• Bitoperatoren ~, &, |, ^, << und >>
• Ein-/Ausgabe als Zeichenkette von 0 und 1

10 / 14

Sieb des Eratosthenes mit bitset

primes_bitset.cpp 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

#include <bitset>
#include <iostream>

using namespace std;

const unsigned int n = 50;

int main() {
bitset<n> primes;
[Link]();
[Link](0); [Link](1);

for (unsigned int k = 2; k*k < n; k++)


if (primes[k])
for (unsigned int i = k; i*k < n; i++)
[Link](i*k);

for (unsigned int k = 0; k < n; k++)


if (primes[k])
cout << k << ” ”;
cout << endl;

return 0;
}

11 / 14

Seite 6 von 8
Container-Datentypen Programmieren II für Studierende der Mathematik — WiSe 2025/26

Endliche Abbildungen in STL (map)

• Dictionary, Associative array, Symbol table, Zuordnungstabelle


• Datentyp map<𝐾 , 𝑉 > für beliebige 𝐾 und 𝑉
• Strikte Totalordnung auf 𝐾
• I.d.R. balancierter Binärbaum
• insert von Paaren und erase in 𝑂 (log(𝑛))
• Iteratoren bidirektional
• Suchen in 𝑂 (log(𝑛)) mit find
• operator[] legt mit Standardkonstruktor Werte automatisch an (𝑂 (log(𝑛)))

12 / 14

Interaktive Erhebung von Übungspunkten mit map


input_exc.cpp << endl;

#include <map> return 0;


#include <string> }
#include <iostream>
#include <iomanip>
#include <fstream> [Link]

using namespace std; Meyer


Mueller
int main(int argc, char* argv[]) { Moser
map<string, int> points; Maier

ifstream names(argv[1]);
string s; Teilnehmer Punkte:
while (names >> s) points[s]; Moser 3
Mueller 4
cout << ”Teilnehmer Punkte:” << endl; Maurer 2
int n; Neuer Teilnehmer (j/N)? j
while (cin >> s >> n) { Moser 5
if ([Link](s) != [Link]()) Maier 0
points[s] += n; Maurer 2
else { Meyer 0
char c; Moser 8
cout << ”Neuer Teilnehmer (j/N)? ”; cin >> c; Mueller 4
if (c == 'j') [Link](pair<string, int>{s, n});
}
}

for (pair<string, int> entry: points)


cout << setw(10) << [Link]
<< setw(3) << [Link]
13 / 14

Seite 7 von 8
Container-Datentypen Programmieren II für Studierende der Mathematik — WiSe 2025/26

Worthäufigkeit mit map


[Link] for (auto entry: vektor) {
cout << setw(10) << [Link]
#include <map> << setw(5) << [Link]
#include <string> << endl;
#include <iostream> if (++count >= 10) break;
#include <iomanip> }
#include <fstream>
#include <vector> cout << endl << ”Wortzahl: ” << setw(8) << nwort << endl;
#include <algorithm>
return 0;
using namespace std; }

bool ordnung(const pair<string, int>& a,


const pair<string, int>& b) { ; 7
return [Link] > [Link]; ( 6
} ) 6
s 6
int main(int argc, char* argv[]) { b 5
map<string, int> freq; , 4
int nwort = 0; << 4
a 4
ifstream ein(argv[1]); c 4
string wort; * 3
while (ein >> wort) {
nwort++; Wortzahl: 82
freq[wort]++;
}

vector<pair<string, int>> vektor([Link](), [Link]());


stable_sort([Link](), [Link](), ordnung);

int count = 0;
14 / 14

Seite 8 von 8
Programmieren II für Studierende der Mathematik WiSe 2025/26

Exceptions

Exceptions: Motivation

• Kommunikation von Fehlerfall (und


Informationen dbzgl.) an aufrufenden int divF(int a, int b) {
Code if (b == 0) {
// ???
• errno (vom Typ int) trägt zu wenig }
Information, erfordert immer return a / b;
manuelle Behandlung im aufrufenden }
Code

1 / 10

Exceptions

divF
• Ausdruck throw 𝑣 für beliebigen
int divF(int a, int b) {
Wert 𝑣 wirft exception
if (b == 0)
• Rückabwicklung von throw string{”div(_, 0)”};
Funktionsaufrufen return a / b;
• Sprung in nächsten passenden }

catch-Block
int main() {
• Passend anhand von Typ von try {
Vereinbarung bei catch cout << divF(2, 0) << endl;
} catch (const string& str) {
• Falls kein passender catch-Block:
cerr << str << endl;
std::terminate → Abbruch des exit(1);
Programms }
}

2 / 10

Seite 1 von 5
Exceptions Programmieren II für Studierende der Mathematik — WiSe 2025/26

Exceptions in Standardlibrary

what
runtime_error e{”Beschreibung”};
Methode .what() liefert C-Zeichenkette
cout << [Link]() << endl;

Beschreibung

3 / 10

Vordefinierte Typen für Exceptions (stdexcept)

Logik-Fehler Laufzeit-Fehler

Fehler liegt im Programm Fehler liegt außerhalb des Programms


logic_error Generischer Logik-Fehler runtime_error Generischer
domain_error Definitionsbereichsfehler Laufzeit-Fehler
invalid_argument Funktionsargument range_error Wertebereichsfehler
unzulässig overflow_error Ergebnis betragsmäßig
length_error Datenstruktur zu groß zu groß für Datentyp
out_of_range Unzulässiger Index bei underflow_error Ergebnis betragsmäßig
Zugriff in Datenstruktur zu klein für Datentyp

4 / 10

Seite 2 von 5
Exceptions Programmieren II für Studierende der Mathematik — WiSe 2025/26

Exceptions bei Speicherreservierung

• new-Operator kann exceptions vom Typ std::bad_alloc werfen


• bad_alloc stammt aus Header-Datei <new>

5 / 10

Beispiel: Exceptions bei Speicherreservierung

dyn_bad_alloc.cpp n: 100000000000
Zu wenig Speicherplatz fuer a vorhanden: std::bad_alloc
#include <iostream>
#include <new>
#include <cstddef>

using namespace std;

int main() {
size_t n;
cout << ”n: ”; cin >> n;

double* a;
try {
a = new double[n];

a[0] = 3.14;
cout << a[0] << endl;
} catch (const bad_alloc& e) {
cerr << ”Zu wenig Speicherplatz fuer a vorhanden: ”
<< [Link]() << endl;
return 1;
}

return 0;
}

6 / 10

Seite 3 von 5
Exceptions Programmieren II für Studierende der Mathematik — WiSe 2025/26

Bereichsüberprüfter Elementen-Zugriff

• Für 𝑣 vom Typ vector<𝑇 > liefert 𝑣 .at(𝑛 ) Referenz auf Element von 𝑣 mit Index 𝑛
• Wirft exceptions vom Typ out_of_range wenn Index unzulässig

7 / 10

Beispiel: Bereichsüberprüfter Elementen-Zugriff


vector_exc.cpp }

#include <iostream>
#include <new> n: 100000000000
#include <vector> Beginn
Vor Vereinbarung
using namespace std; Zu wenig Speicherplatz fuer a vorhanden:
std::bad_alloc
int main() { Ende
vector<double>::size_type n;
cout << ”n: ”; cin >> n;
n: 10
cout << ”Beginn” << endl; Beginn
try { Vor Vereinbarung
cout << ”Vor Vereinbarung” << endl; Nach Vereinbarung
vector<double> a(n); Zugriff ausserhalb von Grenzen:
cout << ”Nach Vereinbarung” << endl; vector::_M_range_check: __n (which is 10) >= this->size()
[Link](n) = 1; ↩→ (which is 10)
cout << ”Nach Zugriff” << endl; Ende
} catch (const bad_alloc& e) {
cerr << ”Zu wenig Speicherplatz fuer a vorhanden: ” << endl
<< ” ” << [Link]() << endl;
} catch (const out_of_range& e) {
cerr << ”Zugriff ausserhalb von Grenzen:” << endl
<< ” ” << [Link]() << endl;
} catch (...) {
cerr << ”Andere exception” << endl;
throw;
}
cout << ”Ende” << endl;

return 0;
8 / 10

Seite 4 von 5
Exceptions Programmieren II für Studierende der Mathematik — WiSe 2025/26

Exceptions bei Ein-/Ausgabe

• Für Strom 𝑠, Bitmaske 𝑚, Methode 𝑠 .exceptions(𝑚 )


• Wenn bit in Stromzustand von 𝑠 gesetzt wird, das auch in 𝑚 gesetzt ist → exception
• exceptions vom Typ ios::failure

9 / 10

Beispiel: Exceptions bei Ein-/Ausgabe

iostream_exc.cpp n: abc
I/O-exception: basic_ios::clear: iostream error
#include <iostream>

using namespace std;

int main() {
[Link](ios::failbit|ios::badbit);

try {
int n;
cout << ”n: ”; cin >> n;
cout << ”n: ” << n << endl;
} catch (const ios::failure& e) {
cerr << ”I/O-exception: ”
<< [Link]() << endl;
}

return 0;
}

10 / 10

Seite 5 von 5
Programmieren II für Studierende der Mathematik WiSe 2025/26

Funktionsobjekte

Funktionstypen und Funktionszeiger

⟨Fun'typ⟩ → ⟨Rückgabetyp⟩ “(” ⟨Param'liste⟩ “)” Funktionstypen


using ArithFun = double(double);

• Zeiger von Funktionstypen möglich int main() {


• Auto. Umwandlung Funktionsname ArithFun* funptr = sqrt;

→ Zeiger auf Funktion cout << funptr(2) << endl;


• Funktionsauswertungsoperator für }
Zeiger auf Funktion definiert
1.41421

1 / 15

Vereinbarungssyntax für Zeiger auf Funktion

Funktionszeigervereinbarung

• Statt „Umweg“ mit Typalias für int main() {


double (*funptr)(double) = sqrt;
Funktionstyp
• Historisch üblicher (auch typedef) cout << funptr(2) << endl;
• Nicht zu empfehlen }

1.41421

2 / 15

Seite 1 von 8
Funktionsobjekte Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Sehnentrapezregel

1,0
∫ 𝑏
𝑓 (𝑎) + 𝑓 (𝑏)
𝑓 (𝑥) d𝑥 ≈ (𝑏 − 𝑎) 0,8
𝑎 2
0,6

0,4

0,2

0,0
−0,5 0,0 0,5 1,0 1,5

3 / 15

Beispiel: Sehnentrapezregel

1,0
∫ 𝑏
𝑓 (𝑎) + 𝑓 (𝑏)
𝑓 (𝑥) d𝑥 ≈ (𝑏 − 𝑎) 0,8
𝑎 2

𝑛−1
𝑓 (𝑥𝑖 ) + 𝑓 (𝑥𝑖+1 )
≈ (𝑥𝑖+1 − 𝑥𝑖 ) 0,6
𝑖=0
2
( )
𝑓 (𝑎) + 𝑓 (𝑏) ∑ 0,4
𝑛−1
=ℎ + 𝑓 (𝑥𝑖 )
2 𝑖=1
0,2
𝑏 −𝑎
mit ℎ = 𝑛 , 𝑥𝑖 = 𝑎 + 𝑖ℎ
0,0
−0,5 0,0 0,5 1,0 1,5

3 / 15

Seite 2 von 8
Funktionsobjekte Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Sehnentrapezregel (Funktionszeiger)

trapezoidal_fptr.cpp 0.754598
0.847006
#include <iostream> 0.868203
#include <cmath> 0.873407
0.874702
using namespace std; 0.875025
0.875106
double trapezoidal(double g(double), 0.875126
int n, double a = 0, double b = 1) { 0.875131
const double h = (b - a) / n; 0.875133
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

double f(double x) {
x -= 0.25;
return exp(-x * x);
}

int main() {
for (int n = 1; n <= 512; n *= 2)
cout << trapezoidal(f, n) << endl;

return 0;
}

4 / 15

Beispiel: Sehnentrapezregel (Typsynonym)


trapezoidal_ftyp.cpp 0.754598
0.847006
#include <iostream> 0.868203
#include <cmath> 0.873407
0.874702
using namespace std; 0.875025
0.875106
using ArithFun = double(double); 0.875126
0.875131
double trapezoidal(ArithFun g, 0.875133
int n, double a = 0, double b = 1) {
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

double f(double x) {
x -= 0.25;
return exp(-x * x);
}

int main() {
for (int n = 1; n <= 512; n *= 2)
cout << trapezoidal(f, n) << endl;

return 0;
}

5 / 15

Seite 3 von 8
Funktionsobjekte Programmieren II für Studierende der Mathematik — WiSe 2025/26

Funktionsobjekte in STL (function)

• Motivation: Funktionsparameter nicht nur Zeiger auf Funktion sondern beliebige


Funktionsobjekte
• STL stellt Klasse function<𝑇 > für 𝑇 Funktionstyp (z.B. double(double))
• Header <functional>
• operator() passend überladen; function<𝑇 > ist selbst Funktionsobjekt
• Stellt Konstruktor mit einem Parameter für beliebiges Funktionsobjekt mit
passendem operator() (auch Zeiger auf Funktion) → implizite Typkonvertierung
• Deswegen als Funktionsparameter immer lieber function<𝑇 > statt Zeiger auf
Funktion

6 / 15

Beispiel: Sehnentrapezregel (function)


trapezoidal_stl.cpp x -= 0.25;
return exp(-x * x);
#include <iostream> }
#include <cmath>
#include <functional> int main() {
for (int n = 1; n <= 512; n *= 4)
using namespace std; cout << trapezoidal(f, n) << endl;

class NormV { cout << endl;


private:
double mw, stdabw; for (int n = 1; n <= 512; n *= 4)
cout << trapezoidal(NormV{}, n) << endl;
public:
NormV (double mw_ = 0, double stdabw_ = 1) return 0;
: mw(mw_), stdabw(stdabw_) {} }

double operator()(double x) const {


return M_2_SQRTPI / (2 * M_SQRT2 * stdabw) 0.754598
* exp(-((x - mw) * (x - mw)) / (2*stdabw*stdabw)); 0.868203
} 0.874702
}; 0.875106
0.875131
double trapezoidal(function<double(double)> g,
int n, double a = 0, double b = 1) { 0.320457
const double h = (b - a) / n; 0.340082
double s = (g(a) + g(b))/2; 0.341266
for (int i = 1; i <= n - 1; i++) 0.34134
s += g(a + i*h); 0.341344
return h * s;
}

double f(double x) {
7 / 15

Seite 4 von 8
Funktionsobjekte Programmieren II für Studierende der Mathematik — WiSe 2025/26

Partielle Funktionsanwendung (bind)

• Oft nützlich: manche Parameter einer


Funktion vorgeben/vertauschen/… Vertausche
• STL stellt hierfür Funktion bind
double do_sub(double a, double b) {
• Funktion bind nimmt return a - b;
Funktionsobjekt und beliebig viele }
Argumente, kopiert gegebene
Parameter in zurückgegebenen Wert int main() {
using namespace placeholders;
• Spezieller Namensraum von function<double(double, double)>
Platzhaltern für Argumente des rev_sub = bind(do_sub, _2, _1);
Funktionsaufrufs cout << rev_sub(3, 1) << endl;
std::placeholders: _1, _2, . . . }

• operator() auf Wert erzeugt von


bind übergibt Parameter aus Wert -2

und aus Funktionsaufruf


8 / 15

Beispiel: Sehnentrapezregel (bind)


trapezoidal_bind.cpp 0.754598
0.847006
#include <iostream> 0.868203
#include <cmath> 0.873407
#include <functional> 0.874702
0.875025
using namespace std; 0.875106
0.875126
double trapezoidal(function<double(double)> g, 0.875131
int n, double a = 0, double b = 1) { 0.875133
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

double f(double x, double offset=0) {


x += offset;
return exp(-x * x);
}

int main() {
using namespace placeholders;
function<double(double)> f_shifted
= bind(f, _1, -0.25);

for (int n = 1; n <= 512; n *= 2)


cout << trapezoidal(f_shifted, n) << endl;

return 0;
}
9 / 15

Seite 5 von 8
Funktionsobjekte Programmieren II für Studierende der Mathematik — WiSe 2025/26

Partielle Funktionsanwendung (bind) – Referenzen

Inkrementiere
void inc(int& n) { n++; }
int main() {
int i = 0;
Funktion ref (oder cref für const) zur function<void()>
do_inc = bind(inc, ref(i));
Erzeugung von wrapper-Objekten für
Referenzen als gebundene Argumente von cout << i << endl;
bind do_inc();
cout << i << endl;
}

0
1

10 / 15

Funktionsobjekte für Methoden (mem_fn)

Für 𝑇 eine Klasse und 𝑚 eine Methode von 𝑇 :


mem_fn(&𝑇 ::𝑚 ) liefert Funktionsobjekt mit erstem Parameter Referenz (oder Zeiger) auf
Objekt von 𝑇 und restlichen Parametern wie 𝑚.

11 / 15

Seite 6 von 8
Funktionsobjekte Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Sehnentrapezregel (mem_fn)


trapezoidal_mem_fn.cpp using namespace placeholders;
NormV stdNorm{};
#include <iostream> function<double(double)> stdNormEval =
#include <cmath> bind(mem_fn(&NormV::eval), ref(stdNorm), _1);
#include <functional>
for (int n = 1; n <= 512; n *= 4)
using namespace std; cout << trapezoidal(stdNormEval, n) << endl;

class NormV { return 0;


private: }
double mw, stdabw;

public: 0.320457
NormV (double mw_ = 0, double stdabw_ = 1) 0.340082
: mw(mw_), stdabw(stdabw_) {} 0.341266
0.34134
double eval(double x) const { 0.341344
return M_2_SQRTPI / (2 * M_SQRT2 * stdabw)
* exp(-((x - mw) * (x - mw)) / (2*stdabw*stdabw));
}
};

double trapezoidal(function<double(double)> g,
int n, double a = 0, double b = 1) {
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

int main() {
12 / 15

Lambda-Ausdrücke

⟨Lambda⟩ → “[” [⟨Capture⟩ {“,” ⟨Capture⟩}] “]” “(” ⟨Params⟩ “)” “->” ⟨Type⟩ “{” ⟨Body⟩ “}”
⟨Capture⟩ → [“&”] ⟨Variable⟩
| “this”
• Erstellt eine closure, captures (gebundene Variablen) werden kopiert
• Typ von Lambda-Ausdrücken nicht ausdrückbar
• entweder konvertieren, oder
• auto

function<double(double)> f
= [](double x) -> double { return exp(-x * x); };

double off = -0.25;


function<double(double)> g
= [&f, off](double x) -> double { return f(x + off); };

13 / 15

Seite 7 von 8
Funktionsobjekte Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: Sehnentrapezregel (Lambda-Ausdrücke)


trapezoidal_lambda.cpp 0.754598
0.847006
#include <iostream> 0.868203
#include <cmath> 0.873407
#include <functional> 0.874702
0.875025
using namespace std; 0.875106
0.875126
double trapezoidal(function<double(double)> g, 0.875131
int n, double a = 0, double b = 1) { 0.875133
const double h = (b - a) / n;
double s = (g(a) + g(b))/2;
for (int i = 1; i <= n - 1; i++)
s += g(a + i*h);
return h * s;
}

int main() {
function<double(double)> f
= [](double x) -> double { return exp(-x * x); };

double off = -0.25;


function<double(double)> g
= [&f, off](double x) -> double { return f(x + off); };

for (int n = 1; n <= 512; n *= 2)


cout << trapezoidal(g, n) << endl;

return 0;
}

14 / 15

Beispiele: Datenstrukturen von Funktionsobjekten


fun_eval.cpp cout << ”Unknown function: ” << fun << endl;
}
#include <iostream> }
#include <map> }
#include <functional>
#include <cmath>
> exp 1
using namespace std; 2.71828
> log10 100
using ArithFun = double(double); 2
> arccos -1
const map<string, function<ArithFun>> funs{ 3.14159
{”exp”, static_cast<ArithFun*>(exp)}, > sinh 0.25
{”ln”, static_cast<ArithFun*>(log)}, Unknown function: sinh
{”log10”, [](double x) -> double { return log(x)/log(10); }}, >
{”cos”, static_cast<ArithFun*>(cos)},
{”sin”, static_cast<ArithFun*>(sin)},
{”arccos”, static_cast<ArithFun*>(acos)},
{”arcsin”, static_cast<ArithFun*>(asin)}
};

int main() {
while (true) {
string fun;
double val;
cout << ”> ”;
if (!(cin >> fun >> val))
break;

try {
cout << ([Link](fun))(val) << endl;
} catch (const out_of_range&) {
15 / 15

Seite 8 von 8
Programmieren II für Studierende der Mathematik WiSe 2025/26

Patterns

Kriterien für Programmqualität

Nicht nur Korrektheit

Lesbarkeit Ist nachvollziehbar was das Programm tut?


Testbarkeit Ist es einfach zu testen ob das Programm sich korrekt verhält?
Änderbarkeit Ist es einfach das Programm anzupassen sobald die Anforderungen
sich ändern?
Wiederverwertbarkeit Sind die Teile des Programms strukturell gut geeignet
wiederverwendet zu werden sobald Programm geändert/neues
Programm entwickelt werden muss?

Hohe Qualität → langfristig weniger Fehler, leichtere Fehlersuche, schnellere


Entwicklung

1 / 21

Begriff: Pattern/Anti-Pattern

Pattern Beobachtete Muster in Code hoher Qualität


Anti-Pattern (auch Code smells) Beobachtete Muster in Code niedriger Qualität

• Einzelne Anti-/Patterns oft Programmiersprachen- und Konzept-übergreifend


(Bibliotheken/Klassen/Methoden/Funktionen/…)
• Keine komplett starren Muster; nur teilweise Umsetzung der Empfehlungen oft
schon nützlich
• Oft nicht unabhängig voneinander
• Patterns verwenden, Anti-Patterns vermeiden

2 / 21

Seite 1 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Pattern: Kurzer Code

• Kürzerer Code bei gleicher Funktionalität oft besser


• Ziel sind features, nicht lines of code
• Code der nicht existiert hat auch keine bugs
• Weniger Code ist automatisch übersichtlicher
• Konziser Code jedoch gelegentlich auch schlechter verständlich

3 / 21

Pattern: Resource Acquisition Is Initialisation (RAII)

• Ressource (Betriebsmittel) ist alles was nur begrenzt zur Verfügung steht
(Arbeitsspeicher, CPU-Zeit, Dateien, Netzwerk, …)
• Ressourcen werden vom Betriebssystem verwaltet
Programm akquiriert Ressourcen; müssen oft wieder freigegeben werden (Speicher,
Dateien, …) sonst Probleme (Speicherleck, …)
• Freigabe wird jedoch oft vergessen
• Ansatz: akquiriere Ressource in Konstruktor, Freigabe in Destruktor → Verknüpft
Ressource mit Lebensdauer eines Objekts sodass Freigabe nicht vergessen werden
kann (Destruktoraufruf i.d.R. implizit)
• Leitmotiv von C++

4 / 21

Seite 2 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Einschub: Smart Pointer

Array auf dem Heap


• Standardlibrary stellt in <memory> int main() {
Klasse unique_ptr<𝑇 > unique_ptr<int[]> v{new int[1000]};
• Konstruktor nimmt Zeiger als
for (int i = 0; i < 1000; i++)
Parameter (jedoch explicit, keine v[i] = i;
implizite Typkonvertierung) }
• Destruktor ruft delete auf Zeiger auf
• Objekte von unique_ptr<𝑇 > nicht …
kopierbar ==…== All heap blocks were freed --
↩→ no leaks are possible

5 / 21

Pattern: Separation of Concerns

• Saubere Trennung zwischen Programmteilen


• Keine Abhängigkeiten von z.B. interner Darstellung anderer Datenstrukturen
• Je komplexer das Problem, desto wertvoller ist feine Aufteilung
• Faustregel für gute Aufteilung: kann ich die Aufgabe eines Programmteils in
natürlicher Sprache kurz beschreiben?

6 / 21

Seite 3 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Anti-Pattern: Feature Envy

• Ein Programmteil 𝐴 implementiert Funktionalität die eigentlich in anderen


Programmteil 𝐵 gehört
• 𝐵 sollte stattdessen ein geeignet abstraktes feature haben für diesen
Anwendungszweck
• Verletzt separation of concerns

Matrix m; Matrix m;
double wert = 7.2; double wert = 7.2;
int x = 3, y = 17; int x = 3, y = 17;
[Link][y * 5 + x] = wert; [Link](x, y) = wert;

7 / 21

Pattern: Design by Contract

• Programm aufteilen in kleine Teile (Klassen, Methoden)


• Teile separat voneinander entwickeln und testen
• Teile bekommen vor/bei Implementierung klar definierte Aufgaben, Eigenschaften,
Erwartungen und Verhalten (gemeinsam der contract)
→ Teile werden austauschbar
• Setze größere Programmteile (am Ende das ganze Programm) zusammen aus
kleineren Teilen
• Idee: wenn alle kleinen Teile ihrer Spezifikation genügen, dann auch das ganze
Programm

8 / 21

Seite 4 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Design by Contract: Entwicklung von Teilen

• In objektorientierten Sprachen (C++, Java, …) sind Klassen angedacht für die Rolle
als Teile
• Vor/Beim Implementieren überlegen und dokumentieren:
• Welche Leistungen erbringen Methoden der Klasse/die Klasse insgesamt?
I.d.R. in der Form von Nachbedingungen und Beschreibung der Effekte der Methode
• Welche Annahmen machen Methoden über Zustand des Objekts und ihre Parameter?
I.d.R. in der Form von Vorbedingungen
• Nur dokumentierte Leistungen verwenden und stets sicherstellen, dass
dokumentierte Annahmen erfüllt sind
• Klasseninvarianten sind Vorbedingung und Nachbedingung für alle Methoden (für
Konstruktoren nur Nachbedingung)

9 / 21

Einschub: assert

• Ideal: Typen der Parameter aller Methoden garnicht in der Lage invalide Werte
auszudrücken
• Realistischer: public Methoden überprüfen ihre Parameter und werfen exceptions
wenn invalide
• Parameter an private Methoden jedoch gut unter Kontrolle – diverse
Überprüfungen scheinen verschwenderisch
• <cassert> enthält Präprozessor-Makro assert; für 𝑏 Ausdruck vom Typ bool,
verwende assert((𝑏 ))
• 𝑏 wird zur Laufzeit ausgewertet, bricht Ausführung ab falls false
• Übersetzung mit Compiler-Kommandozeilenparameter -DNDEBUG entfernt alle
asserts

10 / 21

Seite 5 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Beispiel: assert

g++ [Link]; ./[Link]


[Link]

#include <iostream> assert: _copy/[Link]: int


#include <cassert> ↩→ main(): Assertion `(1 == 2)'
↩→ failed.
using namespace std;

int main() {
assert((1 == 2));
g++ -DNDEBUG [Link]; ./[Link]
cout << ”output” << endl;
}
output

11 / 21

Anti-Pattern: Code Duplikation

• Code wird an verschieden Stellen kopiert, evtl. jeweils leicht angepasst


• Unübersichtlich und unleserlich
• Änderungen werden leicht inkonsistent, werden oft nicht korrekt an alle anderen
Stellen übertragen wo i.W. identischer Code vorkommt
• Relevante Unterschiede gehen unter in viel umliegendem kopiertem Code und
werden schwer erkennbar
• Stattdessen Code-Struktur überdenken; Code in geteilte Funktionen auslagern

12 / 21

Seite 6 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Pattern: Don't Repeat Yourself (DRY)

• Jedes feature und jedes Stück Wissen (Konstanten/Algorithmen/…) sollte an einer


Stelle im Programm repräsentiert sein
• Sobald sich Anforderungen ändern muss nur die eine Stelle geändert werden
• Vermeidet Inkonsistenz innerhalb des Programms und damit Fehler

13 / 21

Anti-Pattern: Lange Funktionen/Methoden

• Zu lange Funktionen/Methoden sind oft Symptom anderer Probleme:


• Zu viel Funktionalität ⇒ schlechte Wiederverwertbarkeit
• Code-Duplikation
• Umständliche Lösung ⇒ oft schlechtere Lesbarkeit, mehr Gelegenheiten für Fehler
• Kürzere Funktionen/Methoden i.d.R. einfacher zu verstehen
• Funktionen/Methoden vergleichsweise einfach separat zu testen ⇒ umfangreiche
Funktionen/Methoden erschweren Fehlersuche

14 / 21

Seite 7 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Anti-Pattern: Zu große Klassen

• Auch Gott-Objekt
• Probleme analog zu denen zu langer Funktion/Methode
• Programmfluss durch umfangreiche Klasse oft schwer nachzuvollziehen
• Zusätzlich oft Symptom schlechter Rollenverteilung/unsauberer Trennung von
Programmteilen

15 / 21

Anti-Pattern: Viele Variablen

Oft Symptom für:


• Code-Duplikation; Zwischenwerte evtl. mehrfach berechnet mit anderen Namen?
• Berechnungsablauf unsauber definiert/strukturiert
• Zu große Funktion/Methode/Klasse
• Ergebnisse von Berechnungen werden nicht (mehr) verwendet

16 / 21

Seite 8 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Pattern: Wenige Variablen, kleiner Gültigkeitsbereich

Möglichst wenige Variablen im aktuellen Gültigkeitsbereich:


• Einfachere Übersicht über alle relevanten Variablen
• Kürzere Einlesezeit
• Reduziert versehentliche Verwechslungen

Hierfür:
• Immer minimalen Gültigkeitsbereich wählen
• Keine globalen Variablen verwenden
• Schleifenvariablen in Schleife deklarieren
• Zwischenergebnisse von Methoden nicht in Objekt-Attributen speichern
• Variablen bei erster Verwendung deklarieren; nicht davor
• Siehe „Viele Variablen“, „Code Duplikation“

17 / 21

Pattern: Gute Namen

• Name der Variable sollte ihre Funktion beschreiben nicht Inhalt (const int BITS =
8; nicht const int EIGHT = 8)
• Mittlere Länge oft am Besten
• Zu kurz (1-3 Zeichen) nicht aussagekräftig
• Zu lang behindert Lesefluss
• Je kleiner der Gültigkeitsbereich, desto besser sind kurze Namen gerechtfertigt (z.B.
Schleifenvariable i)

18 / 21

Seite 9 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Anti-Pattern: Hungarian Notation/Name typing

set<Student> getSetOfAllStudents();
map<Seminar, set<Student>> studentSetPerSeminarMap;

Nach der nächsten Änderung:

list<Student> getSetOfAllStudents();
map<Seminar, list<Student>> studentSetPerSeminarMap;

Typannotationen bereits hinreichend; Kodierung zusätzlich im Namen redundant

19 / 21

Pattern: Gut passender Typ

• Bei Einführen einer Variable/Deklarieren einer Datenstruktur stets Typ wählen, der
zu späterem Inhalt und Verwendung möglichst exakt passt
• Z.B. vorzeichenlose Zahlentypen wo möglich
• Lieber ABC (siehe Vererbung) statt konkreter davon abgeleiteter Implementierung
• Make invalid states unrepresentable

20 / 21

Seite 10 von 11
Patterns Programmieren II für Studierende der Mathematik — WiSe 2025/26

Anti-Pattern: Cargo Cult Programming

Dinge verwenden, die man nicht wirklich verstanden hat


• (Anti-)Patterns beachten ohne Gründe zu verstehen
• Code aus anderer Stelle kopieren und verwenden

Unverstandenen Code kann man auch nicht debuggen

21 / 21

Seite 11 von 11
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Blatt 0 – Lösungsvorschlag

Aufgabe 1 Implementieren Sie ein Hauptprogramm, das über Kommandozeilenparameter eine vorzeichen-
lose ganze Zahl 𝑛 und den Namen einer Datei übergeben bekommt. Die Datei soll eingelesen und alle in der
Datei enthaltenen Zeichenketten von druckbaren Nichtzwischenraum-Zeichen der Länge 𝑛 oder länger auf
separaten Zeilen ausgegeben werden. Es soll jeweils nur die Gesamtzeichenkette ausgegeben werden, nicht
einzelne Teilzeichenketten (insb. wird jedes Zeichen maximal so oft ausgegeben, wie es in der Eingabedatei
vorkommt).
Achten Sie auf geeignete Fehlerbehandlung beim Verarbeiten der Kommandozeilenparameter und beim Öff-
nen der Datei.
Hinweis (Kontrollergebnis). Mit Kommandozeilenparametern und Inhalt der Datei [Link] wie unten,
könnte folgende Ausgabe produziert werden:

Lorem
17;
Ipsum

./[Link] 3 [Link]

[Link]

Lorem 17; Ipsum !

[Link]

#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <locale>
#include <cstring>

using namespace std;

int main(int argc, char* argv[]) {


if (argc != 3) {
cerr << ”usage: ” << argv[0] << ” <N> <FILE>” << endl;
return 2;
}

istringstream arg_inp{argv[1]};
unsigned int n;
arg_inp >> n;

ifstream ein{argv[2]};
if (!ein) {
cerr << ”Could not open: ” << argv[2] << endl
<< strerror(errno) << endl;

Seite 1 von 4
Aufgabe 2 Programmieren II für Studierende der Mathematik — WiSe 2025/26

return 1;
}

char c;
string s;
while ([Link](c)) {
if (isprint(c, locale()) && !isspace(c, locale()))
s += c;
else {
if ([Link]() >= n) cout << s << endl;
s = ””;
}
}

if ([Link]() >= n) cout << s << endl;

return 0;
}

Aufgabe 2 Zur näherungsweisen Berechnung der Kosinusfunktion an der Stelle 𝑥 ∈ R nahe 0 soll, mit
𝑛 ∈ N, die Taylor-Reihe verwendet werden:
Õ
𝑛
𝑥 2𝑘
𝑐𝑛 (𝑥) := (−1)𝑘
(2𝑘)!
𝑘=0

Implementieren Sie diese Reihe durch eine Iteration mit einer geeigneten Rekursionsbeziehung als Funktion
cos_taylor mit einem Parameter und Rückgabetyp jeweils sinnvoll. Brechen Sie die Iteration ab, sobald gilt:

𝑥 2𝑘
≤ 𝜀 · |𝑐𝑘 (𝑥)|
(2𝑘)!

taylor

double cos_taylor(double x) {
double s = 1, a = 1;
int k = 0;
while (abs(a) > numeric_limits<double>::epsilon() * abs(s)) {
a *= -1 * x * x / (2 * k + 1) / (2 * k + 2);
s += a;
k++;
}

return s;
}

Seite 2 von 4
Aufgabe 2 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Implementieren Sie unter Verwendung Ihrer Funktion cos_taylor und folgender Identitäten eine Funktion
mycos:

 cos(−𝑥) 𝑥<0

 𝑥 



 cos 𝑥 − 2𝜋 · 2𝜋 2𝜋 ≤ 𝑥



 − cos(𝑥 − 𝜋)
 𝜋 ≤ 𝑥 < 2𝜋
cos(𝑥) =

 − cos(𝜋 − 𝑥) 𝜋/2 < 𝑥 < 𝜋

 q 2



 1 − cos 𝜋2 − 𝑥 𝜋/4 < 𝑥 < 𝜋/2


𝑐𝑛 (𝑥) sonst, 𝑛 ∈ N

mycos

double mycos(double x) {
if (x < 0)
return mycos(-x);
if (x >= 2 * PI)
return mycos(fmod(x, 2 * PI));
if (PI <= x)
return - mycos(x - PI);
if (PI / 2 < x)
return - mycos(PI - x);
if (PI / 4 < x) {
double s = mycos(PI / 2 - x);
return sqrt(1 - s * s);
}

return cos_taylor(x);
}

Implementieren Sie ein Hauptprogramm, das Ihre Funktion mycos mit der arith. Funktion cos aus der Stan-
dardlibrary vergleicht. Geben Sie hierfür eine Tabelle mit beschrifteten Spalten aus, welche für 𝑥 = −𝜋 für
𝑥 und danach in Schritten der Größe 20 1
𝜋 aufsteigend bis inklusive 𝜋 jeweils 𝑥, cos angewendet auf 𝑥 und
mycos angewendet auf 𝑥 enthält. Passen Sie die Anzahl von ausgegebenen Nachkommastellen gut an die zur
Verfügung stehende Genauigkeit an.

[Link]

#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>

using namespace std;

double PI = 4 * atan(1);

taylor
mycos

const int d10 = numeric_limits<double>::digits10;

int main() {

Seite 3 von 4
Aufgabe 2 Programmieren II für Studierende der Mathematik — WiSe 2025/26

cout << setprecision(d10) << scientific << setw(d10 + 7) << ”x” << setw(d10 + 8) <<
↩→ ”cos(x)” << setw(d10 + 8) << ”mycos(x)” << endl;
for (int i = 0; i <= 40; i++) {
double x = -PI + i * (PI / 20);
cout << setw(d10 + 7) << x
<< setw(d10 + 8) << cos(x)
<< setw(d10 + 8) << mycos(x)
<< endl;
}
}

x cos(x) mycos(x)
-3.141592653589793e+00 -1.000000000000000e+00 -1.000000000000000e+00
-2.984513020910303e+00 -9.876883405951377e-01 -9.876883405951378e-01
-2.827433388230814e+00 -9.510565162951535e-01 -9.510565162951535e-01
-2.670353755551324e+00 -8.910065241883678e-01 -8.910065241883679e-01
-2.513274122871834e+00 -8.090169943749473e-01 -8.090169943749475e-01
-2.356194490192345e+00 -7.071067811865475e-01 -7.071067811865475e-01
-2.199114857512855e+00 -5.877852522924730e-01 -5.877852522924731e-01
-2.042035224833366e+00 -4.539904997395467e-01 -4.539904997395466e-01
-1.884955592153876e+00 -3.090169943749473e-01 -3.090169943749476e-01
-1.727875959474386e+00 -1.564344650402308e-01 -1.564344650402308e-01
-1.570796326794897e+00 6.123233995736766e-17 0.000000000000000e+00
-1.413716694115407e+00 1.564344650402309e-01 1.564344650402308e-01
-1.256637061435917e+00 3.090169943749475e-01 3.090169943749476e-01
-1.099557428756428e+00 4.539904997395468e-01 4.539904997395466e-01
-9.424777960769379e-01 5.877852522924731e-01 5.877852522924731e-01
-7.853981633974483e-01 7.071067811865476e-01 7.071067811865475e-01
-6.283185307179586e-01 8.090169943749475e-01 8.090169943749475e-01
-4.712388980384690e-01 8.910065241883679e-01 8.910065241883679e-01
-3.141592653589793e-01 9.510565162951535e-01 9.510565162951535e-01
-1.570796326794897e-01 9.876883405951378e-01 9.876883405951378e-01
0.000000000000000e+00 1.000000000000000e+00 1.000000000000000e+00
1.570796326794897e-01 9.876883405951378e-01 9.876883405951378e-01
3.141592653589793e-01 9.510565162951535e-01 9.510565162951535e-01
4.712388980384690e-01 8.910065241883679e-01 8.910065241883679e-01
6.283185307179586e-01 8.090169943749475e-01 8.090169943749475e-01
7.853981633974483e-01 7.071067811865476e-01 7.071067811865475e-01
9.424777960769379e-01 5.877852522924731e-01 5.877852522924731e-01
1.099557428756428e+00 4.539904997395468e-01 4.539904997395466e-01
1.256637061435917e+00 3.090169943749475e-01 3.090169943749476e-01
1.413716694115407e+00 1.564344650402309e-01 1.564344650402308e-01
1.570796326794897e+00 6.123233995736766e-17 0.000000000000000e+00
1.727875959474386e+00 -1.564344650402308e-01 -1.564344650402308e-01
1.884955592153876e+00 -3.090169943749473e-01 -3.090169943749476e-01
2.042035224833366e+00 -4.539904997395467e-01 -4.539904997395466e-01
2.199114857512855e+00 -5.877852522924730e-01 -5.877852522924731e-01
2.356194490192345e+00 -7.071067811865475e-01 -7.071067811865475e-01
2.513274122871834e+00 -8.090169943749473e-01 -8.090169943749475e-01
2.670353755551324e+00 -8.910065241883678e-01 -8.910065241883679e-01
2.827433388230814e+00 -9.510565162951535e-01 -9.510565162951535e-01
2.984513020910303e+00 -9.876883405951377e-01 -9.876883405951378e-01
3.141592653589793e+00 -1.000000000000000e+00 -1.000000000000000e+00

Seite 4 von 4
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Blatt 1 – Lösungsvorschlag

Aufgabe 3 Erstellen Sie eine Klasse Complex, die komplexe Zahlen 𝑧 = 𝑟 · 𝑒 𝑖𝜑 , 𝑟 ≥ 0, 𝜑 ∈ [−𝜋, 𝜋], intern
mithilfe der Datenkomponenten r und phi speichert. Auch für r und phi soll sichergestellt werden, dass die
obigen Invarianten stets gelten.

Um mit sinnvoller Kapselung sicherzustellen, dass die Invarianten erfüllt sind sollten r und phi auf
jeden Fall private sein. Dies verhindert, dass mit der Klasse Complex nicht assoziierter Code durch
direkten Zugriff auf die Attribute die Invariante beliebig verletzen kann.

Klasse

class Complex {
private:
double r, phi;

public:

Konstruktor

Arithmetik

Funktionen

Ausgabe
};

√ Konstruktor soll Argumente x und y akzeptieren und die komplexe Zahl 𝑧 = 𝑥 + 𝑖𝑦 erzeugen (d.h. 𝑟 =
Der
𝑥 2 + 𝑦 2 und 𝜑 = atan2(𝑦, 𝑥)). Sind x, y oder beide nicht angegeben, so soll stattdessen jeweils 0 verwendet
werden. D.h. den Konstruktor ohne Argumente aufzurufen soll die komplexe Zahl 0 anlegen.

Konstruktor

Complex(double Re=0, double Im=0):


r(sqrt(Re*Re + Im*Im)), phi(atan2(Im, Re)) {}

Überladen Sie die arithmetischen Operatoren +, -, * und / in mathematisch sinnvoller Weise.

Seite 1 von 7
Aufgabe 3 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Hinweis. Für 𝑧 1 = 𝑟 1 · 𝑒 𝑖𝜑1 und 𝑧 2 = 𝑟 2 · 𝑒 𝑖𝜑2 gilt mit 𝑘 ∈ Z jeweils geeignet:



𝑧 1 ± 𝑧 2 = 𝑟 · 𝑒 𝑖𝜑 mit 𝑟 = 𝑟 12 + 𝑟 22 ± 2 · 𝑟 1𝑟 2 cos(𝜑 1 − 𝜑 2 )
𝜑 = atan2(𝑟 1 sin(𝜑 1 ) ± 𝑟 2 sin(𝜑 2 ), 𝑟 1 cos(𝜑 1 ) ± 𝑟 2 cos(𝜑 2 ))

𝑧 1 · 𝑧 2 = 𝑟 · 𝑒 𝑖𝜑 mit 𝑟 = 𝑟 1 · 𝑟 2
𝜑 = 𝜑 1 + 𝜑 2 + 𝑘 · 2𝜋 ∈ [−𝜋, 𝜋]
𝑧1 𝑟1
= 𝑟 · 𝑒 𝑖𝜑 mit 𝑟 =
𝑧2 𝑟2
𝜑 = 𝜑 1 − 𝜑 2 + 𝑘 · 2𝜋 ∈ [−𝜋, 𝜋]

In den Fällen * und / gilt, wegen der Invariante für phi, jeweils |𝜑 1 ± 𝜑 2 | ≤ 2𝜋. Es reicht daher aus
geeignet ±2𝜋 zu addieren um wieder in den gewünschten Bereich zu kommen.

Arithmetik

friend Complex operator+(Complex z1, Complex z2) {


Complex z;
z.r = sqrt(z1.r*z1.r + z2.r*z2.r + 2*z1.r*z2.r*cos([Link] - [Link]));
[Link] = atan2(z1.r*sin([Link]) + z2.r*sin([Link]),
z1.r*cos([Link]) + z2.r*cos([Link]));
return z;
}

friend Complex operator-(Complex z1, Complex z2) {


Complex z;
z.r = sqrt(z1.r*z1.r + z2.r*z2.r - 2*z1.r*z2.r*cos([Link] - [Link]));
[Link] = atan2(z1.r*sin([Link]) - z2.r*sin([Link]),
z1.r*cos([Link]) - z2.r*cos([Link]));
return z;
}

friend Complex operator*(Complex z1, Complex z2) {


Complex z;
z.r = z1.r * z2.r;
[Link] = [Link] + [Link];

if ([Link] > M_PI) [Link] -= 2*M_PI;


if ([Link] < -M_PI) [Link] += 2*M_PI;

return z;
}

friend Complex operator/(Complex z1, Complex z2) {


Complex z;
z.r = z1.r / z2.r;
[Link] = [Link] - [Link];

if ([Link] > M_PI) [Link] -= 2*M_PI;


if ([Link] < -M_PI) [Link] += 2*M_PI;

return z;
}

Seite 2 von 7
Aufgabe 3 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Erstellen Sie zudem für die Exponentialfunktion (exp), den Hauptwert der Quadratwurzel (sqrt), den Log-
arithmus (log) und die allgemeine Potenzfunktion (pow) jeweils entsprechende Funktionen.
Hinweis. Für 𝑧 = 𝑟 · 𝑒 𝑖𝜑 gilt mit 𝑘 ∈ Z jeweils geeignet:

exp(𝑧) = 𝑟 ′ · 𝑒 𝑖𝜑 mit 𝑟 ′ = 𝑒 𝑟 cos(𝜑 )
𝜑 ′ = 𝑟 sin(𝜑) + 𝑘 · 2𝜋 ∈ [−𝜋, 𝜋]
√ ′ √
𝑧 = 𝑟 ′ · 𝑒 𝑖𝜑 mit 𝑟 ′ = 𝑟
𝜑
𝜑′ =
2
log(𝑧) = ln(𝑟 ) + 𝑖𝜑

Im Fall exp muss 𝜑 in den gewünschten Bereich gebracht werden. Hierfür verwenden wir zunächst
die Funktion fmod; diese berechnet den Rest der Division zweier Gleitpunktzahlen wiederum als Gleit-
punktzahl. Um 𝜑 ∈ [−2𝜋, 2𝜋] wieder in den gewünschten Bereich zu bringen, reicht es dann wieder
±2𝜋 zu addieren. Alternativ, aber weniger performant, könnte auch eine Schleife verwendet werden.

Funktionen

friend Complex sqrt(Complex z1) {


Complex z;
z.r = sqrt(z1.r);
[Link] = [Link] / 2;
return z;
}

friend Complex exp(Complex z1) {


Complex z;
z.r = exp(z1.r * cos([Link]));
[Link] = z1.r * sin([Link]);

[Link] = fmod([Link], 2*M_PI);


if ([Link] > M_PI) [Link] -= 2*M_PI;
if ([Link] < -M_PI) [Link] += 2*M_PI;

return z;
}

friend Complex log(Complex z1) {


return Complex{log(z1.r), [Link]};
}

friend Complex pow(Complex z1, Complex z2) {


return exp(z2 * log(z1));
}

Überladen Sie den Shiftoperator << geeignet, sodass die Ausgabe komplexer Zahlen wie in der Standardbiblio-
thek erfolgt.
Beispiel. Für 𝑧 = 41 + 23,1 · 𝑖 soll (41,23.1) ausgegeben1 werden.

1 ohne aktive Ausgabe-Manipulatoren

Seite 3 von 7
Aufgabe 3 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Es ist sinnvoll Methoden, die den Zustand des Objekts (also die Attribute) nicht verändern mit const
zu markieren.

Ausgabe

double real() const {


return r * cos(phi);
}

double imag() const {


return r * sin(phi);
}

friend ostream& operator<<(ostream& stream, Complex z) {


return stream << ”(” << [Link]() << ”,” << [Link]() << ”)”;
}

Erstellen Sie ein Hauptprogramm in dem zwei Zahlen 𝑧 1 und 𝑧 2 zunächst als complex<double>-Zahlen ein-
gelesen werden. Erzeugen Sie auch die entsprechenden Zahlen vom Typ Complex. Berechnen und geben Sie
die folgenden Ausdrücke für die Zahlen vom Typ Complex und zum Vergleich auch für die Zahlen vom Stan-
darddatentyp complex<double> aus:
𝑧1 − 𝑧2 √
𝑧1 · 𝑧2 exp(𝑧 1 ) log(𝑧 2 ) 𝑧𝑧12
𝑧1 + 𝑧2

Hauptprogramm

int main()
{
complex<double> w1,w2;

cout << ”z1 z2: ”;


cin >> w1 >> w2;

Complex z1{[Link](), [Link]()}, z2{[Link](), [Link]()};

cout << fixed << setprecision(numeric_limits<double>::digits10);


cout << ”(z1-z2)/(z1+z2):” << endl
<< ” ” << (z1-z2)/(z1+z2) << ” ” << (w1-w2)/(w1+w2) << endl;
cout << ”sqrt(z1*z2):” << endl
<< ” ” << sqrt(z1*z2) << ” ” << sqrt(w1*w2) << endl;
cout << ”exp(z1):” << endl
<< ” ” << exp(z1) << ” ” << exp(w1) << endl;
cout << ”log(z2):” << endl
<< ” ” << log(z2) << ” ” << log(w2) << endl;
cout << ”pow(z1,z2):” << endl
<< ” ” << pow(z1,z2) << ” ” << pow(w1,w2) << endl;

return 0;
}

[Link]

#include <iostream>
#include <iomanip>

Seite 4 von 7
Aufgabe 3 Programmieren II für Studierende der Mathematik — WiSe 2025/26

#include <complex>
#include <cmath>
#include <limits>

using namespace std;

Klasse

Hauptprogramm

Führen Sie Ihr Programm aus für 𝑧 1 = 1 + 𝑖 und 𝑧 2 = 2 + 𝑖.

z1 z2: (1, 1) (2, 1)


(z1-z2)/(z1+z2):
(-0.230769230769231,0.153846153846154) (-0.230769230769231,0.153846153846154)
sqrt(z1*z2):
(1.442615274452683,1.039778260055571) (1.442615274452683,1.039778260055571)
exp(z1):
(1.468693939915886,2.287355287178843) (1.468693939915885,2.287355287178842)
log(z2):
(0.804718956217050,0.463647609000806) (0.804718956217050,0.463647609000806)
pow(z1,z2):
(-0.309743504928493,0.857658012588736) (-0.309743504928494,0.857658012588736)

Seite 5 von 7
Aufgabe 4* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Aufgabe 4* Vereinbaren Sie ein Template für eine Funktion pNorm. Templateparameter sollen ein
Gleitpunktzahlen-Typ 𝑇 (diese Eigenschaft muss hier und im Folgenden nicht überprüft werden) und eine
positive ganze Zahl 𝑝 sein. pNorm soll für einen Parameter vom Datentyp complex aus der Standardlibrary
(für den gegebenen Gleitpunktzahlen-Typ) die 𝑝-Norm dieser komplexen Zahl berechnen und als Wert des
gegebenen Gleitpunktzahlen-Typs zurückgeben.
Hinweis. Die 𝑝-Norm ∥ · ∥ 𝑝 einer komplexen Zahl ist definiert als:
( )1
∥𝑎 + 𝑖𝑏 ∥ 𝑝 := |𝑎|𝑝 + |𝑏 |𝑝 𝑝

pNorm

template<class T = double, unsigned int p = 2>


T pNorm(const complex<T>& z) {
return pow(pow(abs([Link]()), p) + pow(abs([Link]()), p), static_cast<T>(1) / p);
}

Vereinbaren und implementieren Sie eine von complex abgeleitete Klasse complex_norm. Die Definition soll
ein Template mit Parametern für den zu verwendenen Gleitpunktzahlentyp und eine Norm-Funktion sein. Es
soll sich eine Instanz Ihres templates pNorm für den zweiten Parameter einsetzen lassen.
Die Klasse complex_norm soll eine konstante Methode norm haben, die die als template Parameter übergeben
Norm-Funktion aufruft.

complex norm

template<class T = double, T _norm(const complex<T>&) = pNorm<T>>


class complex_norm : public complex<T> {
public:
complex_norm(T real_, T imag_): complex<T>(real_, imag_) {}

T norm() const {
return _norm(*this);
}
};

Vereinbaren Sie unter Verwendung von complex_norm und pNorm einen parametrisierten Typalias
complex_pnorm. Parameter sollen ein Gleitpunktzahlentyp 𝑇 und eine positive ganze Zahl 𝑝 sein.

complex pnorm

template<class T = double, unsigned int p = 2>


using complex_pnorm = complex_norm<T, pNorm<T, p>>;

Die 𝑝-Normen sind äquivalent. D.h. für jedes Paar 1 < 𝑝 1 ≤ 𝑝 2 ∈ N ∪ {∞} existiert eine Konstante 𝑐 ∈ R
sodass für alle 𝑥 ∈ C gilt:
∥𝑥 ∥ 𝑝 1 ≤ 𝑐 ∥𝑥 ∥ 𝑝 2

Es gilt:
∥1 + 𝑖 ∥ 𝑝 1
𝑐=
∥1 + 𝑖 ∥ 𝑝 2

Implementieren Sie ein Hauptprogramm, das zunächst 𝑐 für 𝑝 1 = 2 und 𝑝 2 = 3 berechnet und mit einer gut an
die Genauigkeit angepassten Anzahl von Nachkommastellen ausgibt. Danach sollen solange komplexe Zahlen

Seite 6 von 7
Aufgabe 4* Programmieren II für Studierende der Mathematik — WiSe 2025/26

𝑧𝑘 von der Standardeingabe eingelesen werden, solange dies nicht fehlschlägt und jeweils das Verhältnis
𝑐𝑘 = ∥𝑧 ∥2
∥𝑧 ∥ 3 berechnet und ebenfalls ausgegeben werden. Es soll zudem ausgegeben werden ob 𝑐 𝑘 ≤ 𝑐 gilt.

[Link]

#include <iostream>
#include <iomanip>
#include <limits>
#include <cmath>
#include <complex>

using namespace std;

pNorm

complex norm

complex pnorm

int main() {
complex_pnorm<double, 2> z1{1, 1};
complex_pnorm<double, 3> z2{1, 1};

double bound = [Link]() / [Link]();

cout << setprecision(numeric_limits<double>::digits10 + 1) << boolalpha;


cout << bound << endl;

double real_, imag_;


while (true) {
cout << ”real, imag = ”;
if (!(cin >> real_ >> imag_)) break;

z1 = complex_pnorm<double, 2>{real_, imag_};


z2 = complex_pnorm<double, 3>{real_, imag_};
double val = [Link]() / [Link]();
cout << val << ” ” << (val <= bound) << endl;
}
}

Beispiel (Kontrollergebnis). Ihr Programm könnte sich verhalten, wie folgt:

1.122462048309373
real, imag = 1.5 2.0
1.111619628853093 true
real, imag = 7.0 7.0
1.122462048309373 true
real, imag = 3.7 42.1
1.003627499490993 true
real, imag =

Seite 7 von 7
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Blatt 2 – Lösungsvorschlag

Aufgabe 5 Erstellen Sie eine Klasse Polynom, die Polynome über den reellen Zahlen (approximiert durch
∑𝑛 𝑘
double) 𝑝 = 𝑘=0 𝑎𝑘 𝑥 intern mithilfe einer Datenkomponente a vom Typ vector<double> speichert. Es
sollen nur die Koeffizienten 𝑎𝑘 bis einschließlich des Höchstkoeffizienten gespeichert werden. Der Höchstko-
effizient ist per Definition ungleich 0.

Um zu verhindern, dass die Invariante bzgl. des Höchstkoeffizienten verletzt wird, also das Element des
internen Vektors mit höchstem Index stets ungleich 0 ist, wird a als private vereinbart.
Es wird zudem im Konstruktor, der einen vector<double> als Argument nimmt, und im Folgenden
darauf geachtet dass alle neu gebildeten Polynome stets normalisiert werden.

Klasse

class Polynom {
private:
vector<double> a;

explicit Polynom(int n) : a(n + 1, 0) {}


void normalize() {
while ([Link]() > 0 && [Link]() == 0)
a.pop_back();
}

public:

Konstruktor Nullpolynom
Polynom(const vector<double>& a_) : a(a_) {
normalize();
}

grad
deriv

Arithmetische Operatoren

Einausgabe
};

Das Nullpolynom soll durch einen leereren Koeffizientenvektor dargestellt werden1 und durch einen Kon-
struktor mit leerer Argumentenliste erzeugt werden können.

Konstruktor Nullpolynom

Polynom() : a(0) {}

Erstellen Sie die folgenden Methoden:


1 Überzeugen Sie sich davon, dass die Invariante bzgl. des Höchstkoeffizienten hierdurch nicht verletzt wird

Seite 1 von 5
Aufgabe 5 Programmieren II für Studierende der Mathematik — WiSe 2025/26

grad Berechnet den Grad des Polynoms auf das sie angewandt wird möglichst effizient und gibt ihn als int
zurück (−1 für das Nullpolynom)

grad

int grad() const { return [Link]() - 1; }

deriv Liefert für das Polynom 𝑝 das abgeleitete Polynom 𝑝 ′

deriv

Polynom deriv() const {


if (grad() <= 0)
return Polynom{};

Polynom p{grad() - 1};


for (unsigned int i = 1; i < [Link](); i++)
p.a[i - 1] = i * a[i];

return p;
}

Überladen Sie die arithmetischen Operatoren für Addition, Multiplikation und Subtraktion von Objekten der
Klasse Polynom in mathematisch sinnvoller Weise. Zur Implementierung der Multiplikation können Sie fol-
gende Beziehung verwenden:

( ) ( )

𝑚 ∑
𝑛 ∑
𝑚+𝑛 ∑
min(𝑘,𝑚)
𝑎𝑖 𝑥 𝑖 · 𝑏𝑗𝑥 𝑗 = 𝑐𝑘 𝑥 𝑘 mit 𝑐𝑘 = 𝑎𝑖 𝑏𝑘 −𝑖
𝑖=0 𝑗=0 𝑘=0 𝑖=max(0,𝑘 −𝑛)

Hinweis. Zur Berechnung von min und max stehen in der Standarbibliothek (<algorithm>) bereits gleichna-
mige Implementierungen für beliebige Typen zur Verfügung.

Arithmetische Operatoren

friend Polynom operator+(Polynom p, Polynom q) {


int m = [Link](), n = [Link]();

Polynom r{max(m, n)};


for (int i = 0; i < max(m, n) + 1; i++) {
if (i < m + 1)
r.a[i] += p.a[i];
if (i < n + 1)
r.a[i] += q.a[i];
}

[Link]();

return r;
}

Polynom operator-() const {


Polynom r{*this};

for (unsigned int i = 0; i < [Link](); i++)

Seite 2 von 5
Aufgabe 5 Programmieren II für Studierende der Mathematik — WiSe 2025/26

r.a[i] = -r.a[i];

return r;
}
friend Polynom operator-(Polynom p, Polynom q) { return p + (-q); }

friend Polynom operator*(Polynom p, Polynom q) {


int m = [Link](), n = [Link]();

if (m < 0 || n < 0)
return Polynom{};

Polynom r{m + n};


for (int k = 0; k <= m + n; k++)
for (int i = max(0, k - n); i <= min(k, m); i++)
r.a[k] += p.a[i] * q.a[k - i];

return r;
}

Polynome sollen durch Einlesen von 𝑛 𝑎 0 𝑎 1 𝑎 2 . . . 𝑎𝑛 von der Standardeingabe erstellt werden können.
Zudem sollen Polynome in der Form 𝑎 0 + 𝑎 1 *x + 𝑎 2 *x^2 + . . . + 𝑎𝑛 *x^𝑛 ausgegeben werden können.
Überladen Sie die Shift-Operatoren entsprechend.

Einausgabe

friend istream& operator>>(istream& stream, Polynom &p) {


int n;
stream >> n;

p = Polynom{max(-1, n)};
for (unsigned int i = 0; i < [Link](); i++)
stream >> p.a[i];
[Link]();

return stream;
}
friend ostream& operator<<(ostream& stream, const Polynom& p) {
bool at_begin = true;
for (unsigned int i = 0; i < [Link](); i++) {
if (p.a[i] == 0)
continue;

if (!at_begin)
stream << ” + ”;
else
at_begin = false;

if (p.a[i] != 1 || i == 0)
stream << p.a[i];
if (p.a[i] != 1 && i != 0)
stream << ”*”;
if (i != 0)
stream << ”x”;
if (i > 1)
stream << ”^” << i;
}

Seite 3 von 5
Aufgabe 5 Programmieren II für Studierende der Mathematik — WiSe 2025/26

if (at_begin)
stream << ”0”;

return stream;
}

Schreiben Sie ein Hauptprogramm in dem Sie zwei Polynome 𝑝 und 𝑞 von der Standardeingabe einlesen.
Berechnen Sie dann (𝑝𝑞) ′ und 𝑝 ′𝑞 + 𝑝𝑞 ′ und geben Sie die beiden Resultate aus. Geben Sie zudem jeweils den
Grad der Polynome 𝑝, 𝑞, (𝑝𝑞) ′ und (𝑝𝑞) ′ − 𝑝 ′𝑞 − 𝑝𝑞 ′ aus.

Hauptprogramm

int main() {
Polynom p, q;

cout << ”p q: ”;
cin >> p >> q;

// cout << ”p = ” << p << endl;


// cout << ”-p = ” << -p << endl;
// cout << ”q = ” << q << endl;
// cout << ”p+q = ” << p+q << endl;
// cout << ”p-q = ” << p-q << endl;
// cout << ”p*q = ” << p*q << endl;

cout << ”(pq)' = ” << (p * q).deriv() << endl;


cout << ”p'q + pq' = ” << ([Link]() * q + p * [Link]()) << endl;

cout << ”grad(p) = ” << [Link]() << endl;


cout << ”grad(q) = ” << [Link]() << endl;
cout << ”grad((pq)') = ” << (p * q).deriv().grad() << endl;
cout << ”grad((pq)' - (p'q + pq')) = ” << ((p * q).deriv() - ([Link]() * q + p *
↩→ [Link]())).grad() << endl;

return 0;
}

[Link]

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

Klasse

Hauptprogramm

Führen Sie Ihr Programm aus für 𝑝 = 1 − 2𝑥 + 𝑥 2 und 𝑞 = 𝑥 + 2𝑥 2 + 3𝑥 3 + 4𝑥 4 + 5𝑥 5 .


Hinweis (Kontrollergebnis). Ihr Programm könnte die folgende Ausgabe produzieren:

Seite 4 von 5
Aufgabe 5 Programmieren II für Studierende der Mathematik — WiSe 2025/26

p q: 2 1 -2 1
5 0 1 2 3 4 5
(pq)' = 1 + -36*x^5 + 35*x^6
p'q + pq' = 1 + -36*x^5 + 35*x^6
grad(p) = 2
grad(q) = 5
grad((pq)') = 6
grad((pq)' - (p'q + pq')) = -1

Seite 5 von 5
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Blatt 3 – Lösungsvorschlag

Aufgabe 6 Erstellen Sie eine Klasse rational deren Objekte rationale Zahlen modellieren. Die interne
Darstellung soll Zähler und Nenner jeweils als private Datenkomponenten vom Typ long int speichern.
Die interne Darstellung jeder Zahl soll eindeutig sein, insb. der gespeicherte Bruch also jederzeit vollständig
gekürzt sein.
Hinweis (Euklidischer Algorithmus). Gegeben zwei Zahlen 𝑎 ∈ N \ {0} und 𝑏 ∈ N mit obdA. 𝑎 > 𝑏 ist ihr
größter gemeinsamer Teiler ggT(𝑎, 𝑏) ∈ N die größte natürliche Zahl, sodass 𝑎 und 𝑏 beide ohne Rest
durch ggT(𝑎, 𝑏) teilbar sind.
Es gilt ggT(𝑎, 𝑏) = ggT(𝑏, 𝑎 mod 𝑏) und ggT(𝑎, 0) = 𝑎.
Ein Bruch kann vollständig gekürzt werden indem Zähler 𝑝 ∈ N und Nenner 𝑞 ∈ N \ {0} jeweils geteilt
werden durch ggT(𝑝, 𝑞).

Klasse

class rational {
private:
long int p, q;

void normalisieren() {
long int teiler = ggt(p, q);
p /= teiler;
q /= teiler;

if (q < 0) {
p = -p;
q = -q;
}
if (p == 0)
q = 1;
}
static long int ggt(long int a, long int b) {
if (a == 0) return abs(b);
if (b == 0) return abs(a);

long int h;
do {
h = a % b;
a = b;
b = h;
} while (h != 0);

return abs(a);
}

public:
rational(long int p_ = 0, long int q_ = 1): p(p_), q(q_) {
normalisieren();
}

Typkonvertierung

Seite 1 von 6
Aufgabe 6 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Arithmetik

Einausgabe
};

Überladen Sie die arithmetischen Grundoperationen, die arithmetischen Zuweisungsoperatoren und die Ope-
ratoren zur Ein- und Ausgabe auf Strömen. Für 𝑝 ∈ N und 𝑞 ∈ N \ {0} sollen rationale Zahlen in der Form
𝑝 /𝑞 ein- und ausgegeben werden, optional mit einem positiven oder negativen Vorzeichen davor.

Arithmetik

rational& operator+=(const rational& r) {


p *= r.q;
p += r.p * q;
q *= r.q;
normalisieren();
return *this;
}
friend rational operator-(const rational& r) {
return rational{-r.p, r.q};
}
rational& operator-=(const rational& r) {
return *this += -r;
}
rational& operator*=(const rational& r) {
p *= r.p;
q *= r.q;
normalisieren();
return *this;
}
rational& operator/=(const rational& r) {
return *this *= rational{r.q, r.p};
}

friend rational operator+(rational r, const rational& s) {


return r += s;
}
friend rational operator-(rational r, const rational& s) {
return r -= s;
}
friend rational operator*(rational r, const rational& s) {
return r *= s;
}
friend rational operator/(rational r, const rational& s) {
return r /= s;
}

Seite 2 von 6
Aufgabe 6 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Einausgabe

friend ostream& operator<<(ostream& stream, const rational& r) {


return stream << r.p << ”/” << r.q;
}
friend istream& operator>>(istream& stream, rational& r) {
long int p_, q_;
char c;
stream >> p_ >> c >> q_;
if (c != '/' || q_ == 0)
[Link](ios::failbit);
else
r = rational{p_, q_};
return stream;
}

Implementieren Sie eine Typkonvertierung von rational nach long double.

Typkonvertierung

operator long double() {


return static_cast<long double>(p) / static_cast<long double>(q);
}

Erstellen Sie eine Potenzfunktion pow zur Berechnung von 𝑟 𝑛 für rationales 𝑟 und 𝑛 ∈ Z.

pow

rational pow(const rational& r, long int z) {


rational res{1};

for (long int i = 0; i < abs(z); i++) {


if (z < 0)
res /= r;
else
res *= r;
}

return res;
}

∑𝑛 𝑘.
Erstellen Sie eine Funktion zur Berechnung von 𝑘=1 𝑘𝑟

Potreihe

rational pot_reihe(const rational& r, long int n) {


rational res{0};
for (long int k = 1; k <= n; k++) {
res += static_cast<rational>(k) * pow(r, k);
}
return res;
}

Seite 3 von 6
Aufgabe 6 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Erstellen Sie eine Funktion zur Berechnung des Kettenbruchs

𝑎0
𝑏0 +
𝑎1
𝑏1 +
𝑎2
𝑏2 +
.. 𝑎𝑛−1
. 𝑏𝑛−1 +
𝑏𝑛
mit 𝑎 0, . . . , 𝑎𝑛−1, 𝑏 0, . . . , 𝑏𝑛 ∈ N.

Kettenbruch

rational kettenbruch(const vector<long int>& a, const vector<long int>& b) {


vector<long int>::size_type n = [Link]();
rational res{b[n]};
for (vector<long int>::size_type i = n; i > 0; i--)
res = rational(b[i - 1]) + rational(a[i - 1]) / res;
return res;
}

Erstellen Sie ein Hauptprogramm und berechnen Sie die folgenden Beispiele sowohl als rationale Zahl wie
auch eine dezimale Näherung:

a) Folgender Kettenbruch: b)

𝑛
2 10
𝑘𝑟 𝑘 mit 𝑟 = , − und 𝑛 = 8
3 7
𝑘=1

c)
1
3+ 𝑛𝑟 𝑛+2 − (𝑛 + 1)𝑟 𝑛+1 + 𝑟 2 10
1 mit 𝑟 = , − und 𝑛 = 8
7+ (𝑟 − 1) 2 3 7
1
15 +
1 d) Kettenbrüche mit 𝑎 0 = 4, 𝑏 0 = 0, 𝑎𝑖 = 𝑖 2 , und
1+ 𝑏𝑖 = 2𝑖 − 1 für 𝑖 ≥ 1 und 𝑛 = 1, 2, . . . , 10
292

Hinweis. Die Kettenbrüche aus a) und d) sind Approximationen von 𝜋. Die Ausdrücke in b) und c) sollten für
gleiches 𝑟 jeweils identische Ergebnisse liefern.

[Link]

#include <iostream>
#include <iomanip>
#include <vector>

using namespace std;

Klasse

pow

Potreihe

Seite 4 von 6
Aufgabe 6 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Kettenbruch

int main() {
cout << setprecision(18);

long int n = 8;
vector<rational> rs{rational{2, 3}, -rational{10, 7}};

// a)
rational r = kettenbruch({1, 1, 1, 1}, {3, 7, 15, 1, 292});
cout << ”kettenbruch(...) = ”
<< r << ” =~ ”
<< static_cast<long double>(r) << endl;
cout << endl;

// b)
for (rational r: rs)
cout << ”pot_reihe(” << r << ”, ” << n << ”) = ”
<< pot_reihe(r, n) << ” =~ ”
<< static_cast<long double>(pot_reihe(r, n)) << endl;
cout << endl;

// c)
for (rational r: rs) {
rational p = (rational{n} * pow(r, n + 2) - rational{n + 1} * pow(r, n + 1) + r) / pow(r -
↩→ rational{1}, 2);
cout << ”p = ” << p << ” =~ ” << static_cast<long double>(p) << endl;
}
cout << endl;

// d)
for (long int n = 1; n <= 10; n++) {
vector<long int> a(n), b(n + 1);
a[0] = 4;
b[0] = 0;
for (long int i = 1; i <= n; i++) {
if (i < n)
a[i] = i * i;
b[i] = 2 * i - 1;
}

rational k = kettenbruch(a, b);


cout << ”k[” << setw(2) << n << ”] = ”
<< k << ” =~ ”
<< static_cast<long double>(k) << endl;
}
}

kettenbruch(...) = 103993/33102 =~ 3.1415926530119026

pot_reihe(2/3, 8) = 33734/6561 =~ 5.14159426916628563


pot_reihe(-10/7, 8) = 493413370/5764801 =~ 85.5907029574828342

p = 33734/6561 =~ 5.14159426916628563
p = 493413370/5764801 =~ 85.5907029574828342

Seite 5 von 6
Aufgabe 6 Programmieren II für Studierende der Mathematik — WiSe 2025/26

k[ 1] = 4/1 =~ 4
k[ 2] = 3/1 =~ 3
k[ 3] = 19/6 =~ 3.16666666666666667
k[ 4] = 160/51 =~ 3.13725490196078431
k[ 5] = 1744/555 =~ 3.14234234234234234
k[ 6] = 644/205 =~ 3.14146341463414634
k[ 7] = 2529/805 =~ 3.14161490683229814
k[ 8] = 183296/58345 =~ 3.14158882509212443
k[ 9] = 3763456/1197945 =~ 3.14159331187992771
k[10] = 4317632/1374345 =~ 3.14159254044653999

Seite 6 von 6
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Blatt 4 – Lösungsvorschlag

Aufgabe 7 Sei 𝑁 ∈ N, 𝑁 ≥ 2. Vereinbaren Sie 𝑁 ≤ 232 als Konstante für das gesamte Programm am
besten durch Zuweisung eines Literals in hexadezimaler Schreibweise. Kennt man die Primzahlen kleiner als
𝑁 , so lässt sich die Primfaktorzerlegung einer Zahl 𝑛 ∈ 𝑁 mit 𝑛 < 𝑁 2 durch fortgesetztes Abdividieren der
Primteiler bestimmen.
Erstellen Sie zunächst eine Klasse Primzahlen mit einer internen statischen Datenkomponente vom Typ
bitset<𝑁 >, die für jede Zahl 𝑘 ∈ 𝑁 mit 𝑘 < 𝑁 speichert ob sie eine Primzahl ist, oder nicht. Implementieren
Sie eine statische Komponentenfunktion die das Sieb des Eratosthenes verwendet um die statische Datenkom-
ponente korrekt zu befüllen. Rufen Sie diese statische Komponentenfunktion zu Beginn des Hautprogramms
einmal auf.
Hinweis (Sieb des Eratosthenes). Um alle Primzahlen kleiner als 𝑁 zu bestimmen können wir ausgehend von
𝑘 = 2 alle Vielfachen von Primzahlen 𝑖 ·𝑘 streichen mit 𝑖 ≥ 𝑘 und 𝑖 ·𝑘 < 𝑁 . Die erste noch nicht gestrichene
Zahl ist dann die nächste Primzahl 𝑘. Iterieren Sie dieses Verfahren solange 𝑘 2 < 𝑁 .

Primzahlen

class Primzahlen {
private:
static bitset<N> primes;

public:
static void init() {
[Link]();
[Link](0); [Link](1);

for (unsigned long k = 2; k*k < N; k++)


if (primes[k])
for (unsigned long i = k; i * k < N; i++)
[Link](i*k);
}

Primzahlen Ist Primzahl


};

bitset<N> Primzahlen::primes{};

Fügen Sie zur Klasse Primzahlen eine statische Komponentenfunktion ist_primzahl hinzu die für ein als
Parameter gegebenes 𝑛 mit 0 ≤ 𝑛 < 𝑁 vom Typ unsigned long als bool zurück gibt ob 𝑛 eine Primzahl ist.

Primzahlen Ist Primzahl

static bool ist_primzahl(unsigned long n) {


if (n < N)
return primes[n];
else if (N * N > n) {
for (unsigned long k = 2; k < N; k++)
if (primes[k] && n % k == 0)
return false;

Seite 1 von 12
Aufgabe 7 Programmieren II für Studierende der Mathematik — WiSe 2025/26

return true;
} else {
ostringstream err;
err << n << ” lies outside of acceptable range for ist_primzahl [0..” << N*N << ”)”;
throw domain_error([Link]());
}
}

Erstellen Sie eine Klasse Primfaktor. Objekte der Klasse Primfaktor sollen zur Modellierung einer Primzahl-
potenz der Form 𝑝 𝑘 für 𝑝 eine Primzahl und sowohl 𝑝 wie auch 𝑘 vom Typ int dienen.

Primfaktor

class Primfaktor {
public:
unsigned long p, k;

Primfaktor(unsigned long p_ = 0, unsigned long k_ = 1): p(p_), k(k_) {}

Ausgabe Primfaktor
};

Erstellen Sie eine Klasse Primfaktorzerlegung. Objekte der Klasse Primfaktorzerlegung modellieren eine
natürliche Zahl indem intern eine Liste von Primfaktoren vom Datentyp list<Primfaktor> gespeichert wird.
Als Invariante der Klasse soll sichergestellt werden, dass die Liste von Primfaktoren stets aufsteigend nach
Primzahl sortiert vorliegt und für alle gespeicherten Primfaktoren 𝑝 𝑘 gilt, dass 𝑘 ≥ 1.

Primfaktorzerlegung

class Primfaktorzerlegung {
private:
list<Primfaktor> factors;

public:

Primfaktorzerlegung Konstruktor

Typumwandlung

Primfaktorzerlegung Ist Primzahl

Ausgabe Primfaktorzerlegung
};

Erstellen Sie einen Konstruktor für die Klasse Primfaktorzerlegung mit einem Argument 0 < 𝑛 < 𝑁 2 vom
Typ unsigned long. Für 𝑛 = 1 soll die interne Liste leer angelegt werden. Verwenden Sie zur Berechnung der

Seite 2 von 12
Aufgabe 7 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Primfaktorzerlegung die Funktion ist_primzahl aus der Klasse Primzahlen.

Primfaktorzerlegung Konstruktor

Primfaktorzerlegung(unsigned long n = 1) {
for (unsigned long p = 2; p * p <= n && p * p < N; p++) {
if (!Primzahlen::ist_primzahl(p))
continue;

unsigned long k = 0;
while (n % p == 0) {
k++;
n /= p;
}

if (k >= 1)
factors.push_back(Primfaktor{p, k});
}

if (n != 1)
factors.push_back(Primfaktor{n});
}

Überladen Sie den Ausgabeoperator für die Klasse Primfaktorzerlegung sodass die Ausgabe in der Form
𝑝 1 ^𝑘 1 *. . .*𝑝𝑛 ^𝑘𝑛 erfolgt. Für die leere Liste soll 1 ausgegeben werden.

Ausgabe Primfaktor

friend ostream& operator<<(ostream& stream, Primfaktor pf) {


stream << pf.p;
if (pf.k != 1)
stream << ”^” << pf.k;
return stream;
}

Ausgabe Primfaktorzerlegung

friend ostream& operator<<(ostream& stream, Primfaktorzerlegung pfz) {


if ([Link]())
return stream << ”1”;

for (list<Primfaktor>::iterator i = [Link]();


i != [Link]();
i++) {
if (i != [Link]())
stream << ”*”;
stream << *i;
}

return stream;
}

Fügen Sie einen Typumwandlungsoperator hinzu um Objekte der Klasse Primfaktorzerlegung zu Werten
vom Typ unsigned long implizit konvertieren zu können.

Seite 3 von 12
Aufgabe 7 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Typumwandlung

operator unsigned long() {


unsigned long res = 1;
for (Primfaktor pf: factors)
while (pf.k-- > 0)
res *= pf.p;
return res;
}

Erstellen Sie eine mit der Klasse Primfaktorzerlegung befreundete Funktion ist_primzahl die für ein Ar-
gument vom Typ Primfaktorzerlegung unter direkter Verwendung der internen Darstellung als Liste von
Primfaktoren bestimmt und als bool zurückgibt ob das Argument eine Primzahl ist.

Primfaktorzerlegung Ist Primzahl

friend bool ist_primzahl(const Primfaktorzerlegung& pfz) {


if ([Link]())
return false;

list<Primfaktor>::const_iterator it = [Link]();
return it->k == 1 && ++it == [Link]();
}

Erstellen Sie ein Hauptprogramm, das für Zahlen der Form 2 𝑗 − 1 mit 𝑗 ∈ {1, . . . , 64} und 2 𝑗 − 1 ≤ 𝑁 2 − 1
jeweils die Primfaktorzerlegung ausgibt. Sofern es sich um eine Primzahl handelt soll jeweils zusätzlich der
Text Mersenne-Primzahl ausgegeben werden. Überprüfen Sie zudem ob das Ergebnis der Typumwandlung
von der Primfaktorzerlegung zurück zu unsigned int identisch ist mit der zu faktorisierenden Zahl und
geben Sie eine geeignete Warnung aus, sollte dem nicht so sein.

[Link]

#include <iostream>
#include <iomanip>
#include <sstream>
#include <bitset>
#include <list>

using namespace std;

const unsigned long N = 0x100000000;

Primzahlen

Primfaktor

Primfaktorzerlegung

int main() {
Primzahlen::init();

unsigned long zweip = 1;


while (true) {

Seite 4 von 12
Aufgabe 7 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Primfaktorzerlegung pfz{zweip};
cout << setw(20) << zweip;
if (ist_primzahl(pfz))
cout << ” Mersenne-Primzahl: ”;
else
cout << ”: ”;
cout << pfz << endl;
if (zweip != static_cast<unsigned long>(pfz))
cout << ”Typumwandlung (” << static_cast<unsigned long>(pfz) << ”) entspricht nicht
↩→ Argument (” << zweip << ”)” << endl;

unsigned long old_zweip = zweip;


zweip = (zweip << 1) | 1;
if (zweip > N * (N - 1) + (N - 1) || old_zweip == zweip)
break;
}
}

1: 1
3 Mersenne-Primzahl: 3
7 Mersenne-Primzahl: 7
15: 3*5
31 Mersenne-Primzahl: 31
63: 3^2*7
127 Mersenne-Primzahl: 127
255: 3*5*17
511: 7*73
1023: 3*11*31
2047: 23*89
4095: 3^2*5*7*13
8191 Mersenne-Primzahl: 8191
16383: 3*43*127
32767: 7*31*151
65535: 3*5*17*257
131071 Mersenne-Primzahl: 131071
262143: 3^3*7*19*73
524287 Mersenne-Primzahl: 524287
1048575: 3*5^2*11*31*41
2097151: 7^2*127*337
4194303: 3*23*89*683
8388607: 47*178481
16777215: 3^2*5*7*13*17*241
33554431: 31*601*1801
67108863: 3*2731*8191
134217727: 7*73*262657
268435455: 3*5*29*43*113*127
536870911: 233*1103*2089
1073741823: 3^2*7*11*31*151*331
2147483647 Mersenne-Primzahl: 2147483647
4294967295: 3*5*17*257*65537
8589934591: 7*23*89*599479
17179869183: 3*43691*131071
34359738367: 31*71*127*122921
68719476735: 3^3*5*7*13*19*37*73*109
137438953471: 223*616318177
274877906943: 3*91625968981
549755813887: 7*79*8191*121369

Seite 5 von 12
Aufgabe 7 Programmieren II für Studierende der Mathematik — WiSe 2025/26

1099511627775: 3*5^2*11*17*31*41*61681
2199023255551: 13367*164511353
4398046511103: 3^2*7^2*43*127*337*5419
8796093022207: 431*9719*2099863
17592186044415: 3*5*23*89*397*683*2113
35184372088831: 7*31*73*151*631*23311
70368744177663: 3*47*499069107643
140737488355327: 2351*4513*13264529
281474976710655: 3^2*5*7*13*17*97*241*257*673
562949953421311: 127*4432676798593
1125899906842623: 3*11*31*251*601*1801*4051
2251799813685247: 7*103*2143*11119*131071
4503599627370495: 3*5*53*157*1613*2731*8191
9007199254740991: 6361*1416003655831
18014398509481983: 3^4*7*19*73*22906579627
36028797018963967: 23*31*89*881*3191*201961
72057594037927935: 3*5*17*29*43*113*127*15790321
144115188075855871: 7*32377*635879915089
288230376151711743: 3*59*233*1103*2089*3033169
576460752303423487 Mersenne-Primzahl: 576460752303423487
1152921504606846975: 3^2*5^2*7*11*13*31*41*61*151*331*1321
2305843009213693951 Mersenne-Primzahl: 2305843009213693951
4611686018427387903: 3*1537228672809129301
9223372036854775807: 7^2*73*127*337*60247241209
18446744073709551615: 3*5*17*257*641*439125228929

Seite 6 von 12
Aufgabe 8* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Aufgabe 8* Ziel ist es die Lösung der vorhergegangenen Übungsaufgabe von der Äquivalenz von 𝑝-Normen
in eine bessere Code-Struktur zu bringen und mit unittests zu versehen. Falls Sie das entsprechende Übungs-
blatt nicht (vollständig) bearbeitet haben, können Sie auch den bereitgestellten Lösungsvorschlag verwenden.
Bei der Umstrukturierung von bestehendem Code ohne (wesentliche) Änderung der Funktionalität spricht
man von refactoring.
Legen Sie ein Verzeichnis für Ihr Projekt an. Verschieben Sie Ihre Lösung in eine Quellcodedatei (z.B. und
im Folgenden [Link]) unterhalb des von Ihnen erstellten Verzeichnisses. Formulieren Sie eine Datei
[Link] um die Übersetzungseinheit [Link] zu einer ausführbaren Binärdatei zu übersetzen und
legen Sie diese ebenfalls in dem Verzeichnis ab.

project('pnorms', 'cpp')

executable('pnorms', '[Link]')

Sie sollten nun in der Lage sein mit einem geeigneten meson-Befehl das builddir anlegen zu lassen, ihre Lösung
mit ninja zu übersetzen und Sie danach interaktiv auszuführen.
Erstellen Sie eine Datei pnorms.h mit Deklarationen für die in [Link] definierten templates. Inkludieren
Sie pnorms.h auch in [Link].
Hinweis. Sie werden die Definitionen, die Teil der Klasse complex_norm sind, in [Link] ändern müssen.
Sie dürfen die Klasse complex_norm nicht erneut vereinbaren und müssen stattdessen ihre Komponenten
außerhalb einer Klassen-Deklaration definieren.

pnorms_unittests/pnorms.h

#pragma once

#include <complex>

template<class T = double, unsigned int p = 2>


T pNorm(const std::complex<T>& z);

template<class T = double, T _norm(const std::complex<T>&) = pNorm<T>>


class complex_norm : public std::complex<T> {
public:
complex_norm(T real_, T imag_);
T norm() const;
};

template<class T = double, unsigned int p = 2>


using complex_pnorm = complex_norm<T, pNorm<T, p>>;

Deklaration pnorm bound

Definition complex norm

template<class T, T _norm(const complex<T>&)>


complex_norm<T, _norm>::complex_norm(T real_, T imag_): complex<T>(real_, imag_) {}

template<class T, T _norm(const complex<T>&)>


T complex_norm<T, _norm>::norm() const {
return _norm(*this);

Seite 7 von 12
Aufgabe 8* Programmieren II für Studierende der Mathematik — WiSe 2025/26

An dieser Stelle sollten Sie erneut in der Lage sein ihre Lösung zu übersetzen und interaktiv auszuführen.
Deklarieren Sie in der Header-Datei und definieren Sie in [Link] eine zusätzliches Funktionstemplate
pnorm_bound. Die Funktionen sollen einen Parameter 𝑧 vom Typ complex<𝑇 > akzeptieren und einen Wert
vom Typ 𝑇 zurückgeben. Zusätzlich zu 𝑇 soll das template zwei weitere Parameter 𝑝 1 und 𝑝 2 akzeptieren. Der
∥𝑧 ∥
zurückgegebene Wert soll ∥𝑧 ∥𝑝𝑝1 sein.
2

Deklaration pnorm bound

template<class T = double, unsigned int p1, unsigned int p2>


T pnorm_bound(std::complex<T> = std::complex<T>{1.0, 1.0});

Definition pnorm bound

template<class T, unsigned int p1, unsigned int p2>


T pnorm_bound(complex<T> z) {
complex_pnorm<T, p1> z1{[Link](), [Link]()};
complex_pnorm<T, p2> z2{[Link](), [Link]()};
return [Link]() / [Link]();
}

Passen Sie Ihr Hauptprogramm in [Link] an, sodass dieses pnorm_bound geeignet verwendet, wo immer
sinnvoll möglich.
Lagern Sie ihr Hautprogramm aus in eine zweite Übersetzungseinheit pnorms_interact.cpp.
Hinweis. Es wird notwendig sein die in pnorms_interact.cpp verwendeten Instanzen der templates aus
pnorms.h in [Link] explizit zu instanziieren.

Sie werden zudem Ihre [Link] anpassen müssen, sodass [Link] nun als Programmbibliothek
übersetzt und mit dem Resultat der Übersetzung von pnorms_interact.cpp gelinkt wird.

pnorms_unittests/[Link]

#include <complex>
#include <cmath>

#include ”pnorms.h”

using namespace std;

template<class T, unsigned int p>


T pNorm(const complex<T>& z) {
return pow(pow(abs([Link]()), p) + pow(abs([Link]()), p), static_cast<T>(1) / p);
}

Definition complex norm

Definition pnorm bound

template double pNorm<double, 2>(const complex<double>&);

Seite 8 von 12
Aufgabe 8* Programmieren II für Studierende der Mathematik — WiSe 2025/26

template double pNorm<double, 3>(const complex<double>&);


template class complex_norm<double, pNorm<double, 2>>;
template class complex_norm<double, pNorm<double, 3>>;
template double pnorm_bound<double, 2, 3>(complex<double>);

pnorms_unittests/pnorms_interact.cpp

#include <iostream>
#include <iomanip>
#include <limits>

#include ”pnorms.h”

using namespace std;

int main() {
double bound = pnorm_bound<double, 2, 3>();

cout << setprecision(numeric_limits<double>::digits10 + 1) << boolalpha;


cout << bound << endl;

double real_, imag_;


while (true) {
cout << ”real, imag = ”;
if (!(cin >> real_ >> imag_)) break;

double val = pnorm_bound<double, 2, 3>(complex<double>{real_, imag_});


cout << val << ” ” << (val <= bound) << endl;
}

pnorms_unittests/[Link]

project('pnorms', 'cpp')

pnorms_lib = library('libpnorms', '[Link]')


executable('pnorms', 'pnorms_interact.cpp', link_with : pnorms_lib)

Meson Test

An dieser Stelle sollten Sie erneut in der Lage sein ihre Lösung zu übersetzen und interaktiv auszuführen.

Befehle

Befehle subproject setup

meson setup --wipe build


ninja -C build

Befehle Test

Fügen Sie Ihrem meson Projekt das unit testing framework Google Test als subproject hinzu. Lassen Sie es

Seite 9 von 12
Aufgabe 8* Programmieren II für Studierende der Mathematik — WiSe 2025/26

von meson aus der WrapDB installieren.

Befehle subproject setup

mkdir -p subprojects
meson wrap install gtest

Fügen Sie eine weitere Übersetzungseinheit pnorms_unittests.cpp hinzu. Spezifizieren Sie in [Link]
geeignet, dass das Ergebnis der Übersetzung von pnorms_unittsets.cpp zu einer ausführbaren Binärdatei
mit dem Befehl meson test ausführbar sein soll. Geben Sie hierbei auch an, dass die Binärdatei gelinkt werden
soll mit der in der meson Variable gtest_main_dep im subproject gtest bereitgestellten dependency.
Hinweis. Es ist dann nicht notwendig in pnorms_unittests.cpp ein Hauptprogramm zu implementieren.

Meson Test

gtest = subproject('gtest')
test('gtest',
executable('pnorms_gtest', 'pnorms_unittests.cpp',
link_with : pnorms_lib,
dependencies : gtest.get_variable('gtest_main_dep')
)
)

Selbst mit pnorms_unittests.cpp einer komplett leeren Datei sollten Sie Ihr Projekt weiterhin übersetzen
und interaktiv ausführen können. Es sollte auch möglich sein mit einem geeignete meson test-Befehl die
aus pnorms_unittests.cpp übersetzte ausführbare Binärdatei ausführen zu lassen. Es werden hierbei aber
natürlich noch keinerlei Tests ausgeführt.

Befehle Test

meson test -C build

Implementieren Sie in pnorms_unittests.cpp beliebig viele unit tests, die Ihnen sinnvoll erscheinen.
∥𝑧 ∥ 2
Es sei im Folgenden 𝐵(𝑧) := ∥𝑧 ∥ 3 ; Implementieren Sie aber mindestens auch eine Test-Suite PNorms bestehend
aus den folgenden Tests:

• 𝐵(1 + 𝑖) ≈ 1,122 462 048 309


Hinweis. Verwenden Sie das von Google Test bereitgestellte Präprozessor-Makro EXPECT_NEAR(𝑎 , 𝑎 ′ , 𝜀 ),
welches prüft, dass gilt: |𝑎 − 𝑎 ′ | ≤ 𝜀.
𝜀 soll hierbei nicht unnötig groß sein.
• 𝐵(1,5 + 2,0𝑖) ≤ 𝐵(1 + 𝑖)
Hinweis. Verwenden Sie hier und im Folgenden das von Google Test bereitgestellte Präprozessor-Makro
EXPECT_LE (analog zu EXPECT_EQ).

• 𝐵(7,0 + 7,0𝑖) ≤ 𝐵(1 + 𝑖)


• 𝐵(3,7 + 42,1𝑖) ≤ 𝐵(1 + 𝑖)

Seite 10 von 12
Aufgabe 8* Programmieren II für Studierende der Mathematik — WiSe 2025/26

pnorms_unittests/pnorms_unittests.cpp

#include <gtest/gtest.h>
#include <complex>

#include ”pnorms.h”

namespace {
using namespace std;

const double bound = pnorm_bound<double, 2, 3>();

TEST(PNorms, ExpectedBound) {
const double expected_bound = 1.122462048309373;
EXPECT_NEAR(bound, expected_bound, 1e-12);
}
TEST(PNorms, Example1) {
EXPECT_LE((pnorm_bound<double, 2, 3>(complex<double>{1.5, 2.0})), bound);
}
TEST(PNorms, Example2) {
EXPECT_LE((pnorm_bound<double, 2, 3>(complex<double>{7.0, 7.0})), bound);
}
TEST(PNorms, Example3) {
EXPECT_LE((pnorm_bound<double, 2, 3>(complex<double>{3.7, 42.1})), bound);
}
}

The Meson build system


Version: 1.6.0
Source dir: …/pnorms_unittests
Build dir: …/pnorms_unittests/build
Build type: native build
Project name: pnorms
Project version: undefined
C++ compiler for the host machine: g++ (gcc 14.2.1 ”g++ (GCC) 14.2.1 20241116”)
C++ linker for the host machine: g++ [Link] 2.43.1
Host machine cpu family: x86_64
Host machine cpu: x86_64

Executing subproject gtest

gtest| Project name: gtest


gtest| Project version: 1.15.2
gtest| C++ compiler for the host machine: g++ (gcc 14.2.1 ”g++ (GCC) 14.2.1 20241116”)
gtest| C++ linker for the host machine: g++ [Link] 2.43.1
gtest| Run-time dependency threads found: YES
gtest| Build targets in project: 6
gtest| Subproject gtest finished.

Build targets in project: 7

pnorms undefined

Subprojects
gtest: YES

Found ninja-1.12.1 at …/bin/ninja

Seite 11 von 12
Aufgabe 8* Programmieren II für Studierende der Mathematik — WiSe 2025/26

ninja: Entering directory `build'


[1/18] Compiling C++ object [Link].p/[Link].o
[2/18] Compiling C++ object pnorms.p/pnorms_interact.cpp.o
[3/18] Linking target [Link]
[4/18] Generating symbol file [Link].p/[Link]
[5/18] Linking target pnorms
[6/18] Compiling C++ object
↩→ subprojects/googletest-1.15.2/googletest/libgtest_main.so.p/src_gtest_main.cc.o
[7/18] Compiling C++ object
↩→ subprojects/googletest-1.15.2/googlemock/libgmock_main.so.p/src_gmock_main.cc.o
[8/18] Compiling C++ object …/pnorms_unittests.cpp.o
[9/18] Compiling C++ object
↩→ subprojects/googletest-1.15.2/googlemock/[Link].p/src_gmock-[Link].o
[10/18] Compiling C++ object
↩→ subprojects/googletest-1.15.2/googletest/[Link].p/src_gtest-[Link].o
[11/18] Linking target subprojects/googletest-1.15.2/googletest/[Link]
[12/18] Generating symbol file
↩→ subprojects/googletest-1.15.2/googletest/[Link].p/[Link]
[13/18] Linking target subprojects/googletest-1.15.2/googletest/libgtest_main.so
[14/18] Linking target subprojects/googletest-1.15.2/googlemock/[Link]
[15/18] Generating symbol file
↩→ subprojects/googletest-1.15.2/googletest/libgtest_main.so.p/libgtest_main.[Link]
[16/18] Generating symbol file
↩→ subprojects/googletest-1.15.2/googlemock/[Link].p/[Link]
[17/18] Linking target pnorms_gtest
[18/18] Linking target subprojects/googletest-1.15.2/googlemock/libgmock_main.so
ninja: Entering directory …/pnorms_unittests/build'
ninja: no work to do.
1/1 gtest OK 0.01s

Ok: 1
Expected Fail: 0
Fail: 0
Unexpected Pass: 0
Skipped: 0
Timeout: 0

Full log written to …/pnorms_unittests/build/meson-logs/[Link]

Seite 12 von 12
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Blatt 5 – Lösungsvorschlag

Aufgabe 9 Eine Permutation ist eine Bijektion zwischen einer Menge (hier [0, 𝑛) ⊂ N) und sich selbst. Die
Abbildungsvorschrift einer Permutation auf [0, 𝑛) schreiben wir als Tabelle mit zwei Zeilen und 𝑛 Spalten.
Wir betrachten im Folgenden die Permutation 𝜋:

 
0 1 2 3 4 5 6 7 8 9
(1)
3 1 6 5 4 7 0 2 9 8

Die Schreibweise als Tabelle ist zu verstehen sodass gilt: 𝜋 (0) = 3, 𝜋 (1) = 1, 𝜋 (2) = 6, . . . 𝜋 (9) = 8.
Wir bezeichnen für zwei Permutation 𝜎, 𝜌 auf [0, 𝑛) mit 𝜎 ◦ 𝜌 die Komposition der Permutationen. Die Defi-
nition ist identisch mit der Komposition von Abbildungen: für alle 𝑘 ∈ [0, 𝑛) gilt (𝜎 ◦ 𝜌)(𝑘) = 𝜎 (𝜌 (𝑘)). Wir
verstehen die Komposition ◦ rechts-assoziativ. Wir bezeichen mit 𝜋 𝑛 die 𝑛-fache Komposition von 𝜋 mit sich
selbst. D.h. 𝜋 0 = id, 𝜋 1 = 𝜋, 𝜋 2 = 𝜋 ◦ 𝜋, 𝜋 3 = 𝜋 ◦ 𝜋 ◦ 𝜋, . . ., 𝜋 𝑛 = 𝜋 ◦ 𝜋 𝑛−1
Als eine Transposition 𝜏 = (𝑘 𝑙) auf [0, 𝑛) bezeichnen wir die Permutation mit 𝜏 (𝑖) = 𝑖 für alle 𝑖 ∈ [0, 𝑛) \ {𝑘, 𝑙 }
und 𝜏 (𝑘) = 𝑙, 𝜏 (𝑙) = 𝑘.
Es ist ein bekanntes Resultat, dass sich jede Permutation darstellen lässt als Komposition von Transpositionen.
D.h. für jede Permutation 𝜋 auf [0, 𝑛) existiert eine endliche Folge von Transpositionen 𝜏1, 𝜏2, . . . , 𝜏𝑚 auf [0, 𝑛)
mit 𝜋 (𝑘) = (𝜏1 ◦ 𝜏2 ◦ . . . ◦ 𝜏𝑚 )(𝑘) für alle 𝑘 ∈ [0, 𝑛).
Hierfür zerlegen wir die Permutation 𝜋 auf [0, 𝑛) zunächst in Zykel. Ein Zykel ist eine Folge der Form
(𝑎 𝜋 (𝑎) 𝜋 2 (𝑎) . . . 𝜋 ℓ𝑎 −1 (𝑎)) für 𝑎 ∈ [0, 𝑛). ℓ𝑎 ∈ N \ {0} bezeichnet hierbei die stets wohldefinierte kleinste
Zahl, sodass 𝜋 ℓ𝑎 (𝑎) = 𝑎.
Um 𝜋 in Zykel zu zerlegen wählen wir zunächst eine beliebige Zahl 𝑎 ∈ [0, 𝑛) und bestimmen den Zykel in
dem sie enthalten ist. Falls nicht alle Zahlen aus [0, 𝑛) bereits in diesem ersten Zykel erwähnt wurden, so
wählen wir ein 𝑏 ∈ [0, 𝑛) das noch nicht erwähnt wurde und bestimmen den Zykel in dem 𝑏 liegt. Durch
Iteration des Verfahrens erhalten wir so eine Menge von disjunkten Zykeln. Zykel der Form (𝑎), d.h. 𝜋 (𝑎) = 𝑎
können wir verwerfen.
Es gilt für eine Permutation 𝜋 mit Zykeln (𝑎 0 𝑎 1 𝑎 2 . . . 𝑎𝑚 ), (𝑏 0 𝑏 1 . . . 𝑏𝑙 ), . . ., dass 𝜋 = (𝑎 0 𝑎 1 ) ◦ (𝑎 1 𝑎 2 ) ◦
. . . ◦ (𝑎𝑚−1 𝑎𝑚 ) ◦ (𝑏 0 𝑏 1 ) ◦ . . . ◦ (𝑏𝑙 −1 𝑏𝑙 ) ◦ . . ., also eine Komposition von Transpositionen.
Erstellen Sie zunächst eine Klasse Transposition zur Darstellung einer Transposition. Verwenden Sie hierfür
zwei private Attribute n und k vom Typ int. Implementieren Sie einen Konstruktor für Transposition der
zwei Parameter vom Typ int akzeptiert und die Attribute n und k damit initialisiert. Prüfen Sie im Körper des
Konstruktors ob 𝑛 == 𝑘 und werfen Sie, falls dem so ist, eine geeignete exception.

Transposition

class Transposition {
private:
int n, k;

public:
Transposition(int n_, int k_): n(n_), k(k_) {
if (n == k) {
ostringstream os;
os << ”Transposition(” << n_ << ”, ” << k_ << ”)”;
throw invalid_argument([Link]());

Seite 1 von 13
Aufgabe 9 Programmieren II für Studierende der Mathematik — WiSe 2025/26

}
}

Transposition Auswertung

Transposition Ausgabe
};

Überladen Sie den Ausgabeoperator für Transposition sodass Objekte der Klasse ausgegeben werden können
in der Form (𝑛 𝑘 ).

Transposition Ausgabe

friend ostream& operator<<(ostream& stream, const Transposition& t) {


return stream << ”(” << t.n << ” ” << t.k << ”)”;
}

Erstellen Sie eine Klasse Permutation zur Darstellung von Permutationen durch ein private Attribut als Liste
von Transpositionen (list<Transposition>). Erstellen Sie zunächst Konstruktoren für Permutation mit:

• keinen Parametern; es soll die Identitätsfunktion erzeugt werden


• zwei Parametern vom Typ int; es soll für Parameter 𝑛 und 𝑘 die Transposition (𝑛 𝑘) erzeugt werden
• einem Parameter vom Typ Transposition

Permutation

class Permutation {
private:
list<Transposition> perm;

public:
Permutation(): perm() {}
Permutation(int n_, int k_): perm({Transposition(n_, k_)}) {}
Permutation(const Transposition& t): perm({t}) {}

Permutation Konstruktor

Permutation Auswertung

Methode getPerm

Permutation Multiplikationsoperator

Permutation Ausgabe

Seite 2 von 13
Aufgabe 9 Programmieren II für Studierende der Mathematik — WiSe 2025/26

};

Implementieren Sie einen Konstruktor für Permutation mit einem Parameter vom Typ map<int, int>. Die
gegebene endliche Abbildung soll als Permutation aufgefasst werden. Führen Sie im Körper des Konstruktors
die Zerlegung des Parameters in Zykel durch, bestimmen Sie die Darstellung der durch die endliche
Abbildung dargestellten Permutation als Komposition von Transpositionen und speichern Sie diese als
list<Transposition> im Attribut des erzeugten Objektes.

Permutation Konstruktor

Permutation(const map<int, int>& pi) {


set<int> seen{};
for (auto p: pi) {
if ([Link]([Link]) != [Link]())
continue;

list<int> cycle{[Link]};
while ([Link]([Link]()) != [Link])
cycle.push_back([Link]([Link]()));
[Link]([Link](), [Link]());

if ([Link]() <= 1)
continue;

list<int>::const_iterator curr = [Link](),


next = ++([Link]());
for (; next != [Link](); curr++, next++)
perm.push_back(Transposition{*curr, *next});
}
}

Überladen Sie den Ausgabeoperator für Permutation sodass Objekte der Klasse ausgegeben werden können
als Folge von Transpositionen in der Form (𝑛 1 𝑘 1 ) (𝑛 2 𝑘 2 ) . . ..

Permutation Ausgabe

friend ostream& operator<<(ostream& stream, const Permutation& p) {


list<Transposition>::const_iterator curr = [Link](),
next = ++([Link]());

for (; curr != [Link](); curr++, next++) {


stream << *curr;
if (next != [Link]())
stream << ” ”;
}
return stream;
}

Implementieren Sie ein Hauptprogramm in dem Sie 𝑛 und dann eine Permutation zunächst als Wert vom
Typ map<int, int> für Indizes [0, 𝑛) von der Standardeingabe einlesen. Übergeben Sie die resultierende
endliche Abbildung dann an den Konstruktor von Permutation und geben Sie das resultierende Objekt auf
der Standardausgabe aus. Sie sollten eine Folge von Transpositionen auf der Standardausgabe erhalten. Testen
Sie ihr Programm mit der Permutation 𝜋 aus 1.
Hinweis (Kontrollergebnis).
𝜋 = (0 3) (3 5) (5 7) (7 2) (2 6) (8 9)

Seite 3 von 13
Aufgabe 9 Programmieren II für Studierende der Mathematik — WiSe 2025/26

[Link]

#include <iostream>
#include <set>
#include <map>
#include <list>
#include <algorithm>
#include <stdexcept>
#include <sstream>

using namespace std;

Transposition

Permutation

Funktion ausgeben

int main() {
int n;
cout << ”n: ”; cin >> n;
map<int, int> pi;
for (int i = 0; i < n; i++) {
cout << ”pi[” << i << ”]: ”;
cin >> pi[i];
}

Permutation p{pi};
cout << p << endl;

Auswertung Kontrollausgabe

Kontrollausgabe multipliziert

return 0;
}

n: 10
pi[0]: 3
pi[1]: 1
pi[2]: 6
pi[3]: 5
pi[4]: 4
pi[5]: 7
pi[6]: 0
pi[7]: 2
pi[8]: 9
pi[9]: 8
(0 3) (3 5) (5 7) (7 2) (2 6) (8 9)
p[0]: 3 p[1]: 1 p[2]: 6 p[3]: 5 p[4]: 4 p[5]: 7 p[6]: 0 p[7]: 2 p[8]: 9 p[9]: 8
q[0]: 3 q[1]: 1 q[2]: 6 q[3]: 5 q[4]: 4 q[5]: 7 q[6]: 0 q[7]: 2 q[8]: 9 q[9]: 8

Überladen Sie die Funktionsauswertungsoperatoren für die Klassen Transposition und Permutation mathe-
matisch sinnvoll.

Seite 4 von 13
Aufgabe 9 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Transposition Auswertung

int operator()(int i) const {


if (i == k)
return n;
else if (i == n)
return k;
else
return i;
}

Permutation Auswertung

int operator()(int i) const {


for (list<Transposition>::const_reverse_iterator it = [Link]();
it != [Link]();
it++)
i = (*it)(i);
return i;
}

Erweitern Sie Ihr Hauptprogramm sodass dieses den Funktionsauswertungsoperator von Permutation ver-
wendet um für die Werte 𝑘 ∈ [0, 𝑛) jeweils 𝜋 (𝑘) auszugeben wobei 𝜋 die von der Standardeingabe eingelesene
Permutation bezeichne.

Funktion ausgeben

void ausgeben(ostream& stream, const string& c, const Permutation& p, int n) {


for (int i = 0; i < n; i++) {
cout << c << ”[” << i << ”]: ” << p(i);
if (i != n - 1)
cout << ” ”;
}
cout << endl;
}

Auswertung Kontrollausgabe

ausgeben(cout, ”p”, p, n);

Implementieren Sie eine Methode getPerm für Permutation die Zugriff auf das private Attribut, welches die
Liste von Transpositionen enthält, als konstante Referenz ermöglicht.

Methode getPerm

const list<Transposition>& getPerm() const {


return perm;
}

Überladen Sie den Multiplikationsoperator für Permutation sodass dieser die Komposition ◦ zweier Permu-
tation berechnet.

Seite 5 von 13
Aufgabe 9 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Permutation Multiplikationsoperator

Permutation& operator*=(Permutation o) {
[Link]([Link](), [Link]);
return *this;
}
friend Permutation operator*(Permutation a, const Permutation& b) {
return a *= b;
}

Erweitern Sie ihr Hauptprogramm sodass dieses die Methode getPerm verwendet um Zugriff auf die Trans-
positionen zu erhalten in die sich 𝜋 zerlegen lässt. Berechnen Sie eine neue Permutation 𝜋 ′ indem Sie die
Transpositionen jeweils als Permutation auffassen (mit dem geeigneten Konstruktor von Permutation) und
geeignet aufmultiplizieren. Geben Sie zum Vergleich mit 𝜋 auch für 𝜋 ′ für die Werte 𝑘 ∈ [0, 𝑛) jeweils 𝜋 ′ (𝑘)
aus.

Kontrollausgabe multipliziert

Permutation q{};
for (Transposition t: [Link]())
q = Permutation{t} * q;
ausgeben(cout, ”q”, q, n);

Seite 6 von 13
Aufgabe 10* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Aufgabe 10* Wir bezeichnen im Folgenden die 𝑖-te Zeile einer Matrix 𝑀 mit 𝑚𝑖 , die 𝑗-te Spalte einer Matrix
𝑀 mit 𝑚 𝑗 und den 𝑗-ten Eintrag der 𝑖-ten Zeile einer Matrix 𝑀 mit 𝑚𝑖𝑗 .
Das zyklischeJacobi-Verfahren
 dient zur Berechnung der Eigenwerte und Eigenvektoren einer symmetrischen
Matrix 𝐴 = 𝑎 𝑗 𝑖 ∈ R𝑛×𝑛 . Hierfür wird eine Zerlegung 𝐴 = 𝑈 𝐴′𝑈 𝑇 bestimmt mit 𝐴′ diagonal. Es
𝑖,𝑗=0,...,𝑛−1
lassen sich dann die Eigenwerte von 𝐴 ablesen in der Diagonale von 𝐴′ und die Eigenvektoren von 𝐴 in den
Spalten von 𝑈 .
Ausgehend von 𝐴alt = 𝐴 und 𝑈 alt = 1𝑛 wird im Rahmen eines Zyklus jeweils 𝐴neu und 𝑈 neu berechnet. Für den
nächsten Zyklus fungieren die so berechneten 𝐴neu und 𝑈 neu dann als 𝐴alt und 𝑈 alt . Innerhalb eines Zyklus
werden hierfür zeilenweise die Indexpaare (𝑖, 𝑗) der oberen rechten Hälfte des aktuellen 𝐴 durchlaufen, d.h.
𝑗 = 𝑖 + 1, . . . , 𝑛 − 1 mit 𝑖 = 0, . . . , 𝑛 − 1. Eine derartige Berechnung für ein Indexpaar (𝑖, 𝑗) nennen wir die
Auslöschung von (𝑖, 𝑗).
Das Verfahren ist erfolgreich und kann beendet werden, sobald gilt:
 𝑛−1 Õ
Õ 𝑛−1
𝑁 𝐴 neu ≤ 𝜀 · 𝑁 (𝐴) mit 𝑁 (𝐴) := 2 𝑎𝑖𝑗
2

𝑖=0 𝑗=𝑖+1
Ansonsten soll das Verfahren abgebrochen werden, sobald eine vorgegebene Anzahl von Zykeln cycmax = 50
überschritten wird.
Um das Indexpaar (𝑖, 𝑗) auszulöschen berechnen Sie jeweils das Folgende. Die berechneten 𝐴aus und 𝑈 aus
fungieren dann als 𝐴alt und 𝑈 alt für das nächste Indexpaar bzw. 𝐴neu und 𝑈 neu .
 𝑗  𝑖
𝑎 alt − 𝑎 alt
𝑗 𝑖
𝜏=  𝑖
2 𝑎 alt
𝑗
sign(𝜏)
𝑡 = √
|𝜏 | + 1 + 𝜏 2
1
𝑐 =√
1 + 𝑡2
𝑠 = 𝑐𝑡
𝑎𝑖′ = 𝑐𝑎 alt − 𝑠𝑎 alt
𝑖 𝑗

𝑎 ′𝑗 = 𝑠𝑎𝑖alt + 𝑐𝑎 alt
𝑗
(𝑎 ′′ )𝑖 = 𝑐 (𝑎 ′ )𝑖 − 𝑠 (𝑎 ′ ) 𝑗
(𝑎 ′′ ) 𝑗 = 𝑠 (𝑎 ′ )𝑖 + 𝑐 (𝑎 ′ ) 𝑗

 (𝑎 ′ )𝑙𝑘 𝑙 ∈ {𝑖, 𝑗 }, 𝑘 ∉ {𝑖, 𝑗 }


aus  𝑘 
 ′′ 𝑘
𝑎 (𝑎 )𝑙 𝑘 ∈ {𝑖, 𝑗 } 𝑘 = 0, 1, . . . 𝑙 = 0, 1, . . .
𝑙 =  𝑘


 𝑎 alt sonst
 𝑙
    𝑖 2
𝑁 𝐴aus = 𝑁 𝐴alt − 2 𝑎 alt
𝑗

𝑢𝑖aus = 𝑐𝑢𝑖alt − 𝑠𝑢 alt


𝑗

𝑢 aus
𝑗 = 𝑠𝑢𝑖alt + 𝑐𝑢 alt
𝑗
Erstellen Sie eine Funktion jacobi die das zyklische Jacobi-Verfahren durchführt, wie beschrieben. Rückga-
bewert der Funktion soll sein, ob das Verfahren erfolgreich war.
Hinweis. Da die beschriebenen Operationen auf ganzen Zeilen bzw. Spalten agieren, empfiehlt sich die Dar-
stellung von Matrizen als valarrays und die Verwendung der zur Verfügung stehenden Methoden (slice
und slice_array) um direkt rechnen zu können auf ganzen Zeilen bzw. Spalten.

Seite 7 von 13
Aufgabe 10* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Rechnen Sie die folgenden Beispiele:

a) b)
5 1 −2 0 −2 5
© 1 −3 ª
­ 6 2 0 6 ®
­ ®
5 4 1 1 ­−2 −3 8 −5 −6 0®
© ª 𝐵 =­ ®
­4 5 1 1® ­ 0 2 −5 5 1 −2®
𝐴=­ ® ­ ®
­1 1 4 2® ­−2 0 −6 1 6 −3®
«1 1 2 4¬ « 5 6 0 −2 −3 8¬

Hinweis (Kontrollergebnisse). Die Eigenwerte von 𝐴 sind {1; 10; 5; 2}. Die Eigenwerte von 𝐵 sind ca.
{−1,6; −1,6; 16; 4,4; 4,4; 16}.

[Link]

#include <valarray>
#include <algorithm>
#include <iostream>
#include <iomanip>

using namespace std;

template<class T> class matrix {


private:
valarray<T> v;
size_t m, n;

public:
matrix(size_t m_ = 0, size_t n_ = 0, T val_ = T())
: v(val_, m_ * n_),m(m_),n(n_) {}

size_t nrows() const { return m; }


size_t ncolumns() const { return n; }

matrix<T>& operator=(T x) { v = x; return *this; }

class matrix_slice : public slice {


friend class matrix;
private:
valarray<T>& v;

matrix_slice(valarray<T>& v_, size_t start, size_t size, size_t stride)


: slice(start, size, stride), v(v_) {}

public:
T& operator[](size_t i)
{ return v[start() + i*stride()]; }

matrix_slice& operator=(const matrix_slice&) = delete;


matrix_slice& operator=(valarray<T> w) {
v[static_cast<slice>(*this)] = w;
return *this;
}
matrix_slice& operator=(T x) {
v[static_cast<slice>(*this)] = x;
return *this;
}

Seite 8 von 13
Aufgabe 10* Programmieren II für Studierende der Mathematik — WiSe 2025/26

operator valarray<T>() {
return v[static_cast<slice>(*this)];
}
};

matrix_slice operator[](size_t i) { return row(i); }


matrix_slice row(size_t i)
{ return matrix_slice{v, i*n, n, 1}; }
matrix_slice column(size_t j)
{ return matrix_slice{v, j, m, n}; }

matrix_slice diag()
{ return matrix_slice{v, 0, min(m, n), n + 1}; }

auto begin() { return std::begin(v); }


auto end() { return std::end(v); }

friend istream& operator>>(istream& stream, matrix<T>& a) {


for (T& x: a)
stream >> x;
return stream;
}
};

using Vektor = valarray<double>;


using Matrix = matrix<double>;

bool jacobi(Matrix a, Vektor& ew, Matrix& ev, int maxcyc = 50, double eps = 1e-12) {
size_t n{[Link]()};
double sum_ndq{0};

for (size_t i = 0; i < n; i++)


for (size_t j = i + 1; j < n; j++)
sum_ndq += a[i][j] * a[i][j];
sum_ndq *= 2;

double sum_ndq_start{sum_ndq};

ev = 0;
[Link]() = 1;

bool success = false;


for (int cyc = 0; cyc < maxcyc; cyc++) {
for (size_t i = 0; i < n; i++)
for (size_t j = i + 1; j < n; j++) {
double c, s, t, tau;
if (abs(a[i][j]) <= eps) {
c = 1;
s = 0;
} else {
tau = (a[j][j] - a[i][i]) / (2 * a[i][j]);
t = (tau >= 0 ? 1 : -1) / (abs(tau) + sqrt(1 + tau * tau));
c = 1 / sqrt(1 + t * t);
s = c * t;
}

sum_ndq -= 2 * a[i][j] * a[i][j];

Seite 9 von 13
Aufgabe 10* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Vektor temp_i{[Link](i)};
Vektor temp_j{[Link](j)};
[Link](i) = c * temp_i - s * temp_j;
[Link](j) = s * temp_i + c * temp_j;

temp_i = [Link](i);
temp_j = [Link](j);
[Link](i) = c * temp_i - s * temp_j;
[Link](j) = s * temp_i + c * temp_j;

temp_i = [Link](i);
temp_j = [Link](j);
[Link](i) = c * temp_i - s * temp_j;
[Link](j) = s * temp_i + c * temp_j;
}

if (sum_ndq <= eps * sum_ndq_start) {


success = true;
break;
}
}

ew = [Link]();
return success;
}

int main() {
size_t n;
cout << ”n: ”; cin >> n;

Matrix a(n, n);


cout << ”Matrix: ” << endl;
cin >> a;

Matrix ev(n, n);


Vektor ew(n);
if (jacobi(a, ew, ev))
cout << ”Jacobi-Verfahren erfolgreich” << endl;
else
cout << ”Jacobi-Verfahren nicht erfolgreich” << endl;

cout << ”Eigenwerte:” << endl;


for (double x: ew)
cout << ” ” << setprecision(4) << setw(8) << fixed << x << endl;
cout << endl;

cout << ”Eigenvektoren:” << endl;


for (size_t i = 0; i < n; i++) {
cout << ” ”;
for (size_t j = 0; j < n; j++)
cout << setprecision(4) << setw(8) << fixed << ev[i][j];
cout << endl;
}
}

Seite 10 von 13
Aufgabe 10* Programmieren II für Studierende der Mathematik — WiSe 2025/26

n: 4
Matrix:
5 4 1 1
4 5 1 1
1 1 4 2
1 1 2 4
Jacobi-Verfahren erfolgreich
Eigenwerte:
1.0000
10.0000
5.0000
2.0000

Eigenvektoren:
0.7071 0.6325 -0.3162 0.0000
-0.7071 0.6325 -0.3162 0.0000
0.0000 0.3162 0.6325 -0.7071
0.0000 0.3162 0.6325 0.7071

n: 6
Matrix:
5 1 -2 0 -2 5
1 6 -3 2 0 6
-2 -3 8 -5 -6 0
0 2 -5 5 1 -2
-2 0 -6 1 6 -3
5 6 0 -2 -3 8
Jacobi-Verfahren erfolgreich
Eigenwerte:
-1.5987
-1.5987
16.1427
4.4560
4.4560
16.1427

Eigenvektoren:
-0.2001 0.4691 -0.0854 -0.6209 -0.4161 0.4171
-0.4610 0.3273 -0.3143 0.5528 0.2775 0.4460
0.2632 0.5924 0.7216 0.2349 0.0600 0.0173
0.4691 0.2001 -0.4171 0.4161 -0.6209 -0.0854
0.3273 0.4610 -0.4460 -0.2775 0.5528 -0.3143
0.5924 -0.2632 -0.0173 -0.0600 0.2349 0.7216

Seite 11 von 13
A. Jacobi-Verfahren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Anhang

A. Jacobi-Verfahren

Wir beobachten zunächst, dass eine symmetrische Matrix 𝐴 ähnlich ist zu einer Diagonalmatrix, d.h. es exis-
tiert 𝑈 mit 𝐴′ = 𝑈 ⊤𝐴𝑈 diagonal und 𝑈 orthogonal. Aus 𝐴′ und 𝑈 lassen sich die Eigenwerte bzw. die Eigen-
vektoren der Matrix 𝐴 ablesen.
Ansatz des Jacobi-Verfahrens ist es die Matrix 𝐴 sukzessive einer Folge von Transformationen zu unterwerfen.
Jede der Transformationen hat hierbei ausgehend von 0𝐴 = 𝐴 die Form 𝑘+1𝐴 = 𝑘+1𝑄𝑇 𝑘 𝐴 𝑘+1𝑄. Hierbei werden
die 𝑘 𝑄 geeignet gewählt, sodass 𝑘+1𝐴 an einer Stelle (𝑖, 𝑗) mit 𝑖 ≠ 𝑗 den Wert 0 hat, wobei 𝐴𝑖𝑗 i.A. noch
verschieden war von 0. Man sagt 𝑘 𝑄 lösche das Element der Matrix 𝑘 𝐴 an der Stelle (𝑖, 𝑗) aus.
Um das Element 𝑎𝑖𝑗 mit 𝑖 > 𝑗 auszulöschen aus einer Matrix 𝐴 wird 𝑄 gewählt als eine Givens-Rotation für
einen geeigneten Winkel 𝜗:

1 ··· 0 ··· 0 ··· 0 


©. .. .. .. .. ª® 
 cos 𝜗 𝑖′ = 𝑗 ′ = 𝑖
­ .. . . . .® 

­ 
 cos 𝜗 𝑖′ = 𝑗 ′ = 𝑗
­ ® 

­0 · · · cos 𝜗 · · · − sin 𝜗 · · · 0® 

­. .. .. .. .. ®® 
 sin 𝜗 𝑖 ′ = 𝑖, 𝑗 ′ = 𝑗
𝑄 = ­­ ..

. . . .® 𝑄 𝑖𝑗 ′ =
­ ® 
 − sin 𝜗 𝑖 ′ = 𝑗, 𝑗 ′ = 𝑖
­0 · · · sin 𝜗 · · · cos 𝜗 · · · 0® 

­. 

­ .. .. .. . . .. ®® 


1 𝑖′ = 𝑗 ′
­ . . . .® 
0 sonst
··· ··· · · · 1¬ 
«0 0 0

Es ist:
 𝑖  
𝑄𝑇 𝐴𝑄 = cos(2𝜗)𝐴𝑖𝑗 + 1/2 sin(2𝜗) 𝐴𝑖𝑖 − 𝐴 𝑗𝑗
𝑗
2𝐴𝑖𝑗 1
tan(2𝜗) = =: mit 𝐴𝑖𝑗 = 0
𝐴 𝑗𝑗 − 𝐴𝑖𝑖 𝜏
sign(𝜏)
𝑡 := √
|𝜏 | + 1 + 𝜏 2
1
cos 𝜗 = √
1 + 𝑡2
sin 𝜗 = 𝑡 · cos 𝜗

Die verschiedenen Varianten des Jacobi-Verfahren unterscheiden sich nun darin in welcher Reihenfolge die
Elemente von 𝐴 außerhalb der Diagonalen ausgelöscht werden. Klassisch wird jeweils das betragsmäßig größ-
te verbleibende nicht-Diagonal-Element gefunden und ausgelöscht. In diesem Fall ist die Konvergenz gegen
eine Diagonalmatrix naheliegend unter Betrachtung dessen, dass die Summe der Quadrate der nicht-Diagonal-
Elemente monoton fällt.
𝑛 (𝑛−1)
Eine Abfolge von 𝑁 := 2 Auslöschungen nennt man eine Zyklus.

Das zyklische Jacobi-Verfahren Statt jedes mal das betragsmäßig größte verbleibende nicht-Diagonal-
Element zu finden, löscht das zyklische Jacobi-Verfahren in jedem Zyklus alle nicht-Diagonal-Elemente in
einer beliebigen aber festen Reihenfolge ungeachtet ihrer Größe aus.

Seite 12 von 13
A. Jacobi-Verfahren Programmieren II für Studierende der Mathematik — WiSe 2025/26

Es ergibt sich eine Folge von Transformationen mit 0𝐴 = 𝐴, 𝑘+1𝐴 = (𝑛,𝑛−1)𝑘 𝐴 und:
 
𝑘
(𝑖,𝑗 ) 𝐴 = (𝑖,𝑗𝑘) 𝑄𝑇 𝑃 𝑘 𝑘
(𝑖,𝑗 ) 𝐴 (𝑖,𝑗 ) 𝑄


 𝑘
𝐴 𝑖 > 𝑗 −1
  

 (𝑖,𝑗 −1)
𝑘 𝑘
𝑃 (𝑖,𝑗 ) 𝐴 := (𝑖 −1,𝑛) 𝐴 𝑖 > 1


𝑘 𝐴 sonst

𝑘 : 𝑘 𝑘 𝑘 𝑘 𝑘
...(𝑖 ′ ,𝑗 ′ ) 𝑄 = (1,2) 𝑄 (1,3) 𝑄 . . . (1,𝑗 ′ ) 𝑄 (2,3) 𝑄 (2,4) 𝑄 . . . (2,𝑗 ′𝑘) 𝑄 . . . (𝑖 ′,𝑗 ′𝑘) 𝑄
𝑘
𝑄 := ...(𝑛,𝑛−1)𝑘 𝑄
1...𝑘
𝑄 := 1𝑄 2𝑄 . . . 𝑘 𝑄

 1...𝑘
𝑄 𝑖 > 𝑗 −1
 

 
...(𝑖,𝑗 −1)
𝑃 ...(𝑖,𝑗 ) 𝑄 := ...(𝑖 −1,𝑛) 𝑄
1...𝑘 1...𝑘
𝑖>1


 1...𝑘 −1𝑄 sonst

 
𝑘
Es lässt sich zeigen, dass 𝐴 ebenfalls konvergiert gegen eine Diagonalmatrix und, wegen

𝑘 ∈N 
𝑘
𝐴 = 1...𝑘 𝑄𝑇 𝐴 1...𝑘 𝑄, konvergiert 𝑄
1...𝑘
gegen eine Matrix, die Spaltenweise die Eigenvektoren
𝑘 ∈N
von 𝐴 enthält.
 
Wir beobachten, dass bei der Multiplikation von 𝑃 (𝑖,𝑗𝑘) 𝐴 mit (𝑖,𝑗𝑘) 𝑄 nur jeweils die 𝑖-te Zeile und die 𝑗-te
Spalte verändert werden, wie folgt:
     
𝑎𝑖′ := (cos 𝜗) 𝑃 (𝑖,𝑗𝑘) 𝐴 − (sin 𝜗) 𝑃 (𝑖,𝑗𝑘) 𝐴
𝑖 𝑗
     
𝑎 ′𝑗 := (sin 𝜗) 𝑃 (𝑖,𝑗𝑘) 𝐴 + (cos 𝜗) 𝑃 (𝑖,𝑗𝑘) 𝐴
𝑖 𝑗
(𝑎 ′′ )𝑖 := (cos 𝜗) (𝑎 ′ )𝑖 − (sin 𝜗) (𝑎 ′ ) 𝑗
(𝑎 ′′ ) 𝑗 := (sin 𝜗)(𝑎 ′ )𝑖 + (cos 𝜗) (𝑎 ′ ) 𝑗
 ′ 𝑖′


 (𝑎 ) 𝑗 ′ 𝑗 ′ ∈ {𝑖, 𝑗 }, 𝑖 ′ ∉ {𝑖, 𝑗 }


 ′′ 𝑖 ′
𝑘 𝑖′ (𝑎 ) ′ 𝑖 ′ ∈ {𝑖, 𝑗 }
(𝑖,𝑗 ) 𝐴 𝑗 ′ =    𝑗   ′

 𝑘
𝑖

 𝑃 (𝑖,𝑗 ) 𝐴 𝑗 ′ sonst

 
Analog für die Berechnung von ...(𝑖,𝑗
1...𝑘
)
𝑄 aus 𝑃 1...𝑘
...(𝑖,𝑗 )
𝑄 :
     
...(𝑖,𝑗 ) 𝑄 𝑖 = (cos 𝜗) 𝑃 ...(𝑖,𝑗 𝑄 − (sin 𝜗) 𝑃 𝑄
1...𝑘 1...𝑘 1...𝑘
) 𝑖 ...(𝑖,𝑗 ) 𝑗
     
...(𝑖,𝑗 ) 𝑄 𝑗 = (sin 𝜗) 𝑃 ...(𝑖,𝑗 ) 𝑄 + (cos 𝜗) 𝑃 ...(𝑖,𝑗 )𝑄
1...𝑘 1...𝑘 1...𝑘
𝑖 𝑗

Seite 13 von 13
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Blatt 6 – Lösungsvorschlag

Aufgabe 11 Erstellen Sie eine Funktion tabulate ohne Rückgabewert mit den Parametern:

• eine Referenz auf einen Ausgabestrom,


• einen Namen vom Typ string und
• ein Funktionsobjekt für Funktionen {⊥, ⊤} × {⊥, ⊤} → {⊥, ⊤}, die Menge {⊥, ⊤} identifizieren wir
hierbei mit dem Typ bool (im Folgenden zweistellige logische Verknüpfungen).

Die Funktion soll auf dem gegebenen Ausgabestrom eine Wahrheitstafel (beschriftet oben links mit dem Na-
men) für die als Parameter übergegebene Funktion ausgeben.

tabulate

void tabulate(ostream &stream, string fn, function<BinBoolOp> f) {


int w = max(1, static_cast<int>([Link]()));
stream << setw(w) << fn;
for (bool y: {false, true})
stream << setw(2) << y;
stream << endl;
for (bool x: {false, true}) {
stream << setw(w) << x;
for (bool y: {false, true}) {
stream << setw(2) << f(x, y);
}
stream << endl;
}

Aufruf commutative
}

Rufen Sie im Hauptprogramm Ihre Funktion tabulate auf für die logischen Verknüpfungen ∧, ∨, ⊕ und ⇒
(logische Implikation).
Hinweis. Die Header-Datei <functional> stellt für ∧, ∨ und ⊕ jeweils die Funktionsobjekte bit_and<bool>(),
bit_or<bool>() bzw. bit_xor<bool>() zur Verfügung.

bool_functions.cpp

#include <iostream>
#include <iomanip>
#include <functional>
#include <algorithm>

using namespace std;

Gleichheitsoperator

commutative

Seite 1 von 10
Aufgabe 11 Programmieren II für Studierende der Mathematik — WiSe 2025/26

tabulate

bool logical_implies(bool a, bool b) {


return !a || b; // Äquivalent: a <= b
}

int main() {
tabulate(cout, ”&”, bit_and<bool>());
cout << endl;
tabulate(cout, ”|”, bit_or<bool>());
cout << endl;
tabulate(cout, ”^”, bit_xor<bool>());
cout << endl;
tabulate(cout, ”=>”, logical_implies);
}

Überladen Sie den Gleichheitsoperator für Funktionsobjekte für zweistellige logische Verknüpfungen in ma-
thematisch sinnvoller Weise.

Gleichheitsoperator

using BinBoolOp = bool(bool, bool);

bool operator==(function<BinBoolOp> f, function<BinBoolOp> g) {


for (bool x: {false, true})
for (bool y: {false, true})
if (f(x, y) != g(x, y))
return false;
return true;
}

Erstellen Sie eine Funktion commutative die für ein als Parameter übergebenes Funktionsobjekt für eine zwei-
stellige logische Verknüpfung feststellt ob dieses kommutativ ist und geben Sie das Resultat als Rückgabewert
vom Typ bool zurück. Verwenden Sie den von Ihnen überladenen Gleichheitsoperator für Funktionsobjekte
für zweistellige logische Verknüpfungen und bind in geeigneter Weise.

commutative

bool commutative(function<BinBoolOp> f) {
using namespace placeholders;
return f == bind(f, _2, _1);
}

Erweitern Sie Ihre Funktion tabulate sodass diese nun auch ausgibt ob die jeweils betrachtete zweistellige
logische Verknüpfung kommutativ ist.

Seite 2 von 10
Aufgabe 12 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Aufruf commutative

stream << ”Kommutativ: ” << commutative(f) << endl;

& 0 1
0 0 0
1 0 1
Kommutativ: 1

| 0 1
0 0 1
1 1 1
Kommutativ: 1

^ 0 1
0 0 1
1 1 0
Kommutativ: 1

=> 0 1
0 1 1
1 0 1
Kommutativ: 0

Aufgabe 12 In der Header-Datei <algorithm> ist die Funktion for_each definiert. Sie nimmt drei Parame-
ter. Die ersten beiden Parametern sind Iteratoren und beschreiben ein Intervall [𝑖, 𝑖 ′ ) eines Objektes eines
Behälterdatentyps mit Elementen vom Typ 𝑇 . Der dritte Parameter ist ein Funktionsobjekt für den Funktions-
typ mit einem Parameter vom Typ 𝑇 und keinem Rückgabewert, also Rückgabetyp void.
Lesen Sie in Ihrem Hautprogramm solange Werte vom Typ double von der Standardeingabe ein (mit Einga-
beaufforderung) und hängen Sie diese an ein Objekt der Klasse list<double> hinten an, bis das Einlesen von
der Standardeingabe fehlschlägt. Verwenden Sie dann die Funktion for_each und für dessen Parameter einen
geeigneten Funktionsauruf von bind um das arithmetische Mittel der eingelesenen Werte zu berechnen und
geben Sie dieses aus.

Hauptprogramm Eingabe

list<double> vals;
double x;
while (cout << ”x: ”, cin >> x)
vals.push_back(x);

Hilfsfunktion foreach

void f(double& sum, double x) { sum += x; }

Hauptprogramm foreach

double sum = 0;
using namespace placeholders;
for_each([Link](), [Link](), bind(f, ref(sum), _1));
cout << ”MW (for_each): ” << (sum / [Link]()) << endl;

Seite 3 von 10
Aufgabe 12 Programmieren II für Studierende der Mathematik — WiSe 2025/26

In der Header-Datei <numeric> ist die Funktion accumulate definiert. Sie nimmt vier Parameter. Die ersten
beiden Parameter beschreiben erneut ein Intervall [𝑖, 𝑖 ′ ) eines Objektes eines Behälterdatentyps mit Elemen-
ten vom Typ 𝑇 . Der dritte Parameter ist ein Anfangswert von beliebigem Typ 𝑇 ′ . Der vierte Parameter ist ein
Funktionsobjekt für den Funktionstyp mit zwei Parametern und 𝑇 ′ als Rückgabetyp. Der erste Parameter des
Funktionstyps ist ebenfalls vom Typ 𝑇 ′ und der zweite vom Typ 𝑇 . Bei Aufruf der Funktion accumulate auf
einen Behälter mit Inhalt (im gegebenen Intervall) 𝑥 0, 𝑥 1, . . . , 𝑥𝑛 , Anfangswert 𝑦0 und Funktionsobjekt 𝑓 wird
als Rückgabewert von accumulate der Wert 𝑓 (. . . 𝑓 (𝑓 (𝑦0, 𝑥 1 ), 𝑥 2 ) . . . , 𝑥𝑛 ) geliefert.
Erweitern Sie Ihr Hauptprogramm sodass dieses zusätzlich auch accumulate benutzt um erneut das arithme-
tische Mittel der eingelesenen Werte zu berechnen und geben Sie auch diesen Wert zur Kontrolle aus.

Hilfsfunktion accumulate

double g(int& count, double prev, double x) {


int ncount = count + 1;
double next = (prev * count + x) / ncount;
count = ncount;
return next;
}

Hauptprogramm accumulate

int count = 0;
double mean = accumulate([Link](), [Link](),
0.0, bind(g, ref(count), _1, _2));
cout << ”MW (accumulate): ” << mean << endl;

foreach_mean.cpp

#include <iostream>
#include <list>
#include <functional>
#include <algorithm>
#include <numeric>

using namespace std;

Hilfsfunktion foreach

Hilfsfunktion accumulate

int main() {

Hauptprogramm Eingabe

Hauptprogramm foreach

Hauptprogramm accumulate
}

Seite 4 von 10
Aufgabe 12 Programmieren II für Studierende der Mathematik — WiSe 2025/26

x: 3
x: 1
x: 4
x: 0
x: 5
x: -1
x: 6
x: -2
x: 7
x: -3
x:
MW (for_each): 2
MW (accumulate): 2

Seite 5 von 10
Aufgabe 13* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Aufgabe 13* Es soll im Folgenden das Konvergenzverhalten des Newton-Verfahrens zur Bestimmung von
Nullstellen analytischer Funktionen 𝑓 untersucht werden.
Für 𝑓 : C → C betrachten wir die Folge (𝑧)𝑛∈N mit 𝑧 0 ∈ C beliebig und:

𝑓 (𝑧𝑘 )
𝑧𝑘+1 = 𝑧𝑘 −
𝑓 ′ (𝑧𝑘 )

Wir untersuchen die Konvergenz der Folge (𝑧)𝑛∈N für Startwerte 𝑧 0 in einem Rechteck mit Eckpunkten 𝑎 + 𝑖𝑏
und 𝑐 + 𝑖𝑑.
Implementieren Sie eine Klasse Konvergenz. Ein Objekt der Klasse soll der Untersuchung einer Instanz des
Problems dienen, also konkrete Werte enthalten für 𝑎, 𝑏, 𝑐, 𝑑, 𝑓 , 𝑓 ′ , sowie 𝑀, 𝑁 ∈ N und 𝐷 ∈ R.
Implementieren Sie eine statische Komponentenfunktion newtonverfahren für als Parameter gegebene 𝑧 0 ,
𝑓 und 𝑓 ′ . Liefern Sie als Rückgabewert einen bool Wert der beschreibt ob bei der Iteration des Newton-
Verfahrens nach maximal itmax = 200 Durchläufen die relative Abweichung aufeinander folgender Glieder
𝜀 = 10−12 unterschritten hat, die Folge also auf einen Wert 𝑧 konvergiert ist. Stellen Sie sicher, dass der Wert
𝑧 auf geeignete Weise an den Code, in dem die Funktion newtonverfahren später aufgerufen wird, zurück
kommuniziert werden kann.
Implementieren Sie eine Methode analysieren in der Sie das Verfahren auf den Punkten eines homogenen
rechteckigen Gitters mit 𝑀 ·𝑁 Punkten ausführen. Speichern Sie den jeweils gefunden Wert 𝑧 (die Nullstelle) in
einem Vektor komplexer Zahlen ab, jedoch nur wenn er von allen bereits gespeicherten Werten betragsmäßig
um mehr als 𝐷 > 0 abweicht. Halten Sie zudem fest wie oft jede Nullstelle und der Fall, dass die Folge nicht
innerhalb von itmax Gliedern bis auf 𝜀 konvergiert ist, aufgetreten ist.
Überladen Sie den Ausgabeoperator für Objekte der Klasse Konvergenz sodass die beobachteten Nullstellen
jeweils zusammen mit ihrer Häufigkeit ausgegeben werden.
Analysieren Sie die folgenden Beispiele für geeignet einzulesende Werte 𝑎, 𝑏, 𝑐, 𝑑, 𝑀, 𝑁 und 𝐷 (z.B. −4, 4, 4,
−4, 500, 500, 1 · 10−3 ):

𝑝 1 (𝑧) = 𝑧 3 − 1 (1)
𝑝 2 (𝑧) = 𝑧 + 3𝑧 − 𝑖
3 2
(2)
𝑝 3 (𝑧) = 𝑧 − 2𝑖𝑧 − 13𝑧 + 14𝑖𝑧 + 24𝑧 − 1
5 4 3 2
(3)
𝑧
𝑓4 (𝑧) = 𝑒 (𝑧 − 1)
3
(4)

Erzeugen Sie jeweils eine Ausgabedatei, die die Einzugsbereiche der einzelnen Nullstellen graphisch darstellt.
Verwenden Sie hierfür das Format P6 oder P3 (resultiert in sehr großen Dateien) zur Erstellung einer Datei im
PPM-Format (für Informationen zum Format siehe Kommandozeilen-Befehl man 5 ppm oder auch Wikipedia1 ).

Erstellen Sie, als statische Datenkomponente der Klasse Konvergenz, einen Vektor von mindestens 7 verschie-
denen Farben.
Erzeugen Sie nach Analyse des Konvergenzverhaltens für eine Instanz des Problems jeweils eine Datei im PPM-
Format in der jeder Pixel einem der betrachteten Gitterpunkte entspricht. Ordnen Sie jeder beobachteten Null-
stelle eine Farbe zu und färben Sie die Pixel jeweils in der Farbe der Nullstelle zu dem das Newton-Verfahren,
gestartet am entsprechenden Gitterpunkt, konvergiert. Im Falle der Nicht-Konvergenz soll der Pixel weiß
gefärbt werden.
Hinweis. Es sollte eine fraktale Struktur klar zu erkennen sein. Es handelt sich um sog. Newtonfraktale2 .

1 [Link]/wiki/Portable_Anymap
2 [Link]/wiki/Newtonfraktal

Seite 6 von 10
Aufgabe 13* Programmieren II für Studierende der Mathematik — WiSe 2025/26

[Link]

#include <complex>
#include <functional>
#include <vector>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <fstream>

using namespace std;

using Complex = complex<double>;

class Farbe {
public:
unsigned char r, g, b;
Farbe() {}
Farbe(unsigned char r_, unsigned char g_, unsigned char b_): r(r_), g(g_), b(b_) {}

friend ostream& operator<<(ostream& stream, const Farbe& farbe) {


return stream << farbe.r << farbe.g << farbe.b;
}
};

class Konvergenz {
private:
static const vector<Farbe> farbtabelle;
static constexpr unsigned long itmax = 200;
static constexpr double eps = 1e-12;

const function<Complex(Complex)>& f, fderiv;


double a, b, c, d;
int M, N;
double D;

vector<Complex> nullstellen{};
vector<int> haeufigkeiten{{0}};

vector<int> bild;
ofstream bildstrm;

public:
Konvergenz (const function<Complex(Complex)>& f_, const function<Complex(Complex)>&
↩→ fderiv_,
double a_, double b_, double c_, double d_, int M_, int N_, double D_,
string bildstrm_):
f(f_), fderiv(fderiv_), a(a_), b(b_), c(c_), d(d_), M(M_), N(N_), D(D_),
↩→ bildstrm(bildstrm_, ios::binary)
{
bildstrm << ”P6” << ” ” << M << ” ” << N << ” ” << 255 << ” ”;
}

static bool newtonverfahren(Complex z0, Complex& z, function<Complex(Complex)> f,


↩→ function<Complex(Complex)> fderiv) {
unsigned long its = itmax;
Complex z_prev;
z = z0;

Seite 7 von 10
Aufgabe 13* Programmieren II für Studierende der Mathematik — WiSe 2025/26

do {
z_prev = z;
z = z - f(z) / fderiv(z);

its--;
} while (its > 0 && abs(z - z_prev) > eps * abs(z_prev));

return its > 0;


}

Konvergenz& analysieren() {
for (int n = N - 1; n >= 0; n--)
for (int m = 0; m < M; m++) {
Complex pos{a + m * (b - a) / (M - 1), c + n * (d - c) / (N - 1)};

Complex nullstelle;
bool konvergiert = newtonverfahren(pos, nullstelle, f, fderiv);

vector<int>::size_type i = 0;
if (konvergiert) {
for (i = 0; i < [Link](); i++) {
if (abs(nullstellen[i] - nullstelle) >= D)
continue;

haeufigkeiten[i + 1]++;
break;
}

if (i >= [Link]()) {
nullstellen.push_back(nullstelle);
haeufigkeiten.push_back(1);
}

i++;
} else {
haeufigkeiten[0]++;
}

bild.push_back(i);
}

for (int nullstelle: bild) {


int farbe
= nullstelle
? round(((double) ([Link]() - 2)) * ((double) nullstelle - 1) / ((double)
↩→ ([Link]() - 1))) + 1
: 0;
bildstrm << farbtabelle[farbe];
}

return *this;
}

friend ostream& operator<<(ostream& out, const Konvergenz& k) {


out << ”N/A(” << [Link][0] << ”)”;
for (vector<int>::size_type i = 0; i < [Link](); i++)
out << ”, ” << [Link][i] << ”(” << [Link][i + 1] << ”)”;
return out;

Seite 8 von 10
Aufgabe 13* Programmieren II für Studierende der Mathematik — WiSe 2025/26

}
};

const vector<Farbe> Konvergenz::farbtabelle{


{255u, 255u, 255u},
{255u, 0u, 0u}, {255u, 255u, 0u}, {0u, 255u, 0u}, {0u, 255u, 255u}, {0u, 0u, 255u}, {255u,
↩→ 0u, 255u}
};

int main() {
using namespace std::complex_literals;

double a, b, c, d, D;
int M, N;

cout << ”a, b, c, d, M, N, D: ”;


cin >> a >> b >> c >> d >> M >> N >> D;

cout << setprecision(log10(1.0 / D));

cout << Konvergenz{


[] (Complex z) -> Complex { return z * z * z - 1.0; },
[] (Complex z) -> Complex { return 3.0 * z * z; },
a, b, c, d, M, N, D,
”newton_p1.ppm”
}.analysieren() << endl;

cout << Konvergenz{


[] (Complex z) -> Complex { return z * z * z + 3.0 * z * z - 1i; },
[] (Complex z) -> Complex { return 3.0 * z * z + 6.0 * z; },
a, b, c, d, M, N, D,
”newton_p2.ppm”
}.analysieren() << endl;

cout << Konvergenz{


[] (Complex z) -> Complex { return z * z * z * z * z - 2.0 * 1i * z * z * z * z - 13.0 * z
↩→ * z * z + 14.0 * 1i * z * z + 24.0 * z - 1.0; },
[] (Complex z) -> Complex { return 5.0 * z * z * z * z - 8.0 * 1i * z * z * z - 36.0 * z *
↩→ z + 28.0 * 1i * z + 24.0; },
a, b, c, d, M, N, D,
”newton_p3.ppm”
}.analysieren() << endl;

cout << Konvergenz{


[] (Complex z) -> Complex { return exp(z) * (z * z * z - 1.0); },
[] (Complex z) -> Complex { return exp(z) * (z * z * z - 1.0) + exp(z) * 3.0 * z * z; },
a, b, c, d, M, N, D,
”newton_f4.ppm”
}.analysieren() << endl;
}

a, b, c, d, M, N, D: -4 4 -4 4 500 500 1e-3


N/A(0), (-0.5,0.866)(82210), (-0.5,-0.866)(82210), (1,0)(85580)
N/A(0), (-3.01,0.11)(33882), (-0.394,-0.472)(124782), (0.402,0.362)(91336)
N/A(0), (-1.48,0.628)(62841), (0.0417,-0.00101)(85351), (1.42,0.646)(62342),
↩→ (3.08,0.364)(19587), (-3.06,0.363)(19879)
N/A(69600), (-0.5,0.866)(71390), (1,0)(37620), (-0.5,-0.866)(71390)

Seite 9 von 10
Aufgabe 13* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Gleichung 1 Gleichung 2

Gleichung 3 Gleichung 4

Seite 10 von 10
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Blatt 7 – Lösungsvorschlag

Aufgabe 14 Eine symmetrische, positiv definite Matrix 𝐴 = (𝑎𝑖 𝑗 )𝑖,𝑗=0,...,𝑛−1 ∈ R𝑛×𝑛 ist darstellbar als Pro-
dukt 𝐴 = 𝐿𝐿𝑇 mit 𝐿 = (𝑙𝑖 𝑗 )𝑖,𝑗=0,...,𝑛−1 einer unteren linken Dreiecksmatrix mit positiven Hauptdiagonalele-
menten (Cholesky-Zerlegung). 𝐿 kann berechnet werden wie folgt:
q Í 𝑗 −1 2 
𝑙𝑗 𝑗 𝑎 𝑗 𝑗 − 𝑘=0 𝑙 𝑗𝑘 

= 

 Í 𝑗 −1  𝑗 = 0, . . . , 𝑛 − 1


𝑙𝑖 𝑗 = 𝑙 1𝑗 𝑗 𝑎𝑖 𝑗 − 𝑘=0 𝑙𝑖𝑘 𝑙 𝑗𝑘 𝑖 = 𝑗 + 1, . . . , 𝑛 − 1 

Erstellen Sie eine Klasse Cholesky mit einem Konstruktor, der die Cholesky-Zerlegung für eine als Refe-
renzarameter übergegebene Matrix durchführt und nur 𝐿 als Attribut des erzeugten Objektes speichert. Der
Konstruktor soll ein weiteres Argument 𝜀 akzeptieren (optional mit Voreinstellung 1 · 10−10 ). Wenn bei der
Í
Berechnung von 𝑙 𝑗 𝑗 der Radikant betragsmäßig kleiner oder gleich 𝜀 · Spur(𝐴) mit Spur(𝐴) = 𝑛−1
𝑖=0 𝑎𝑖𝑖 ist, soll
das Programm unter Ausgabe einer aussagekräftigen Fehlermeldung geeignet abgebrochen werden. Zudem
soll der Konstruktor überprüfen ob die gegebene Matrix quadratisch ist und geeignet abbrechen, falls nicht.

Typaliase

using DVector = vector<double>;


using DMatrix = vector<vector<double>>;

Cholesky

class Cholesky {
private:
DMatrix L;

public:
Cholesky (const DMatrix& matrix, double eps = 1e-10)
: L([Link](), vector<double>([Link]())) {
unsigned int n = [Link]();
for (unsigned int i = 0; i < n; i++)
if (matrix[i].size() != n) {
ostringstream sstr;
sstr << ”Matrix nicht quadratisch an Zeile ” << i;
throw invalid_argument{[Link]()};
}

double trace = 0;
for (unsigned int i = 0; i < n; i++)
trace += matrix[i][i];

for (unsigned int j = 0; j < n; j++) {


L[j][j] = matrix[j][j];
for (unsigned int k = 0; k < j; k++)
L[j][j] -= L[j][k] * L[j][k];

if (abs(L[j][j]) <= abs(eps * trace)) {


ostringstream sstr;
sstr << ”Diagonalelement zu klein bei ” << j;

Seite 1 von 12
Aufgabe 14 Programmieren II für Studierende der Mathematik — WiSe 2025/26

throw runtime_error{[Link]()};
}
L[j][j] = sqrt(L[j][j]);

for (unsigned int i = j + 1; i < n; i++) {


L[i][j] = matrix[i][j];
for (unsigned int k = 0; k < j; k++)
L[i][j] -= L[j][k]*L[i][k];
L[i][j] /= L[j][j];
}
}
}

getEntry

Cholesky Ausgabe

solve
};

Implementieren Sie eine Methode getEntry von Cholesky mit zwei Parametern 𝑖 und 𝑗. Rückgabewert der
Ímin(𝑖,𝑗 )
Methode soll 𝑎𝑖 𝑗 = 𝑘=0 𝑙 𝑗𝑘 𝑙𝑖𝑘 als Wert vom Typ double sein.

getEntry

double getEntry(unsigned int i, unsigned int j) const {


double res = 0;
for (unsigned int k = 0; k <= min(i, j); k++) {
res += L[j][k] * L[i][k];
}
return res;
}

Überladen Sie den Ausgabeoperator für Cholesky geeignet. Es soll die ursprüngliche Matrix 𝐴 unter Verwen-
dung der Methode getEntry ausgegeben werden.

Cholesky Ausgabe

friend ostream& operator<<(ostream& stream, const Cholesky& c) {


for (unsigned int i = 0; i < [Link](); i++) {
stream << ”(”;
for (unsigned int j = 0; j < [Link](); j++)
stream << setw(9) << [Link](i, j);
stream << ”)”;
if (i + 1 < [Link]())
stream << endl;
}

Seite 2 von 12
Aufgabe 14 Programmieren II für Studierende der Mathematik — WiSe 2025/26

return stream;
}

Implementieren Sie ein Hauptprogramm in dem zunächst die Dimension 𝑛 gefolgt von den Einträgen einer
𝑛 × 𝑛-Matrix 𝐴 von der Standardeingabe eingelesen wird. Es soll dann ein Objekt der Klasse Cholesky aus 𝐴
erzeugt werden (also die Cholesky-Zerlegung durchgeführt werden). Geben Sie das erstellte Objekt dann zur
Kontrolle auf der Standardausgabe aus.

[Link]

#include <cmath>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <stdexcept>

using namespace std;

Typaliase

Cholesky

Ausgabeoperator Vektor

int main() {
unsigned int n;
cout << ”n: ”;
cin >> n;

DMatrix A(n, vector<double>(n));


for (unsigned i = 0; i < n; i++) {
cout << ”A[” << i << ”][0..” << n - 1 << ”]: ”;
for (double& x: A[i])
cin >> x;
}

Einlesen Vektor

cout << endl;

Cholesky L{A};
cout << L << endl
<< endl;

Kontrollausgabe Vektor

cout << endl;

Aufruf solve
}

Ein lineares Gleichungssystem der Form 𝐴𝑥 = 𝑏 kann bei gegebenem 𝑏 = (𝑏𝑖 )𝑖=0,...,𝑛−1 und gesuchtem

Seite 3 von 12
Aufgabe 14 Programmieren II für Studierende der Mathematik — WiSe 2025/26

𝑥 = (𝑥𝑖 )𝑖=0,...,𝑛−1 mithilfe der Cholesky-Zerlegung von 𝐴 effizient gelöst werden. Hierfür lösen wir die Glei-
chungssysteme 𝐿𝑦 = 𝑏 mit 𝑦 = (𝑦𝑖 )𝑖=0,...,𝑛−1 und 𝐿𝑇 𝑥 = 𝑦 wie folgt:
!
1 Õ 𝑖 −1
𝑦𝑖 = 𝑏𝑖 − 𝑙𝑖𝑘 𝑦𝑘 𝑖 = 0, . . . , 𝑛 − 1
𝑙𝑖𝑖
𝑘=0
!
1 Õ
𝑛−1
𝑥𝑖 = 𝑦𝑖 − 𝑙𝑘𝑖 𝑥𝑘 𝑖 = 𝑛 − 1, . . . , 0
𝑙𝑖𝑖
𝑘=𝑖+1

Implementieren Sie eine Methode solve von Cholesky die 𝑏 als Referenzparameter akzeptiert und 𝑥 liefert.

solve

DVector solve(const DVector& b) {


DVector y([Link]()), x([Link]());
for (unsigned int i = 0; i < [Link](); i++) {
y[i] = b[i];
for (unsigned int k = 0; k < i; k++)
y[i] -= L[i][k] * y[k];
y[i] /= L[i][i];
}
for (unsigned int m = 0; m < [Link](); m++) {
const unsigned int i = [Link]() - m - 1;
x[i] = y[i];
for (unsigned int k = i + 1; k < [Link](); k++)
x[i] -= L[k][i] * x[k];
x[i] /= L[i][i];
}
return x;
}

Erweitern Sie ihr Hauptprogramm darum, dass auch 𝑏 eingelesen und zur Kontrolle wieder ausgegeben wird.
Rufen Sie dann mit 𝑏 die Methode solve auf dem Objekt der Klasse Cholesky auf und geben Sie das Ergebnis
ebenfalls aus.

Einlesen Vektor

DVector b(n);
cout << ”b[0..” << n - 1 << ”]: ”;
for (double& x: b)
cin >> x;

Ausgabeoperator Vektor

ostream& operator<<(ostream& stream, const DVector& v) {


stream << ”(”;
for (unsigned int i = 0; i < [Link](); i++)
stream << setw(9) << v[i];
stream << ”)”;
return stream;
}

Seite 4 von 12
Aufgabe 14 Programmieren II für Studierende der Mathematik — WiSe 2025/26

Kontrollausgabe Vektor

cout << b << endl;

Aufruf solve

cout << [Link](b) << endl;

Testen Sie Ihr Programm mit den folgenden Beispielen:

a) b) c)

10 1 2 3 4 12
© ª © ª
1 0 3 0 10 ­ 1 9 −1 2 −3® ­−8®
© ª © ª ­ ® ­ ®
1 2 4 −3 ­0 4 0 0® ­ 8 ® ­ 2 −1 7 3 −5® 𝑥 = ­ 1 ®
© ª © ª ­ ®𝑥 = ­ ® ­ ® ­ ®
­2 13 23 ® 𝑥 = ­−21 ® ­3 0 25 20® ­158® ­3 2 3 12 1 ® ­3®
«4 23 77¬ «−73¬ «0 0 20 61¬ «304¬ « 4 −3 −5 1 15 ¬ «4¬

n: 3
A[0][0..2]: 1 2 4
A[1][0..2]: 2 13 23
A[2][0..2]: 4 23 77
b[0..2]: -3 -21 -73

( 1 2 4)
( 2 13 23)
( 4 23 77)

( -3 -21 -73)

( 1 0 -1)

n: 4
A[0][0..3]: 1 0 3 0
A[1][0..3]: 0 4 0 0
A[2][0..3]: 3 0 25 20
A[3][0..3]: 0 0 20 61
b[0..3]: 10 8 158 304

( 1 0 3 0)
( 0 4 0 0)
( 3 0 25 20)
( 0 0 20 61)

( 10 8 158 304)

( 1 2 3 4)

n: 5
A[0][0..4]: 10 1 2 3 4
A[1][0..4]: 1 9 -1 2 -3
A[2][0..4]: 2 -1 7 3 -5

Seite 5 von 12
Aufgabe 14 Programmieren II für Studierende der Mathematik — WiSe 2025/26

A[3][0..4]: 3 2 3 12 1
A[4][0..4]: 4 -3 -5 1 15
b[0..4]: 12 -8 1 3 4

( 10 1 2 3 4)
( 1 9 -1 2 -3)
( 2 -1 7 3 -5)
( 3 2 3 12 1)
( 4 -3 -5 1 15)

( 12 -8 1 3 4)

( 2.21842 -2.0232 -2.14001 0.691682 -1.489)

Seite 6 von 12
Aufgabe 15* Programmieren II für Studierende der Mathematik — WiSe 2025/26

Aufgabe 15* Vereinbaren Sie eine abstrakte Basisklasse (ABC) PDF zur Modellierung von Wahrscheinlich-
keitsdichtefunktionen (PDFs) R → [0, 1]. Abgeleitete Klassen, die PDFs modellieren, sollen eine konstante
Methode implementieren, die den Mittelwert der Verteilung als Rückgabewert vom Typ double liefert.

PDF

class PDF {
public:
virtual double getMean() const = 0;
virtual ~PDF() = default;
};

Implementieren Sie Klassen für die folgenden Familien von PDFs inkl. Überladung des Funktionsauswertungs-
operators:
 
𝑥 −𝜇  2
Normalverteilung PDFs 𝑓 mit zwei Parametern 𝜇, 𝜎 ∈ R, Mittelwert 𝜇 und 𝑓 (𝑥) = 𝜎 √12𝜋 exp − 12 𝜎 .

Gleichverteilung PDFs 𝑓 mit zwei Parametern ( 𝑎, 𝑏 ∈ R mit 𝑎 < 𝑏 (geeignete Fehlerbehandlung im Kon-
1
𝑎 ≤𝑥 ≤𝑏
struktor), Mittelwert 𝑎 + 𝑏 −𝑎
2 und 𝑓 (𝑥) = 𝑏 −𝑎
0 sonst

Exponentialverteilung PDFs 𝑓 mit Parameter 𝜆 ∈ R, Mittelwert 1


𝜆 und 𝑓 (𝑥) = 𝜆 exp(−𝜆𝑥)

Verteilungen

class NormV : public PDF {


public:
double mean, stddev;
NormV (double mean_ = 0, double stddev_ = 1)
: mean(mean_), stddev(stddev_) {}
virtual double operator()(double x) const {
return M_2_SQRTPI / (2 * M_SQRT2 * stddev) * exp(-((x - mean) * (x - mean)) /
↩→ (2*stddev*stddev));
}
double getMean() const {
return mean;
}
};
class GleichV : public PDF {
private:
double from, to;

public:
GleichV(double from_, double to_): from(from_), to(to_) {
if (to <= from) {
ostringstream sstr;
sstr << ”NormV(” << from << ”, ” << to << ”)”;
throw invalid_argument([Link]());
}
}
virtual double operator()(double x) const {
if (x < from || x > to)
return 0;
return 1 / (to - from);
}
double getMean() const {

Seite 7 von 12
Aufgabe 15* Programmieren II für Studierende der Mathematik — WiSe 2025/26

return from + (to - from) / 2;


}
};
class ExpV : public PDF {
public:
double rate;
ExpV(double rate_): rate(rate_) {}
virtual double operator()(double x) const {
if (x < 0)
return 0;
return rate * exp(- rate * x);
}
double getMean() const {
return 1 / rate;
}
};

Implementieren Sie eine Klasse SumV für Summen von einer oder mehr PDFs. Stellen Sie die Einhaltung der
Invariante bzgl. der Anzahl der enthaltenen PDFs stets sicher. Zur Modellierung von SumV empfiehlt sich
ein privates Attribut vom Typ list<unique_ptr<PDF>>. Implementieren Sie einen Konstruktor für SumV, der
einen Wert vom Typ „Zeiger auf PDF“ akzeptiert. Implementieren Sie einen Additions-Zuweisungsoperator
(+=) als Methode von SumV mit Parameter vom Typ „Zeiger auf PDF“. Der Mittelwert einer Summe von PDFs
ist die Summe der Mittelwerte der einzelnen PDFs.

SumV

class SumV : public PDF {


private:
list<unique_ptr<PDF>> pdfs;

public:
explicit SumV(PDF* pdf) { *this += pdf; }
SumV(const SumV&) = delete;

SumV& operator+=(PDF* pdf) {


pdfs.push_back(unique_ptr<PDF>{pdf});
return *this;
}

double getMean() const {


double sum = 0;
for (const unique_ptr<PDF>& pdf: pdfs)
sum += pdf->getMean();
return sum;
}
};

Implementieren Sie ein Hauptprogramm, das von der Standardeingabe zunächst den Namen einer PDF-
Familie (norm, gleich oder exp) einliest. Es soll dann eine endliche Abbildung von Namen von PDF-Familien
auf geeignete Funktionsobjekte verwendet werden um die benötigten Parameter abzufragen und dann ein
Objekt der passenden Klasse zu initialisieren. Dies soll iteriert werden solange das Einlesen des Namens einer
PDF-Familie nicht fehlschlägt. Die Eingabe nicht implementierter Namen soll geeignet behandelt werden
und die Eingabe weiterer PDFs nicht verhindern.
Die eingegebenen PDFs sollen jeweils direkt nach ihrer Erzeugung einem Objekt der Klasse SumV beigefügt
werden und der Mittelwert der so konstruierten PDF am Ende einmal ausgegeben werden.

Seite 8 von 12
Aufgabe 15* Programmieren II für Studierende der Mathematik — WiSe 2025/26

[Link]

#include <sstream>
#include <cmath>
#include <memory>
#include <list>
#include <string>
#include <map>
#include <functional>
#include <iostream>

using namespace std;

PDF

Verteilungen

SumV

const map<string, function<PDF*()>> pdfs{


{”norm”, []() -> PDF* {
double mean, stddev;
cout << ”mean, stddev = ”;
cin >> mean >> stddev;
return new NormV{mean, stddev};
}
},
{”gleich”, []() -> PDF* {
double from, to;
cout << ”from, to = ”;
cin >> from >> to;
return new GleichV{from, to};
}
},
{”exp”, []() -> PDF* {
double rate;
cout << ”rate = ”;
cin >> rate;
return new ExpV{rate};
}
}
};

int main() {
unique_ptr<SumV> sum;
while (true) {
string fun;
cout << ”sum += ”;
if (!(cin >> fun)) {
cout << endl;
break;
}

try {
PDF* pdf = ([Link](fun))();

Seite 9 von 12
Aufgabe 15* Programmieren II für Studierende der Mathematik — WiSe 2025/26

if (sum)
*sum += pdf;
else
sum = unique_ptr<SumV>{new SumV{pdf}};
} catch (const out_of_range&) {
cout << ”Unknown distribution: ” << fun << endl;
}
}

cout << ”[Link]() = ” << sum->getMean() << endl;


}

Beispiel (Kontrollergebnis). Ihr Programm könnte sich verhalten, wie folgt:

sum += norm
mean, stddev = 3 1
sum += norm
mean, stddev = 1 1
sum += gleich
from, to = -1 1
sum += exp
rate = 0.1
sum +=
[Link]() = 14

Seite 10 von 12
A. Cholesky-Zerlegung Programmieren II für Studierende der Mathematik — WiSe 2025/26

Anhang

A. Cholesky-Zerlegung

Wir zeigen zunächst, dass für eine symmetrisch, positiv definite 𝑛 × 𝑛 Matrix 𝐴 eine Zerlegung 𝐴 = 𝐿𝐿𝑇 mit
𝐿 einer unteren linken Dreiecksmatrix existiert.

Wir konstruieren hierfür eine Folge von Matrizen 𝐴 (𝑖 ) 𝑖 ∈ {0,...,𝑛} mit 𝐴 (0) := 𝐴, Vektoren 𝑏𝑖 und Matrizen 𝐵 (𝑖 ) ,
sodass 𝐴 (𝑖 ) blockweise folgende Form hat:
1𝑖 −1 0
© ª
𝐴 (𝑖 ) = ­ 𝑎𝑖,𝑖 𝑏𝑇𝑖 ®
0 (𝑖 )
« 𝑏𝑖 𝐵 ¬
Da 𝐴 symmetrisch ist, hat auch 𝐴 (0) diese Form.
Sei außerdem, blockweise, (𝐿𝑖 )𝑖 ∈ {0,...,𝑛} :
1 0
© 𝑖 −1 √ ª
­
𝐿𝑖 := ­ 𝑎 𝑖,𝑖 0 ®®
0 √ 1 𝑏𝑖 1𝑛−𝑖
« 𝑎𝑖,𝑖 ¬
Da 𝐴 positiv definit ist, ist 𝑎𝑖,𝑖 > 0 und somit 𝐿𝑖 reell.
Hiermit, blockweise:
1 0
© 𝑖 −1 ª
𝐴 (𝑖 )
= 𝐿𝑖 𝐴 (𝑖+1) 𝐿𝑖𝑇 𝐴 (𝑖+1) ­
=­ 1 0 ®
0 (𝑖 ) 𝑇
®
0 𝐵 − 𝑎𝑖,𝑖 𝑏𝑖 𝑏𝑖
1
« ¬

Wegen 𝐴 (0) = 𝐴 und 𝐴 (𝑛) = 1𝑛 ist dann 𝐿 := 𝐿1 𝐿2 . . . 𝐿𝑛 mit der gewünschten Eigenschaft, jedoch nicht
notwendigerweise eindeutig.
Zur effizienteren Berechnung betrachten wir 𝐴 = 𝐿𝐿𝑇 als Ansatz:
.
𝐿1,1 0 0 𝐿1,1 𝐿2,1 𝐿3,1
© 𝐿1,1
2 .. ª
©𝐿 ª© ª
­ 2,1 𝐿2,2 0 · · ·®­ 0 𝐿2,2 𝐿3,2 · · ·® ­­𝐿2,1 𝐿1,1 𝐿2,1
2 + 𝐿2 ®
®
𝐴 = ­­𝐿3,1 𝐿3,2 𝐿3,3 ®­ 0
®­ 0 𝐿3,3 ® = ­
2,2
® ­𝐿3,1 𝐿1,1 𝐿3,1 𝐿2,1 + 𝐿3,2 𝐿2,2 𝐿3,1
2 + 𝐿2 + 𝐿2 ®
­ ®
. . ®­ .. ® ­
3,2 3,3
.. . .
.. . .. .. ®
« . ¬« ¬ « . .
¬
Es lässt sich 𝐿 komponentenweise ablesen:
p
𝐴 0 0
© 1,1 q ª
­ 𝐴2,1 𝐴2,2 − 𝐿2,1 2 ®
­ 𝐿1,1 0 · · · ®
­ q ®
𝐿 = ­ 𝐴3,1 𝐴3,2 −𝐿3,1 𝐿2,1 ®
­ 𝐿1,1 𝐿2,2 𝐴3,3 − 𝐿3,1 − 𝐿3,2
2 2
®
­ ®
­ .. . . .®
« . ¬
Verallgemeinert: q
 Í
 𝐴 𝑗,𝑗 − 𝑗 −1 𝐿 2
 𝑖=𝑗


  𝑘=1 𝑗,𝑘 
Í
𝐿𝑖,𝑗 = 1 𝐴𝑖,𝑗 − 𝑗 −1 𝐿𝑖,𝑘 𝐿 𝑗,𝑘 𝑖>𝑗

 𝐿 𝑗,𝑗 𝑘=1

0
 sonst

Wir beobachten auch, dass in diesem Ausdruck für 𝐿𝑖,𝑗 nur wiederum 𝐿𝑖 ′,𝑗 ′ vorkommen mit 𝑖 ′ ≤ 𝑖, 𝑗 ′ ≤ 𝑗 und
stets zumindest 𝑖 ′ < 𝑖 oder 𝑗 ′ < 𝑗.

Seite 11 von 12
B. Vorwärts- und Rückwärtseinsetzen Programmieren II für Studierende der Mathematik — WiSe 2025/26

B. Vorwärts- und Rückwärtseinsetzen

Für 𝐷 eine Dreiecksmatrix, betrachten wir ein Gleichungssystem der Form 𝐷𝑥 = 𝑏. Im Folgenden sei 𝐷 o.b.d.A.
eine untere linke Dreiecksmatrix. Es lässt sich 𝐷𝑥 = 𝑏 schreiben als:
𝐷 1,1𝑥 1 = 𝑏1
𝐷 2,1𝑥 1 + 𝐷 2,2𝑥 2 = 𝑏2
𝐷 3,1𝑥 1 + 𝐷 3,2𝑥 2 + 𝐷 3,3𝑥 3 = 𝑏3
.. .. .. .. ..
. . . . .
Í𝑚
𝐷 𝑥
𝑘=1 𝑚,𝑘 𝑘 = 𝑏𝑚

Wir können umstellen nach 𝑥:

𝐷 1,1 𝑏 1 = 𝑥1
1
𝐷 2,1
𝐷 2,2 𝑏 2 − 𝐷 2,2 𝑥 1 = 𝑥2
1
𝐷 3,1 𝐷 3,2
𝐷 3,3 𝑏 3 − 𝐷 3,3 𝑥 1 − 𝐷 3,3 𝑥 2 = 𝑥3
1

.. .. .. .. ..
. . . . .
Í𝑚−1 𝐷𝑚,𝑖
𝐷𝑚,𝑚 𝑏𝑚 − 𝐷𝑚,𝑚 𝑥𝑖 = 𝑥𝑚
1
𝑖=1

Wir beobachten, dass in der obigen Gleichung für 𝑥 1 aus 𝑥 nur 𝑥 1 vorkommt, sodass diese sich direkt lösen
lässt. Analog kommen in der Gleichung für 𝑥𝑚 keine 𝑥𝑘 vor mit 𝑘 > 𝑚. Wir können 𝑥 1, 𝑥 2, 𝑥 3, . . . also in dieser
Reihenfolge direkt berechnen.

Seite 12 von 12
Ludwig-Maximilians-Universität München WiSe 2025/26
Mathematisches Institut
Gregor Kleen

Programmieren II für Studierende der Mathematik


Beispielklausur — Lösungsvorschlag

Aufgabe 1 2 3 4 Σ
Punkte 12 8 14 30 64

Bearbeitungszeit 90 Minuten
Beispielklausur — Lösung Programmieren II für Studierende der Mathematik — WiSe 2025/26

1 (12 Punkte) Welche Ausgabe produziert das folgende Programm?


Hinweis. Bei uint8_t handelt es sich um einen vorzeichenlosen Ganzzahltyp mit genau 8 bit.

#include <cstdint>
#include <iostream>

using namespace std;

int f1(int& i, int j) { j += --i; return i++; }


uint8_t f2(int& i, int j) { i -= j; return i; }
int& f3(int& i, int& j) { j -= 3*i; return i; }
int f4(int i, int j) { double x = i / j; return x; }

void print(int i, int j, int k) {


cout << i << ” ” << j << ” ” << k << endl;
}

int main() {
int i, j, k;
i = 1; j = 2; k = f1(i, j); print(i, j, k);
i = 1; j = 2; k = f2(i, j); print(i, j, k);
i = 1; j = 2; k = f3(i, j); print(i, j, k);
i = 1; j = 2; k = f4(i, j); print(i, j, k);
}

1 2 0
-1 2 255
1 -1 1
1 2 0

2 (8 Punkte) Wir möchten eine eigene Version einer Spezialisierung des Standardlibrary-Datentyps map
implementieren. Wir betrachten daher endliche Abbildungen 𝑚 : 𝐾 → R mit 𝐾 ⊂ Z und 𝐾 endlich.
(a) Geben Sie eine Vereinbarung einer Klasse map an, deren Objekte jeweils eine Abbildung 𝑚 : 𝐾 → R
speichern mit 𝐾 ⊂ Z und 𝐾 endlich. Verwenden Sie zur Modellierung von Elementen von Z und R
jeweils geeignete arithmetische Datentypen.
Es soll im Prinzip möglich sein über die Schlüssel-Wert-Zuordnungen eines Objektes Ihrer Klasse
map effizient zu iterieren.
Sie dürfen beliebige Container-Datentypen aus der Standardlibrary verwenden, jedoch nicht map1 .
Sie brauchen als Komponenten Ihrer Klasse map nur Attribute anzugeben. Es ist weder ein Konstruk-
tor, noch sind Methoden oder andere Klassenkomponenten erforderlich.

class map {
list<pair<int, double>> xs;
};

Alternativ wäre, neben zahlreichen weiteren Möglichkeiten, auch folgende Konstruktion sinn-
voll. Sie entspricht in etwa dem container-Adapter flat_map aus der Standardlibrary:

1 auch keine der weiteren Analoga aus der Standardlibrary, also insb. multimap, unordered_map oder flat_map

Seite 1 von 4
Beispielklausur — Lösung Programmieren II für Studierende der Mathematik — WiSe 2025/26

class flat_map {
list<int> keys;
list<double> values;
};

Die Assoziation der Schlüssel mit den zugehörigen Werten geschieht hier in der Reihenfolge
der Elemente der beiden Listen.

(b) Welche Zugriffsattribute haben die Datenkomponenten Ihrer Version von map? Warum haben Sie
genau jene Zugriffsattribute gewählt?
Hinweis. Es kann, je nach gewählter Implementierung, nützlich sein in Ihrer Argumentation den
Begriff einer Invariante zu verwenden.

xs ist (standardmäßig) private.


Es muss sichergestellt werden, dass xs keine „duplicate keys“ enthält; d.h. verschiedene Paare,
die die selbe ganze Zahl als erstes Element enthalten.
Da xs insofern an einer Invariante „teilnimmt“ muss sichergestellt sein, dass xs nicht von
außerhalb der Klasse verändert werden kann. Folglich darf xs nicht public sein.

3 (14 Punkte) Implementieren Sie ein Hauptprogramm welches eine Zeichenkette (das Muster) und den
Namen einer Datei als Kommandozeilenparameter akzeptiert. Der Inhalt der Datei soll zeilenweise durch-
laufen werden. Wenn in einer Zeile das gegebene Muster vorkommt, soll diese ausgegeben werden.
Es sollen zusätzlich die Zeilen direkt vor und direkt nach Zeilen, die das Muster enthalten, ausgegeben
werden. Unabhängig davon soll jede Zeile maximal einmal und unter Erhalt der Reihenfolge ausgegeben
werden.

grep

int main(int argc, char* argv[]) {


if (argc != 3) {
cerr << ”usage: ” << argv[0] << ” <PATTERN> <FILE>” << endl;
exit(2);
}

ifstream ein(argv[2]);
if (!ein) { cerr << ”Could not open: ” << argv[2] << endl; exit(1); }

string line, prev_line;


bool have_prev_line = false, had_match = false;
while (getline(ein, line)) {
if ([Link](argv[1]) != string::npos) {
if (have_prev_line)
cout << prev_line << endl;
cout << line << endl;
have_prev_line = false;
had_match = true;
} else if (had_match) {
cout << line << endl;
have_prev_line = false;
had_match = false;
} else {
prev_line = line;
have_prev_line = true;

Seite 2 von 4
Beispielklausur — Lösung Programmieren II für Studierende der Mathematik — WiSe 2025/26

}
}
}

4 (30 Punkte) Wir betrachten geschlossene Polygonzüge in C. Ein Polygonzug besteht aus einer Folge
(𝑧𝑖 )𝑖=0,...,𝑛−1 mit 𝑛 ∈ N und 𝑧𝑖 ∈ C. Wir verstehen aufeinander folgende Punkte 𝑧𝑖 und 𝑧𝑖+1 , bzw. 𝑧𝑛−1 und
𝑧 0 als jeweils mit einer Strecke verbunden.
Wir modellieren einen Polygonzug als Objekt der folgenden Klasse PolChain:

class PolChain {
public:
vector<complex<double>> z;
};

(a) Übernehmen Sie genau die oben angegebene Vereinbarung der Klasse PolChain. Fügen Sie Konstruk-
toren für die folgenden Fälle hinzu, jeweils mit geeigneten Parametern:
i. Der leere Polygonzug (enthält 0 Punkte)
ii. Eine gegebene konstante Referenz auf einen vector<complex<double>> von Punkten

PolChain() {}
PolChain(const vector<complex<double>>& z_): z(z_) {}

(b) Implementieren Sie eine konstante Methode size in Ihrer Klasse PolChain, die die Anzahl von Punk-
ten 𝑛 des Polygonzugs als Rückgabewert liefert.

int size() const { return [Link](); }

(c) Implementieren Sie eine konstante Methode length in Ihrer Klasse PolChain, die die Länge des Po-
lygonzugs als Rückgabewert liefert; d.h. die Summe der Längen der Streckensegmente zwischen den
Punkten.

double length() const {


double l = 0;
int n = [Link]();
if (n > 1)
for (int i = 0; i < n; i++)
l += abs(z[(i + 1) % n] - z[i]);
return l;
}

(d) Überladen Sie den Zuweisungs-Additionsoperator für Objekte Ihrer Klasse PolChain semantisch
sinnvoll.

PolChain& operator+=(const PolChain& p) {


int n = size();
[Link](n + [Link]());
for (int i = 0; i < [Link](); i++)
z[n + i] = p.z[i];
return *this;
}

Seite 3 von 4
Beispielklausur — Lösung Programmieren II für Studierende der Mathematik — WiSe 2025/26

(e) Implementieren Sie in Ihrer Klasse PolChain zusätzlich Konstruktoren für die folgenden Fälle, jeweils
mit geeigneten Parametern:
i. Die Eckpunkte eines regelmäßigen 𝑛-Ecks, wobei die Eckpunkte jeweils auf dem Einheitskreis
um den Nullpunkt liegen
ii. Die Eckpunkte eines regelmäßigen 𝑛-Ecks, wobei die Eckpunkte jeweils auf einem Kreis mit
Radius 𝑟 um den Nullpunkt liegen
iii. Die Punkte 𝑓 (1), 𝑓 (𝑓 (1)), . . . , 𝑓 𝑛 (1) die entstehen durch 1, 2, . . . , 𝑛-facher Anwendung einer ge-
gebenen Funktion 𝑓 : C → C auf 1 ∈ C.

PolChain(int n, double r = 1): z(n) {


for (int i = 0; i < n; i++)
z[i] = polar(r, i * 2 * M_PI / n);
}
PolChain(int n, function<complex<double>(complex<double>)> f): z(n) {
z[0] = f(1);
for (int i = 1; i < n; i++)
z[i] = f(z[i - 1]);
}

(f) Erstellen Sie eine konstante Methode winding in Ihrer Klasse PolChain mit einem Parameter 𝑞 ∈ C
und Rückgabetyp int. Es soll zurückgegeben werden:
( )
1 ∑
𝑛−1
𝑧𝑘+1 − 𝑞
ln wobei 𝑧𝑛 := 𝑧 0
2𝜋𝑖 𝑧𝑘 − 𝑞
𝑘=0

Werfen Sie eine exception falls 𝑞 auf dem Polygonzug liegt. Hierfür können Sie prüfen ob die rela-
tive Abweichung zwischen der Summe der Abstände von 𝑞 jeweils zu 𝑧𝑖 und 𝑧𝑖+1 und dem Abstand
zwischen 𝑧𝑖 und 𝑧𝑖+1 selbst unterhalb von 𝜀 = 1 · 10−6 liegt.
Werfen Sie auch eine exception falls der Rückgabewert betragsmäßig um mehr als 𝜀𝐿 von der nächs-
ten2 ganzen Zahl abweicht, mit 𝜀 = 1·10−6 und 𝐿 der Länge des Polygonzugs (Sie können die Methode
length verwenden auch wenn Sie sie nicht implementiert haben).

int winding(complex<double> q) const {


int n = size(), w;
complex<double> s = 0;
for (int k = 0; k < n; k++) {
double d1 = abs(q - z[k]),
d2 = abs(z[(k + 1) % n] - q),
d3 = abs(z[(k + 1) % n] - z[k]);
if (d1 + d2 - d3 < 1e-6 * d3)
throw invalid_argument(”Given point lies on polygonal chain”);

s += log((z[(k + 1) % n] - q) / (z[k] - q));


}
s /= 2 * M_PI * complex<double>{0,1};
w = static_cast<int>([Link]() + 0.5);

if (abs(complex<double>{static_cast<double>(w), 0} - s) > 1e-6 * length())


throw runtime_error(”Winding number not close enough to whole number”);

return w;
}

2 Sie können an dieser Stelle einfach auf die reelle Achse projezieren

Seite 4 von 4

Das könnte Ihnen auch gefallen