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

pro1

Das Dokument ist ein Skript für den Kurs 'Programmieren I für Studierende der Mathematik' an der Ludwig-Maximilians-Universität München im Sommersemester 2026. Es umfasst verschiedene Themen wie die von-Neumann-Architektur, formale Sprachen, Programmiersprachen, insbesondere C++, sowie grundlegende Programmierkonzepte und -strukturen. Das Inhaltsverzeichnis listet die Kapitel und deren Unterthemen auf, die im Kurs behandelt werden.

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)
1 Ansichten270 Seiten

pro1

Das Dokument ist ein Skript für den Kurs 'Programmieren I für Studierende der Mathematik' an der Ludwig-Maximilians-Universität München im Sommersemester 2026. Es umfasst verschiedene Themen wie die von-Neumann-Architektur, formale Sprachen, Programmiersprachen, insbesondere C++, sowie grundlegende Programmierkonzepte und -strukturen. Das Inhaltsverzeichnis listet die Kapitel und deren Unterthemen auf, die im Kurs behandelt werden.

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 SoSe 2026

Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik

Skript 00
Typographische Konventionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Skript 01
Die von-Neumann-Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Die hypothetische Maschine 𝑀 ′ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Unterprogramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Skript 02
Formale Sprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Programmiersprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Assembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Verfassen und Übersetzen von C++-Programmen . . . . . . . . . . . . . . . . . . . . . . . . . 2
Der Standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Extended Backus-Naur Form (EBNF) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
EBNFs für C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Programmaufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
if-Abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
while-Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Skript 03
Ein-/Ausgabe in C++ (<iostream>, <iomanip>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Vordefinierte Ströme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Formatierte Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Formatierte Eingabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Skript 04
Arithmetische Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Vergleich von Ganzzahl- und Gleitpunktarithmetik . . . . . . . . . . . . . . . . . . . . . . . . 1
Ganzzahldatentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Gleitpunktdatentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Arithmetische Kenngrößen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Ganzzahlliterale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Gleitpunktliterale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Auswertung von Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Implizite Typumwandlung von arithmetischen Operanden . . . . . . . . . . . . . . . . . . . . 7
Implizite Typumwandlung von Funktionsparametern und bei Zuweisungen . . . . . . . . . . 8
Explizite Typumwandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

Seite 1 von 3
Inhaltsverzeichnis Programmieren I für Studierende der Mathematik — SoSe 2026

Grundrechenarten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Zuweisungsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Inkrement- und Dekrementoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Logische- & Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Binäroperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Ternärer Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Komma-Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Vorrang der Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Skript 05
Mathematische Standardfunktionen (<cmath>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
atan2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Fehlerbehandlung mit errno (<cerrno>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Weitere Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
do-while-Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
for-Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
switch-Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
continue und break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Skript 06
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Vereinbarung von Variablen von Array-Typ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Zugriff auf Array-Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Arrays als Funktionsparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Mehrdimensionale Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Skript 07
STL-Vektoren (<vector>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Skript 08
Zeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Zeichen-Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Klassifizierungs- und Umwandlungsfunktionen für Zeichen (<locale>) . . . . . . . . . . . . . 2
Zeichenketten (<string>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Skript 09
Ein-/Ausgabe-Ströme (<iostream>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Buffering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Unformatierte Ein-/Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Stromzustand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Dateisysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Ein-/Ausgabe auf Dateien (<fstream>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Ein-/Ausgabe auf Strings (<sstream>) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Skript 10
Zeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Zeigerarithmetik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Kommandozeilenargumente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Skript 11
Dynamische Speicherzuweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Referenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Referenzparameter/Rückgabe von Referenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Zeiger auf Konstanten & Konstante Zeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Konstante Referenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

Seite 2 von 3
Inhaltsverzeichnis Programmieren I für Studierende der Mathematik — SoSe 2026

Einfache Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Skript 12
Überladung von Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Skript 13
This space intentionally left blank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Seite 3 von 3
Skript 00 Programmieren I für Studierende der Mathematik — SoSe 2026

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 00 Programmieren I für Studierende der Mathematik — SoSe 2026

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 Tabulatorzeichen ('\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

Seite 2 von 2
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

Die von-Neumann-Architektur

Die von-Neumann-Architektur ist ein Konzept zur Realisierung eines universellen Rechners (universal com-
puter), d.h. einer Rechenmaschine mit austauschbarem Programm. Statt, wie historische Rechenmaschinen,
nur einen fest eingebauten Rechenablauf durchlaufen zu können, lassen sich bei einem universellen Rechner
beliebige Folgen von Anweisungen eingeben und anschließend ausführen.
Charakteristisch für die von-Neumann-Architektur ist es nur einen geteilten Speicher zu haben in dem sowohl
Bewegtdaten (d.h. während der Ausführung als veränderlich genommene Werte), wie auch das auszuführende
Programm gespeichert werden.

Prozessor

Befehlszähler Befehlsadresse
Anweisung
... Steuereinheit

Hauptspeicher
Register
Eingabe
...
... Recheneinheit
Ausgabe

Steuersignale & Speicheradressen


Daten

Abbildung 1: Schematischer Aufbau eines von-Neumann-Rechners

Abbildung 1 zeigt den Aufbau eines von-Neumann-Rechners.


Es gibt folgende Komponenten:

Prozessor (auch Rechenkern, Central Processing Unit, CPU)

Steuereinheit (auch Control Unit, CU) Kontrolliert die interne Verschaltung aller anderen Kompo-
nenten, z.B. welche Rechenoperation die Recheneinheit gerade durchführt.
Ruft Anweisung an Stelle gegeben durch Inhalt des internen Befehlszählers aus Hauptspeicher
ab, interpretiert sie, steuert andere Kompenten entsprechend, erhöht Befehlszähler (fetch-execute-
cycle).
Recheneinheit (auch Arithmetic Logic Unit, ALU) Enthält Register, d.h. interne Speicherzellen die
Werte zur Verarbeitung zwischenspeichern können.
Ruft Daten aus Hauptspeicher/Eingabe ab, führt Berechnung (z.B. Addition, Multiplikation, …) auf
Werten aus Hauptspeicher und Registern durch, speichert neue Werte in Registern, in Hauptspei-
cher oder sendet sie an Ausgabe.

Seite 1 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

Hauptspeicher (auch (main) memory, primary storage, Random Access Memory, RAM) Speichert
Daten (Werte und Anweisungen) und lässt deren Veränderung der gespeicherten Daten durch
Recheneinheit zu.
Speicher ist aufgeteilt in Speicherzellen, die jeweils eine Adresse haben, also i.d.R. von 1 aufsteigend
durchnummeriert sind. Jede Speicherzelle speichert genau ein Datum (Wert oder Anweisung).
Ein-/Ausgabe (auch input/output, I/O) Modelliert Interaktion mit Systemen außerhalb des Rechners abs-
trakt.
Z.B. eine Tastatur mit Pufferspeicher (buffer) um Zahlen im Ganzen zur Verfügung zu stellen, statt ein-
zelne Zeichen.
Z.B. ein Drucker der zeilenweise auf ein Papierband druckt.

Die Übertragung von Daten zwischen den Komponenten ist technisch i.d.R. über einen gemeinsamen Daten-
pfad (d.h. einen Satz geteilte Kabel) realisiert. Den gemeinsamen Datenpfad nennt man den bus. Die Steuerein-
heit kontrolliert welche Komponente zu jedem Zeitpunkt die auf dem bus anliegenden Daten liest und welche
Komponente zeitgleich Daten auf dem bus zur Verfügung stellt.

Die hypothetische Maschine 𝑴 ′

𝑀 ′ ist ein hypothetischer von-Neumann-Rechner mit, ggü. modernen und kommerziell verfügbaren Rechnern,
vereinfachtem und reduziertem Befehlssatz.
𝑀 ′ ist eine Akkumulatormaschine, d.h. sie hat genau ein Register 𝑅 (auch als Akkumulator bezeichnet). Im
Register ist stets eine Zahl gespeichert (zu Beginn der Ausführung 0).
𝑀 ′ besitzt (konzeptionell) unbeschränkt viele Speicherzellen, die wir mit 𝑆 1 , 𝑆 2 , . . . bezeichnen. Speicherzellen
können sowohl Anweisungen wie auch Zahlen enthalten, wie bei von-Neumann-Maschinen charakteristisch1 .
Aus didaktischen Gründen wird strikt zwischen Anweisungen und Zahlen unterschieden. Eine gegebene Spei-
cherzelle 𝑆𝑘 enthält also zu jedem Zeitpunkt entweder eine Anweisung oder eine Zahl.
Anmerkung. Es wäre realistischer eine Kodierung für Anweisungen in den von der Maschine verwendeten
Zahlentyp anzugeben und Anweisungen nur kodiert zu speichern.
Dies würde es auch ermöglichen während der Ausführung Kodierungen neuer Anweisungen zu berechnen
und später ebenfalls auszuführen. Man spräche von self modifying code.
Zusätzlich steht 𝑀 ′ eine Ein- und Ausgabe zur Verfügung. Die Eingabe besteht aus einer Folge von Zahlen.
Es gibt eine Anweisung (siehe unten), die die nächste Zahl aus der Eingabe einlesen kann. Ebenso gibt es
Anweisungen die Zeilen (entweder mit einem festen Text oder mit der Darstellung einer Zahl) zur Ausgabe
hinzufügen können.
Wird ein Wert vom Programm von der Eingabe eingelesen sagen wir auch er wird eingegeben. Analog, dass
bei Ausgabe der Darstellung eines Wertes auf die Ausgabe der Wert ausgegeben wird.
Die Ausführung startet stets mit der Anweisung in 𝑆 1 . Wird jemals versucht die Anweisung in einer Speicher-
zelle 𝑆𝑘 auszuführen, 𝑆𝑘 enthält jedoch garkeine Anweisung, so bricht die Ausführung mit einer Fehlermel-
dung ab.
Bei Ausführung einer Anweisung werden die im Register und den Speicherzellen gespeicherten Werte ange-
passt. Siehe unten für eine Auflistung von zur Verfügung stehenden Anweisungen und ihrem Effekt.
Nachdem eine Anweisung in Speicherzelle 𝑆𝑘 ausgeführt wurde, schreitet die Ausführung gewöhnlicherweise
mit der Anweisung in der Speicherzelle 𝑆𝑘+1 fort. Handelt es sich bei der Anweisung um einen Sprungbefehl
1𝑀 ′
ist somit, und weil manche der Befehle (JUMP, RECALL, COPY) indirekt Zugreifen auf Register, eine Random-Access Stored-Program-
Maschine (RASP)

Seite 2 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

(JUMP, POSITIVE, NEGATIVE, ZERO) so kann die Ausführung auch mit dem Inhalt einer anderen Speicherzelle
fortschreiten; siehe hierzu unten den Effekt der jeweiligen Anweisung.
Der Inhalt der Speicherzellen 𝑆 1 , …vor Start der Ausführung bildet das Programm. Wir notieren Programme
als Textdateien mit einer Anweisung pro Zeile, wobei die 1. Zeile dem Inhalt von 𝑆 1 entspricht, usw. Speicher-
zellen vor Start der Ausführung mit Zahlen statt Anweisungen zu belegen ist nicht möglich.
Manche der Anweisungen akzeptieren einen Parameter. Dieser Parameter (im Folgenden 𝑛 bzw. 𝑥) wird als
Dezimalzahl in üblicher Schreibweise im Programm notiert. Als Dezimaltrennzeichen fungiert stets der Punkt
(.).
Es ist möglich das Programm mit Kommentaren zu versehen. Hierbei handelt es sich um zusätzlichen Text, der
bei Interpretation des Programmtextes durch die Implementierung der Maschine ignoriert wird. Kommentare
beginnen stets mit # und erstrecken sich von dort bis zum Ende der jeweiligen Zeile.

Implementierung Zum Formulieren von Programmen und zum Testen selbiger steht eine Implementie-
rung der Maschine online zur Verfügung:

[Link]/~gkleen/eval-m

Download
Program

Upload…
B Anleitung

1 JUMP(6) Reload Step Run Reset Re-Run


2 # a
3 # b, s - a Program/Input was changed since last load. Changes will have no effect until next Reload, Reset or Re-Run.
4 # c, (b - s)*(s - a)
5 # c + a + b, s n 〈Sn〉 Register Run

C
6 READ 1 JUMP(6) 6 JUMP(6)
7 STORE(2) # a in S1 2 3 READ
8 READ 3 3 STORE(2)
9 STORE(3) # b in S2 4 -6 READ
10 READ STORE(3)
5 6
11 STORE(4) # c in S3 READ
6 READ
12 ADD(2) # c + a in R STORE(4)
7 STORE(2) ADD(2)
13 ADD(3) # c + a + b in R 8 READ ADD(3)
14 STORE(5) # c + a + b in S4 9 STORE(3) STORE(5)
15 CONST(2) 10 READ CONST(2)
16 SWAP(5) # c + a + b in R, 2 in S4 11 STORE(4) SWAP(5)
17 DIV(5) 12 ADD(2) DIV(5)
18 STORE(5) # s in S4 STORE(5)
13 ADD(3)
19 SUB(2) # s - a in R SUB(2)
14 STORE(5)
20 SWAP(3) # s - a in S2, b in R SWAP(3)
15 CONST(2)
21 SUB(5) # b - s in R, Vorzeichen! SUB(5)
16 SWAP(5) MULT(3)
22 MULT(3)
17 DIV(5) SWAP(4)
23 SWAP(4) # (b - s)*(s - a) in S3, c in R
18 STORE(5) SUB(5)
24 SUB(5) # c - s in R
19 SUB(2) MULT(4)
25 MULT(4) # (c - s)*(b - s)*(s - a) in R
20 SWAP(3) MULT(5)
26 MULT(5) # s*(c - s)*(b - s)*(s - a) in R
21 SUB(5) SQRT
27 SQRT

A
22 MULT(3) PRINT
28 PRINT
23 SWAP(4)

E
29 STOP
24 SUB(5)
25 MULT(4)
26 MULT(5)

D
27 SQRT
28 PRINT
⇒ 29 STOP

Input Output
1 3 4 5
F 6

G
Quellcode Impressum Datenschutz Kontakt Barrierefreiheit

Abbildung 2: Benutzeroberfläche der Implementierung von 𝑀 ′

Abbildung 2 zeigt die Benutzeroberfläche der Implementierung.


Das Programm wird eingegeben in A. Hierbei entspricht die links angezeigte Zeilennummer jeweils der Num-
mer der Speicherzelle, in die die jeweilige Anweisung später geladen wird.
In F wird die Eingabe als Folge von Zahlen abgetrennt durch Leerzeichen und/oder Zeilenumbrüche einge-
geben. Die laufende Maschine wird bei Auswertung einer READ-Instruktion die jeweils nächste Zahl hieraus
einlesen.

Seite 3 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

In B sind Schaltflächen zur Steuerung der Maschine. Durch Klick auf Load bzw. Reload wird das in A einge-
geben Progamm in den Speicher der Maschine geladen.
Falls beim Laden des Programms ein Problem auftritt, erscheint eine Meldung dbzgl. in C. Auch wenn beim
Lauf der Maschine Probleme auftreten, werden diese dort gemeldet.
In D wird stets der aktuelle Speicherinhalt der Maschine und der Inhalt des Registers angezeigt. Zusätzlich
wird mit einem Pfeil markiert, welche Instruktion im Speicher die Maschine als nächstes ausführen wird.
Durch Klick auf Step führt die Maschine jeweils die aktuell so markierte Instruktion aus.
In E wird protokolliert welche Schritte die Maschine bisher getätigt hat.
Wenn die Maschine bei Auswertung einer PRINT- oder einer WRITE-Instruktion Ausgabe produziert, erscheint
diese in G.
Bei Klick auf Run in B führt die Maschine viele Schritte direkt hintereinander aus. Reset versetzt die Imple-
mentierung wieder in den Ausgangszustand ohne geladenes Programm. Re-Run lädt das (i.A. zwischenzeitlich
veränderte) Programm neu aus A und führt dann viele Schritte direkt hintereinander aus; es kombiniert inso-
fern Reload und Run .

Anweisungen Im Folgenden bezeichnet ⟨𝑆𝑛 ⟩ den aktuellen Inhalt der Speicherzelle 𝑆𝑛 bzw. analog für ⟨𝑅⟩,
𝑛 ∈ N \ {0} eine positive natürliche Zahl und 𝑥 ∈ R eine reelle Zahl.

STOP Beendet Ausführung des Programms


NOP Leeranweisung; kein Effekt
READ Lies nächste auf dem Eingabemedium zur Verfügung stehende
Zahl und speichert sie in 𝑅
PRINT WRITE ”. . .” Gibt ⟨𝑅⟩ bzw. gegebenen Text (zwischen Anführungszeichen) auf
Ausgabemedium aus
STORE(𝑛 ) Speichert ⟨𝑅⟩ in 𝑆𝑛
CONST(𝑥 ) RECALL(𝑛 ) RECALL Speichert 𝑥 bzw. ⟨𝑆𝑛 ⟩ bzw. ⟨𝑆 ⟨𝑅⟩ ⟩ in 𝑅
SWAP(𝑛 ) Vertauscht Inhalte von 𝑆𝑛 und 𝑅
COPY(𝑛 ) Speichert ⟨𝑆𝑛 ⟩ in 𝑆 ⟨𝑅⟩
ADD(𝑛 ) SUB(𝑛 ) MULT(𝑛 ) DIV(𝑛 ) Speichert ⟨𝑅⟩ + ⟨𝑆𝑛 ⟩ bzw. ⟨𝑅⟩ − ⟨𝑆𝑛 ⟩ bzw. ⟨𝑅⟩ · ⟨𝑆𝑛 ⟩ bzw. ⟨𝑆⟨𝑅⟩
𝑛⟩
in 𝑅
⟨𝑅⟩

⟨𝑅⟩ bzw. ln(⟨𝑅⟩) bzw.
SIGN ABS SQRT EXP LOG SIN Speichert | ⟨𝑅⟩ | bzw. |⟨𝑅⟩| bzw. ⟨𝑅⟩ bzw. 𝑒
COS TAN FLOOR CEIL sin(⟨𝑅⟩) bzw. cos(⟨𝑅⟩) bzw. tan(⟨𝑅⟩) bzw. ⌊⟨𝑅⟩⌋ bzw. ⌈⟨𝑅⟩⌉ in 𝑅
TRUNC ROUND Rundet ⟨𝑅⟩ zur Null hin2 bzw. zur nächsten ganzen Zahl3 und
speichert Ergebnis in 𝑅
RAND Speichert gleichverteilte zufällige4 Zahl aus [0, 1) in 𝑅
POS Steht POS in Speicherzelle 𝑆𝑛 , so speichert die Maschine bei
Ausführung der Anweisung 𝑛 in 𝑅
JUMP(𝑛 ) JUMP Fortführung des Programms mit Befehl in Zelle 𝑆𝑛 bzw. 𝑆 ⟨𝑅⟩
POSITIVE(𝑛 ) NEGATIVE(𝑛 ) Falls ⟨𝑅⟩ > 0 bzw. ⟨𝑅⟩ < 0 bzw. ⟨𝑅⟩ = 0: Fortführung des
ZERO(𝑛 ) Programms mit Befehl in Zelle 𝑆𝑛 ; sonst kein Effekt

2 [Link](⟨𝑅⟩) · ⌊|⟨𝑅⟩|⌋
3 Beizwei gleich weit entfernten ganzen Zahlen wird unter ihnen die gerade Zahl gewählt
4 Aufrufe von RAND liefern bei jedem Lauf der hypothetischen Maschine die selbe Folge von Zahlen

Seite 4 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel (Formel von Heron). Bezeichnen 𝑎, 𝑏 und 𝑐 die Seitenlängen eines Dreiecks so gilt für die Fläche 𝐴
des Dreiecks:
√ 𝑎 +𝑏 +𝑐
𝐴 = 𝑠 · (𝑠 − 𝑎) · (𝑠 − 𝑏) · (𝑠 − 𝑐) 𝑠 :=
2

Das folgende Programm liest die Seitenlängen 𝑎, 𝑏 und 𝑐 ein und gibt 𝐴 aus.

heron.m

1 JUMP(6)
2 # a
3 # b, s - a
4 # c, (b - s)*(s - a)
5 # c + a + b, s
6 READ
7 STORE(2) # a in S1
8 READ
9 STORE(3) # b in S2
10 READ
11 STORE(4) # c in S3
12 ADD(2) # c + a in R
13 ADD(3) # c + a + b in R
14 STORE(5) # c + a + b in S4
15 CONST(2)
16 SWAP(5) # c + a + b in R, 2 in S4
17 DIV(5)
18 STORE(5) # s in S4
19 SUB(2) # s - a in R
20 SWAP(3) # s - a in S2, b in R
21 SUB(5) # b - s in R, Vorzeichen!
22 MULT(3)
23 SWAP(4) # (b - s)*(s - a) in S3, c in R
24 SUB(5) # c - s in R
25 MULT(4) # (c - s)*(b - s)*(s - a) in R
26 MULT(5) # s*(c - s)*(b - s)*(s - a) in R
27 SQRT
28 PRINT
29 STOP

Beispiel (Arithmetisches Mittel). Für (𝑥𝑘 )𝑘 ∈ {1,...,𝑛} mit 𝑥𝑘 ≥ 0 eine Folge von nichtnegativen Zahlen (z.B. die

Messdaten aus einem physikalischen Experiment) soll das arithmetische Mittel 𝑥 = 𝑛1 𝑛𝑘=1 𝑥𝑘 berechnet
werden.
Die Folge von Werten soll von der Eingabe gelesen werden. Um dem laufenden Programm zu kommuni-
zieren, dass keine weitere Eingabe folgt, wird als Endkennung nach der letzten Zahl 𝑥𝑛 eine negative Zahl
eingelesen.
Die derartige Verarbeitung von Daten als Strom ist dann nützlich, wenn nicht davon ausgegangen wer-
den kann, dass genügend Speicherplatz zur Verfügung steht um die Gesamtheit aller angefallenen Daten
zwischenspeichern zu können.

arith.m

1 JUMP(4)
2 # k
3 # laufende Summe

Seite 5 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

4 CONST(0)
5 STORE(2)
6 STORE(3)
7 READ
8 NEGATIVE(15)
9 ADD(3)
10 STORE(3)
11 CONST(1)
12 ADD(2)
13 STORE(2)
14 JUMP(7)
15 RECALL(3)
16 DIV(2)
17 PRINT
18 STOP

Wir beobachten, dass in 𝑆 8 eine Bedingung geprüft wird, die Abbruchbedingung. Ist die Bedingung nicht
erfüllt (die soeben eingelesen Zahl also positiv) werden einige weitere Instruktionen ausgeführt, der Schlei-
fenkörper, bis dann in 𝑆 14 zurück zur Überprüfung der Abbruchbedingung gesprungen wird. Man nennt
eine derartige Struktur eine Schleife. Der Schleifenkörper wird immer wieder ausgeführt, solange bis die
Abbruchbedingung zutrifft.

Unterprogramme

Schon bei der Verwendung von Maschinensprache ist es nützlich die geschriebenen Programme in kleinere
Bestandteile aufzuteilen. Ziel ist es bei Implementierung bzw. Anpassung von Unterprogrammen nicht das
gesamte Programm „im Kopf behalten“ zu müssen, sondern sich stattdessen auf die klar abgesteckte Funktio-
nalität des Unterprogramms beschränken zu können.
Beispiel (Quadratwurzel mit eingebauter Anweisung). Wir implementieren ein Programm, das einen Wert
einliest, die Quadratwurzel des Wertes berechnet und das Ergebnis ausgibt.
Wir verwenden aus Gründen der Übersichtlichkeit die eingebaute Anweisung zur Berechnung der Qua-
dratwurzel. Es wäre jedoch auch denkbar innerhalb des Unterprogramms ein anderes Verfahren zur Be-
rechnung einer Approximation der Quadratwurzel zu verwenden.

sqrt_builtin.m

1 JUMP(16)
2
3
4 # arg
5 # ret
6
7
8 # Unterprogramm sqrt; erwartet Eingabe in S4, hinterlaesst Ausgabe in S5
9 RECALL(4)
10 SQRT
11 STORE(5)
12 JUMP(19)
13
14
15 # Hauptprogramm; interagiert mit Ein-/Ausgabe
16 READ
17 STORE(4)

Seite 6 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

18 JUMP(9)
19 RECALL(5)
20 PRINT
21 STOP

Das Beispielprogramm besteht aus zwei Unterprogrammen.


Das als sqrt bezeichnete Unterprogramm erwartet es, dass von einer anderen Stelle des Programms an den
Anfang von sqrt gesprungen wird. Hierbei wird ebenfalls erwartet, dass die dem Unterprogramm übergebe-
nen Parameter (hier die Zahl von der die Quadratwurzel berechnet werden soll) an bestimmter Stelle (hier 𝑆 4 )
zur Verfügung stehen. Es ist Aufgabe des aufrunden Codes dies sicher zu stellen.
Das als Hauptprogramm bezeichnete Unterprogramm übernimmt die Interaktion mit der Ein-/Ausgabe und
ruft das Unterprogramm sqrt auf. Das Hauptprogramm erwartet es, dass das aufgerufene Unterprogramm
an die richtige Stelle zurückspringt und dass das das Ergebnis der Berechnung in 𝑆 5 hinterlegt wurde.
Man spricht bzgl. diesen gegenseitigen Erwartungen zwischen aufrufendem Code und aufgerufenem Unter-
programm von einer calling convention.
Ein Problem bzgl. der im Beispielprogramm verwendeten calling convention ist, dass das Unterprogramm
sqrt bei jedem Aufruf (egal woher) stets an die selbe Stelle im Hauptprogramm (𝑆 19 ) zurück springt. Der
Aufruf des Unterprogramms von mehreren verschiedenen Stellen des Hauptprogramms aus, ist nicht sinnvoll
möglich.
Beispiel (Quadratwurzel mit return pointer). Um das Unterprogramm sqrt beliebig oft auch von unterschiedli-
chen Codestellen aus aufrufbar zu machen verwenden wir einen return pointer. Wir berechnen vor Sprung
in das Unterprogramm die Rückkehradresse und legen diese in einer Speicherzelle (hier 𝑆 6 ) ab. Aus dem
Unterprogramm zurück in die aufrufende Codestelle springen wir dann mithilfe eines indirekten Sprung-
befehls.

sqrt_rp.m

1 JUMP(18)
2
3
4 # rp
5 # arg
6 # ret
7
8
9 # Unterprogramm sqrt; erwartet Eingabe in S4, Rueckkehradresse in S6, hinterlaesst Ausgabe
↩→ in S5
10 RECALL(4)
11 SQRT
12 STORE(5)
13 RECALL(6)
14 JUMP
15
16
17 # Hauptprogramm; interagiert mit Ein-/Ausgabe
18 CONST(4)
19 STORE(7)
20 READ
21 STORE(4)
22 POS
23 ADD(7)

Seite 7 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

24 STORE(6)
25 JUMP(10)
26 RECALL(5)
27 PRINT
28 STOP

Stack

Bei der Verwendung von Unterprogrammen stellt sich schnell heraus, dass es enorm nützlich ist, wenn Un-
terprogramme sich gegenseitig aufrufen können.
Ein Problem mit unserer bisherigen calling convention ist dbzgl., dass mehrere Unterprogramme die selbe
Speicherzelle für den return pointer verwenden, die Information bzgl. der Rückkehradresse also beim Aufruf
von Unterprogrammen verloren geht5 .
Die konventionelle Lösung dieses Problems bedient sich einer Datenstruktur namens stack (zu deutsch Kel-
lerspeicher oder gelegentlich auch Stapelspeicher). Der stack ist in Analogie zu z.B. einem Stapel von Tellern
benannt. Es lassen sich bei einem stack zusätzliche Werte zur Datenstruktur hinzufügen (push; einen weite-
ren Teller oben auf den Stapel legen). Es lassen sich auch Werte entfernen (pop), jedoch stets nur der zuletzt
hinzugefügt Wert (man kann nur den obersten Teller vom Stapel nehmen).
Der Ansatz ist es nun vor jedem Sprung in ein Unterprogramm zunächst die Rückkehradresse auf einen stack
zu legen. Sobald das Unterprogramm dann zurückkehren möchte kann es die Rückkehradresse vom stack
entfernen und an diese Stelle springen. Wird zwischenzeitlich aus dem Unterprogramm heraus ein Weiteres
aufgerufen6 , so wird schlichtweg eine weitere Rückkehradresse auf den stack gelegt. Da der stack (zumindest
prinzipiell) unbeschränkt wachsen kann, ist so eine beliebige Verschachtelung von Unterprogramm-Aufrufen
möglich.
Beispiel (Rekursive Berechnung von Fibonacci-Zahlen). Wir realisieren einen stack in 𝑀 ′ indem wir in einer
Speicherzelle einen pointer (zu deutsch auch Zeiger) speichern; den stack pointer. Der stack pointer ist die
Zahl 𝑛, die zur Speicherzelle 𝑆𝑛 korrespondiert, in der der zuletzt zum stack hinzugefügte Wert steht. Man
sagt der pointer zeigt auf den top of the stack.
Weitere Werte können zum stack hinzugefügt werden, indem zunächst der stack pointer um eins erhöht
wird und dann der neue Wert an die so referenzierte Stelle geschrieben wird. Zum Entfernen eines Werts
muss der stack pointer nur um eins reduziert werden. Im Gegensatz zu konventionellen Architekturen
wächst der stack hier also in positive Richtung, d.h. in Richtung aufsteigender Adressen.
Mit 𝑆 2 dem stack pointer und 𝑆 1 der Zahl 1 fügen die folgenden Anweisungen den Wert in 𝑅 dem stack
hinzu:

stack push

STORE(3) # temporaere Ablage


RECALL(2)
ADD(1)
STORE(2)
COPY(3)

Ebenso entfernen die folgenden Anweisungen einen Wert vom stack und legen ihn in 𝑅 ab:

5 Es wäre denkbar für jedes Unterprogramm eine eigene Speicherzelle zum Speichern des return pointers zu verwenden, es könnten
sich dann aber immernoch Unterprogramme nicht rekursiv selbst aufrufen.
6 auch rekursiv das selbe Unterprogramm erneut

Seite 8 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

stack pop

RECALL(2)
RECALL
STORE(3)
RECALL(2)
SUB(1)
STORE(2)
RECALL(3)

Das folgende Programm liest eine Zahl 𝑛 von der Eingabe ein und gibt 𝐹 (𝑛), die 𝑛-te Fibonacci-Zahl, auf
der Ausgabe aus.
Es gilt:
𝐹 (0) = 0
𝐹 (1) = 1
𝐹 (𝑘) = 𝐹 (𝑘 − 1) + 𝐹 (𝑘 − 2)

Im Unterprogramm zur Berechnung von 𝐹 (𝑘) für gegebenes 𝑘 wird selbiges Unterprogramm zweimal
aufgerufen. Derartige Aufrufe eines Unterprogramms aus sich selbst heraus nennt man rekursiv. Abseits
davon die Rückkehradresse auf dem stack zu sichern ist es auch notwendig den Inhalt von Speicherzellen,
in die im Unterprogramm geschrieben wird, ebenfalls auf dem stack zu sichern, da sie sonst überschrieben
würden.

fib_recursive.m

1 JUMP(10)
2 # stack pointer (sp)
3 # lsc
4 # arg
5 # ret
6 # tmp
7 # 1
8 # rp
9
10 CONST(1)
11 STORE(7)
12 CONST(99)
13 STORE(2) # initialisiere sp
14 CONST(8)
15 STORE(3) # initialisere Anzahl von Instruktionen in Unterprogramm-Aufruf
16 READ
17 STORE(4)
18 POS # Beginn: Unterprogramm-Aufruf, R ist aktuelle position
19 ADD(3) # R ist position nach folgendem JUMP (r)
20 STORE(6) # Beginn: stack push, tmp = r
21 RECALL(2) # lies sp
22 ADD(7)
23 STORE(2)
24 COPY(6) # Ende: stack push, speichere r in S_sp (push auf stack abgeschlossen)
25 JUMP(32) # Ende: Unterprogramm-Aufruf
26 RECALL(5)
27 PRINT
28 STOP
29
30

Seite 9 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

31 # Unterprogramm, Erwartet arg in S4, Liefert F(arg) in S5


32 RECALL(4) # R = arg
33 SUB(7)
34 POSITIVE(38) # weiter wenn arg <= 1
35 RECALL(4)
36 STORE(5) # ret = arg
37 JUMP(88) # return
38 RECALL(2) # arg > 1, Beginn: stack push, push(arg)
39 ADD(7)
40 STORE(2)
41 COPY(4) # Ende: stack push
42 RECALL(4)
43 SUB(7)
44 STORE(4) # arg = arg - 1
45 POS # Beginn: Unterprogramm-Aufruf
46 ADD(3)
47 STORE(6)
48 RECALL(2)
49 ADD(7)
50 STORE(2)
51 COPY(6)
52 JUMP(32) # Ende: Unterprogramm-Aufruf
53 RECALL(2) # Beginn: stack pop
54 RECALL
55 STORE(8)
56 RECALL(2)
57 SUB(7)
58 STORE(2)
59 RECALL(8)
60 STORE(4) # Ende: stack pop, arg = pop()
61 RECALL(5) # ret = F(arg - 1)
62 STORE(6) # Beginn: stack push, push(ret)
63 RECALL(2)
64 ADD(7)
65 STORE(2)
66 COPY(6) # Ende: stack push
67 RECALL(4)
68 SUB(7)
69 SUB(7)
70 STORE(4) # arg = arg - 2
71 POS # Beginn: Unterprogramm-Aufruf
72 ADD(3)
73 STORE(6)
74 RECALL(2)
75 ADD(7)
76 STORE(2)
77 COPY(6)
78 JUMP(32) # Ende: Unterprogramm-Aufruf
79 RECALL(2) # Beginn: stack pop
80 RECALL
81 STORE(8)
82 RECALL(2)
83 SUB(7)
84 STORE(2)
85 RECALL(8) # Ende: stack pop, R = pop()
86 ADD(5) # R = F(arg - 1) + ret = F(arg - 1) + F(arg - 2)
87 STORE(5) # ret = R
88 RECALL(2) # Beginn: Unterprogramm-return

Seite 10 von 11
Skript 01 Programmieren I für Studierende der Mathematik — SoSe 2026

89 RECALL
90 STORE(8)
91 RECALL(2)
92 SUB(7)
93 STORE(2)
94 RECALL(8)
95 JUMP

Anmerkung (Rekursive Berechnung von Fibonacci-Zahlen). Es lässt sich durch Experimentation hier be-
reits ein Gefühl für die Laufzeitcharakteristik von Algorithmen herstellen. Wir beobachten, dass bei nur
wenig größerer Eingabe (z.B. 8 statt 2) die Anzahl von Schritten zur vollständigen Berechnung massiv
stärker zunimmt. In der Tat hat der oben implementierte Algorithmus exponentielle Laufzeit 𝑂 (2𝑛 ).

Seite 11 von 11
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

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 𝐴𝑛

Programmiersprachen

Als Maschinensprache bezeichnet man eine formale Sprache deren Worte direkt von einem Rechner verarbeitet
werden können. Z.B. sind die Folgen von Anweisungen für die hypothetische Maschine 𝑀 ′ , die Sie bereits
betrachtet haben, Programmtexte in Maschinensprache.
Schon nach oberflächlicher Interaktion mit einer Maschinensprache stellt sich die Erkenntnis ein, dass kon-
krete Problemstellungen durch die Formulierung eines Programmtextes in Maschinensprache zu lösen sehr
umständlich ist. Es muss viel Zeit und gedankliche Kapazität verwendet werden auf den Umgang mit „Imple-
mentierungsdetails“ der Maschine; z.B. das Anpassen nahezu sämtlicher Sprungbefehle nach jeder Änderung,
die die Anzahl von Zeilen im Programmtext verändert.
Es existieren daher für tatsächlich produktiv verwendete Maschinenarchitekturen stets Werkzeuge um Pro-
grammtexte in Maschinensprache aus für Menschen zugänglicheren Spezifikationen abzuleiten.

Assembler

Ein Assembler ist ein Computerprogramm, dass in der Lage ist eine Spezifikation in Assemblersprache (assem-
bly language, oder auch nur Assembler bzw. assembly), den Quelltext (source code), in Maschinensprache zu
übersetzen. Bei tatsächlich produktiv verwendeten Maschinenarchitekturen sind die Anweisungen der Ma-
schinensprache i.d.R. als Zahlen (in aller Regel im Binärsystem, d.h. zur Basis 2) kodiert. Assemblersprache
hingegen verwendet zur Darstellung einer Anweisung mnemonische Zeichenketten.
Zusätzlich unterstützen Assembler in aller Regel Marken (labels). Hierbei werden Stellen im Programmtext
in Assemblersprache benannt, indem z.B. am Anfang der Zeile ein vom Programmierer gewähltes Wort (die
Marke) geschrieben wird. Es kann dann im Programmtext beliebig Bezug auf derart benannte Stellen im Pro-
grammtext genommen werden, indem z.B. als Parameter eines Sprungbefehls an Stelle einer Speicheradresse
stattdessen die zugehörige Marke geschrieben wird. Der Vorteil hiervon ist, dass bei Umstrukturierung des
Programms die Parameter von Sprungbefehlen nicht mehr angepasst werden müssen.

1 (Σ∗ , ◦) ist ein Monoid, der freie Monoid über Σ

Seite 1 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

Zusätzliche unterstützen Assembler in aller Regel Sektionen des ihnen übergebenen Programmtextes bei der
Übersetzung in Maschinensprache neu anzuordnen (daher der Name; vom englischen „to assemble“, „zusam-
menfügen“). Es kann so eine für den Programmierer natürlichere Reihenfolge der Sektionen gewählt werden,
ohne dass dies den resultierenden Programmtext in Maschinensprache beeinflusst.

Compiler

Assemblersprachen sind jedoch weiterhin sehr eng an konkrete Maschinenarchitekturen gebunden und sehr
wenig abstrakt. Unter Abstraktion verstehen wir hier, dass der Programmierer sich weniger mit den „Im-
plementierungesdetails“ der Maschine, oder sogar verwendeten Programmbibiliotheken, auseinander setzen
muss. Es kann stattdessen eine Spezifikation in einer Programmiersprache (programming language) verfasst
werden, die sich (idealerweise) nur mit für das konkret zu lösende Problem relevanten Aspeketen befassen
muss. Z.B. die Verwaltung des stacks wird in gängigen Programmiersprachen vollständig automatisieret, so-
dass die assoziierten Operationen in den vom Programmierer verfassten Quelltexten keine Erwähnung mehr
findet.
Unter einem Compiler versteht man ein Computerprogramm, dass in der Lage ist einen in einer Program-
miersprache verfassten Quelltext in Maschinensprache zu übersetzen, gelegentlich unter Zuhilfenahme eines
geeigneten Assemblers.

C++

Diese Veranstaltung beschäftigt sich mit der Programmiersprache C++.

Verfassen und Übersetzen von C++-Programmen

Zum Verfassen von Programmtexten können Sie jeden gewöhnlichen2 Texteditor verwenden. Per Konvention
speichern wir Quelltexte von C++-Programmen in Dateien mit Dateiendung .cpp, z.B. [Link].

[Link]

#include <iostream>

using namespace std;

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

Nachdem ein Programmtext verfasst und in einer Datei gespeichert wurde, erfolgt die Übersetzung in einen
Programmtext in Maschinensprache auf der Konsole (auch Kommandozeile, console, terminal). Ein geeigneter
Befehl zum Übersetzen einer Datei namens [Link] wäre z.B. der Folgende:

g++ [Link]

Bei der Übersetzung können Fehler auftreten. Insb. spricht man von einem Syntaxfehler, wenn der Inhalt der
zu übersetzenden Datei nicht als Wort der formalen Sprache erkannt werden konnte. Abseits davon werden

2 Jene, die in der Lage sind reine Textdateien (Dateiendung z.B. .txt) zu speichern und nicht nur Dateien in einem editorspezifischen

Format, welches sich mit der graphischen Darstellung des Textes befasst (Dateiendungen z.B. .docx, .odt, uvw.)

Seite 2 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

vom Compiler noch viele weitere Eigenschaften des spezifizierten Programms überprüft. Kommt es zu einem
Fehler wird dieser auf der Konsole ausgegeben und die Übersetzung abgebrochen.
Es gibt auch Warnungen, welche zwar ausgegeben werden aber die Übersetzung nicht abbrechen.
Bei erfolgreicher Übersetzung wird i.d.R. keinerlei Ausgabe auf der Konsole produziert, jedoch eine Datei
namens [Link] erzeugt, welche den übersetzen Programmtext in Maschinensprache enthält. Man spricht von
einer ausführbaren Binärdatei. Der Programmtext in Maschinensprache kann vom Betriebssystem mit folgen-
dem Befehl zur Ausführung durch den Rechner „eingereicht“ werden:

./[Link]

Man sagt das, durch den Quelltext spezifizierte und in eine ausführbare Binärdatei übersetze, Programm wird
ausgeführt.
Im Falle unseres Beispiels [Link] von oben, sollte hierbei auf der Konsole ausgegeben werden:

Hello, World!

Der Standard

Die Programmiersprache C++ ist in einem internationalen Standard spezifiziert, Implementierungen von Über-
setzungsumgebungen (insb. also Compiler) folgen der Norm ISO/IEC 14882. In dieser Veranstaltung verwen-
den wir die Version ISO/IEC 14882:2014, oft abgekürzt als C++14.
Die genaue Version des Standards ist in der Praxis vor Allem relevant bzgl. dem Hinzufügen neuer Aspekte der
Sprache in späteren Versionen. Die Organisationen und Gremien, die den Standard erstellen, achten darauf,
dass Programme die für eine Übersetzungsumgebung erstellt wurden, die einer älteren Version des Standards
genügt, sich in Übersetzungsumgebungen, die einer neueren Version des Standards genügen, identisch verhal-
ten. D.h. z.B. Programme die für C++14 geschrieben wurden, sollen sich auch in C++17 und später identisch
verhalten.
Der Standard enthält eine Definition von C++ als formale Sprache, d.h. eine rekursive Aufzählung von
Worten3 , welche genau die syntaktisch zulässigen C++-Programm sind. Zusätzlich belegt der Standard
C++-Programme mit einer Semantik. Es wird also beschrieben wie sich eine abstrakte und nicht näher
spezifizierte Rechenmaschine bei Auswertung des Programms verhalten würde4 . Von einer Implementierung
einer Übersetzungsumgebung für C++ wird dann erwartet, dass sich mit ihr übersetzte und ausgeführte
Programme bzgl. ihrer Interaktion mit der Außenwelt (also z.B. Ein- und Ausgabemedien) verhalten als ob
sie auf der abstrakten Maschine des Standards ausgeführt würden.
Der Standard lässt jedoch manche Aspekte des Verhaltens von C++-Programmen offen.
Diese Aspekte werden unterteilt in die folgenden Kategorien:

Implementierungsabhängiges Verhalten Bei der im Standard beschriebenen abstrakten Rechenmaschine


handelt es sich tatsächlich um eine Familie von Maschinen, die sich in den Werten diverser Parameter
voneinander unterscheiden. Ein Beispiel für einen derartigen Parameter wäre der genaue Wertebereich
der für Werte von natürlichen Zahlen5 zur Verfügung steht. Der Standard beschreibt das Verhalten des
Programms in Abhängigkeit der Werte dieser Parameter. Z.B. ist festgelegt was passiert, wenn ein Wert
für eine natürliche Zahl (z.B. durch arithmetische Operationen) den zur Verfügung stehenden Bereich

3 Tatsächlich werden hierfür ähnliche Operationen benutzt wie jene, die wir zuvor für formale Sprachen eingeführt haben. Eine
vereinfachte Form der im Standard verwendeten Schreibweise lernen wir später als Extended Backus-Naur Forms kennen.
4 Oft können wir unsere zuvor eingeführte hypothetische Maschine 𝑀 ′ als gedankliches Modell verwenden
5 eines spezifischen Datentyps z.B. unsigned int

Seite 3 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

verlässt. Es ist jedoch eine Funktion der Belegung der Parameter der abstrakten Maschine, bei welchen
Werten dieses Verhalten auftritt.
Verhalten, dass von der Wahl der Parameter der abstrakten Maschine abhängt, bezeichnet der Standard
als implementierungsabhängig. Der Standard fordert, dass jede Implementierung einer Übersetzungs-
umgebung für C++ Dokumentation bereitstellen muss, in der für jeden implementierungsabhängigen
Aspekt des Standards nachgeschlagen werden kann, wie sich die Übersetzungsumgebung dbzgl. verhält.
Es ist sinnvoll neue Programme ggÜ. implementierungsabhängigem Verhalten robust zu gestalten. D.h.
Programme so zu formulieren, dass sie unabhängig von der Belegung der Parameter der abstratken
Maschine stets korrekte Ergebnisse berechnen. Es müssen aber nicht genau die selben korrekten Er-
gebnisse berechnet werden – es ist z.B. zulässig dass eine Näherung an einen mathematisch korrekten
Zahlenwert berechnet wird, aber die Genauigkeit dieser Näherung von implementierungsabhängigen
Aspekten gesteuert wird. Als Beispiel könnte man sich ein Programm vorstellen, das eine Näherung an
die Kreiszahl 𝜋 berechnet und ausgibt, jedoch mit unterschiedlich vielen Nachkommastellen, je nach-
dem wieviel Genauigkeit die Übersetzungsumgebung bei der Arithmetik mit Approximationen von
reellen Zahlen ohne weiteren Aufwand zur Verfügung stellt.
Unspezifiziertes Verhalten Die im Standard beschriebene abstrakte Rechenmaschine ist nondeterminis-
tisch. Bzgl. manchen Aspekten des Verhaltens ist es zulässig, dass jede Ausführung eines Programms i.A.
ein anderes Ergebnis liefert. Wo immer möglich versucht der Standard den Satz von möglichen Verhal-
ten bzgl. eines gegebenen Programms einzuschränken. Z.B. ist es möglich innerhalb eines Programms
anzugeben, dass eine „Speicherzelle“ für eine natürliche Zahl zur Verfügung stehen soll. Es ist dann
jedoch zunächst unspezifiziert welcher Wert in dieser Speicherzelle gespeichert ist, solange bis sie das
erste mal vom Programm aus geschrieben wurde. Wird eine derartige Speicherzelle also gelesen, bevor
sie das erste mal geschrieben wurde, ist das Ergebnis dieser Lese-Operation unspezifiziertes Verhalten
und es wird i.A. für jede Ausführung des Programms ein anderer Wert geliefert.
Es ist wichtig neue Programme so zu gestalten, dass die Korrektheit der Ergebnisse, die sie berechnen,
nicht von unspezifiziertem Verhalten abhängt. Oft liefern einzelne Stellen innerhalb eines Programms,
deren Verhalten unspezifiziert ist, über mehrere Ausführungen hinweg augenscheinlich identische Wer-
te. Hierfür gibt es jedoch per Definition keine Garantie und bei der nächsten Ausführung (z.B. auf ei-
nem anderen physischen Rechner, oder zu einer deutlich späteren Zeit) könnten andere Werte geliefert
werden. Ein Programm bei dem die Korrektheit davon abhängt, dass eine Stelle mit unspezifiertem
Verhalten sich auf eine bestimmte Weise verhält, ist nicht korrekt.
Undefiniertes Verhalten Der Standard belegt manche Worte von C++ als formaler Sprache mit keiner se-
mantischen Bedeutung. Er macht also garkeine Aussage darüber, wie sich diese Programme verhalten.
Der Standard definiert dies indem er manche semantische Konstruktionen, die in Programmen vorkom-
men könnten, als undefiniertes Verhalten deklariert. Programme die an irgendeiner Stelle undefiniertes
Verhalten enthalten, haben keine Bedeutung.
In der Regel handelt es sich bei undefiniertem Verhalten um semantische Konstrukte, für die Überset-
zungsumgebungen annehmen dürfen, dass sie in Programmen nie vorkommen. Dies dient in der Regel
dazu Compilern zu erlauben zusätzliche Annahmen über die Struktur der Programme machen zu dürfen,
die sie übersetzen. Es ist dadurch möglich beim Kompilieren zusätzliche Optimierungen und nützliche
Änderungen an der Struktur des erzeugten Programms in Maschinensprache vorzunehmen, ohne dabei
festlegen zu müssen, wie mit besonders „problematischen“ Programmen umgegangen werden muss.
Es wird von Übersetzungsumgebungen in aller Regel kein Versuch unternommen bzgl. der Existenz
von undefiniertem Verhalten in einem Programm zu warnen oder darauf in irgendeiner Weise speziell
zu reagieren. In der Tat ist es oft beweisbar unmöglich ein Computerprogramm anzugeben, das in der
Lage ist für beliebige C++-Programme als Eingabe eine verlässliche Aussage zu treffen, ob das C++-
Programm überhaupt undefiniertes Verhalten enthält, oder nicht.

Seite 4 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

Es muss darauf geachtet werden Programme zu schreiben, die kein undefiniertes Verhalten enthalten.
In der Praxis produzieren Programme mit undefiniertem Verhalten bei Ausführung selten zuverlässig
korrekte Ergebnisse. Oft äußert sich undefiniertes Verhalten bei Ausführung des Programms in unver-
hergesehener und subtiler Weise, z.B. könnten Teile des Programms ohne klar erkennbaren Grund ein-
fach nicht ausgeführt werden, Berechnungen die den selben Wert liefert sollten unterschiedliche Werte
liefern oder Berechnungen statt einem erwarteten korrekten Wert stattdessen einen zufällig erschei-
nenden Wert liefern. Gelegentlich äußert sich undefiniertes Verhalten auch darin, dass die Ausführung
des Programms an einer unverhersehbaren Stelle vom Betriebssystem einfach abgebrochen wird.

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 Form6 .
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 𝐿(𝐺) := 𝐿(𝑆).
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

6 Im Allgemeinen lässt sich nicht jede Sprache durch eine Grammatik beschreiben. EBNFs beschreiben wiederrum eine Teilmenge
aller Grammatiken, die kontextfreien Grammatiken.

Seite 5 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

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.

Programmaufbau

Wir präsentieren nur einen stark gekürzten Ausschnitt7 einer Grammatik von C++ als formale Sprache.
Im Laufe der Veranstaltung werden wir immer wieder zusätzliche syntaktische Aspekte einführen. Die unten
aufgeführte Grammatik ist insofern nichteinmal für Zwecke der Veranstaltung vollständig.
⟨Übersetzungseinheit⟩ → {⟨Vereinbarung⟩}
⟨Vereinbarung⟩ → ⟨BlockVereinbarung⟩
| ⟨Fun'Vereinbarung⟩
⟨BlockVereinbarung⟩ → ⟨Einf'Vereinbarung⟩
| ⟨UsingDecl'⟩
⟨UsingDecl'⟩ → “using” “namespace” ⟨Identifier⟩ “;”
⟨Einf'Vereinbarung⟩ → {⟨Dekl'Spez'⟩}+ ⟨Init'Dekl'⟩ {“,” ⟨Init'Dekl'⟩} “;”
⟨Init'Dekl'⟩ → ⟨Dekl'⟩ [⟨Init'⟩]
⟨Init'⟩ → “=” ⟨Ausdruck⟩
| “(” ⟨Ausdruck⟩ {“,” ⟨Ausdruck⟩} “)”
| [“=”] “{” [⟨Ausdruck⟩ {“,” ⟨Ausdruck⟩}] “}”
⟨Fun'Vereinbarung⟩ → {⟨Dekl'Spez'⟩}+ ⟨Dekl'⟩ “(” [⟨Param'Dekl'⟩ {“,” ⟨Param'Dekl'⟩}] “)” ⟨Block⟩
⟨Param'Dekl'⟩ → {⟨Dekl'Spez'⟩}+ ⟨Dekl'⟩ [“=” ⟨Ausdruck⟩]
⟨Dekl'⟩ → ⟨Identifier⟩
⟨Identifier⟩ → ⟨NichtZiffer⟩ {⟨Ziffer⟩ | ⟨NichtZiffer⟩}
⟨NichtZiffer⟩ → /[a-z]/ | /[A-Z]/ | “_”
⟨Ziffer⟩ → /[0-9]/
⟨Block⟩ → “{” {⟨Anweisung⟩} “}”
⟨Anweisung⟩ → ⟨Block⟩
| [⟨Ausdruck⟩] “;”
| ⟨BlockVereinbarung⟩

Anweisungen

Der Compiler übersetzt jede Anweisung innerhalb der Übersetzungseinheit zu einer entsprechenden Folge
von Befehlen in Maschinensprache. Bei zusammengesetzten Anweisungen (auch Blöcke) der Form ⟨Block⟩ wer-
den die enthaltenen Anweisungen in der selben Reihenfolge in Befehle übersetzt und, unter Erhalt der Rei-
henfolge, zusammengesetzt.

7 Im Standard nimmt die unkommentierte Zusammenfassung der vollständigen Grammatik immerhin 19 A4-Seiten ein

Seite 6 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

Man kann also sagen, dass bei Ausführung des C++-Programms der Reihe nach eine Anweisung nach der
anderen ausgewertet wird. In der Regel folgt dies dem üblichen Lesefluss links nach rechts, oben nach unten
im Quelltext.

Ausdrücke

Ausdrücke (Worte in der Sprache von ⟨Ausdruck⟩) sind korrekt geformte arithmetische bzw. logische Ausdrü-
cke. Runde Klammern “(”, “)” können in ihrer konventionellen Bedeutung verwendet werden. Für Arithmetik
zur Verfügung stehen insb. die Grundrechenarten “+”, “-”, “*”, “/” und der Modulo-Operator “%”. Als logische
Verknüpfungen stehen “&&” (und), “||” (oder) und präfix-Negation mit “!” zur Verfügung. Als Vergleiche
stehen “==”, “<=”, “>=”, “!=” (≠), “<” und “>” zur Verfügung.
Zusätzlich sind auch Zuweisungen mit insb. den Operatoren “=”, “+=”, “-=”, “*=”, “/=” und “%=”
(Teil-)Ausdrücke.
Namen von Variablen (⟨Identifier⟩) und Literale (z.B. “"Text"”, “'z’”, “539”, “12.75”) sind ebenfalls
(Teil-)Ausdrücke.
Ausdrücke können direkt als Anweisungen fungieren. Bei Auswertung der Anweisung wird dann der Aus-
druck ausgewertet. Z.B. im Falle einer Zuweisung verändert dies den in der erwähnten Variable gespeicherten
Wert.
Ausdrücke können mit “,” links-assoziativ zusammengesetzt werden. Es wird zuerst der linke Teilausdruck
ausgewertet und danach der Rechte. Der zusammengesetzte Ausdruck nimmt den Wert des rechten Teilaus-
drucks an.

Variablen

Variablen sind Namen für Speicherbereiche. Wir identifizieren eine Variable üblicherweise mit ihrem Namen,
einem Wort in der Sprache von ⟨Identifier⟩.
Variablen müssen zunächst vereinbart werden. Dies geschieht im Rahmen einer ⟨Einf’Vereinbarung⟩.
Der Compiler übersetzt die Deklaration einer Variablen in geeignete Anweisungen um einen Speicherbereich
für die Variable zu berechnen und für diese Verwendung zu reservieren. Für unsere Zwecke liegen die mit
Variablen assoziierten Speicherbereiche zunächst immer auf dem stack.
Nach ihrer Vereinbarung kann eine Variable in Ausdrücken verwendet werden, bis ihr Speicherbereich wieder
freigegeben wird. Dies geschieht automatisch mit Ende des syntaktischen Gültigkeitsbereichs der Variable.
Vereinfachend beginnt für uns der syntaktische Gültigkeitsbereich einer Variable mit ihrer Vereinbarung und
endet gemeinsam mit dem Block, der die Vereinbarung enthält8 9 .
Der syntaktische Gültigkeitsbereich einer Variable erstreckt sich nicht über Funktionsaufrufe in die Körper
aufgerufener Funktionen.
Beispiel. Wir vereinbaren drei Variablen a, b und c und initialisieren sie hierbei mit konkreten Werten. Danach
vereinbaren wir eine weitere Variable s und verwenden im Ausdruck mit dem wir s initialisieren a, b und
c. Danach verwenden wir auch s in einem Ausdruck, dessen Auswertung die Ausgabe eines berechneten
Wertes auf der Standardausgabe zur Folge hat.

8Wenn die Vereinbarung nicht Teil eines Blocks ist, so endet der syntaktische Gültigkeitsbereich gemeinsam mit der Übersetzungs-
einheit.
9An Kontrollstrukturen wie insb. if-Abfragen und while-Schleifen gegebene Anweisungen werden hier aufgefasst als wären sie

ein Block, der nur die gegebene Anweisung enthält.

Seite 7 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

heron_const.cpp

#include <iostream>
#include <cmath>

using namespace std;

int main() {
double a = 3, b = 4, c = 5;
double s = (a + b + c) / 2;
cout << sqrt(s * (s - a) * (s - b) * (s - c)) << endl;
}

Funktionen

Funktionen erfüllen die gleiche Rolle wie Unterprogramme aus unserer Betrachtung von 𝑀 ′ : es handelt sich
um abgegrenzte Codebereiche, welche von (anderem) Code aus aufgerufen werden können.
Funktionen haben, analog zu Variablen, ebenfalls Namen der Form ⟨Identifier⟩.
Der Aufruf einer Funktion ist ein Ausdruck. Funktionen müssen vereinbart worden sein bevor sie aufgerufen
werden können. Es wird, an übliche mathematische Schreibweise angelehnt, der Name der Funktion gefolgt
von runden Klammern geschrieben. Innerhalb der runden Klammern steht eine komma-separierte Folge von
weiteren Ausdrücken, die Argumente der aufgerufenen Funktion. Funktionen, die keine Argumente erwar-
ten, sind zulässig. Beim Aufruf selbiger sind die runden Klammern dennoch erforderlich. Bei Auswertung
des Funktionsaufrufs werden die Argumente an geeigneter Stelle auf dem stack abgelegt und zu einer mit
der Funktion assoziierten Stelle im übersetzten Programm gesprungen. Sobald die Auswertung der Funktion
abgeschlossen ist, wird in den aufrufenden Code zurück gesprungen.
Die genaue calling convention hängt von der Maschinenarchitektur, dem Betriebssystem und dem Compiler
ab.
Funktionen können Rückgabewerte haben. In diesem Fall wertet ein Funktionsaufruf (als Ausdruck) zum Rück-
gabewert aus.
Übersetzungseinheiten, die zu ausführbaren Binärdateien übersetzt werden sollen, müssen stets eine Funk-
tion mit Namen main enthalten. Man spricht hierbei vom Hauptprogramm. Wird die ausführbare Binärdatei
vom Betriebssystem ausgeführt, so entspricht dies (in etwa) einem Funktionsaufruf der Funktion main. Etwa-
ige Argumente der Funktion werden vom Betriebssystem bereitgestellt und der Rückgabewert wird an das
Betriebssystem zurück kommuniziert. Per Konvention werden Rückgabewerte der Funktion main ungleich
null interpretiert als Indikation, dass bei der Ausführung des Programms ein Fehler aufgetreten ist.
Es stehen in der Standardlibrary zahlreiche vordefinierte Funktionen zur Verfügung, z.B. sqrt, abs, sin, cos,
tan, etc. Vor Verwendung von Funktionen (aber auch Variablen bzw. Konstanten) aus der Standardlibrary
müssen diese der Übersetzungseinheit bekannt gemacht werden. Dies geschieht durch geeignete #include-
Anweisungen an den Präprozessor und durch using-Direktiven der Form ⟨UsingDecl’⟩.
Eigene Funktionen können in Form eine ⟨Fun’Vereinbarung⟩ vereinbart werden.
Um das „sofortige“ Zurückspringen aus der Funktion herbeizuführen und gleichzeitig einen Rückgabewert
festzulegen, wird die return-Anweisung verwendet:

Seite 8 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

⟨Anweisung⟩ → . . . | ⟨SprungAnweisung⟩
⟨SprungAnweisung⟩ → “return” [⟨Ausdruck⟩] “;”

Datentypen

Insb. Variablen, Rückgabewerte von Funktionen und Parameter von Funktionen haben jeweils genau einen
Typ. Der Typ eines Werts gibt an, wie die in einem Speicherbereich gesicherten Werte zu interpretieren sind.
Es stehen einige eingebaute Datentypen, sowie eine Vielzahl von weiteren Datentypen in der Standardlibrary
zur Verfügung. Wir präsentieren an dieser Stelle nur sehr wenige, welche in folgenden Beispielen verwendet
werden:

int Ganze Zahl


double Approximation einer reellen Zahl
bool Wahrheitswert, true oder false
char Einzelner Buchstabe
string Datentyp aus der Standardlibrary; Zeichenkette beliebiger Länge

Bei Vereinbarung einer Variable, eines Funktionsarguments und einer Funktion (hier für den Typ des Rück-
gabewerts) muss jeweils ein Typ angegeben werden als einer der ⟨Dekl’Spez’⟩.
Hat eine Funktion keinen Rückgabewert, wird an Stelle des Typs des Rückgabewerts der eingebaute Datentyp
void eingesetzt.
Beispiel. Wir vereinbaren eine Funktion sum vom Funktionstyp „akzeptiert zwei Parameter vom Typ double
und liefert einen Rückgabewert vom Typ double“.

sum

double sum(double a, double b) {


return a + b;
}

if-Abfragen

⟨Anweisung⟩ → . . . | ⟨Auswahlanw'⟩
⟨Auswahlanw'⟩ → “if” “(” ⟨Kondition⟩ “)” ⟨Anweisung⟩ [“else” ⟨Anweisung⟩]
⟨Kondition⟩ → ⟨Ausdruck⟩
| {⟨Dekl'Spez'⟩} ⟨Dekl'⟩ ⟨Init'⟩
Eine if-Abfrage wertet die ihr übergebene Anweisung (in der Regel ein Block) aus, gdw. die gegebene Kondi-
tion zu true auswertet.
Ist die Kondition ein Ausdruck, so wertet sie zu dem Wert aus, zu dem auch der Ausdruck auswertet. Wenn der
Ausdruck zu einem Wert von einem Typ auswertet, der nicht bool ist, so wird dieser Wert implizit konvertiert
zu einem Wert vom Typ bool. I.d.R. werden Werte verschieden von 0 konvertiert zu true und nur 0 zu false.
Ist die Kondition eine Vereinbarung, so wertet sie zu dem Wert aus, der nach der Vereinbarung in der Verein-
barten Variable gespeichert ist.
Die mit else abgetrennte Anweisung wird ausgeführt, gdw. die gegebene Kondition zu false auswertet.

Seite 9 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

Der syntaktische Gültigkeitsbereich einer in der Kondition vereinbarten Variable ist genau beide ihr überge-
bene Anweisungen.
In Analogie zu den bedingten Sprunganweisungen von 𝑀 ′ lassen sich if-Abfragen verstehen als ein bedingter
Sprung gefolgt von der gegebenen Anweisung. Wenn die Bedingung zu false auswertet, so wird über die
gegebene Anweisung hinweg gesprungen, sonst wird sie ausgeführt.

while-Schleifen

⟨Anweisung⟩ → . . . | ⟨Wiederholanw'⟩
⟨Wiederholanw'⟩ → “while” “(” ⟨Kondition⟩ “)” ⟨Anweisung⟩

Eine while-Schleife wertet die ihr übergebene Ausweisung (in der Regel ein Block) immer wieder (unbe-
schränkt oft) aus, solange die gegebene Kondition zu true auswertet.
Die Kondition wird vor jeder Ausführung der Anweisung erneut geprüft. Erst wenn sie zu false auswertet
bricht die Schleife ab und es wird die gegebene Anweisung nicht noch einmal ausgeführt.
Der syntaktische Gültigkeitsbereich einer in der Kondition vereinbarten Variable ist genau die der Schleife
übergebene Anweisung.
In Analogie zu einer Konstruktion für 𝑀 ′ lassen sich while-Schleifen verstehen als ein bedingter Sprung,
die gegebene Anweisung und dann ein unkonditionaler Sprungbefehl zurück. Wenn die Bedingung zu false
auswertet, so wird über die gegebene Anweisung und den folgenden Sprungbefehl hinweg gesprungen, sonst
wird beides ausgeführt, wobei der Sprungbefehl am Ende zurück springt, sodass als nächstes die Bedingung
erneut ausgewertet wird.

Beispiel. Wir geben tabellarisch die Partialsummen von 𝑛𝑖=1 𝑖 aus, für 𝑛 = 10.

[Link]

#include <iostream>
#include <iomanip>

using namespace std;

int main() {
int n = 10, i = 0, s = 0;
while (i <= n) {
s = s + i;
cout << fixed << setprecision(2)
<< setw(2) << i << setw(6) << s << endl;
i += 1;
}
return 0;
}

0 0
1 1
2 3
3 6
4 10
5 15
6 21

Seite 10 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

7 28
8 36
9 45
10 55

∑ 𝑘
Beispiel. Wir geben für 𝑛 ∈ {0, . . . , 10} tabellarisch den Wert von 𝑛𝑘=0 𝑥𝑘! (Partialsummen der Exponentialrei-
he) aus. Wir nehmen 𝑥 = 1 konstant. Zum Vergleich geben wir auch den Wert der eingebauten Funktion
exp, ausgewertet ebenfalls für 𝑥, aus.

[Link]

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

using namespace std;

double exp_reihe(double x, int n) {


int k = 1;
double a = 1, s = 1;
while (k <= n) {
a *= x/k;
s += a;
k += 1;
}
return s;
}

int main() {
double x = 1;
cout << ”x = ” << x << endl;
cout << ”exp(x) = ” << exp(x) << endl;

cout << setw(3) << ”n” << ” ” << setw(15) << ”exp_reihe(x, n)” << endl;
int n = 0;
while (n <= 10) {
cout << setw(3) << n << ” ” << setw(15) << exp_reihe(x, n) << endl;
n += 1;
}
return 0;
}

x = 1
exp(x) = 2.71828
n exp_reihe(x, n)
0 1
1 2
2 2.5
3 2.66667
4 2.70833
5 2.71667
6 2.71806
7 2.71825
8 2.71828
9 2.71828

Seite 11 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026

10 2.71828

Seite 12 von 12
Skript 03 Programmieren I für Studierende der Mathematik — SoSe 2026

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 2
Skript 03 Programmieren I für Studierende der Mathematik — SoSe 2026

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)

Seite 2 von 2
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

Arithmetische Datentypen

Der Großteil der eingebauten Datentypen in C++ sind die arithmetischen Datentypen.
Es stehen sowohl Ganzzahl- wie auch Gleitpunktdatentypen zur Verfügung.

Vergleich von Ganzzahl- und Gleitpunktarithmetik

Ganzzahldatentypen sind jeweils in der Lage ganzzahlige Werte aus einem endlichen Bereich exakt darzustel-
len. Der Bereich hängt vom Typ ab.
Solange weder Argumente noch das Ergebnis einer Rechenoperation den Wertebereich des jeweiligen Typs
verlassen, ist garantiert, dass das Ergebnis exakt korrekt ist.
Beispiel (Bereichsüberlauf). Wir multiplizieren zwei eingegebene und in einer Variable vom Typ int gespei-
cherte Zahlenwerte und geben das Ergebnis aus. Unintuitiverweise ist das Ergebnis bei der Eingabe großer
positiver Zahlen negativ.
Es tritt ein Bereichsüberlauf beim vorzeichenbehafteten Ganzzahldatentyp int auf.
Wenn das mathematisch korrekte Ergebnis von Rechenoperationen auf vorzeichenbehafteten Ganzzahlda-
tentypen den durch den Typ darstellbaren Bereich verlässt, ist dies undefiniertes Verhalten. D.h. der C++-
Standard belegt das Programm als Ganzes mit keiner Bedeutung. Es wäre also neben einem mathematisch
inkorrektem Ergebnis des Ausdrucks (wie hier) insb. auch ein Absturz des Programms oder beliebige Sei-
teneffekte eine korrekte Umsetzung des Standards.

[Link]

#include <iostream>

using namespace std;

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

cout << (a * b) << endl;


}

a, b: 1000 2000
2000000

a, b: 1000000 2000000
-1454759936

Um Zahlen außerhalb des Wertebereichs von Ganzzahldatentypen zumindest approximieren zu können ste-
hen auch Gleitpunktdatentypen zur Verfügung. Diese sind in der Lage endlich viele Werte über einen signifi-
kant größeren Bereich darzustellen, jedoch hängt der Abstand zwischen benachbarten darstellbaren Werten
von ihrer Größenordnung ab.
Beispiel (Ungenauigkeit bei Berechnungen mit Gleitpunktdatentypen). Wir addieren in jedem Schritt auf ei-
nen großen, in einer Variable vom Typ float gespeicherten Zahlenwert in jedem Schritt 1. Unintuitiver-
weise ist ab einer gewissen Schranke das Ergebnis der Addition mit 1 identisch zum vorherigen Wert.

Seite 1 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

float ist ein Gleitpunktdatentyp.


Der Abstand der von float darstellbaren Werte übersteigt in der hier gewählten Größenordnung 1. Das
mathematisch korrekte Ergebnis von Rechenoperationen auf Gleitpunktdatentypen wird auf den nächsten
durch den Typ darstellbaren Wert gerundet.

float_inc.cpp

#include <iostream>
#include <iomanip>

using namespace std;

int main() {
float prev = 0b11111111'11111111'11111100, // int literal, basis 2
curr = prev + 1;
while (prev != curr) {
prev = curr; curr += 1;
cout << fixed << setprecision(1) << prev << ” ” << curr << endl;
}
}

16777213.0 16777214.0
16777214.0 16777215.0
16777215.0 16777216.0
16777216.0 16777216.0

Ganzzahldatentypen

⟨Int'Typ⟩ → [“signed” | “unsigned”] “char”


| [“signed” | “unsigned”] ([⟨Int'TypMod⟩] “int” | ⟨Int'TypMod⟩ )
| “bool”
⟨Int'TypMod⟩ → “short” | “long” | “long” “long”
Die Werte vom Typ bool sind true und false.
Werte der anderen Ganzzahldatentypen (⟨Int’Typ⟩) 𝑇 sind jeweils alle ganze Zahlen innerhalb eines vom Typ
abhängigen Intervalls [min𝑇 , max𝑇 ] ∩ Z. Die genauen Schranken sind implementierungsabhängig (hängen
also vom Compiler, der Maschinenarchitektur und dem Betriebssystem ab), lassen sich jedoch für einen Typ
𝑇 mit numeric_limits<𝑇 >::min() bzw. numeric_limits<𝑇 >::max() berechnen (Header-Datei <limits>).
Datentypen die in der folgenden Tabelle als „Vorzeichen?“ markiert sind, sind in der Lage sowohl positive wie
auch negative Werte zu speichern, sie sind vorzeichenbehaftet. Bei Datentypen 𝑇 die mit „Modulo?“ markiert
sind, verstehen sich die arithmetischen Operationen modulo max𝑇 + 1.
In diesem Sinne ist auch die Typumwandlung (implizit und explizit) in Werte eines mit „Modulo?“ markierten
Typs eine arithmetische Operation und das Ergebnis somit modulo max𝑇 + 1 mit 𝑇 dem Ergebnisdatentyp.
Wir beobachten, dass genau die vorzeichenlosen Ganzzahldatentypen, mit Ausnahme von bool, „Modulo?“
sind. Dies gilt auch über die untige Tabelle hinaus für alle Ganzzahldatentypen, die die Sprache bereit stellt.
Typische1 Werte für Kenngrößen von Ganzzahldatentypen sind wie folgt:

1 g++-14.2.1 20241116, Linux (x86_64), CXXFLAGS: -std=c++14

Seite 2 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

𝑇 Binärstellen (𝑡) min𝑇 max𝑇 Vorzeichen? Modulo?

bool 1 0 1
char −27 27 − 1 ✓
unsigned char 8 0 28 − 1 ✓
short int −215 215 − 1 ✓
unsigned short int 16 0 216 −1 ✓
int −231 231 − 1 ✓
unsigned int 32 0 232 − 1 ✓
long int −263 263 − 1 ✓
unsigned long int 64 0 264 −1 ✓
long long int −263 263 − 1 ✓
unsigned long long int 64 0 264 − 1 ✓

Gleitpunktdatentypen

⟨Float'Typ⟩ → “float”
| [“long”] “double”
Gleitpunktdatentypen (⟨Float’Typ⟩) in C++ folgen der Norm IEEE-754 in nicht spezifizierter Version, mutmaß-
lich jedoch der von 2008 (IEEE Std 754-20082 , auch bekannt als ISO/IEC/IEEE 60559:2011 bzw. IEC 559:2011).
Werte von Gleitpunktdatentypen sind die speziellen Werte +∞, −∞, NaN („Not A Number“) und Tripel be-
stehend aus Vorzeichen, Exponent und Mantisse (𝑠, 𝑒, 𝑚). Mit jedem Gleitpunktdatentyp ist eine Basis 𝐵 asso-
ziiert. Die Darstellung einer Gleitpunktzahl 𝑥 als Tripel versteht sich als:
Õ
𝑡
𝑥 = (−1)𝑠 · 𝑚 · 𝐵𝑒 𝑚= 𝑧𝑖 𝐵 −𝑖
𝑖=1

Es ist hierbei 0 ≤ 𝑚 < 𝐵 und 𝑒 min ≤ 𝑒 ≤ 𝑒 max . Zur Darstellung von 𝑚 werden stets genau 𝑡 viele Ziffern
(𝑧𝑖 )𝑖 ∈ {1,...,𝑡 } zur Basis 𝐵 verwendet.
Werden zur Darstellung der Mantisse einer Zahl 𝑥 führende Nullen verwendet3 , so nennt man sie subnormal.
Andere als Tripel dargestellte Gleitpunktzahlen nennt man normalisiert. Für arithmetische Operationen auf
subnormalen Gleitpunktzahlen stehen nicht, wie sonst, die vollen 𝑡 + 1 Stellen zur Basis 𝐵 an Präzision zur
Verfügung.
Die mathematisch korrekten Ergebnisse von Rechenoperationen auf Gleitpunktzahlen werden stets zur nächs-
ten darstellbaren Gleitpunktzahl gerundet.
Da auch 0 als Tripel mit Vorzeichen dargestellt wird, kennt die Darstellung +0 und −0 als unterschiedliche
Werte. Betragsmäßig hinreichend kleine jedoch negative Zahlen werden zu −0 gerundet.
Zahlen die betragsmäßig hinreichend groß sind um den ansonsten darstellbaren Bereich zu verlassen, werden
als ±∞ dargestellt.
−0, +∞ und −∞ lassen sich insofern als die Ergebnisse von Grenzprozessen verstehen, die aus dem Negativen
zu 0 konvergieren bzw. ins Positive bzw. ins Negative divergieren. NaN versteht sich als Ausdruck dessen, dass
2 [Link]/10.1109/IEEESTD.2008.4610935
3 D.h. ∃𝑖 ′ > 1 ∀𝑘 < 𝑖 ′, 1 ≤ 𝑘. 𝑧𝑘 = 0

Seite 3 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026


das Ergebnis einer Operation sich nicht durch eine Gleitpunktzahl approximieren lässt. Z.B. liefert −1 den
Wert NaN.
Die Maschinengenauigkeit 𝜀 bietet eine Abschätzung der zur Verfügung stehenden Genauigkeit. Bei Rundung
des Ergebnisses einer Rechenoperation 𝑥 zu einer darstellbaren Gleitkommazahl rd(𝑥) ergibt sich in Abhän-
gigkeit von der Rundungsmethode ein relativer Fehler von höchstens 2 · 𝜀:
(
𝑥 − rd(𝑥) 𝜀 bei Rundung zur nächsten Gleitpunktzahl
≤ (1)
𝑥 2 · 𝜀 sonst

Es liegt nahe die Fortpflanzung des (derart beschränkten) relativen Fehlers unter den zur Verfügung stehenden
Rechenoperationen zu untersuchen. Wir bezeichnen für 𝑥 ∈ R im Folgenden mit 𝑥 eine Näherung an den math.
korrekten Wert 𝑥 und mit 𝜀𝑥 := 𝑥 𝑥−𝑥 den relativen Fehler von 𝑥. Es ist 𝑥 = (1 + 𝜀𝑥 ) · 𝑥.
Wir betrachten den relativen Fehler des Ergebnisses einer Addition bzw. Subtraktion bzw. Multiplikation bzw.
Division zweier Zahlen in Abhängigkeit der relativen Fehler der Operanden:
𝑥 ± 𝑦 − (𝑥 ± 𝑦) 𝑥 ± 𝑦 − (𝑥 ± 𝑦)
𝜀𝑥±𝑦 = =
𝑥 ±𝑦 𝑥 ±𝑦
(1 + 𝜀𝑥 )𝑥 ± (1 + 𝜀 𝑦 )𝑦 − (𝑥 ± 𝑦)
=
𝑥 ±𝑦
𝑥 𝑦
= 𝜀𝑥 ± 𝜀𝑦 (2)
𝑥 ±𝑦 𝑥 ±𝑦

𝑥 · 𝑦 − 𝑥𝑦 𝑥𝑦 − 𝑥𝑦
𝜀𝑥 ·𝑦 = =
𝑥𝑦 𝑥𝑦
(1 + 𝜀𝑥 )(1 + 𝜀 𝑦 )𝑥𝑦 − 𝑥𝑦
=
𝑥𝑦
= 𝜀𝑥 + 𝜀 𝑦 + 𝜀𝑥 𝜀 𝑦

𝑥/𝑦 − 𝑥/𝑦 𝑥/𝑦 − 𝑥/𝑦


𝜀𝑥/𝑦 = 𝑥/𝑦
= 𝑥/𝑦

1 + 𝜀𝑥
= −1
1 + 𝜀𝑦
𝜀𝑥 − 𝜀 𝑦
=
1 + 𝜀𝑦

Wir beobachten, dass bei der Subtraktion von beinahe gleichen Zahlen 𝑥 ≈ 𝑦 der Nenner in Gleichung 2
betragsmäßig klein wird. Der relative Fehler des Ergebnisses der Subtraktion nahe beieinander liegenden
Zahlen wird also sehr groß. Man spricht bei diesem Effekt von Auslöschung und sagt die Subtraktion sei in
diesem Fall schlecht konditioniert.
Die Auslöschung tritt zwar insb. bei der hier besprochenen Darstellung von Werten als Gleitpunktzahlen auf,
ist jedoch eine prinzipielle Eigenschaft des Rechnens mit fehlerbehafteten Größen. Insofern kann sie zwar
nicht gelöst, aber zumindest vermieden werden. Beim Umformulieren eines Problems zur Vermeidung von
Auslöschung und verwandter Effekte spricht man von Vorkonditionierung.

Arithmetische Kenngrößen

In der Standardlibrary stehen, für 𝑇 ein arithmetischer Datentyp, diverse Kenngrößen 𝑐 zur Verfügung. Um
auf sie zugreifen zu können muss zunächst die Header-Datei <limits> inkludiert werden. Die Kenngrößen

Seite 4 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

stehen dann als Konstanten unterhalb von numeric_limits zur Verfügung als numeric_limits<𝑇 >::𝑐.
Z.B. liefert numeric_limits<double>::infinity() den Wert +∞ als Wert vom Typ double.
Typische4 Werte einiger dieser Kenngrößen sind wie folgt:

Konstante float double long double Bedeutung

radix 2 2 2 𝐵
digits 24 53 64 𝑡 +1
digits10 6 15 18 Minimale dezimale Genauigkeit
max_digits10 9 17 21 Maximale dezimale Genauigkeit
min_exponent −27 +3 −210 +3 −214 +3 𝑒 min
max_exponent 27 210 214 𝑒 max
lowest() −3,4 · 1038 −1,8 · 10308 −1,19 · 104 932 Kleinste Zahl
denorm_min() 1,4 · 10−45 4,94 · 10−324 3,65 · 10−4 951 Kleinste positive Zahl
min() 1,18 · 10−38 2,23 · 10−308 3,36 · 10−4 932 Kleinste positive normalisierte Zahl
max() 3,4 · 1038 1,8 · 10308 1,19 · 104 932 Größte Zahl
epsilon() 1,19 · 10−7 2,22 · 10−16 1,08 · 10−19 min{𝑥 | 𝑥 Zahl, 𝑥 > 0, 1 + 𝑥 > 1} = 2 · 𝜀
infinity() ∞
quiet_NaN() NaN

Mit Gleichung 1 der Definition für 𝜀, liefert numeric_limits<𝑇 >::epsilon() für einen Gleitpunktdatentyp 𝑇
den Wert 2 · 𝜀, also unabhängig von der verwendeten Rundungsart5 und somit implementierungsunabhängig,
eine obere Schranke an den relativen Fehler der Darstellung als Gleitpunktzahl.

Literale

⟨Literal⟩ → ⟨Int'Literal⟩
| ⟨Char'Literal⟩
| ⟨Float'Literal⟩
| ⟨StringLiteral⟩
| ⟨Bool'Literal⟩
⟨Bool'Literal⟩ → “false” | “true”
⟨Char'Literal⟩ → “'” ⟨Zeichen⟩ “'”
⟨StringLiteral⟩ → “"” {⟨Zeichen⟩} “"”
Literale sind konstante Ausdrücke. Sie werten aus zu dem Wert, den sie repräsentieren.

Ganzzahlliterale

⟨Int'Literal⟩ → ⟨Dez'Literal⟩ [⟨Int'Suffix⟩]


| ⟨Okt'Literal⟩ [⟨Int'Suffix⟩]
4 g++-14.2.1 20241116, Linux (x86_64), CXXFLAGS: -std=c++14
5 Die gewählte Rundungsart und einige weitere implementierungsabhängige Einstellungen bzgl. des Umgangs mit Werten von Gleit-

punktdatentypen bezeichnet man gesamtheitlich als die floating point environment

Seite 5 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

| ⟨Hex'Literal⟩ [⟨Int'Suffix⟩]
| ⟨Bin'Literal⟩ [⟨Int'Suffix⟩]
⟨Dez'Literal⟩ → /[1-9]/ {[“'”] /[0-9]/}
⟨Okt'Literal⟩ → “0” {[“'”] /[0-7]/}
⟨Hex'Literal⟩ → (“0x” | “0X”) (/[0-9]/ | /[a-f]/ | /[A-F]/) {[“'”] (/[0-9]/ | /[a-f]/ | /[A-F]/)}
⟨Bin'Literal⟩ → (“0b” | “0B”) (“0” | “1”) {[“'”] (“0” | “1”)}
⟨Int'Suffix⟩ → ⟨Uns'Suffix⟩ [⟨LongSuffix⟩ | ⟨L'LongSuffix⟩]
| (⟨LongSuffix⟩ | ⟨L'LongSuffix⟩) [⟨Uns'Suffix⟩]
⟨Uns'Suffix⟩ → “u” | “U”
⟨LongSuffix⟩ → “l” | “L”
⟨L'LongSuffix⟩ → “ll” | “LL”
Anmerkung. Dezimale Ganzzahlliterale erlauben keine führenden Nullen. Ganzzahlliterale mit einer führen-
den Null werden stattdessen oktal d.h. zur Basis 8 interpretiert.
Anmerkung. Ganzzahlliterale sind stets positiv. Ein negatives Vorzeichen wird interpretiert als Negation („unä-
re Subtraktion“).
Ganzzahlliterale haben als Typ stets einen Ganzzahldatentyp mit Zahlbereich größer oder gleich dem des
Typs int. Es wird stets der Typ mit dem kleinstmöglichen Zahlenbereich gewählt, der in der Lage ist den
Wert des Literals darzustellen.
Etwaige Suffixe schränken die Wahl des Ganzzahldatentyps ein. Mit ⟨Uns’Suffix⟩ werden bei der Auswahl des
Typs nur vorzeichenfreie Ganzzahldatentypen in Betracht gezogen, mit ⟨LongSuffix⟩ nur Ganzzahldatentypen
mit Zahlbereich mindestens so groß wie der von long int und mit ⟨L’LongSuffix⟩ nur Ganzzahldatentypen
mit Zahlbereich mindestens so groß wie der von long long int.
Bei dezimalen Ganzzahlliteralen ohne Suffix ⟨Uns’Suffix⟩ werden nur vorzeichenbehaftete Ganzzahldatenty-
pen in Betracht gezogen.

Gleitpunktliterale

⟨Float'Literal⟩ → ([⟨Ziffern⟩] “.” ⟨Ziffern⟩ | ⟨Ziffern⟩ “.”) [⟨Exponent⟩] [⟨Float'Suffix⟩]


| ⟨Ziffern⟩ ⟨Exponent⟩ [⟨Float'Suffix⟩]
⟨Exponent⟩ → (“e” | “E”) [“+” | “-”] ⟨Ziffern⟩
⟨Ziffern⟩ → /[0-9]/ {[“'”] /[0-9]/}
⟨Float'Suffix⟩ → ⟨FSuffix⟩ | ⟨LDSuffix⟩
⟨FSuffix⟩ → “f” | “F”
⟨LDSuffix⟩ → “l” | “L”
Gleitpunktliterale ohne ⟨Float’Suffix⟩ haben stets Typ double. Literale mit ⟨FSuffix⟩ haben Typ float. Literale
mit ⟨LDSuffix⟩ haben Typ long double.
Ist der Wert eines Gleitpunktliterals im gewählten Gleitpunktdatentyp darstellbar, so hat das Literal als Aus-
druck eben jenen Wert. Ansonsten wird implementierungsabhängig der nächst größere oder kleinere im Gleit-
punktdatentyp darstellbare Wert gewählt.

Ausdrücke

Ausdrücke bestehen aus Funktionsaufrufen, Anwendungen von Operatoren, Namen von Variablen, Zusam-
menfassen von Teilausdrücken in runden Klammern ((, )) und einigen wenigen weiteren speziellen syntakti-
schen Konstruktoren. Letztere werden einzeln besprochen, sobald Sie für Zwecke der Veranstaltung relevant
werden.

Seite 6 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

Auswertung von Ausdrücken

Die Auswertung eines Ausdrucks 𝐸 besteht aus der Berechnung des Werts des Ausdrucks 𝐸 W und dem Durch-
führen von Seiteneffekten (z.B. Änderung von in Variablen gespeicherten Werten) assoziiert mit dem Ausdruck
𝐸S .
Die Reihenfolge der Auswertung der Teilausdrücke eines Ausdrucks ist nur partiell festgelegt. Für 𝐸 1 W, 𝐸 2 W
Werteberechnungen von Ausdrücken schreiben wir 𝐸 1 W ≺ 𝐸 2 W wenn die Berechnung 𝐸 1 W vor 𝐸 2 W statt-
finden muss und analog für Seiteneffekte. Abseits der im Folgenden aufgeführten Einschränkungen an die
Auswertungsreihenfolge steht es dem Compiler frei die zu den Auswertungsschritten korrespondieren Ma-
schinenbefehle beliebig anzuordnen. Insb. darf der Compiler sogar für den selben Ausdruck an verschiedenen
Stellen im Programm verschiedene Anordnungen wählen.
Dies führt in der Praxis gelegentlich zu unintuitivem Verhalten.

• Die Berechnung der Werte der Operanden eines Operators geschieht vor der Berechnung des Wertes
des Ergebnisses des Operators. Dies trifft keine Aussage über die Reihenfolge der Seiteneffekte des
Operators und seiner Operanden.
• Bei Funktionsaufrufen geschieht sowohl die Berechnung der Werte der Funktionsargumente, wie auch
die hiermit assoziierten Seiteneffekte, vor Ausführung des Körpers der Funktion.

Explizite Klammerung, Assoziativität und die Bindungsstärke von Operatoren (der Operatorvorrang) bestim-
men die Bedeutung von Ausdrücken bzgl. der Berechnungen ihres Werts.
Wir sagen ein Operator ◦ sei links-Assoziativ wenn 𝑎 ◦ 𝑏 ◦ 𝑐 identische Bedeutung hat mit (𝑎 ◦ 𝑏) ◦ 𝑐. Analog
ist ein Operator ◦ rechts-Assoziativ wenn 𝑎 ◦ 𝑏 ◦ 𝑐 identische Bedeutung hat mit 𝑎 ◦ (𝑏 ◦ 𝑐).
Wenn ein Operator ∗ stärker bindet (höheren Operatorvorrang besitzt) als ◦, so ist 𝑎 ◦𝑏 ∗𝑐 identisch mit 𝑎 ◦ (𝑏 ∗𝑐).
Wenn stattdessen ∗ schwächer bindet (niedrigeren Operatorvorrang besitzt) als ◦, so ist 𝑎 ◦ 𝑏 ∗ 𝑐 identisch mit
(𝑎 ◦ 𝑏) ∗ 𝑐.

Implizite Typumwandlung von arithmetischen Operanden

Auf die Operanden von zweistelligen arithmetischen Operatoren werden stets die sog. üblichen arithmetischen
Konvertierungen angewandt:

• Wenn mindestens ein Operand von Gleitpunktdatentyp ist, wird der andere Operand in den Gleitpunkt-
datentyp mit dem größten Zahlbereich unter den Typen der Operanden umgewandelt.
• Operanden vom Typ bool werden umgewandelt in int (Zahlbereichserweiterung)
• Wenn beide Operanden jeweils vorzeichenfreie oder vorzeichenbehaftete Ganzzahldatentypen haben,
wird der jeweils andere Operand in den Ganzzahldatentyp mit höchstem Rang unter den Typen der
Operanden umgewandelt.
• Sonst, wenn der Wertebereich des Datentyp des Operands mit vorzeichenbehafteten Ganzzahldaten-
typ den Wertebereich des Datentyps des anderen Operanden vollständig enthält, wird der Operand mit
vorzeichenfreiem Ganzzahldatentyp umgewandelt in den Datentyp des Operanden mit vorzeichenbe-
hafteten Ganzzahldatentyp.
• Sonst werden beide Operanden umgewandelt in dem mit dem vorzeichenbehafteten Ganzzahldatentyp
assoziierten vorzeichenfreien Ganzzahldatentyp.

Der Typ des Werts der Anwendung des Operators ist i.d.R. der Typ der so konvertierten Operanden.
Obige Regeln werden entsprechend auch auf einstellige Operatoren angewandt.
Für den Rang 6 von Ganzzahldatentypen gilt:
6 einer partiellen Ordnungsrelation

Seite 7 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

• bool hat einen Rang echt kleiner als der Rang aller anderen Datentypen
• Der mit einem vorzeichenbehafteten Ganzzahldatentyp assoziierte vorzeichenfreie Ganzzahldatentyp
hat den gleichen Rang
• Alle char-Datentypen haben den gleichen Rang
• long long int > long int > int > short int > signed char

Bei Umwandlung eines Wertes von vorzeichenbehaftetem Datentyp in einen vorzeichenfreien Datentyp 𝑇 ist
der resultierende Wert der kleinste Wert welcher kongruent ist Modulo max𝑇 + 1 zum Ausgangswert.

Implizite Typumwandlung von Funktionsparametern und bei Zuweisungen

Wird eine Funktion aufgerufen, werden die Werte der als Argument übergebenen Ausdrücke implizit kon-
vertiert in den Typ, den die Funktion für ihr Parameter angegeben hat. Die selben Regeln gelten bei der
Zuweisung einer Variable.

Ganzzahlkonvertierungen Ein Wert eines Ganzzahldatentyps kann in einen anderen Ganzzahldatentyp


konvertiert werden. Wenn der Wert im Zieltyp darstellbar ist, wird der Wert in diesem Sinne unverän-
dert übernommen. Ist ansonsten der Zieltyp 𝑇 vorzeichenlos erfolgt die Konvertierung modulo max𝑇 +1.
Ansonsten ist das Ergebnis der Konvertierung implementierungsabhängig.
Bei Konvertierungen nach bool werden Werte ungleich 0 behandelt als true und nur 0 als false. Bei
Konvertierungen von bool in einen anderen Typ wird true konvertiert zu 1 und false konvertiert zu
0.
Gleitpunktkonvertierungen Ein Wert eines Gleitpunktdatentyps kann in einen anderen Gleitpunktdaten-
typ konvertiert werden. Wenn der Wert im Zieltyp darstellbar ist, wird der Wert in diesem Sinne unver-
ändert übernommen. Ansonsten wird, implementierungsabhängig, entweder der nächst größere oder
nächst kleinere darstellbare Wert gewählt. Liegt der Wert im Zieltyp nicht zwischen zwei darstellbaren
Werten, ist das Verhalten undefiniert.
Ganzzahl-Gleitpunkt-Konvertierungen Ein Wert eines Gleitpunktdatentyps kann zu einem Ganzzahlda-
tentyp konvertiert werden. Etwaige Nachkommastellen werden abgeschnitten (vgl. trunc).
Ein Wert eines Ganzzahldatentyps kann in einen Gleitpunktdatentyp konvertiert werden. Hierfür wird,
analog zur Konvertierung zwischen Gleitpunktdatentypen, implementierungsabhagig der nächst grö-
ßere oder nächst kleinere darstellbare Wert gewählt. Liegt der Wert im Zieltyp nicht zwischen zwei
darstellbaren Werten, ist das Verhalten undefiniert.
Bei Konvertierungen nach bool werden Werte ungleich 0 behandelt als true und nur 0 als false. Bei
Konvertierungen von bool in einen anderen Typ wird true konvertiert zu 1 und false konvertiert zu
0.

Anmerkung. Insb. bei Konvertierungen zwischen Gleitpunkt- und Ganzzahldatentypen kann in beide Richtun-
gen Präzision verloren werden.

Explizite Typumwandlung

⟨Typ'Konv'⟩ → “static_cast” “⟨” ⟨Typ⟩ “⟩” “(” ⟨Ausdruck⟩ “)”


Das oben beschriebene Verfahren zur Typkonvertierung kann auch explizit ausgelöst werden. Hierfür emp-
fiehlt es sich den Operator static_cast zu verwenden.

Seite 8 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

Anmerkung. Es stehen auch die sog. C-style cast expressions zur Verfügung (Typ vorm Ausdruck in runden
Klammern, bzw. Typname behandelt wie Name einer einstelligen Funktion). Von den C-style cast expres-
sions ist i.A. abzuraten; hier entscheidet der compiler automatisch ob die Typkonvertierungsregeln wie
beschrieben verwendet werden können, oder die interne Repräsentation des einen Typs einfach als Wert
des anderen Typs interpretiert wird. Das Verhalten von C-style cast expressions vorherzusagen erfordert
also zum Einen sehr gutes Verständnis der anwendbaren Typkonvertierungsregeln und zum Anderen ist
das Ergebnis der Konvertierung oft abhängig von der implementierungsabhängigen Repräsentation von
Werten im Speicher der Maschine.

Grundrechenarten

Die zweistelligen Operatoren *, /, %, +, - sind links-assoziativ.


% berechnet den Rest der Division der Operanden. Die Operanden von % haben stets jeweils einen Ganzzahl-
datentyp.
/berechnet für Operanden von Ganzzahldatentyp den Quotienten und verwirft etwaige Nachkommastellen.
Das Ergebnis wird abgeschnitten.
Die einstelligen Operatoren - und + negieren ihren Operanden bzw. lassen ihn unverändert.
Anmerkung. Es gibt keine Operatoren zur Berechnung der Fakultät oder von Potenzen.

Zuweisungsoperatoren

Der Zuweisungsoperator = weist als Seiteneffekt der Variable auf der linken Seite den Wert des Ausdrucks
auf der rechten Seite zu. Es handelt sich nicht um eine Gleichheit im mathematischen Sinne, sondern um eine
Anweisung für eine einmalige Zuweisung.
Die zusammengesetzten Zuweisungoperatoren *=, /=, %=, +=, -=, >>=, <<=, &=, ^= und |= berechnen jeweils
den Wert des entsprechenden arithmetischen- bzw. Binäroperators mit dem aktuellen Wert der Variable auf
der linken Seite und der Rechten Seite des zusammengesetzten Zuweisungsoperaters. Dieser Wert wird der
Variable auf der linken Seite zugewiesen. Z.B. erhöht der Ausdruck 𝑣 += 2 bei Auswertung seiner Seiteneffekte
den in 𝑣 gespeicherten Wert um 2.
Anwendungen des Zuweisungsoperators oder eines der zusammengesetzten Zuweisungsoperatoren nehmen
jeweils den neu zugewiesenen Wert an7 .
Beispiel. Verkettungen von Zuweisungen sind möglich. Der Zuweisungsoperator und die zusammengesetzten
Zuweisungsoperatoren binden alle gleich stark und rechtsassoziativ:

chain_assign.cpp

#include <iostream>

using namespace std;

int main() {
int i = 5, j = 3;

j = i += j -= 1;

cout << ”i = ” << i << ”, j = ” << j << endl;

7 Genauer wird eine Referenz geliefert auf die soeben zugewiesene Variable

Seite 9 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

i = 7, j = 7

Für 𝐸 1 ◦ 𝐸 2 , wobei ◦ den Zuweisungsoperator oder einen der zusammengesetzten Zuweisungsoperatoren


bezeichne, gilt stets 𝐸 1 W ≺ (𝐸 1 ◦ 𝐸 2 ) S , 𝐸 2 W ≺ (𝐸 1 ◦ 𝐸 2 ) S und (𝐸 1 ◦ 𝐸 2 ) S ≺ (𝐸 1 ◦ 𝐸 2 ) W .

Inkrement- und Dekrementoperatoren

Für eine Variable 𝑣 weisen die Ausdrücke 𝑣 ++ und ++𝑣 jeweils 𝑣 als Seiteneffekt den Wert 𝑣 + 1 zu. Analog
weißen die Ausdrücke 𝑣 -- und --𝑣 jeweils 𝑣 als Seiteneffekt den Wert 𝑣 − 1 zu.
Als Wert nehmen die obigen Ausdrücke jeweils den Wert von 𝑣 an.
Der Unterschied zwischen der präfix und postfix Variante liegt in der Auswertungsreihenfolge. Es ist 𝑣 ++W ≺
𝑣 ++S , ++𝑣 S ≺ ++𝑣 W und analog für --.
D.h. die Postfix-Varianten nehmen den alten Wert von 𝑣 als Wert an und führen dann die Zuweisung des
neuen Wertes von 𝑣 durch. Die Präfix-Varianten hingegen führen die Zuweisung des neuen Wertes von 𝑣
durch und nehmen dann den neuen Wert von 𝑣 als Wert an8 .
Beispiel. Wir erhöhen die Variablen i und j jeweils mit der Präfix- bzw. Postfix-Variante des ++-Operators.

[Link]

#include <iostream>

using namespace std;

int main() {
int i = 5, j = 5;
cout << ”i = ” << i << ”, j = ” << j << endl;

int k;
k = i++;
cout << ”i = ” << i << ”, k = ” << k << endl;

k = ++j;
cout << ”j = ” << j << ”, k = ” << k << endl;
}

i = 5, j = 5
i = 6, k = 5
j = 6, k = 6

Logische- & Vergleichsoperatoren

Die Vergleichsoperatoren ==, !=, <=, >=, <, > liefern einen Wert vom Typ bool welcher beschreibt ob die
Operanden gleich bzw. ungleich bzw. kleiner oder gleich bzw. größer oder gleich bzw. kleiner bzw. größer
sind.

8 Genauer liefert die Präfix-Variante eine Refernz auf die soeben inkrementierte Variable

Seite 10 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

Der einstellige Operator ! liefert für einen Operanden, welcher vom Typ bool sein muss (oder entsprechend
konvertiert wird), die logische Negation des Wertes.
Die Operatoren && und || liefern für Operanden, welche vom Typ bool sein müssen (oder entsprechend
konvertiert werden), die logische Konjunktion bzw. Disjunktion der Werte ihrer Operanden:

&& false true || false true


! false true
false false false false false true
true false
true false true true true true

Für 𝐸 1 && 𝐸 2 ist 𝐸 1 W ≺ 𝐸 2 W und 𝐸 1 S ≺ 𝐸 2 S . Analog für ||.


&& und || werten die Seiteneffekte und berechnen den Wert ihres rechten Operanden nur dann, wenn das
Ergebnis durch den Wert des linken Operanden nicht bereits feststeht. Z.B. wertet false && 𝐸 1 weder die
Seiteneffekte von 𝐸 1 aus, noch wird ein Wert für 𝐸 1 berechnet.
Anmerkung (Häufige Fehler). Die folgenden Konditionen von if-Anweisungen verhalten sich evtl. nicht wie
erwartet:

Code-Fragment Bedeutung der Kondition Bemerkung

if (i == 1) {. . .} falls i den Wert 1 hat


if (i = 1) {. . .} immer weist i den Wert 1 zu
if (1 <= x && x <= 2) {. . .} wenn x in [1, 2]
if (1 <= x <= 2) {. . .} immer 1 <= x ist stets in {0, 1}
if (!(x > 0)) {. . .} wenn x ≤ 0
if (!x > 0) {. . .} wenn x = 0 !x liefert 0 für x ≠ 0 und 1 für x = 0

Binäroperatoren

Die Operatoren <<, >>, |, &, ^ und ~ wirken auf der Binärdarstellung ihrer vorzeichenfreien bzw. vorzeichen-
behafteten aber nicht-negativen Operanden. Ihre Wirkung auf vorzeichenbehafteten, negativen Operanden
ist implementierungsabhängig bzw. im Falle von << für gewisse Operanden undefiniertes Verhalten.
Nicht-negative ganze Zahlen 𝑘 besitzen eine Binärdarstellung als endliche Folge von bits (𝑏𝑖 )𝑖=0,1,...,𝑡 −1 mit
Í𝑡 −1 𝑖
𝑏𝑖 ∈ {0, 1} und 𝑘 = 𝑖=0 𝑏𝑖 2 . Die Anzahl von verwendeten bits 𝑡 hängt vom jeweiligen Ganzzahldatentyp ab.
Schematisch dargestellt z.B. für 𝑡 = 32:

𝑏 31 𝑏 30 𝑏 29 𝑏 28 ··· 𝑏3 𝑏2 𝑏1 𝑏0

&, |, ^
und ~ wenden auf die Binärdarstellung ihrer Operanden bzw. ihres Operanden bit-weise die Verknüp-
fung Und bzw. Oder bzw. exklusives Oder bzw. Nicht an. Die Verknüpfungen sind definiert als:

& 0 1 | 0 1 ^ 0 1
~ 0 1
0 0 0 0 0 1 0 0 1
1 0
1 0 1 1 1 1 1 1 0

Seite 11 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel. Wir stellen die Berechnung des Wertes des Ausdrucks 10 & 12 schematisch dar:

10 0 0 0 0 ··· 1 0 1 0
& & & & & & & &

12 0 0 0 0 ··· 1 1 0 0

0 0 0 0 ··· 1 0 0 0 8

Anmerkung. Obwohl die logischen Operatoren (z.B. &&) angewandt auf Operanden vom Typ bool die gleichen
Werte berechnen, wie die Binäroperatoren (z.B. &), sind die logischen Operatoren dennoch vorzuziehen.
Grund hierfür ist insb. dass die logischen Operatoren stärker binden als die Vergleichsoperatoren, die
Binäroperatoren jedoch schwächer.
Dies führt, bei Verwendung von Binäroperatoren in z.B. Konditionen von if-Abfragen, gelegentlich zu
unintuitiven Ergebnissen.
Die logischen shift-Operatoren << und >> berechnen das Ergebnis einer Verschiebung der Binärdarstellung
ihres linken Operanden.
Anmerkung. Die logischen shift-Operatoren haben keine Seiteneffekte. Z.B. bei der Verwendung einer Varia-
blen x auf der linken Seite eines shift-Operators wie in x << 1 wird der in x gespeicherte Wert durch die
Auswertung des Ausdrucks nicht verändert.
Wir definieren die Wirkung der shift-Operatoren << und >> auf einem Wert 𝑘 mit Binärdarstellung (𝑏𝑖 )𝑖=0,...,𝑡 −1
rekursiv als Funktionen ≪ bzw. ≫, wie folgt:

𝑘 ≪ 0 := 𝑘 𝑘 ≫ 0 := 𝑘
Õ
𝑡 −2 Õ
𝑡 −1
𝑘 ≪ 1 := 𝑏𝑖 2𝑖+1 𝑘 ≫ 1 := 𝑏𝑖 2𝑖 −1
𝑖=0 𝑖=1
𝑘 ≪ (𝑛 + 1) := (𝑘 ≪ 1) ≪ 𝑛 𝑘 ≫ (𝑛 + 1) := (𝑘 ≫ 1) ≫ 𝑛

Oder, äquivalent:

𝑘 ≪ 𝑛 = 𝑘 · 2𝑛 mod 2𝑡 𝑘 ≫ 𝑛 = ⌊𝑘 · 2−𝑛 ⌋

Beispiel. Wir stellen die Berechnung des Wertes des Ausdrucks 3 << 2 schematisch dar für 𝑡 = 16:

3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 12

Ternärer Operator

Der ternäre Operator (auch Auswahloperator), oft bezeichnet als ?:, erlaubt die Bildung von Ausdrücken der
Form 𝑘 ? 𝑡 : 𝑓 mit 𝑘, 𝑡, 𝑓 Teilausdrücken.
Es wird der Ausdruck 𝑘 zu einem Wert vom Typ bool ausgewertet (oder entsprechend konvertiert).

Seite 12 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

Falls 𝑘 zu true auswertet, so werden die Seiteneffekte von 𝑡 ausgewertet und der gesamte Ausdruck nimmt
den Wert an, den 𝑡 bei Auswertung berechnet. Ansonsten werden die Seiteneffekte von 𝑓 ausgewertet und der
gesamte Ausdruck nimmt den Wert an, den 𝑓 bei Auswertung berechnet. Der jeweils „nicht benötigte“ Teil-
ausdruck (𝑡 oder 𝑓 ) wird nicht ausgewertet. Insb. werden nur die Seiteneffekte des „benötigten“ Teilausdrucks
ausgeführt.
Beispiel. Das folgende Programm berechnet den Collatz-Orbit einer Zahl 𝑛, die es von der Standardeingabe
einliest:

[Link]

#include <iostream>

using namespace std;

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

while (n != 1) {
n = n%2 == 0 ? n/2 : 3*n + 1;
cout << n << ” ”;
}
cout << endl;
}

n: 19
58 29 88 44 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1

Komma-Operator

Der Komma-Operator erlaubt die Bildung von Ausdrücken der Form 𝑎 , 𝑏 mit 𝑎, 𝑏 Teilausdrücken. Der
Komma-Operator ist links-assoziativ.
Es ist 𝑎 W ≺ 𝑏 W , 𝑎 W ≺ 𝑏 S , 𝑎 S ≺ 𝑏 W und 𝑎 S ≺ 𝑏 S . D.h. der Komma-Operator wertet erst den Ausdruck auf
seiner linken Seite vollständig aus (inkl. Seiteneffekten) und dann den Ausdruck auf der rechten Seite.
Eine Anwendung des Komma-Operators nimmt insgesamt den Wert an, den der Audruck auf der rechten
Seite berechnet.

Seite 13 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026

Vorrang der Operatoren

Die folgende Tabelle enthält die Operatoren in C++ nach ihrer Bindungsstärke sortiert, d.h. weiter oben auf-
geführte Operatoren binden stärker.
Die Tabelle enthält, zur Übersicht, auch Operatoren die wir noch nicht oder in der Veranstaltung garnicht
behandeln.
Im Folgenden bezeichnet 𝑇 einen Datentyp.

Operatoren Assoziativität Bemerkungen

:: links
() [] -> . ++ -- links ++, -- postfix
typeid static_cast const_cast rechts Bindungsstärke ident. mit vorheriger Zeile
dynamic_cast reinterpret_cast

++ -- + - ! ~ & * sizeof rechts ++, -- präfix; +, -, &, * einsstellig


new delete (𝑇 )

.* ->* links
* / % links
+ - links
<< >> links
< > <= >= links
== != links
& links
^ links
| links
&& links
|| links
?: rechts
= += -= *= /= %= &= ^= |= rechts
<<= >>=

throw rechts
, links

Seite 14 von 14
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

Mathematische Standardfunktionen (<cmath>)

Die Header-Datei <cmath> definiert insb. die folgenden Funktionen. Im Folgenden bezeichnen 𝑥, 𝑦 Werte vom
Typ 𝑇 mit 𝑇 einem Gleitpunktdatentyp1 , 𝐵 die Basis von 𝑇 , 𝑘 ein Wert vom Typ int, 𝑛 den Namen einer
Variablen vom Typ int und 𝑧 den Namen einer Variablen vom Typ 𝑇 . 𝑇 ist jeweils auch Rückgabetyp der
angegebenen Funktionen.

abs(𝑥 ) fabs(𝑥 ) Liefert |𝑥 |


acos(𝑥 ) asin(𝑥 ) atan(𝑥 ) Trigonometrische Umkehrfunktionen
atan2(𝑦 , 𝑥) Argumentfunktion mit Wertebereich [−𝜋, 𝜋]; Siehe unten
cos(𝑥 ) sin(𝑥 ) tan(𝑥 ) Trigonometrische Funktionen
acosh(𝑥 ) asinh(𝑥 ) atanh(𝑥 ) Hyperbolische Umkehrfunktionen
cosh(𝑥 ) sinh(𝑥 ) tanh(𝑥 ) Hyperbolische Funktionen
exp(𝑥 ) exp2(𝑥 ) expm1(𝑥 ) Liefert 𝑒 𝑥 bzw. 2𝑥 bzw. 𝑒 𝑥 − 1
log(𝑥 ) log2(𝑥 ) log10(𝑥 ) Liefert ln(𝑥) bzw. log2 (𝑥) bzw. log10 (𝑥) bzw. ln(1 + 𝑥) bzw.
log1p(𝑥 ) logb(𝑥 ) log𝐵 (𝑥)
frexp(𝑥 , &𝑛 ) Liefert Wert 𝑚 mit |𝑚| ∈ [ 1/2, 1) ∪ {0} und speichert Wert2 𝑒 in
Variable 𝑛, sodass 𝑥 = 𝑚 · 2𝑒
modf(𝑥 , &𝑧 ) Liefert Wert 𝑓 ∈ Z und speichert Wert 𝑖 in Variable 𝑧 mit
sign(𝑥) = sign(𝑖) = sign(𝑓 ), sodass 𝑓 + 𝑖 = 𝑥
fmod(𝑥 , 𝑦) Liefert Wert 𝑓 mit 𝑓 = 𝑥 − 𝑛𝑦 für ein 𝑛 ∈ Z, sodass |𝑓 | < |𝑦| und
sign(𝑓 ) = sign(𝑥); der Gleitpunkt-Rest von 𝑥/𝑦
ldexp(𝑥 , 𝑘 ) pow(𝑥 , 𝑦 ) Liefert 𝑥 · 2𝑘 bzw. 𝑥 𝑦 bzw. 𝑥 · 𝐵𝑘
scalbn(𝑥 , 𝑘 )
√ √ √
sqrt(𝑥 ) cbrt(𝑥 ) hypot(𝑥 , 𝑦) Liefert 𝑥 bzw. 3 𝑥 bzw. 𝑥 2 + 𝑦 2
floor(𝑥 ) ceil(𝑥 ) trunc(𝑥 ) Liefert ⌊𝑥⌋ bzw. ⌈𝑥⌉ bzw. Ergebnis von Rundung zur Null3 hin
round(𝑥 ) bzw. kaufmännischer Rundung4 von 𝑥
copysign(𝑥 , 𝑦) Liefert sign(𝑦) · |𝑥 |
{
𝑥 −𝑦 𝑥 >𝑦
fdim(𝑥 , 𝑦) Liefert Wert 𝑑 =
0 sonst
isfinite(𝑥 ) isinf(𝑥 ) isnan(𝑥 ) Liefert Wert vom Typ bool: 𝑥 ∉ {±∞} bzw. 𝑥 ∈ {±∞} bzw.
isnormal(𝑥 ) 𝑥 = NaN bzw. 𝑥 normalisiert

 true 𝑥 ∈ {−0, −NaN}



signbit(𝑥 ) Liefert Wert 𝑠 vom Typ bool mit 𝑠 = true 𝑥<0


 false sonst

Für 𝑛 ein Wert eines Ganzzahldatentyps 𝑇 ′ definiert die Header-Datei <cstdlib> die Funktion abs(𝑛 ) welche
|𝑛| mit Typ 𝑇 ′ liefert.

1 Die Eigenschaft, dass die aufgeführten Funktionen unter dem gleichen Namen für verschiedene Argumenttypen (und dementspre-
chend Rückgabetypen) zur Verfügung stehen, nennt man Überladung.
2 Von frexp abgelegter Wert unterscheidet sich i.d.R. um 1 von Rückgabewert von logb
3 D.h. sign(𝑥) · ⌊|𝑥 |⌋
4 Bei zwei gleich weit entfernten ganzen Zahlen wird von der Null weg gerundet

Seite 1 von 9
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

atan2
(𝑦)
Die Funktion atan2 berechnet tan−1 𝑥 unter Einbezug der Vorzeichen von sowohl 𝑥 wie auch 𝑦 um den
Quadranten des Ergebnis zu bestimmen:

𝑦
𝜗
𝑥

𝜋
𝜗 = atan2(𝑦, 𝑥). Es ist hier 2 < 𝜗 < 𝜋, da 𝑥 < 0 und 𝑦 > 0

Fehlerbehandlung mit errno (<cerrno>)

Das Einbinden der Header-Datei <cerrno> mit #include macht der Übersetzungsenheit eine globale Variable
errno vom Typ int bekannt.

Viele Standardfunktionen, insb. die mathematischen Standardfunktionen5 aus <cmath>, verwenden diese Va-
riable zum Zurück-Kommunizieren von aufgetretenen Fehlern an den sie aufrufenden Code.
Die Header-Datei <cerrno> vereinbart zudem einige Konstanten vom Typ int. Per Konvention setzen die
Standardfunktionen bei Auftreten eines Fehlers den Wert der Variable errno auf den numerischen Wert einer
der Konstanten um zu Kommunizieren welche Sorte von Fehler aufgetreten ist.
Die folgende Tabelle listet einen Auszug der zur Verfügung stehenden Konstanten dieser Art auf. Der nume-
rische Wert ist implementierungsabhängig6 .

Konstante Wert Bedeutung

EDOM 33 Math. Funktion wurde mit Argument außerhalb ihres Definitionsbereichs aufgerufen
(Definitionsbereichsfehler)
ERANGE 34 Ergebnis der Funktion außerhalb ihres Wertebereichs oder nicht darstellbar
(Wertebereichsfehler)
EINVAL 22 Funktion wurde mit unzulässigem Argumentwert aufgerufen
EACCES 13 Betriebssystem hat Zugriff auf Resource verweigert (i.d.R. Datei)
ENOENT 2 Betriebssystem meldet, dass angefragte Resource nicht existiert (i.d.R. Datei)
ENOSPC 28 Betriebssystem meldet, dass nicht genügend „Platz“ verfügbar ist (i.d.R. Festplatte voll)

5Auf manchen Plattformen wird statt dem hier beschriebenen errno-Mechanismus nur ein anderer Mechanismus zur Kommu-
nikation von Fehlerkonditionen in mathematischen Standardfunktionen unterstützt. Auf diesen Plattformen wird der errno-
Mechanismus an anderen Stellen verwendet, z.B. bei Fehlern beim Öffnen von Dateien.
6 g++-14.2.1 20241116, Linux (x86_64), CXXFLAGS: -std=c++14

Seite 2 von 9
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

Konstante Wert Bedeutung

EPERM 1 Betriebssystem verweigert Durchführen angefragter Operation wegen mangelnder


Berechtigungen

In der Header-Datei <cstring> steht eine Funktion strerror bereit.


Für 𝑛 eine Variable vom Typ int (z.B. errno), die einen der Werte einer der Konstanten aus <cerror> enthält,
liefert strerror(𝑛 ) einen Wert, der mittels den üblichen Strom-Operatoren z.B. auf cout oder cerr ausgege-
ben werden kann. Der Wert enthält eine Beschreibung des aufgetretenen Fehlers als Text.
Z.B. liefert strerror(EDOM) den Text „Numerical argument out of domain“.
Da die Standardfunktionen errno nur im Fehlerfall setzen und ansonsten unverändert lassen, sollte der Wert
von errno manuell auf 0 zurück gesetzt werden bevor eine Standardfunktion aufgerufen wird nach der errno
auf einen aufgetretenen Fehler getestet wird.
Beispiel. Wir behandeln beim Aufruf einiger math. Standardfunktionen auftretende Fehler.

[Link]

#include <iostream>
#include <iomanip>
#include <cmath>
#include <cerrno>
#include <cstring>

using namespace std;

int main() {
errno = 0;
cout << ”sqrt(-1) = ” << sqrt(-1.) << endl;
cout << setw(2) << errno << ” ” << strerror(errno) << endl;

errno = 0;
cout << ”exp(10'000) = ” << exp(10'000.) << endl;
cout << setw(2) << errno << ” ” << strerror(errno) << endl;

errno = 0;
cout << ”log(0) = ” << log(0.) << endl;
cout << setw(2) << errno << ” ” << strerror(errno) << endl;

errno = 0;
cout << ”atan2(0, 0) = ” << atan2(0., 0.) << endl;
cout << setw(2) << errno << ” ” << strerror(errno) << endl;
}

sqrt(-1) = -nan
33 Numerical argument out of domain
exp(10'000) = inf
34 Numerical result out of range
log(0) = -inf
34 Numerical result out of range
atan2(0, 0) = 0
0 Success

Seite 3 von 9
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel. Wir versuchen die mutmaßlich nicht existente Datei /nonexistent zum Lesen zu öffnen. Tritt insb.
beim Öffnen von Dateien ein Fehler auf, so verwendet die Standardlibrary (und, mittelbar, das Betriebssys-
tem) den errno-Mechanismus um den Fehler an das Programm zu kommunizieren.

errno_fstream.cpp

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

using namespace std;

int main() {
errno = 0;
ifstream inp{”/nonexistent”};
if (!inp)
cout << setw(2) << errno << ” ” << strerror(errno) << endl;
}

2 No such file or directory

Weitere Kontrollstrukturen

do-while-Schleifen

⟨Wiederholanw'⟩ → . . .
| “do” ⟨Anweisung⟩ “while” “(” ⟨Ausdruck⟩ “)” “;”
Die do-while-Schleife do 𝑆 while ( 𝐶 ); verhält sich identisch zu:

{ 𝑆 }
while (𝐶 )
𝑆

Beispiel (Moderner euklidischer Algorithmus). Wir berechnen für 𝑎, 𝑏 ∈ N0 den größten gemeinsamen Teiler
ggT(𝑎, 𝑏) von 𝑎 und 𝑏:

gcd

unsigned int gcd(unsigned int a, unsigned int b) {


if (a == 0) return b;
if (b == 0) return a;

do {
int h = a % b;
a = b;
b = h;
} while (b != 0);

return a;

Seite 4 von 9
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

[Link]

#include <iostream>

using namespace std;

gcd

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

cout << ”ggT(” << a << ”, ” << b << ”) = ”


<< gcd(a, b) << endl;
}

a, b: 143 65
ggT(143, 65) = 13

a, b: 3528 3780
ggT(3528, 3780) = 252

Beispiel. Wir verwenden eine do-while-Schleife um solange den Wert einer Variable x von der Kommandozeile
einzulesen, bis dieser positiv ist.

sqrt_check.cpp

#include <iostream>
#include <cmath>

using namespace std;

int main() {
double x;
do {
cout << ”x: ”;
cin >> x;
} while (x < 0);

cout << ”sqrt(” << x << ”) = ” << sqrt(x) << endl;


}

x: -4
x: -1
x: 2
sqrt(2) = 1.41421

Seite 5 von 9
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

for-Schleifen

⟨Wiederholanw'⟩ → . . .
| “for” “(” [⟨Init'anw'⟩] “;” [⟨Kondition⟩] “;” [⟨Ausdruck⟩] “)”
⟨Init'anw'⟩ → ⟨Ausdruck⟩
| {⟨Dekl'Spez'⟩}+ ⟨Init'Dekl'⟩ {“,” ⟨Init'Dekl'⟩}
Die for-Schleife for ( 𝐼 ; 𝐶 ; 𝑈 ) 𝑆 verhält sich identisch zu:

{
𝐼
while (𝐶 ) {
𝑆
𝑈 ;
}
}

Beispiel (𝑎𝑛 ). Wir berechnen 𝑎𝑛 für 𝑎 ∈ R, 𝑛 ∈ N0 :

pot

double pot(double a, unsigned int n) {


double pot = 1;
for (unsigned int i = 0; i < n; i++)
pot *= a;
return pot;
}

[Link]

#include <iostream>

using namespace std;

pot

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

cout << ”pot(” << a << ”, ” << n << ”) = ” << pot(a, n)


<< endl;
}

a, n: 1.4142 0
pot(1.4142, 0) = 1

a, n: 1.4142 2
pot(1.4142, 2) = 1.99996

Seite 6 von 9
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

(𝑛 )
Beispiel (Binomialkoeffizient). Wir berechnen den Binomialkoeffizient 𝑘 für 𝑛, 𝑘 ∈ N0 mit folgender Rekur-
sionsbeziehung:
( )
𝑛
=1
0
( ) ( )
𝑛 𝑛 𝑛 −𝑖
= ·
𝑖 +1 𝑖 𝑖 +1

binom

unsigned int binom(unsigned int n, unsigned int k) {


unsigned int b = 1;
for (unsigned int i = 1; i <= k; n--, i++)
b = b*n/i;
return b;
}

[Link]

#include <iostream>

using namespace std;

binom

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

cout << ”binom(” << n << ”, ” << k << ”) = ” << binom(n, k)


<< endl;
}

n, k: 6 3
binom(6, 3) = 20

n, k: 7 2
binom(7, 2) = 21

switch-Anweisungen

⟨Anweisung⟩ → . . .
| ⟨Mark'Anw'⟩
| “switch” “(” ⟨Kondition⟩ “)” ⟨Anweisung⟩
⟨Mark'Anw'⟩ → “case” ⟨Ausdruck⟩ “:” ⟨Anweisung⟩
| “default” “:” ⟨Anweisung⟩
Bei Auswertung einer switch-Anweisung wird zunächst die Kondition ausgewertet. Der resultierende Wert
wird implizit in einen Ganzzahldatentyp konvertiert (Zahlbereichserweiterungen wie von bool zu int werden
angewandt).

Seite 7 von 9
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

Enthält die der switch-Anweisung übergebene Anweisung eine Marke der Form case 𝑥 und 𝑥 ist identisch
mit dem Wert der Kondition, so wird die Ausführung an der so markierten Anweisung fortgesetzt.
Enthält die Anweisung ansonsten eine Marke der Form default, so wird die Ausführung an der so markierten
Anweisung fortgesetzt.
Ansonsten wird kein Teil der switch-Anweisung ausgeführt.
Die Ausführung schreitet dann von der angesprungenen Anweisung gewöhnlich voran. Insb. werden dabei
weitere angetroffene Marken ignoriert. Man spricht von fallthrough.
Es dürfen innerhalb der switch-Anweisung keine Variablen vereinbart werden, deren syntaktischer Gültig-
keitsbereich sich über eine der Sprungmarken hinweg erstreckt.
Beispiel. Wir zählen die Anzahl von eingegebenen Zeichen, davon die Anzahl von Zeichen aus {a, A} und
davon die Anzahl von Vorkommnissen von A.

[Link]

#include <iostream>

using namespace std;

int main() {
unsigned int total = 0, a = 0, cap_a = 0;

char c;
while (cin >> c)
switch (c) {
case 'A':
cap_a++;
case 'a':
a++;
default:
total++;
}

cout << ”total, a, cap_a: ” << total << ” ” << a << ” ” << cap_a << endl;
}

aaAbBcCdD
total, a, cap_a: 9 3 1

continue und break

⟨SprungAnweisung⟩ → . . .
| “continue” “;”
| “break” “;”

Die continue-Anweisung ist nur innerhalb des Körpers einer Schleife zulässig. Die break-Anweisung zusätz-
lich auch innerhalb einer switch-Anweisung.
Bei Ausführung einer continue-Anweisung wird die Ausführung direkt vor Ende des Körpers der innersten
enthaltenden Schleife fortgesetzt.

Seite 8 von 9
Skript 05 Programmieren I für Studierende der Mathematik — SoSe 2026

Bei Ausführung einer break-Anweisung wird die Ausführung direkt nach der innersten enthaltenen Schleife
bzw. switch-Anweisung fortgesetzt.

Seite 9 von 9
Skript 06 Programmieren I für Studierende der Mathematik — SoSe 2026

Arrays

Unter einem Array versteht man einen zusammenhängenden Speicherbereich, welcher hinreichend groß ist
um i.A. mehrere Werte eines Datentyps zu speichern.
Arrays haben konstante Länge, sie wird bei Deklaration insb. einer Variable von Array-Typ mit angegeben
und ist Teil des Typs. Z.B. ist „Array von 3 int“ ein Array-Typ.
Ein Array von Typ „Array von 𝑁 𝑇 “ enthält 𝑁 viele Elemente, jeweils vom Typ𝑇 , mit den Indizes 0, 1, . . . , 𝑁 −1.
Array-Typen der Länge 0 sind nicht zulässig.

Vereinbarung von Variablen von Array-Typ

Für 𝑇 𝐷 eine Vereinbarung einer Variable mit Namen 𝐷 vom Typ 𝑇 ist 𝑇 𝐷 [𝑁 ] eine Vereinbarung einer
Variable mit Namen 𝐷 vom Typ „Array von 𝑁 𝑇 “.
Beispiel. Das folgende Codefragment vereinbart eine Variable a vom Typ „Array von 3 int“, eine Variable b
vom Typ „Array von 50 double“ und eine Variable c vom Typ „Array von 2 double“.

int a[3];
double b[50], c[2];

Arrays können mit einer Initialisierungsliste mit geschweiften Klammern mit Werten initialisiert werden.
D.h. für 𝐼 0, 𝐼 1, . . . , 𝐼 𝑁 −1 zulässige Initialisierungen für einen Typ 𝑇 ist {𝐼 0 , 𝐼 1 , . . ., 𝐼 𝑁 −1 } eine zulässige
Initialisierung für den Typ „Array von 𝑁 𝑇 “.
Beispiel. Das folgende Codefragment vereinbart eine Variable a vom Typ „Array von 3 int“ mit den Elementen
3, 2 und 1 in dieser Reihenfolge.

int a[3] = {3, 2, 1};

Es ist auch zulässig nur ein Anfangsstück (auch null) der Elemente explizit zu initialisieren. Der Rest der
Elemente wird dann standard-initialisiert. Für eingebaute Datentypen (insb. also arithmetische Datentypen)
erzeugt die standard-Initialisierung einen unbestimmten Wert (d.h. es wird ein beliebiger Wert „zufällig“ ge-
wählt).
Beispiel. Das folgende Codefragment vereinbart eine Variable a vom Typ „Array von 3 int“ mit den Elementen
1 und 2 und einem weiteren Element von unbestimmten Wert.

int a[3] = {1, 2};

Auf die Angabe der Länge des Arrays darf verzichtet werden, wenn alle Elemente initialisiert werden.
Beispiel. Das folgende Codefragment vereinbart eine Variable a vom Typ „Array von 3 int“ mit den Elementen
3, 2 und 1.

int a[] = {3, 2, 1};

Seite 1 von 4
Skript 06 Programmieren I für Studierende der Mathematik — SoSe 2026

Zugriff auf Array-Elemente

Zugriff auf die Elemente von Ausdrücken von Array-Typ (insb. also Variablen von Array-Typ) ist mit dem
Subscript-Operator [] sowohl lesend, wie auch schreibend möglich.
Das Anwenden der meisten Operatoren (Zuweisung, arith. Operationen, etc.) ist nur für einzelne Komponen-
ten von C-Arrays möglich, C-Arrays können nicht gesamtheitlich z.B. zugewiesen oder kopiert werden.
Der Versuch auf ein Element außerhalb der Grenzen eines Arrays (d.h. zu großer Index) zuzugreifen, ist un-
definiertes Verhalten.
Beispiel. Im folgenden Programm vereinbaren wir zunächst eine Variable x vom Typ „Array von 3 double“
mit Elementen 3,14, 1,41 und 2,72 in dieser Reihenfolge.
Wir addieren dann auf jedes der Elemente des Arrays den Wert des 0ten Elements von x mal zwei, d.h. 6,28
bzw. 18,84.
Danach geben wir alle Elemente des Arrays aus.

array_access.cpp

#include <iostream>

using namespace std;

int main() {
double x[3] = { 3.14, 1.41, 2.72 };

for (unsigned int i = 0; i < 3; i++)


x[i] += x[0] * 2;

for (unsigned int i = 0; i < 3; i++)


cout << x[i] << ” ”;
cout << endl;
}

9.42 20.25 21.56

Arrays als Funktionsparameter

Bei der Übergabe eines Arrays als Funktionsparameter wird nur die Anfangsadresse des Speicherbereichs
übergeben, der den Array darstellt. Insb. die Längeninformation geht hierbei also verloren.
Funktionsparameter von Array-Typ können zwar mit Länge vereinbart werden, die Länge wird jedoch igno-
riert und es lässt sich stets ein Array beliebiger Länge übergeben. Es ist sehr einfach versehentlich undefinier-
tes Verhalten auszulösen indem Arrays falscher Länger an Funktionen übergeben werden.
Zusätzlich haben Funktionsparameter von Array-Typ nicht die bisher übliche Werte-Semantik, bei der Argu-
mente in Kopie an den Körper der Funktion übergeben werden.
Stattdessen wird ein Zeiger auf den Speicherbereich des Arrays übergeben, sodass sich, wmgl. unerwartet, Än-
derungen am Parameter innerhalb des Funktionskörpers auch außerhalb der Funktion auswirken. Man spricht
von Referenz-Semantik.
Beispiel. Wir implementieren eine Funktion, welche insb. einen Array von double unbestimmter Länge ak-
zeptiert. Innerhalb dieser Funktion werden die Elemente des übergebenen Arrays verändert.
Es ist bemerkenswert, dass dabei nicht etwa die Elemente einer Kopie des Arrays nur lokal innerhalb des

Seite 2 von 4
Skript 06 Programmieren I für Studierende der Mathematik — SoSe 2026

Funktionskörpers verändert werden. Stattdessen erfolgt die Übergabe des Arrays an die Funktions mit
Referenz-Semantik – bei Veränderung des Arrays vec innerhalb der Funktion scalar_mult werden tat-
sächlich die Elemente des Arrays vec innerhalb der Funktion main angepasst. vec innerhalb der Funktion
scalar_mult zeigt nur auf den Speicherplatz, der der Variable vec innerhalb der Funktion zugewiesen
wurde.
Wir beobachten zusätzlich, dass für den Parameter vec der Funktion scalar_mult keine Längeninformati-
on zur Verfügung steht. Die Funktion scalar_mult könnte mit Arrays beliebiger Länge aufgerufen werden.
Üblicherweise wird sich – wie auch hier – damit beholfen die Länge der Array-Parameter in zusätzlichen
Parametern zu übergeben.

array_param.cpp

#include <iostream>

using namespace std;

void scalar_mult(unsigned int dim, double x, double vec[]) {


for (unsigned int i = 0; i < dim; i++)
vec[i] *= x;
}

int main() {
double vec[3] = {1, 2, 3};

scalar_mult(3, 2, vec);
for (unsigned int i = 0; i < 3; i++)
cout << vec[i] << endl;
}

2
4
6

Mehrdimensionale Arrays

Da sich die Definition, was eine valide Vereinbarung einer Variable von Array-Typ ist, auf eine wiederum
valide Vereinbarung für einen anderen Typ stützt, kann sie rekursiv angewandt werden. Es können also arrays
vereinbart werden, deren Elemente wiederrum arrays sind.
Bei der Initialisierung eines derart mehrdimensionalen arrays darf nur die „äußerste“ Längenspezifikation
weggelassen werden.
Beispiel (Multiplikation von 3 × 3-Matrix mit Vektor). Für 𝑚 ∈ R3×3, 𝑎 ∈ R3 ist für 𝑏 = 𝑚 · 𝑎 und 𝑖 ∈ {1, 2, 3}:


3
𝑏𝑖 = 𝑎𝑖,𝑗 · 𝑏 𝑗
𝑗=1

Wir implementieren ein Programm, das komponentenweise eine Matrix einliest und in der Variable m vom
Typ „Array von 3 Array von 3 double“ speichert. Danach wird ein Vektor komponentenweise eingelesen
und in der Variable a vom Typ „Array von 3 double“ gespeichert.
Es wird dann 𝑏 berechnet, in b zwischengespeichert, und ausgegeben.

Seite 3 von 4
Skript 06 Programmieren I für Studierende der Mathematik — SoSe 2026

[Link]

#include <iostream>

using namespace std;

int main() {
double m[3][3] = {
{ 0.0, 0.0, 0.0 },
{ 0.0, 0.0, 0.0 },
{ 0.0, 0.0, 0.0 }
};
double a[3], b[3] = {0.0, 0.0, 0.0};

for (unsigned int row = 0; row < 3; row++)


for (unsigned int col = 0; col < 3; col++) {
cout << ”m[” << row << ”][” << col << ”]: ”;
cin >> m[row][col];
}

for (unsigned int row = 0; row < 3; row++) {


cout << ”a[” << row << ”]: ”;
cin >> a[row];
}

for (unsigned int i = 0; i < 3; i++)


for (unsigned int j = 0; j < 3; j++) {
b[i] += m[i][j] * a[j];
}

cout << ”m*a:” << endl;


for (unsigned int row = 0; row < 3; row++)
cout << ” ” << b[row] << endl;
}

m[0][0]: 1
m[0][1]: 2
m[0][2]: 3
m[1][0]: 4
m[1][1]: 5
m[1][2]: 6
m[2][0]: 7
m[2][1]: 8
m[2][2]: 9
a[0]: 1
a[1]: 2
a[2]: 3
m*a:
14
32
50

Seite 4 von 4
Skript 07 Programmieren I für Studierende der Mathematik — SoSe 2026

STL-Vektoren (<vector>)

Die Standard Template Library, d.h. die Standardlibrary von C++, stellt in der Header-Datei <vector> einen
parametrisierten Datentyp vector zur Verfügung. Für einen Typ 𝑇 ist ein Wert vom Typ vector<𝑇 > ein con-
tainer (d.h. ein Behälter), welcher zu jedem Zeitpunkt beliebig viele (auch null) Elemente vom Typ 𝑇 enthalten
kann.
Vorteile ggÜ. der Verwendung von C-Arrays sind insb.:

• die Möglichkeit zum Anpassen der Länge zur Laufzeit des Programms,
• das viel reicherere Angebot von Methoden und Operationen auf Vektoren (die container api), insb. das
Auslesen der Länge und
• dass Werte vom Typ vector<𝑇 > konsistent Werte-Semantik haben.

Im Folgenden bezeichnet 𝑇 einen Datentyp, 𝑛 und 𝑘 < 𝑛 Werte vom Typ vector<𝑇 >::size_type (vorzeichen-
lose ganze Zahlen) 𝑡, 𝑡 0, . . . , 𝑡𝑛−1 Werte vom Typ 𝑇 und 𝑎, 𝑎 ′ Werte vom Typ vector<𝑇 >.

vector<𝑇 > 𝑎 Vereinbart Variable 𝑎 vom Typ vector<𝑇 > und Länge 0
vector<𝑇 > 𝑎 (𝑛 ) Vereinbart Variable 𝑎 vom Typ vector<𝑇 >, Länge 𝑛 und allen
Elementen standard-initialisiert
vector<𝑇 > 𝑎 (𝑛 , 𝑡 ) Vereinbart Variable 𝑎 vom Typ vector<𝑇 >, Länge 𝑛 und allen
Elementen Kopien von 𝑡
vector<𝑇 > 𝑎 = 𝑎′ Vereinbart Variable 𝑎 vom Typ vector<𝑇 > initialisiert als Kopie
vector<𝑇 > 𝑎 (𝑎 ′ ) (Länge und Elemente) von 𝑎 ′
vector<𝑇 > 𝑎 = {𝑡 0 , . . ., 𝑡𝑛−1 } Vereinbart Variable 𝑎 vom Typ vector<𝑇 >, Länge 𝑛 und Elementen
vector<𝑇 > 𝑎 {𝑡 0 , . . ., 𝑡𝑛−1 } Kopien von 𝑡 0, . . . , 𝑡𝑛−1
𝑎 .at(𝑘 ) Liefert Referenz auf Element mit Index 𝑘, bricht Ausführung des
Programms ab 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
𝑎 = 𝑎′ 𝑎 = {𝑡 0 , . . ., 𝑡𝑛−1 } Weist 𝑎𝑖 Kopie zu von 𝑎𝑖′ bzw. 𝑡𝑖 und setzt Länge von 𝑎 auf Länge von
𝑎 ′ bzw. 𝑛
𝑎 == 𝑎 ′ 𝑎 != 𝑎 ′ 𝑎 > 𝑎 ′ Vergleicht 𝑎 und 𝑎 ′ lexikographisch
𝑎 < 𝑎 ′ 𝑎 <= 𝑎 ′ 𝑎 >= 𝑎 ′
𝑎 .size() Liefert Länge von 𝑎
𝑎 .resize(𝑛 ) 𝑎 .resize(𝑛 , 𝑡 ) Setzt Länge von 𝑎 auf 𝑛, zusätzliche Elemente werden
standard-initialisiert bzw. initialisiert als Kopie von 𝑡
𝑎 .empty() Liefert als Wert vom Typ bool ob 𝑎 der leere Vektor1 ist
𝑎 .push_back(𝑡 ) Verlängert 𝑎 um 1, neues Element wird initialisiert als Kopie von 𝑡
𝑎 .pop_back() Verkürzt 𝑎 um eins, löscht letztes Element
𝑎 .clear() Setzt Länge von 𝑎 auf 0, löscht alle Elemente

1 D.h. ein Vektor der Länge 0

Seite 1 von 4
Skript 07 Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel. Wir implementieren ein Programm, das die Dimension 𝑛 eines Vektors 𝑎 ∈ R𝑛 erfragt und danach
die Komponenten des Vektors 𝑎. Wir konstruieren hierbei einen Wert a vom Typ vector<double> und
übergeben diesen als Parameter an eine Funktion. Die Funktion berechnet die euklidische Norm des gege-
benen Vektors und liefert diese als Rückgabewert zurück. Das Hauptprogramm gibt den berechneten Wert
dann aus.

[Link]

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

using namespace std;

double norm(vector<double> x) {
double s = 0;
for (vector<double>::size_type i = 0; i < [Link](); i++)
s += x[i] * x[i];
return sqrt(s);
}

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

vector<double> a(n);
cout << ”a[0] ... a[” << n - 1 << ”]: ”;
for (vector<double>::size_type i = 0; i < [Link](); i++)
cin >> a[i];

cout << ”||a|| = ” << norm(a) << endl;


}

n: 3
a[0] ... a[2]: 3 2 1
||a|| = 3.74166

n: 2
a[0] ... a[1]: 4 3
||a|| = 5

Mehrdimensionale Vektoren sind zulässig und werden konstruiert durch Verwendung eines vector-Typs als
Elementtyp.
Beispiel. Wir implementieren erneut ein Programm zur Multiplikation einer Matrix mit einem Vektor.
Da diesmal Vektoren verwendet werden, kann die Dimension der Matrix und des Vektors (d.h. die Längen
der beteiligten Vektoren) zur Laufzeit des Programms frei festgelegt werden.

matvec_stl.cpp

#include <iostream>
#include <vector>

Seite 2 von 4
Skript 07 Programmieren I für Studierende der Mathematik — SoSe 2026

using namespace std;

vector<double> matvecmult( vector<vector<double>> a


, vector<double> x
) {
unsigned int m = [Link](), n = [Link]();
vector<double> y(m, 0.0);
for (unsigned int i = 0; i < m; i++)
for (unsigned int j = 0; j < n; j++)
y[i] += a[i][j] * x[j];
return y;
}

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

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


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

vector<double> x(n);
cout << ”x: ”;
for (unsigned int j = 0; j < n; j++)
cin >> x[j];

vector<double> y = matvecmult(a, x);


cout << ”a*x: ” << endl;
for (unsigned int i = 0; i < m; i++)
cout << ” ” << y[i] << endl;
}

m, n: 2 3
a:
1 2 3
4 5 6
x:
-1
0
2
a*x:
5
8

Nachteile der Verwendung von STL-Vektoren in numerischen Algorithmen Nachteile im Vergleich


zur Verwendung von Arrays sind:

• I.A. höherer Speicherbedarf, da Speicher für Vektoren oft mit einer Zweierpotenz als Größe zugewiesen
wird, für künftige Vergrößerung
• Matrixelemente werden nicht direkt nebeneinander gespeichert (schlechtere cache coherence)
• Zweifach indirekter Zugriff auf Matrixelemente ist i.d.R. nicht optimiert und daher langsamer

Seite 3 von 4
Skript 07 Programmieren I für Studierende der Mathematik — SoSe 2026

Dennoch ist von der Verwendung von Arrays in neuem Code prinzipiell abzuraten. Speziell für numerische
Verfahren optimierte Vektor- und Matrixtypen stehen in C++ ebenfalls zur Verfügung, werden jedoch später
besprochen.

Seite 4 von 4
Skript 08 Programmieren I für Studierende der Mathematik — SoSe 2026

Zeichen

Einzelne Zeichen sind in C++ Werte vom Typ char.


Die Datentypen signed char und unsigned char stehen ebenfalls zur Verfügung. Es ist implementierungsab-
hängig ob char vorzeichenbehaftet ist, oder nicht. char als Typ ist jedoch unabhängig davon stets verschieden
sowohl von signed char, wie auch von unsigned char.
Auf üblichen Maschinenarchitekturen1 ist char vorzeichenbehaftet.
Jedem Zeichen entspricht ein numerischer Wert. Der genaue Wert ist implementierungsabhängig, es ist jedoch
garantiert, dass die Werte aller Zeichen eines gewissen Minimalzeichensatzes (i.d.R. mind. ASCII) alle positiv
sind.

2016 3016 4016 5016 6016 7016


016 ␠ 0 @ P ` p
116 ! 1 A Q a q
216 " 2 B R b r
316 # 3 C S c s
416 $ 4 D T d t
516 % 5 E U e u 30 40 50 60 70 80 90 100 110 120
616 & 6 F V f v 0 ( 2 < F P Z d n x
716 ' 7 G W g w 1 ) 3 = G Q [ e o y
816 ( 8 H X h x 2 ␠ * 4 > H R \ f p z
916 ) 9 I Y i y 3 ! + 5 ? I S ] g q {
A16 * : J Z j z 4 " , 6 @ J T ^ h r |
B16 + ; K [ k { 5 # - 7 A K U _ i s }
C16 , < L \ l | 6 $ . 8 B L V ` j t ~
D16 - = M ] m } 7 % / 9 C M W a k u ␡
E16 . > N ^ n ~ 8 & 0 : D N X b l v
F16 / ? O _ o ␡ 9 ' 1 ; E O Y c m w

Hexadezimal Dezimal

ASCII Zeichentabelle (Auszug)

Zeichen-Literale

⟨Char'Literal⟩ → “'” ⟨Zeichen⟩ “'”


⟨Zeichen⟩ → ⟨Einf'Zeichen⟩ | ⟨Einf'Ersatz⟩ | ⟨Okt'Zeichen⟩ | ⟨Hex'Zeichen⟩
⟨Einf'Ersatz⟩ → “\'” | “\"” | “\?” | “\\” | “\a” | “\b” | “\f” | “\n” | “\r” | “\t” | “\v”
⟨Okt'Zeichen⟩ → “\” /[0-7]/ [/[0-7]/] [/[0-7]/]
⟨Hex'Zeichen⟩ → “\x” {/[0-9a-fA-F]/}+
Mit ⟨Einf’Zeichen⟩ ist jedes Mitglied des imlementierungsabhängigen Minimalzeichensatzes (i.d.R. mind.
ASCII) gemeint, mit Ausnahme des einfachen Anführungszeichens “'”, dem Backslash “\” und des
Zeilenumbruchs.
Die Ersatzdarstellungen ⟨Okt’Zeichen⟩ und ⟨Hex’Zeichen⟩ verwenden direkt den numerischen Wert des ge-
wünschten Zeichens. Die anderen Ersatzdarstellungen haben die folgende Bedeutung:

1 g++-14.2.1 20241116, Linux (x86_64), CXXFLAGS: -std=c++14

Seite 1 von 6
Skript 08 Programmieren I für Studierende der Mathematik — SoSe 2026

“\'” “\"” Einfaches Anführungszeichen bzw. doppeltes Anführungszeichen bzw. Fragezeichen bzw.
“\?” “\\” Backslash
“\a” Bewirkt bei Ausgabe auf Standardausgabe einen hörbaren Ton
“\b” Verschieben der Position der nächsten Ausgabe um ein Zeichen zurück (backspace)
“\f” „Seitenvorschub“
“\n” Zeilenumbruch
“\r” „Wagenrücklauf“, auf manchen Betriebssystemen und in manchen Netzwerkprotokollen
(z.B. für E-Mail) ist “\r\n” die korrekte Darstellung eines Zeilenumbruchs
“\t” Tabulator
“\v” „Vertikaler Tabulator“

Beispiel. Das folgende Programmfragment weist einer Variable c vom Typ char diverse Zeichenliterale zu.

char c;
c = 'A'; c = '5'; c = '*'; c = ' ';
c = '\n'; c = '\''; c = '\\';
c = '\0'; c = '\40'; c = '\x20';

Klassifizierungs- und Umwandlungsfunktionen für Zeichen (<locale>)

In der Header-Datei <locale> stellt die Standardlibrary insb. diverse Funktionen zur Klassifizierung und Um-
wandlung von Zeichen zur Verfügung. Diese sind jeweils abhängig vom sog. Locale einer Sammlung von
Parametern welche die lokalen, insb. sprachlichen, Begebenheiten abzubilden versuchen.
Mit dem Standardkonstruktor kann ein Wert vom Typ locale erzeugt werden, welcher die angesprochenen
Parameter enthält. Es wird hierbei ein geeignetes „default“-locale gewählt, welches zumindest ASCII-Zeichen
sinnvoll verarbeiten kann.
Wird dem Konstruktor stattdessen ein String übergeben, kann auch ein anderer Parametersatz gewählt wer-
den. Der leere String (z.B. also locale(””)) liefert eine vom Betriebssystem bezogene Voreinstellung.
Die folgenden Funktionen erhalten jeweils einen Wert vom Typ locale (z.B. also locale()) als zweiten Pa-
rameter, hier 𝑙. Ansonsten bezeichnet 𝑐 einen Wert vom Typ char.

isspace(𝑐 , 𝑙 ) Liefert bool, ob 𝑐 Zwischenraumzeichen


isprint(𝑐 , 𝑙 ) druckbares Zeichen
iscntrl(𝑐 , 𝑙 ) Steuerzeichen
isupper(𝑐 , 𝑙 ) Großbuchstabe
islower(𝑐 , 𝑙 ) Kleinbuchstabe
isalpha(𝑐 , 𝑙 ) Buchstabe
isdigit(𝑐 , 𝑙 ) Dezimalziffer (/[0-9]/)
ispunct(𝑐 , 𝑙 ) druckbares Zeichen, nicht jedoch Leerzeichen, Buchstabe oder Ziffer
isxdigit(𝑐 , 𝑙 ) Hexadezimalziffer (/[0-9a-fA-F]/)
isalnum(𝑐 , 𝑙 ) Buchstabe oder Dezimalziffer (/[0-9a-zA-Z]/)

Seite 2 von 6
Skript 08 Programmieren I für Studierende der Mathematik — SoSe 2026

isgraph(𝑐 , 𝑙 ) druckbares Zeichen, nicht jedoch Leerzeichen


isblank(𝑐 , 𝑙 ) „horizontales“ Zwischenraumzeichen
tolower(𝑐 , 𝑙 ) Liefert char, zu 𝑐 korrespondierer Klein- bzw. Großbuchstabe
toupper(𝑐 , 𝑙 )

Beispiel. Wir implementieren ein kleines Programm, das eingegebene Buchstaben klassifiziert.

classify_char.cpp

#include <iostream>
#include <iomanip>
#include <locale>

using namespace std;

int main() {
char c, newl;
while (cout << ”Zeichen: ”, cin >> noskipws >> c >> newl) {
cout << ”Code: ” << setw(3) << static_cast<int>(c);

if (iscntrl(c, locale()))
cout << ”, Steuerzeichen”;
if (isspace(c, locale()))
cout << ”, Zwischenraumzeichen”;
if (ispunct(c, locale()))
cout << ”, Interpunktion”;
if (isdigit(c, locale()))
cout << ”, Ziffer”;
if (isalpha(c, locale()))
cout << ”, Buchstabe”;

cout << endl;


}
}

Zeichen: ⇆
Code: 9, Steuerzeichen, Zwischenraumzeichen
Zeichen:
Code: 32, Zwischenraumzeichen
Zeichen: .
Code: 46, Interpunktion
Zeichen: 0
Code: 48, Ziffer
Zeichen: A
Code: 65, Buchstabe
Zeichen: a
Code: 97, Buchstabe
Zeichen:

Seite 3 von 6
Skript 08 Programmieren I für Studierende der Mathematik — SoSe 2026

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)


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 𝑐

Seite 4 von 6
Skript 08 Programmieren I für Studierende der Mathematik — SoSe 2026

𝑠 +𝑡 𝑠 + 𝑠𝐶 𝑠 +𝑐 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(𝑐 , 𝑖 )
𝑠 .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.

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

Seite 5 von 6
Skript 08 Programmieren I für Studierende der Mathematik — SoSe 2026

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«

Methode? b
wort1 wort2 wort3
»«

Methode? c
wort1 wort2 wort3
» wort1 wort2 wort3 «

Seite 6 von 6
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

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. buffered1 . Bei Ausgabeströmen kann, durch Anwenden des IO-Manipulators
unitbuf2 , 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 3
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.

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.

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


2 IO-Manipulatoren unitbuf und nounitbuf aus Header-Datei <ios>
3 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 1 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

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 Ganzzahltyp4 , 𝑠𝐶 eine C-Zeichenkette vom Typ char[] mit Länge5 ≥ 𝑛, 𝑝 ein Wert von vorzei-
chenlosem Ganzzahltyp6 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.

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.

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

Seite 2 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

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 (𝑠 ) ….

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.

Seite 3 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

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.

Dateisysteme

Eine der Aufgaben eines Betriebsysstems ist es, den auf der Maschine laufenden Programmen Zugriff zum Da-
teisystem der Maschine zu geben. Ein Dateisystem beschreibt die Struktur in der Daten im Sekundärspeicher
der Maschine i.d.R. dauerhaft abgelegt und abgerufen werden können. Beispiele für derartigen Sekundärspei-
cher sind mechanische Festplatten, Solid-State-Drives (SSDs) ohne bewegliche Teile, auf die Hauptplatine des
Rechners aufgelötete persistente Speicher-Chips, aber auch USB-Sticks und SD-Karten.
Es gibt eine gewisse Varianz bzgl. der Schnittstellen, die Betriebssysteme zum Zugriff auf die ihm bekannten
Dateisysteme laufenden Programmen zur Verfügung stellen. Das Institute of Electrical and Electronics Engi-
neers (IEEE) spezifiziert jedoch einige übliche Schnittstellen in einer reihe von Standards, gemeinschaftlich
das Portable Operating System Interface (POSIX). Für uns relevant ist hierbei IEEE 1003.1 (in der Version
IEEE 1003.1-2024). Betriebssysteme, die die in POSIX spezifizierten Schnittstellen anbieten, nennt man POSIX-
kompatibel. Nur sehr wenige Betriebssysteme, die gemeinschaftlich einen verschwindend geringen Marktan-
teil haben, sind wirklich vollständig POSIX-kompatibel. Es bietet jedoch quasi jedes Betriebssystem entwe-
der eine zumindest hinreichend vollständige Implementierung der POSIX-Schnittstellen oder zumindest eine
optional aktivierbare Emulationsschicht an. Im Effekt lassen sich Programme die die POSIX-Schnittstellen
verwenden auf den allermeisten Betriebssystemen prinzipiell ausführen.
Wir beschreiben im Folgenden die Struktur eines Dateisystems, wie sie von den in POSIX zur Verfügung
stehenden Schnittstellen impliziert wird. Wenn also im Folgenden z.B. von Dateien die Rede ist, so bezieht
sich das auf das folgende Verständnis dessen, was eine Datei ist.
Ein Dateisystem ist eine Verzeichnisstruktur. Eine Verzeichnisstruktur ist ein gerichteter Graph von Objekten,
d.h. eine Menge von Objekten zusammen mit einer Nachfolgerrelation, die jedem Objekt eine Menge von un-
tergeordneten Objekten zuordnet. Die Kanten des Graphs tragen zusätzliche Informationen. Wir bezeichnen
sie als Verzeichniseinträge. D.h. zusammen damit, dass einem Objekte eine Menge von untergeordneten Ob-
jekten zugeordnet wird, darf diese Zuordnung pro untergeordnetem Objekt noch zusätzliche Informationen
tragen. Insb. tragen die Verzeichniseinträge die Namen der untergeordneten Objekte.
Objekte in der Verzeichnisstruktur können dadurch adressiert werden, dass ein Pfad durch die Struktur an-
gegeben wird. Von einem Objekt der Verzeichnisstruktur aus betrachtet besteht ein derartiger Pfad aus einer
Folge von Objektnamen, die jeweils angeben welcher Zuordnung zu einem untergeordneten Objekt gefolgt
werden soll.
Beispiel. Hat das Objekt 𝑂 1 von dem wir ausgehen also zwei Verzeichniseinträge mit Namen pro1 und pro2
und das Objekt das wir mit dem Namen pro1 dem Objekt 𝑂 1 untergeordnet haben, einen weiteren Ver-
zeichniseintrag mit Namen [Link] für ein Objekt 𝑂 2 , so addressiert der Pfad pro1 [Link] das
Objekt 𝑂 2 ausgehend von 𝑂 1 . Wir sagen der Pfad sei relativ zu 𝑂 1 .
Für eine graphische Darstellung dieser Situation, siehe Abbildung 1.
Es gibt stets ein Quellverzeichnis (auch Wurzelverzeichnis, root directory), ein Objekt in der Verzeichnisstruktur,
das keinem anderen Objekt untergeordnet ist.
Jedes aktuell laufende Programm hat ein current working directory (cwd), ein Verzeichnis in der Verzeichnis-
struktur, ausgehend von welchem Objekte für Zwecke des Programms adressiert werden. Wenn ein Programm

Seite 4 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

home
..
.
..
.

𝑂1
pro1 pro2

[Link]

𝑂2

Verzeichnis
Datei

Abbildung 1: Ein Verzeichnis 𝑂 1 mit zwei Unterverzeichnissen und einer Datei 𝑂 2 unterhalb eines dieser
Unterverzeichnisse

auf eine Datei (oder ein Verzeichnis) zugreifen soll, muss hierfür einer geeigneten Funktion der Standardlibra-
ry ein Pfad zu jener Datei bzw. jenem Verzeichnis übergeben werden. Diese Pfade sind eine Folge von Namen
von Verzeichniseinträgen, abgetrennt voneinander durch den directory separator /. Pfade werden stets relativ
zum current working directory interpretiert, außer sie beginnen mit / in welchem Fall sie stattdessen relativ
zum Quellverzeichnis interpretiert werden. Der Pfad / addressiert insofern ausgehend von jedem Objekt der
Verzeichnisstruktur stets das Quellverzeichnis.
Objekte der Verzeichnisstruktur können von unterschiedlichen Typen sein. Wir betrachten nur Verzeichnisse
(folders) und Dateien (files).
Ein Verzeichnis fungiert ausschließlich als Knoten der Verzeichnisstruktur. Verzeichnissen können Objekte
der Verzeichnisstruktur untergeordnet werden. Jedes Verzeichnis ist sich selbst mit dem Namen . untergeord-
net. Zusätzlich enthält jedes Verzeichnis einen Eintrag mit Namen .. für das ihm übergeordnete Verzeichnis.
Das übergeordnete Verzeichnis des Quellverzeichnis ist das Quellverzeichnis selbst. Ansonsten ist das Quell-
verzeichnis genau allen Verzeichnissen übergeordnet, die dem Quellverzeichnis untergeordnet sind, etc.
Dateien sind stets Blätter der Verzeichnisstruktur, haben also nie untergeordnete Objekte. Dateien haben,
unabhängig von der Verzeichnisstruktur, einen Inhalt. Der Inhalt einer Datei ist eine Folge von Zeichen.

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.

Seite 5 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

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
Kontrollzeichen7 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:

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)

7 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 6 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

𝑠 .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};
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;

Seite 7 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

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
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>

Seite 8 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026

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 9 von 9
Skript 10 Programmieren I für Studierende der Mathematik — SoSe 2026

Zeiger

Variablen sind in C++ benannte Stellen im Speicher. Der Speicher besteht aus Folgen von jeweils direkt anei-
nendar angrenzenden Bytes1 . Ein Byte ist die kleinste addressierbare Einheit von Speicher; jedes Byte besitzt
eine eindeutige Adresse. Adressen sind vorzeichenlose ganze Zahlen.
Die Darstellung eines Wertes, der z.B. in einer Variable gespeichert werden kann, kann i.A. beliebig viele
Bytes wert an Speicher benötigen. Die Anzahl von benötigten Bytes hängt vom Typ des Wertes ab und kann
mit dem sizeof-Operator bestimmt werden, sowohl für einen konkreten Wert, wie auch für einen Typ.
Für jeden Datentyp 𝑇 existiert ein Typ „Zeiger auf 𝑇 “. Werte vom Typ „Zeiger auf 𝑇 “ sind Speicheradressen.
Für 𝑇 𝐷 eine Vereinbarung einer Variable 𝐷 vom Typ 𝑇 ist 𝑇 *𝐷 eine Vereinbarung einer Variable 𝐷 vom
Typ „Zeiger auf 𝑇 “.
Für 𝑥 ein Wert vom Typ 𝑇 liefert die Anwendung des Adressoperators2 & auf 𝑥, d.h. &𝑥 die Adresse des ersten
Bytes der Darstellung von 𝑥 im Speicher als Wert vom Typ „Zeiger auf 𝑇 “.
Für 𝑎 eine Adresse vom Typ „Zeiger auf 𝑇 “ liefert eine Anwendung des Indirektionsoperators3 * auf 𝑎, d.h. *𝑎
den Wert vom Typ 𝑇 , dessen Darstellung an Stelle 𝑎 im Speicher beginnt.
Der Wert der Konstante nullptr kann implizit konvertiert werden zu einem Wert vom Typ „Zeiger auf 𝑇 “,
für 𝑇 beliebig. Der resultierende Wert ist ein null-pointer. Zu versuchen den Indirektionsoperator * auf einen
null-pointer anzuwenden ist undefiniertes Verhalten.
Zeiger können implizit zu Werten vom Typ bool konvertiert werden, also insb. als Konditionen von if-
Abfragen verwendet werden. Nur genau null-pointer werden konvertiert zu false.
Zeiger vom gleichen Typ, auch null-pointer, können mit == und != verglichen werden.
Eine Anwendung des Indirektionsoperators, also für 𝑥 ein Wert vom Typ „Zeiger auf 𝑇 “: ∗x, darf auf der linken
Seite einer Zuweisung stehen. Es wird dann die Speicherstelle deren Adresse 𝑥 enthält angepasst.
Beispiel. Wir vereinbaren eine Variable x vom Typ int. Wir vereinbaren zudem eine Variable xp vom Typ
„Zeiger auf int“. Wir weisen xp die Adresse, an der die Variable x ihren Wert speichert, zu.
Wir wenden den Indirektionsoperator auf xp an um den Wert vom Typ int (da xp vom Typ „Zeiger auf int“
ist) zu erhalten, dessen Darstellung an der Stelle im Speicher beginnt, auf die xp zeigt. Da xp zu diesem
Zeitpunkt die Adresse enthält ab der die Variable x ihren Wert speichert, liefert der Ausdruck *xp den
aktuell in x gespeicherten Wert.

[Link]

#include <iostream>

using namespace std;

int main() {
int x = 3;
int *xp;

xp = &x;

x = 17;

1 Erst in späteren Versionen des Standards gibt es einen Datentyp, der genau einem Byte in diesem Sinne entspricht. Wir verwenden

behelfsweise stattdessen unsigned char.


2 &𝑥 wird gelesen als „Adresse von 𝑥“
3 Man sagt *𝑎, „dereferenziert 𝑎“

Seite 1 von 5
Skript 10 Programmieren I für Studierende der Mathematik — SoSe 2026

cout << (*xp) << endl;


}

17

··· 0 0 0 0 0 0 0 200 ··· 0 0 0 17 ···


99 100 101 102 103 104 105 106 107 108 199 200 201 202 203 204
xp x

Byte
103 Speicheradresse

Schematische Darstellung der Situation direkt vor Ausgabe

Beispiel. Wir übergeben einen Zeiger an eine Funktion und verwenden ihn dort um den Inhalt der Variable,
auf die der Zeiger verweist, anzupassen.

pointer_write.cpp

#include <iostream>

using namespace std;

void modify(double* xp) {


*xp += 2;
}

int main() {
double x = 4;
cout << ”Davor: ” << x << endl;
modify(&x);
cout << ”Danach: ” << x << endl;
}

Davor: 4
Danach: 6

Die Vereinbarung einer Variable 𝐷 als Zeiger „bindet schwächer“ als die Vereinbarung als C-Array.
Beispiel. Die folgenden Deklarationen vereinbaren xps als Variable vom Typ „Array von 3 Zeigern auf double“
und xsp als Variable vom Typ „Zeiger auf Array von 3 double“.

double *xps[3];
double (*xsp)[3];

Anmerkung. Die mathematischen Standardfunktionen frexp und modf erwarten als zweiten Parameter je-
weils einen Wert vom Typ „Zeiger auf int“ bzw. „Zeiger auf 𝑇 “ mit 𝑇 ein Gleitpunktdatentyp.

Seite 2 von 5
Skript 10 Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel (frexp). Wir lesen einen Wert 𝑥 vom Typ double von der Standardeingabe ein und zerlegen ihn in
Werte 𝑦 vom Typ double und 𝑛 vom Typ int mit 𝑥 = 𝑦 · 2𝑛 und 1/2 ≤ |𝑦| < 1.

[Link]

#include <iostream>
#include <cmath>

using namespace std;

int main() {
double x;
cout << ”x: ”; cin >> x;

double y;
int n;
y = frexp(x, &n);

cout << ”x=” << x


<< ” y=” << y
<< ” n=” << n
<< endl;
}

x: 6
x=6 y=0.75 n=3

Zeigerarithmetik

C-Arrays sind im Speicher zusammenhängend, d.h. für 𝑥 ein Wert vom Typ „Array von 𝑁 𝑇 “ und 0 ≤ 𝑛 < 𝑁
folgt auf die Darstellung von 𝑥 [𝑛 ] im Speicher unmittelbar danach die Darstellung von 𝑥 [𝑛 + 1].

··· ···
99 100 101 102 103 104 105 106 107 108 109 110 111 112

x[0] x[1] x[2]

Schematische Darstellung eines Wertes vom Typ „Array von 3 𝑇 “ im Speicher

C-Arrays vom Typ „Array von 𝑁 𝑇 “ können implizit konvertiert werden zu Werten vom Typ „Zeiger auf 𝑇 “.
Für 𝑥 ein Wert vom Typ „Array von 𝑁 𝑇 “ liefert diese Konvertierung die Adresse des ersten Elements von 𝑥,
also &(𝑥 [0]).
Wird ein Parameter einer Funktion vereinbart vom Typ „Array von 𝑁 𝑇 “, so wird insb. die Länge 𝑁 ignoriert
und der Parameter stattdessen vereinbart vom Typ „Zeiger auf 𝑇 “.
Der Subscript-Operator [] darf neben Klassen für die er explizit überladen wurde (z.B. vector) stets auch
angewandt werden auf Werte vom Typ „Array von 𝑁 𝑇 “, jedoch auch Werte vom Typ „Zeiger auf 𝑇 “.
Die Ausdrücke 𝑥 [𝑖 ] und *(𝑥 +𝑖 ) sind für 𝑥 vom Typ „Zeiger auf 𝑇 “ oder „Array von 𝑁 𝑇 “ per Definition
identisch.
Anmerkung. Die vorherige Aussage impliziert bzgl. Addition bzw. Subtraktion zwischen Zeigern und Zahlen,
dass für 𝑥 ′ die Speicheradresse, die ein Zeiger 𝑥 vom Typ „Zeiger auf 𝑇 “ enthält und 𝑠 dem Ergebnis von

Seite 3 von 5
Skript 10 Programmieren I für Studierende der Mathematik — SoSe 2026

sizeof(𝑇 ) (also der Länge der Darstellung von Werten vom Typ 𝑇 in Bytes), die Adresse, die der Zeiger
𝑥 +𝑛 enthält gegeben ist durch 𝑥 ′ + 𝑠 · 𝑛.

Kommandozeilenargumente

Es ist zulässig das Hauptprogramm, d.h. die Funktion main, statt als Funktion ohne Parameter als Funktion
von zwei Parametern zu vereinbaren. Die Typen der Parameter müssen dann int und char *argv[] sein, d.h.
eine ganze Zahl und ein C-Array unbestimmter Länge von C-Zeichenketten. Per Konvention nennen wir den
ersten Parameter argc und den zweiten Parameter argv.
Wird das übersetze Programm [Link] dann auf der Konsole aufgerufen als ./[Link] arg1 arg2 . . . arg𝑛−1 so
wird das Hauptprogramm aufgerufen mit den Parametern 𝑛 und {"[Link]", arg1 , arg2 , . . ., arg𝑛−1 }.
Beispiel. Wir implementieren ein Programm, dass die ihm übergebenen Kommandozeilenargumente auf der
Standardausgabe ausgibt.

[Link]

#include <iostream>

using namespace std;

int main(int argc, char *argv[]) {


for (int i = 1; i < argc; i++)
cout << ”Argument ” << i << ”: ”
<< argv[i]
<< endl;
}

Wir rufen das Programm auf als ./[Link] arg1 arg2 arg3:

Argument 1: arg1
Argument 2: arg2
Argument 3: arg3

Beispiel. Wir verwenden istringstream um auf der Kommandozeile übergebene Argumente als Werte vom
Typ double zu interpretieren.

cli_parse.cpp

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

using namespace std;

int main(int argc, char* argv[]) {


if (argc != 3) {
cerr << ”Usage: ./[Link] <N> <M>” << endl;
return 2;
}

double n, m;
istringstream cli_arg{argv[1]};

Seite 4 von 5
Skript 10 Programmieren I für Studierende der Mathematik — SoSe 2026

cli_arg >> n;
cli_arg.str(argv[2]);
cli_arg.seekg(0);
cli_arg >> m;

cout << ”n=” << n


<< ” m=” << m << endl
<< ”n*m=” << n*m << endl;
}

./[Link] 12 0.25

n=12 m=0.25
n*m=3

Seite 5 von 5
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

Dynamische Speicherzuweisung

Die Lebensdauer einer Variable folgte bisher immer ihrem syntaktischen Gültigkeitsbereichs. D.h. dass der
Speicher, den eine Variable „benennt“, freigegeben wird, sobald der syntaktische Gültigkeitsbereich jener Va-
riable endet. Der in der Variable gespeicherte Wert wird zu diesem Zeitpunkt verworfen und steht dann nicht
mehr zur Verfügung. Selbst dann nicht, wenn die Adresse des Wertes der Variable in einer Variable von Zei-
gertyp gespeichert wurde und die Lebensdauer dieses Zeigers noch nicht geendet hat.
Es ist jedoch auch möglich in C++ dynamisch Speicher zuzuweisen. Hierbei wird das Betriebssystem ange-
wiesen an geeigneter Stelle hinreichend viel Platz für einen Wert von gegebenem Typ zu schaffen und dem
Programm wird ein Zeiger auf diese Stelle im Speicher zur Verfügung gestellt. Derart zugewiesener Speicher
wird erst wieder freigegeben, sobald das Betriebssystem explizit angewiesen wird den Speicher freizugeben.
Es können hierbei Speicherlecks auftreten. Wenn das Programm Speicher dynamisch zuweist und nie expli-
zit frei gibt, wird er auch nicht implizit freigegeben. Wird viel Speicher zugewiesen kann dies dazu führen,
dass für weitere Zuweisungen, oder andere Aktivitäten und Programme auf der selben Maschine, nicht mehr
genügend Speicher zur Verfügung steht.
⟨NewExpr'⟩ → [“::”] “new” (⟨Typ⟩ | “(” ⟨Typ⟩ “)”) [⟨Init'⟩]
Ein Ausdruck der Form new 𝑇 für 𝑇 ein beliebiger Typ weist dynamisch hinreichend viel Speicher zu um dort
einen Wert vom Typ 𝑇 speichern zu können. Der Ausdruck nimmt als Wert einen Zeiger vom Typ „Zeiger auf
𝑇 “ an, der auf den Anfang des zugewiesenen Speicherbereichs zeigt.
Es kann, analog zur Vereinbarung einer Variablen, mit ⟨Init’⟩ angegeben werden, wie der zugewiesen Spei-
cherbereich initialisiert werden soll.
Der new-Operator kann von Klassen überladen werden, die Implementierung der Klasse also selbst spezifizie-
ren wie Speicher für neue Objekte der Klasse dynamisch zugewiesen werden soll. Ist dies im Einzelfall nicht
erwünscht, kann dem new-Operator der scope resolution operator :: vorgestellt werden.
⟨Del'Expr'⟩ → [“::”] “delete” [“[]”] ⟨Ausdruck⟩
Ein Ausdruck der Form delete 𝑥 für 𝑥 ein Wert vom Typ „Zeiger auf 𝑇 “ gibt den dynamisch zugewiesenen
Speicher, auf dessen Anfang 𝑥 zeigt, wieder frei. Gdw. der Speicherbereich unter Angabe eines C-Array-Typs
beim new-Operator zugewiesen wurde, muss die Variante delete[] verwendet werden.
Wurde ::new verwendet um den Speicher zuzuweisen, muss er mit ::delete wieder freigegeben werden.
Beispiel. Wir weisen innerhalb einer Funktion dynamisch genügend Speicher zu um einen Wert vom Typ
double darstellen zu können. Wir geben einen Zeiger auf diesen Speicherbereich aus der Funktion zurück;
der dort gespeicherte Wert bleibt auch außerhalb der Funktion verfügbar, bis er explizit freigegeben wird.
Würden wir, statt Speicher dynamisch zuzuweisen, einen Zeiger auf eine in der Funktion vereinbarte Va-
riable vom Typ double zurückgeben, wäre nicht garantiert, dass der Wert im Hauptprogramm noch zur
Verfügung steht.

alloc_double.cpp

#include <iostream>

using namespace std;

double* alloc() {
double *xp;
xp = new double{17.3};
return xp;

Seite 1 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

int main() {
double *xp = alloc();
cout << *xp << endl;
delete xp;
}

17.3

Referenzen

Referenzen sind zusätzliche Namen für Variablen. Wie Zeiger erlauben sie (i.A. auch schreibenden) Zugriff
auf Stellen im Speicher, die zu Variablen gehören, außerhalb derer syntaktischer Gültigkeitsbereiche wir uns
gerade befinden.
Unterschiede zwischen Zeigern und Referenzen sind:

• Referenzen können nach ihrer Initialisierung nicht verändert werden, sodass sie auf eine andere Stelle
im Speicher zeigen.
• Es gibt keine „Null-Referenzen“; jede Referenz „zeigt“ nach ihrer Initialisierung auf eine tatsächliche
Variable des Programms.
• Im Gegensatz zu Zeigern sind Referenzen selbst keine Werte. D.h. es gibt, ohne weiterführende Me-
thoden, keine C-Arrays oder Vektoren von Referenzen, keine Zeiger auf Referenzen und auch keine
Referenzen auf Referenzen
• Das Bilden und das „Folgen“ von Referenzen zu den referenzierten Werten sind beide transparent. Ei-
ne Verwendung des Adressoperators & oder des Indirektionsoperators * ist für Referenzen also nicht
notwendig.

Im Allgemeinen ist eine Verwendung von Referenzen ggÜ. Zeigern stets vorzuziehen.
Für jeden Datentyp 𝑇 existiert der zugehörige Referenztyp „Referenz auf 𝑇 “. Für 𝑇 𝐷 eine Vereinbarung einer
Variable 𝐷 vom Typ 𝑇 ist 𝑇 & 𝐷 eine Vereinbarung einer Referenz 𝐷 vom Typ „Referenz auf 𝑇 “.
Werte vom Typ 𝑇 können stets implizit konvertiert werden zu einer Referenz vom Typ „Referenz auf 𝑇 “.
Ebenso können Referenzen vom Typ „Referenz auf 𝑇 “ implizit konvertiert werden zu Werten vom Typ 𝑇 . Die
implizite Konvertierung einer Referenz zu einem Wert liefert den in der referenzierten Variable gespeicherten
Wert.
Referenzen können auf der linken Seite von Zuweisungen verwendet werden. Es wird dann der „hinter“ der
Referenz befindliche, also der in der referenzierten Variable gespeicherte, Wert angepasst.
Beispiel. Wir vereinbaren eine Variable x vom Typ int. Wir vereinbaren zudem eine Referenz xr vom Typ
„Referenz auf int“ und initialisieren sie mit dem Ergebnis der impliziten Konvertierung der Variable x zu
einer Referenz. Man sagt xr referenziert x.
Wir ändern dann den in x gespeicherten Wert.
Wenn wir danach versuchen xr auszugeben, wird die Referenz xr, da Sie vom Typ „Referenz auf int“ ist,
hierfür implizit konvertiert zu einem Wert vom Typ int. Es ergibt sich hierbei der Wert der aktuell in x
gespeichert ist, also 17.

Seite 2 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

[Link]

#include <iostream>

using namespace std;

int main() {
int x = 3;
int& xr = x;

x = 17;

cout << xr << endl;


}

17

Referenzparameter/Rückgabe von Referenzen

Referenzen dürfen sowohl als Parameter, wie auch als Rückgabetypen von Funktionen vereinbart werden.
Referenzparameter, also Parameter von Referenztyp, werden bei Aufruf der Funktion (im Gegensatz zu Wer-
teparametern) nicht kopiert, sondern es wird der Funktion eine Referenz auf den als Argument eingesetzten
Wert zur Verfügung gestellt. Änderungen an dem „hinter“ der Referenz gespeicherten Wert wirken sich also
auch außerhalb des Körpers der Funktion aus.
Beispiel. Wir übergeben eine Referenz auf die Variable x (deklariert in main) als Argument an die Funktion
modify. Die Verwendung einer Referenz auf der linken einer Zuweisung verändert den referenzierten Wert,
also in diesem Fall den Wert der in der Variable x gespeichert ist.

reference_write.cpp

#include <iostream>

using namespace std;

void modify(double& xr) {


xr += 2;
}

int main() {
double x = 4;
cout << ”Davor: ” << x << endl;
modify(x);
cout << ”Danach: ” << x << endl;
}

Davor: 4
Danach: 6

Beispiel (swap). Wir vereinbaren eine Funktion swap welche, durch die Verwendung zweier Referenzparame-
ter, die in den referenzierten Variablen gespeicherten Werte miteinander vertauscht.

Seite 3 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

[Link]

#include <iostream>

using namespace std;

void swap(double& x, double& y) {


double h = y;
y = x; x = h;
}

int main() {
double x = 3, y = 7;

cout << ”x=” << x << ” y=” << y << endl;

swap(x, y);

cout << ”x=” << x << ” y=” << y << endl;


}

x=3 y=7
x=7 y=3

Konstanten

Konstanten sind Variablen, die eine spezielle Markierung tragen, welche verursacht, dass sie1 nach Initialisie-
rung nicht mehr verändert werden können.
Für 𝑇 ein Typ ist const 𝑇 der entsprechende Typ „Konstante 𝑇 “. Wird eine Variable mit einem Typ der Form
„Konstante 𝑇 “ vereinbart, so ist diese Variable eine Konstante.
Beispiel. Der Versuch eine als konstant vereinbarte Variable nach ihrer Initialisierung noch zu verändern, wird
vom Compiler abgelehnt.

const_change.cpp

int main() {
const double x = 7; // Initialisation

x = 3; // Attempt to change constant


}

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


const_change.cpp:4:5: error: assignment of read-only variable ‘x’
4 | x = 3; // Attempt to change constant
| ~~^~~

Es empfiehlt sich stets Konstanten statt Variablen zu verwenden, wo immer das möglich ist.

1 im gewöhnlichen Programmfluss – mit etwas Aufwand lässt sich diese Einschränkung umgehen

Seite 4 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

Zeiger auf Konstanten & Konstante Zeiger

Die Bildung eines Typs „Konstante 𝑇 “ mit const „bindet stärker“ als die Bildung eines Typs „Zeiger auf 𝑇 “
mit *. D.h. eine Vereinbarung einer Variable der Form const 𝑇 *𝑥 vereinbart 𝑥 als Variable vom Typ „Zeiger
auf Konstante 𝑇 “.
Man spricht von einem Zeiger auf Konstante.
Zeiger auf Konstanten dürfen selbst verändert werden, d.h. es darf die im Zeiger gespeicherte Adresse ange-
passt werden, sodass der Zeiger auf eine andere Speicherstelle zeigt.
Zeiger auf Konstanten dürfen jedoch nicht verwendet werden um den Wert, der an der Speicherstelle, auf die
gezeigt wird, gespeichert ist, zu verändern.
Es dürfen auch Zeiger auf Konstante gebildet werden, die auf Variablen zeigen, welche nicht selbst als const
vereinbart wurden.
Beispiel. Der Versuch einen Wert „durch“ einen Zeiger auf Konstante zu verändern, wird vom Compiler ab-
gelehnt.

pointer_const_change.cpp

int main() {
double x = 3, y = 7;

const double *xp = &x;

*xp = 4;
}

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


pointer_const_change.cpp:6:7: error: assignment of read-only location ‘* xp’
6 | *xp = 4;
| ~~~~^~~

Beispiel. Den Wert eines Zeigers auf Konstante, d.h. die Adresse auf die gezeigt wird, anzupassen, ist zulässig.

pointer_const_redirect.cpp

#include <iostream>

using namespace std;

int main() {
double x = 3, y = 7;

const double *xp = &x;

xp = &y;

cout << *xp << endl;


}

Seite 5 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

Da Zeiger Werte sind, können auch sie selbst konstant sein. Man spricht in diesem Fall von einem konstanten
Zeiger. Eine Vereinbarung einer Variable der Form 𝑇 * const 𝑥 vereinbart 𝑥 als Variable vom Typ „Konstanter
Zeiger auf 𝑇 “.
Es wird verhindert, dass der Wert des Zeigers, d.h. die darin gespeicherte Adresse, nach Initialisierung noch
verändert wird.
Das Bilden von konstanten Zeigern auf Konstante ist zulässig.
Beispiel. Der Versuch den Wert einer Variable mit Typ „Konstante Zeiger auf double“, d.h. die Adresse, auf
die der Zeiger zeigt, zu verändern, wird vom Compiler abgelehnt.

const_pointer_redirect.cpp

int main() {
double x = 3, y = 7;

double * const xp = &x; // Initialisation

xp = &y; // Attempt to change constant


}

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


const_pointer_redirect.cpp:6:6: error: assignment of read-only variable ‘xp’
6 | xp = &y; // Attempt to change constant
| ~~~^~~~

Beispiel. Das Verändern eines Wertes einer Variablen „durch“ einen konstanten Zeiger ist zulässig.

const_pointer_change.cpp

#include <iostream>

using namespace std;

int main() {
double x = 3;

double * const xp = &x;

*xp = 4;

cout << *xp << endl;


}

Konstante Referenzen

Im Gegensatz zu Zeigern verhalten sich Referenzen immer als wären sie konstant. Referenzen können zwar
verwendet werden um den Wert der refernzierten Variable zu verändern, es kann aber nicht verändert werden
welche Variable referenziert wird.

Seite 6 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

Unter einer konstanten Referenz versteht man insofern das selbe wie unter der, wenig gebräuchlichen, Analo-
gie zu Zeigern: „Referenz auf Konstante“.
Für 𝑇 𝐷 eine Vereinbarung einer Variable 𝐷 vom Typ 𝑇 ist const 𝑇 & 𝐷 eine Vereinbarung einer konstanten
Referenz 𝐷 vom Typ „konstante Referenz auf 𝑇 “.
Das Bilden von konstanten Referenzen, welche eine Variable referenzieren, die nicht selbst als konstant ver-
einbart wurde, ist zulässig.
Beispiel. Der Versuch den Wert einer Variable „durch“ eine konstante Referenz zu verändern, wird vom Com-
piler abgelehnt.

reference_const_change.cpp

int main() {
double x = 3;

const double& xr = x;

xr = 4;
}

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


pointer_const_change.cpp:6:7: error: assignment of read-only location ‘* xp’
6 | *xp = 4;
| ~~~~^~~

Konstante Referenzen haben in der Praxis als Parameter von Funktionen große Bedeutung. Sie stellen eine
sichere Optimierung dar um eine ansonsten unnötige Kopie eines Wertes zu vermeiden.
Beispiel. Wir vereinbaren einen großen Vektor (5 · 108 Einträge, also 4 GB Speicherbedarf).
Würden wir den Vektor als gewöhnliches Argument an die Funktion f übergeben, so müsste eine Kopie
des gesamten Vektors inklusive aller seiner Einträge angelegt werden. Dies würde den Speicherverbrauch
verdoppeln, obwohl das für die Implementierung der Funktion f selbst alles andere als notwendig ist.
Wir übergeben den Vektor stattdessen als konstante Referenz an f. Es muss dann keine Kopie des Vektors
angelegt werden und der Körper der Funktion f hat dennoch ausschließlich lesenden Zugriff auf den Vektor,
was in diesem Fall völlig hinreichend ist.

const_reference_param.cpp

#include <iostream>
#include <vector>

using namespace std;

double f(const vector<double>& xs) {


return [Link](0);
}

int main() {
vector<double> xs(500'000'000, 0.0);

cout << f(xs) << endl;


}

Seite 7 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

Es empfiehlt sich in Funktionsparametern konstante Referenzen statt Kopien von Werten zu akzeptieren,
wann immer das möglich ist.

Einfache Klassen

C++ erlaubt es zusammengesetzte Datentypen zu vereinbaren. Ein zusammengesetzter Datentyp ist zunächst
eine Sammlung von benannten Attributen.
Bei Initialisierung eines Werts von zusammengesetztem Typ wird jeweils ein Satz von allen Attributen des
Typs initialisiert. Danach ist es möglich, anhand ihrer Namen, auf die Werte der Attribute innerhalb des Werts
von zusammengesetztem Typ zuzugreifen.
Eine Definition eines zusammengesetzten Datentyps ist syntaktisch, wie folgt:
⟨KlassenDefinition⟩ → (“class” | “struct”) [⟨Identifier⟩] “{” {⟨KlassenKomponente⟩} “}”
⟨KlassenKomponente⟩ → [(“private” | “public”) “:”] (⟨Fun'Vereinbarung⟩ | ⟨Einf'Vereinbarung⟩)
Eine derartige Definition einer Klasse bzw. eines structs (auch record, zu deutsch gelegentlich Verbund) ist
ein Ausdruck2 . Um ein syntaktisch zulässiges Programm zu bilden muss also i.d.R. auf die Definition eines
zusammengesetzten Datentyps ein Semikolon ; folgen um die Definition zu einer Anweisung (spezifischer,
einer Deklaration) zu machen.
Die Zugriffsattribute “private” und “public” bestimmen ob die Namen von insb. Attributen des zusammen-
gesetzten Datentyps jeweils nur „innerhalb“ des Datentyps selbst zur Verfügung stehen, oder auch außerhalb
davon. Zugriffsattribute sind ab ihrer ersten Verwendung innerhalb einer Klasse so lange gültig, bis ein ande-
res Zugriffsattribut verwendet wird. Bei Verwendung des Schlüsselworts class ist private die Voreinstellung,
bei Verwendung von struct stattdessen public.
Zusammengesetzte Datentypen heißen auch Klassen. Ein Wert von zusammengesetztem Datentyp nennt man
auch Objekt.

Attributszugriff Für 𝑐 ein Wert von zusammengesetztem Typ 𝑇 und 𝑎 ein Attribut von 𝑇 kann auf den in
𝑐 im Attribut 𝑎 gespeicherten Wert zugegriffen werden als 𝑐 .𝑎.
Die Schreibweise 𝑐𝑝 ->𝑎 ist identisch zu (*𝑐𝑝 ).𝑎.

Aggregat-Initialisierung Zunächst können wir zusammengesetzte Datentypen, deren Attribute alle


public sind, mit Aggregat-Initialisierung initialisieren. Wir verwenden hierfür Initialisierungslisten in
geschweiften Klammern, wie bereits bekannt:
⟨Init'⟩ → . . .
| [“=”] “{” [⟨Ausdruck⟩ {“,” ⟨Ausdruck⟩}] “}”

Die Zuordnung der gegebenen Ausdrücke zu den zu initialisierenden Attributen geschieht hierbei nach Rei-
henfolge ihrer Deklaration innerhalb der Definition der Klasse. Nicht erwähnte Attribute werden, in Abhän-
gigkeit von ihrem jeweiligen Typ, nach Voreinstellung initialisiert.

2 Definitionen von insb. structs werden gelegentlich direkt als Typ z.B. bei Vereinbarung einer Variable verwendet

Seite 8 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel. Wir vereinbaren einen zusammengesetzten Datentyp Coordinate bestehend aus zwei Attributen,
jeweils vom Typ double, mit den Namen x und y.
Wir initialisieren eine Variable c vom Typ Coordinate indem wir das Attribut x mit dem Wert 3.0 und das
Attribut y mit dem Wert y initialisieren. Das ist möglich, da alle Attribute von Coordinate (also x und y)
als public deklariert sind.

[Link]

#include <iostream>

using namespace std;

class Coordinate {
public:
double x;
double y;
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”x=” << c.x << ” y=” << c.y << endl;
}

x=3 y=7

Methoden Funktionsdeklarationen innerhalb eines zusammengesetzten Datentyps deklarieren Methoden.


Methoden sind Funktionen, die für 𝑓 der Name der Funktion und 𝑐 ein Wert vom zusammengesetztem Typ, in
Punkt-Schreibweise als 𝑐 . 𝑓 (arg1 , arg2 , . . .) aufgerufen werden. Innerhalb des Körpers der Methode stehen
alle Attribute des Werts 𝑐 „auf dem“ die Methode aufgerufen wird zur Verfügung. Der Zugriff erfolgt direkt
durch Verwendung des Namens des jeweiligen Attributs.
Die Schreibweise 𝑐𝑝 -> 𝑓 (arg1 , arg2 , . . .) ist identisch zu (*𝑐𝑝 ). 𝑓 (arg1 , arg2 , . . .).
Beispiel. Wir vereinbaren eine Klasse Coordinate mit Attributen x und y. Wir vereinbaren zudem in
Coordinate eine Methode norm, die die euklidische Norm des durch die Attribute x und y spezifizierten
Vektors berechnet und zurückgibt.
Wir rufen die Methode norm auf die Variable c auf, welche wir vorher im Hauptprogramm initialisiert
haben.

[Link]

#include <iostream>
#include <cmath>

using namespace std;

class Coordinate {
public:
double x;
double y;

Seite 9 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

double norm() {
return sqrt(x*x + y*y);
}
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”||c||=” << [Link]() << endl;


}

||c||=7.61577

Statische und befreundete Funktionen Funktionsdeklarationen innerhalb eines zusammengesetzten Da-


tentyps kann das Schlüsselwort static vorgestellt werden. In diesem Fall wird keine Methode vereinbart,
sondern eine statische Komponentenfunktion.
Für 𝑓 eine statische Komponentenfunktion eines zusammengesetzten Datentyps 𝑇 wird die Funktion 𝑓 auf-
gerufen als 𝑇 :: 𝑓 (arg1 , arg2 , . . .).
Beispiel. Wir vereinbaren eine Klasse Coordinate mit Attributen x und y. Wir vereinbaren zudem in
Coordinate eine statische Komponentenfunktion norm. norm akzeptiert zwei Parameter, 𝑝 eine reelle Zahl
und 𝑐 ein Wert des Typs Coordinate und berechnet die 𝑝-Norm des durch die Attribute x und y des Werts
𝑐 beschriebenen Vektors.
Wir rufen die statische Komponentenfunktion norm auf und übergeben ihr hierbei insb. den Wert der
Variable 𝑐, welche wir vorher im Hauptprogramm initialisiert haben.

static_norm.cpp

#include <iostream>
#include <cmath>

using namespace std;

class Coordinate {
public:
double x;
double y;

static double norm(double p, Coordinate c) {


return pow(pow(c.x, p) + pow(c.y, p), 1.0/p);
}
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”||c||=” << Coordinate::norm(2, c) << endl;


}

||c||=7.61577

Seite 10 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

Befreundete Funktionen sind statische Komponentenfunktionen, welche jedoch statt mit dem Schlüsselwort
static mit dem Schlüsselwort friend vereinbart werden. Der Unterschied liegt darin, dass, für 𝑓 eine be-
freundete Funktionen des zusammengesetzten Datentyps 𝑇 , die Funktion 𝑓 direkt aufgerufen werden kann,
ohne Angabe von 𝑇 .
Beispiel. Wir vereinbaren norm als befreundete Funktion von Coordinate.
Der Aufruf von norm im Hautprogramm kann direkt als norm(2, c) erfolgen, Coordinate muss nicht
angegeben werden.

friend_norm.cpp

#include <iostream>
#include <cmath>

using namespace std;

class Coordinate {
public:
double x;
double y;

friend double norm(double p, Coordinate c) {


return pow(pow(c.x, p) + pow(c.y, p), 1.0/p);
}
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”||c||=” << norm(2, c) << endl;


}

||c||=7.61577

Konstruktoren Klassen mit privaten Attributen können nicht Aggregat-initialisiert werden. Insb. hierfür
kann in der Definition einer Klasse ein Konstruktor angegeben werden.
Syntaktisch ist die Angabe eines Konstruktors identisch zur Vereinbarung einer Methode mit dem gleichen
Namen wie der Name der Klasse, jedoch komplett ohne Rückgabetyp (nicht einmal void).
Bei Initialisierung einer Variable von Klassentyp erhält der Konstruktor die gegebenen Werte als Argumen-
te, sie werden nicht direkt zur Initialisierung der Attribute verwendet. Stattdessen ist dann der Körper des
Konstruktors verantwortlich dafür die Attribute geeignet mit Werten zu belegen.
Beispiel. Wir vereinbaren eine Klasse Coordinate mit einem Konstruktor. Zu Demonstrationszwecken haben
wir die Vereinbarungen der Attribute x und y vertauscht.
Da für die Attribute x und y keine Sichtbarkeit angegeben ist und das Schlüsselwort class verwendet
wurde, sind x und y beide private. Der Zugriff auf die Attribute x und y direkt aus dem Hauptprogramm
ist somit nicht möglich. Wir vereinbaren stattdessen Methoden getX und getY um den Wert des jeweiligen
Attributs zurückzugeben.

Seite 11 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

coord_constructor.cpp

#include <iostream>

using namespace std;

class Coordinate {
double y, x;

public:
Coordinate(double x_, double y_) {
x = x_; y = y_;
}

double getX() {
return x;
}
double getY() {
return y;
}
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”x=” << [Link]() << ” y=” << [Link]() << endl;
}

x=3 y=7

Destruktoren Definitionen von Klassen dürfen einen Destruktor enthalten.


Syntaktisch ist die Angabe eines Destruktors identisch zur Vereinbarung einer Methode mit, für 𝑇 der Name
der Klasse, dem Namen ~𝑇 , jedoch, wie bei einem Konstruktor, komplett ohne Rückgabetyp (nicht einmal
void). Destruktoren akzeptieren keine Argumente.

Sobald die Lebensdauer eines Wertes von Klassentyp endet (i.d.R. mit Ende des syntaktischen Gültigkeitsbe-
reichs einer Variable von Klassentyp), wird automatisch der Destruktor ausgeführt. Dies kann zielführend
dafür verwendet werden, z.B. im Konstruktor dynamisch zugewiesenen Speicher wieder freizugeben.
Beispiel. Wir vereinbaren eine Klasse C mit einem Konstruktor und einem Destruktor.

[Link]

#include <iostream>

using namespace std;

class C {
public:
C() {
cerr << ”Konstruktor” << endl;
}
~C() {
cerr << ”Destruktor” << endl;

Seite 12 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

}
};

int main() {
{
cerr << ”Vor Vereinbarung von c” << endl;
C c;
cerr << ”Nach Vereinbarung von c” << endl;
}
cerr << ”Nach Ende Gültigkeitsbereich von c” << endl;
}

Vor Vereinbarung von c


Konstruktor
Nach Vereinbarung von c
Destruktor
Nach Ende Gültigkeitsbereich von c

Beispiel (Komplexe Zahlen). Wir vereinbaren eine, sehr rudimentäre, Klasse zur Darstellung von komplexen
Zahlen.

[Link]

#include <iostream>

using namespace std;

class Complex{
private:
double re, im;

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

double real() { return re; }


double imag() { return im; }

friend Complex conj(Complex z) {


[Link] = -[Link];
return z;
}
};

int main() {
Complex z1{1.0, 2.0}, z2{0.0, 0.0};
z2 = conj(z1);

double x3 = 3, y3 = 4;
Complex z3{x3, y3};

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


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

Seite 13 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026

<< ”Re z3 = ” << [Link]() << ” Im z3 = ” << [Link]()


<< endl;
}

Re z1 = 1 Im z1 = 2
Re z2 = 1 Im z2 = -2
Re z3 = 3 Im z3 = 4

Seite 14 von 14
Skript 12 Programmieren I für Studierende der Mathematik — SoSe 2026

Ü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.

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;
}
};

1 mit wenigen Ausnahmen

Seite 1 von 2
Skript 12 Programmieren I für Studierende der Mathematik — SoSe 2026

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 2 von 2
Skript 13 Programmieren I für Studierende der Mathematik — SoSe 2026

This space intentionally left blank

Seite 1 von 1
Programmieren I für Studierende der Mathematik SoSe 2026

Organisatorisches

Organisatorisches

Zielgruppe Studierende des BSc Wirtschafts-/Mathematik, ca. 4. Semester


Umfang 2-Stündige Vorlesung + 2-Stündige Übung ⇒ 6 ECTS
Website [Link]/course/S26/MI/Pro1
(QR-Code auf der nächsten Folie)
Skript Gesamter Stoff der Veranstaltung in Fließtext erklärt
Folien i.d.R. Auszug aus Skript
Inhaltsverzeichnis am Ende der Veranstaltung
Übung Zwei unterschiedliche Termine im stud. PC-Pool
Dort Bearbeitung des wöchentlichen Übungsblattes
Prüfung Schriftliche Klausur
Vstl. 1./2. bzw. letzte/vorletzte Semesterferien-Woche
Bescheide bzgl. Nachteilsausgleich bis spätestens 4 Wochen vor Ende der
Vorlesungszeit jedoch so bald wie möglich
1/2

Veranstaltungs-Website

[Link]

2/2

Seite 1 von 1
Programmieren I für Studierende der Mathematik SoSe 2026

Inhalt

Inhalt

Eine gründliche und wohlfundierte Einführung in den imperativen Teil der


Programmiersprache C++

1/6

Warum systemnahes Programmieren?

• Moderne Rechner (Hardware + Betriebssystem) genügen Reihe internationaler


Standards → Umgebung überall zunächst mal ähnlich
• Systemnahe Programmierung ist grundlegend, d.h.:
• Abstraktionen (z.B. abstraktere Programmiersprachen∗ , Libraries† , Computer Algebra
Systeme‡ , Beweisassistenten§ , …) immer mit weniger abstrakten Methoden
implementiert
• verwendete Techniken/Strukturen/… „am Ende“ immer die selben
• Ziel der Veranstaltung daher gewappnet zu sein wenn geeignete Abstraktion noch
nicht existiert bzw. nicht funktioniert → Programmieren „von Null auf“

∗ Java,Rust, Python, R, Haskell, …


† numpy, tensorflow, …
‡ MatLab, Maple, Mathematica, …
§ Minlog, Coq, Isabelle, Lean, …
2/6

Seite 1 von 3
Inhalt Programmieren I für Studierende der Mathematik — SoSe 2026

Warum C++?

• Ziel der Veranstaltung weniger Kenntnisse von C++, sondern Kenntnisse einer
systemnahen Programmiersprache
• Konkret C++, da (immmernoch) großflächig verwendet in Implementierung
angesprochener abstrakterer Libraries

3/6

Warum Thema 𝑋 wenn 𝑌 existiert?

Diverse Themen der Veranstaltung i.W. abgelöst von abstrakteren Methoden:

Maschinensprache Programmiersprachen (hier C++)


errno-Mechanismus Exceptions
Zeiger Referenzen
C-Arrays STL-Container (vector etc.)

Dennoch vorgestellt weil:


• abstraktere Methoden in anderen Umgebungen evtl. nicht verfügbar
• abstraktere Methoden stets implementiert mit den vorgestellten weniger abstrakten
Methoden
Daher: Eigentümlichkeiten der abstrakteren Methoden besser verständlich durch
weniger abstrakte
4/6

Seite 2 von 3
Inhalt Programmieren I für Studierende der Mathematik — SoSe 2026

Rolle der Mathematik in der Veranstaltung

• Veranstaltung für Studierende der Mathematik, daher:


• formale Definitionen (nur wo hilfreich)
• Aufbau von niedriger Komplexität und Abstraktion hinauf (in der Informatik oft
andersrum)
• Veranstaltung verwendet Mathematik (speziell Numerik) als Rahmen und bespricht
auch speziell für Numerik relevante Konzepte (Gleitpunktarithmetik,
Konditionierung, …), jedoch einführend
• Keine traditionelle Mathematikveranstaltung; eher ein praktischer Programmierkurs
Übungen sind essentiell
• Kurs „beginnt dort wo die Mathematik geklärt ist“:
• mathematische Vorüberlegungen vorgegeben
• Implementierung gefordert

5/6

Vorbereitung auf die Klausur

• Tatsächliche Probleme∗ (mindestens jedoch die Übungsaufgaben)


• Vollständige C++-Programme entwerfen und schreiben
• Die Programme testen und zum Funktionieren bringen
• Solange bis die Programme das Problem tatsächlich korrekt lösen
• Dabei aufpassen, mitdenken und selbst von Korrektheit überzeugen

Zu viele Hilfsmittel† eher hinderlich

∗ Z.B. aus dem „echten Leben“: Haushalts-Finanzplanung mit eigenem Programm statt Tabellenkalu-

klationsprogramm, Programm für bequeme Aufteilung von WG-Ausgaben, Programm für Literaturverwal-
tung für wiss. Arbeiten, eigene Implementierung von 𝑀 ′ , Rechner mit automatischer Fehlerpropagation für
Experimantalphysik-Praktika, …
† Integrated Development Environments (IDEs), Lösungen aus dem Internet (inkl. der Veranstaltungs-

Website), Large Language Models (LLMs, „KI“), … 6/6

Seite 3 von 3
Programmieren I für Studierende der Mathematik SoSe 2026

Computer

Begriff: Universal Computer

• Unterscheidung von Universalrechner vs. Rechenmaschine mit


festem Programm
• Übliche Komponenten:
Hauptspeicher speichert Daten zuverlässig bis sie verändert
werden
Prozessor koordiniert und führt Berechnungen durch
Ein-/Ausgabe interagiert mit Außenwelt

Eine Rechenmaschine

1/2

von-Neumann Architektur

Prozessor

Befehlszähler Befehlsadresse
Anweisung
... Steuereinheit

Hauptspeicher
Register
Eingabe
...
... Recheneinheit
Ausgabe

Steuersignale & Speicheradressen


Daten
2/2

Seite 1 von 1
Programmieren I für Studierende der Mathematik SoSe 2026

Ein hypothetischer Rechner

Die hypothetische Maschine 𝑀 ′

• Theoretisches Modell eines Rechners zu Lehrzwecken


• Implementierung: [Link]/~gkleen/eval-m
• von-Neumann-Rechner
• Nur ein Register in Recheneinheit
• Unbegrenzt viele Speicherzellen 𝑆 1 , 𝑆 2 , . . .
• Notation: ⟨𝑆𝑛 ⟩ Inhalt von 𝑆𝑛
• Ein-/Ausgabe von reellen Zahlen
• Rechenanweisungen ebenfalls auf (approx.) reellen Zahlen
• Manche Anweisungen mit Parameter

1 / 14

Speicherinhalt/Programm

• Speicherzellen können Werte enthalten, aber auch Anweisungen


• Vereinfachung zu Lehrzwecken in 𝑀 ′ :
• kann nur Zahlen speichern/wieder abrufen
• kann nur Anweisungen ausführen
• Vor Beginn der Ausführung manuelles Präparieren des Speicherinhalts
• Speicherinhalt vor Beginn ist das Programm
• Ausführung beginnt mit Anweisung in 𝑆 1 , danach 𝑆 2 , usw.

2 / 14

Seite 1 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026

Ausgewählte Anweisungen

JUMP(𝑛 ) Fortführung des Programms mit Befehl in Zelle 𝑆𝑛


STOP Beendet Ausführung des Programms
READ Lies nächste auf dem Eingabemedium zur
Verfügung stehende Zahl und speichert sie in 𝑅
PRINT Gibt ⟨𝑅⟩ auf Ausgabemedium aus
STORE(𝑛 ) Speichert ⟨𝑅⟩ in 𝑆𝑛
RECALL(𝑛 ) Speichert ⟨𝑆𝑛 ⟩ in 𝑅
ADD(𝑛 ) SUB(𝑛 ) MULT(𝑛 ) Speichert ⟨𝑅⟩ + ⟨𝑆𝑛 ⟩ bzw. ⟨𝑅⟩ − ⟨𝑆𝑛 ⟩ bzw.
DIV(𝑛 ) ⟨𝑅⟩ · ⟨𝑆𝑛 ⟩ bzw. ⟨𝑆⟨𝑅⟩ in 𝑅
√ 𝑛⟩
SQRT Speichert ⟨𝑅⟩ in 𝑅

3 / 14

Beispiel: Formel von Heron

Berechnet Flächeninhalt eines Dreiecks 𝐴


gegeben Seitenlängen 𝑎, 𝑏, 𝑐:
√ 𝑐 𝑎
𝐴 = 𝑠 · (𝑠 − 𝑎) · (𝑠 − 𝑏) · (𝑠 − 𝑐)
𝐴
𝑎 +𝑏 +𝑐
𝑠 :=
2 𝑏

4 / 14

Seite 2 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026

Implementierung: Formel von Heron

heron.m 24 SUB(5) # c - s in R
25 MULT(4) # (c - s)*(b - s)*(s - a) in R
1 JUMP(6) 26 MULT(5) # s*(c - s)*(b - s)*(s - a) in R
2 # a 27 SQRT
3 # b, s - a 28 PRINT
4 # c, (b - s)*(s - a) 29 STOP
5 # c + a + b, s
6 READ
7 STORE(2) # a in S1
8 READ
9 STORE(3) # b in S2
10 READ
11 STORE(4) # c in S3
12 ADD(2) # c + a in R
13 ADD(3) # c + a + b in R
14 STORE(5) # c + a + b in S4
15 CONST(2)
16 SWAP(5) # c + a + b in R, 2 in S4
17 DIV(5)
18 STORE(5) # s in S4
19 SUB(2) # s - a in R
20 SWAP(3) # s - a in S2, b in R
21 SUB(5) # b - s in R, Vorzeichen!
22 MULT(3)
23 SWAP(4) # (b - s)*(s - a) in S3, c in R

5 / 14

Unterprogramme

• Programme mit vielen Anweisungen oft unübersichtlich


• Aufteilung in kleinere Bestandteile hilft
• Hierfür: separate Sektionen im Programm; Sprungbefehle dazwischen

6 / 14

Seite 3 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026

Demo: Unterprogramme, Mehrfachaufruf

⇒ 𝑆 𝑦1 −1 JUMP 𝑥
𝑆 𝑦1 . . .
Hauptprogramm

···
𝑆 𝑦2 −1 JUMP 𝑥
𝑆 𝑦2 . . .

···
𝑆𝑥 ...
Unterprogramm
···

𝑆𝑥+𝑘 JUMP 𝑦1
···

7 / 14

Unterprogramme: Forts.

We can solve any problem by introducing an extra level of indirection. (The funda-
mental theorem of software engineering)

• Problem: Mehrfach-Aufrufen von selbem Unterprogramm nicht möglich


• Hierfür: indirekte Sprünge zur Rückkehr (return) aus Unterprogramm
• JUMP Anweisung (ohne Parameter) interpretiert Zahl in 𝑅 als Adresse (Zeiger) und
springt dorthin

8 / 14

Seite 4 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026

Unterprogramme: Forts. 2

• Problem: Aufrufen von Unterprogramm aus Unterprogramm selbst (rekursiv) nicht


möglich
Relevante Speicherzellen werden überschrieben
• Hierfür: Inhalt relevanter Speicherzellen sichern

9 / 14

Indirekte Sprünge & Speicherzugriffe

JUMP Fortführung des Programms mit Befehl in Zelle 𝑆 ⟨𝑅⟩


RECALL Speichert ⟨𝑆 ⟨𝑅⟩ ⟩ in 𝑅
COPY(𝑛 ) Speichert ⟨𝑆𝑛 ⟩ in 𝑆 ⟨𝑅⟩

10 / 14

Seite 5 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026

Stack

• Datenstruktur
• Speichert Rückkehradressen und Inhalt relevanter Speicherzellen
• Zwei Operationen: push & pop

⟨𝑆 1 ⟩ = 1, 𝑆 2 stack pointer, 𝑆 3 temporäre Ablage

stack pop
stack push
RECALL(2)
STORE(3) RECALL
RECALL(2) STORE(3)
ADD(1) RECALL(2)
STORE(2) SUB(1)
COPY(3) STORE(2)
RECALL(3)
11 / 14

Demo: Stack

demo_stack.m 24 COPY(3)
25 NOP
1 NOP # 1 26 RECALL(2)
2 NOP # stack pointer 27 RECALL
3 NOP # tmp 28 STORE(3)
4 CONST(1) 29 RECALL(2)
5 STORE(1) 30 SUB(1)
6 CONST(99) 31 STORE(2)
7 STORE(2) 32 RECALL(3)
8 JUMP(10) 33 NOP
9 34 PRINT
10 CONST(1000) 35 CONST(3000)
11 NOP 36 NOP
12 STORE(3) 37 STORE(3)
13 RECALL(2) 38 RECALL(2)
14 ADD(1) 39 ADD(1)
15 STORE(2) 40 STORE(2)
16 COPY(3) 41 COPY(3)
17 NOP 42 NOP
18 CONST(2000) 43 STOP
19 NOP
20 STORE(3)
21 RECALL(2)
22 ADD(1)
23 STORE(2)

12 / 14

Seite 6 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026

Demo: Stack (Forts.)

···
𝑅 1 000 ⇒ 𝑆 12 STORE(3)
𝑆 13 RECALL(2)
𝑆1 1
𝑆 14 ADD(1)
𝑆2 99
𝑆 15 STORE(2)
𝑆3 NOP
𝑆 16 COPY(3)

···
𝑆 20 STORE(3)

13 / 14

Calling conventions

Aktionen sowohl von aufrufendem Code und von Unterprogramm notwendig:


• • Speicherinhalte auf stack ablegen
• für Unterprogramm relevante Werte in Speicherzellen ablegen (Argumente)
• Rücksprungadresse berechnen und ablegen
• Sprung in Unterprogramm
• • für aufrufenden Code relevante Werte in Speicherzellen ablegen (Rückgabewert)
• Rücksprungadresse von stack entfernen
• indirekter Sprung zurück
• • gesicherte Speicherinhalte von stack entfernen

Große Wahlfreiheit bzgl. Details und was für konkrete Maschine notwendig ist
Gesamtheitlich: eine calling convention

14 / 14

Seite 7 von 7
Programmieren I für Studierende der Mathematik SoSe 2026

Programmiersprachen

Formale Sprachen

• Endliche, nicht-leere Menge Σ, das Alphabet


• Endliche Folgen 𝑤 von Symbolen aus Σ, ein Wort
• Leeres Wort 𝜀
• Mengen von Wörtern 𝐴, 𝐵, Sprachen
• Diverse Operationen:
• ◦, Konkatenation v. Wörtern
• 𝑤 𝑛 , 𝑛-fache Wiederholung eines Worts
• 𝐴, Komplement einer Sprache
• 𝑤𝐴, 𝐴𝑤, Konkatenation aller Wörter aus 𝐴 mit 𝑤
• 𝐴 ◦ 𝐵, Konkationen aller Wörter aus 𝐴 mit allen aus 𝐵
• 𝐴𝑛 , 𝐴? , 𝐴∗ , 𝐴+ , mehrfache Konkatination von allen Wörtern aus 𝐴

1/5

Programmiersprachen

• Programme in Maschinensprache sehr umständlich


• Assemblersprache: Maschinensprache „lesbar“ für Menschen, Marken, Sektionen
• Assembler: Übersetzt Assemblersprache in Maschinensprache
• Programmiersprache: Abstrakt (z.B. Verwaltung des Stack automatisch)
• Compiler: Übersetzt Programmiersprache in Maschinensprache

2/5

Seite 1 von 3
Programmiersprachen Programmieren I für Studierende der Mathematik — SoSe 2026

Der Standard

• C++ ist eine Programmiersprache


• C++ ist spezifiziert in Dokument der ISO∗ und der IEC† : ISO/IEC 14882
• Neue Versionen dieses Dokuments ca. alle 3 Jahre
• C++ standards committee achtet sehr stark auf Abwärtskompatibilität; d.h. alter Code
sollte mit neuer Sprache immernoch funktionieren
• In dieser Iteration der Pro1 und direkt folgender Pro2: C++14
• Standard ist nicht frei verfügbar, jedoch „Entwürfe“ schon
siehe [Link]/w/cpp/links
• Gelegentlich Referenzen auf andere Standards:
• ISO/IEC 9899 für C, die „Vorgängersprache“
• IEEE 754 für Gleitpunktarithmetik
• IEEE 1003.1 (POSIX) für Interaktion mit dem Betriebssystem

∗ International Organization for Standardization


† International Electrotechnical Commission 3/5

Der Standard (Forts.)

Der Standard lässt Aspekte des Verhaltens von Programmen offen:

Implementierungsabhängig Compiler/Betriebssystem/Maschinenarchitektur
dokumentieren Verhalten → dort nachschauen
Unspezifiziert Standard spezifiziert Rahmen an mögliches Verhalten (z.B. irgendeine
ganze Zahl in bestimmten Bereich); jede Ausführung des Programms
„wählt“ was passiert
Undefiniert Programme die undefiniertes Verhalten enthalten, werden vom Standard
mit keiner Bedeutung belegt

4/5

Seite 2 von 3
Programmiersprachen Programmieren I für Studierende der Mathematik — SoSe 2026

Übersetzung/Ausführen von C++ Programmen

• Formuliere Programm, speichere in .cpp-Datei


• Übersetze Datei mit Konsolenbefehl g++ → ausführbare Binärdatei [Link]
• Führe das Programm aus mit Konsolenbefehl ./[Link]

5/5

Seite 3 von 3
Programmieren I für Studierende der Mathematik SoSe 2026

Grammatiken & EBNF

Grammatik

• Bisher: Sprachen als Mengen, Bilden von Sprachen mit Mengen-Operationen


• Konziser: Spezifikation mit Grammatik
• Grammatik 𝐺 = (Σ, 𝑉 , 𝑃, 𝑆)
• 𝑉 Variablen, auch Non-Terminale
• 𝑃 Produktionen: linke Seite → rechte Seite
• 𝑆 ∈ 𝑉 Startsymbol

1/4

Extended Backus-Naur Form (EBNF)

Für 𝑣, 𝑟 1, 𝑟 2 ∈ 𝑉 Variablen, definiere 𝐿(𝑣) die von 𝑣 induzierte Sprache∗ :

Produktion Induzierte Sprache


⟨𝑣⟩ → “𝑤 ” 𝐿(𝑣) = {𝑤 }
⟨𝑣⟩ → /[𝑐 1 -𝑐𝑛 ]/ 𝐿(𝑣) = {𝑐 1, . . . , 𝑐𝑛 }
⟨𝑣⟩ → [𝑟 1 ] 𝐿(𝑣) = 𝐿(𝑟 1 ) ? = {𝜀} ∪ 𝐿(𝑟 1 )

⟨𝑣⟩ → {𝑟 1 } 𝐿(𝑣) = 𝐿(𝑟 1 ) ∗ = 𝑛∈N0 𝐴𝑛

⟨𝑣⟩ → {𝑟 1 }+ 𝐿(𝑣) = 𝐿(𝑟 1 ) + = 𝑛∈N,𝑛≥1 𝐴𝑛
⟨𝑣⟩ → 𝑟 1 𝑟 2 𝐿(𝑣) = 𝐿(𝑟 1 ) ◦ 𝐿(𝑟 2 ) = {𝑤 1 ◦ 𝑤 2 | 𝑤 1 ∈ 𝐿(𝑟 1 ), 𝑤 2 ∈ 𝐿(𝑟 2 )}
⟨𝑣⟩ → 𝑟 1 | 𝑟 2 𝐿(𝑣) = 𝐿(𝑟 1 ) ∪ 𝐿(𝑟 2 )

Definiere Sprache 𝐿(𝐺) der Grammatik 𝐺 als 𝐿(𝐺) := 𝐿(𝑆)

∗ O.b.d.A. sei ⟨𝑣⟩ linke Seite von genau einer Produktion 2/4

Seite 1 von 2
Grammatiken & EBNF Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: arith. Ausdrücke


Herleitung für “(17+3)*2”:

⟨Ausdruck⟩ → ⟨Prod'⟩ {“+” ⟨Prod'⟩}


→ ⟨Prod'⟩
..
.
→ ⟨Faktor⟩ “*” ⟨Faktor⟩
⟨Faktor⟩ → {/[0-9]/} +
→ ({/[0-9]/}+ | “(” ⟨Ausdruck⟩ “)”) “*” ⟨Faktor⟩
| “(” ⟨Ausdruck⟩ “)” ..
⟨Produkt⟩ → ⟨Faktor⟩ {“*” ⟨Faktor⟩} .
⟨Ausdruck⟩ → ⟨Prod'⟩ {“+” ⟨Prod'⟩} → “(” /[0-9]/ /[0-9]/ “+” /[0-9]/ “)” “*” /[0-9]/
→ “(” “1” /[0-9]/ “+” /[0-9]/ “)” “*” /[0-9]/
..
.
→ “(” “1” “7” “+” “3” “)” “*” “2”
→ “(17+3)*2”
3/4

EBNFs für C++

• Symbole für C++ sind tokens


• ein token i.A. mehrere alphanumerische Zeichen
• tokens durch nicht-alphanumerische Zeichen abgetrennt
Beispiel: “fuenf* 2” → “fuenf” “*” “2”
• EBNFs auf tokens nach Präprozessor
Beispiel: #include ist Präprozessor-Direktive
• Kommentare ab “//” bis Zeilenende oder zwischen “/*” und “*/”

4/4

Seite 2 von 2
Programmieren I für Studierende der Mathematik SoSe 2026

Programmaufbau

Programmaufbau

⟨Übersetzungseinheit⟩ → {⟨Vereinbarung⟩}
⟨Vereinbarung⟩ → ⟨Einf'Vereinbarung⟩
| ⟨Fun'Vereinbarung⟩
C++-Programme bestehen aus Vereinbarungen:
• Variablen
• Funktionen

1 / 14

Variablen

⟨Einf'Vereinbarung⟩ → {⟨Dekl'Spez'⟩}+ ⟨Init'Dekl'⟩ {“,” ⟨Init'Dekl'⟩} “;”


⟨Init'Dekl'⟩ → ⟨Identifier⟩ [⟨Init'⟩]
⟨Init'⟩ → “=” ⟨Ausdruck⟩
| “(” ⟨Ausdruck⟩ {“,” ⟨Ausdruck⟩} “)”
| [“=”] “{” [⟨Ausdruck⟩ {“,” ⟨Ausdruck⟩}] “}”
⟨Identifier⟩ → ⟨NichtZiffer⟩ {⟨Ziffer⟩ | ⟨NichtZiffer⟩}
⟨NichtZiffer⟩ → /[a-z]/ | /[A-Z]/ | “_”
⟨Ziffer⟩ → /[0-9]/
• Namen für Speicherbereiche
• Deklaration einer Variable reserviert Speicher auf dem stack
• Variable „enthält“ Wert
• Definition einer Variable setzt initialen Wert

2 / 14

Seite 1 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026

Funktionen

• Analog zu Unterprogrammen
• Name analog zu Variablen
• Aufruf einer Funktion ist ein Ausdruck
Beispiel: sqrt(2)
• Funktionen können Argumente haben, runde Klammern immer erforderlich
• Funktionen können Rückgabewerte haben
Aufruf wertet aus zu Rückgabewert
• Funktion main ist Hauptprogramm

3 / 14

Vordefinierte Funktionen

• Viele Funktionen in Standardlibrary: sqrt, abs, sin, cos, tan, etc.


• Vor Verwendung der Übersetzungseinheit bekannt machen
• Hierfür: #include (#include <cmath> für obige) und using namespace std;

4 / 14

Seite 2 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026

Datentypen

• Typ eines Werts


• Variablen haben stets genau einen Typ
• eingebaute Datentypen und viele weitere Datentypen in Standardlibrary

int Ganze Zahl


double Approximation einer reellen Zahl
bool Wahrheitswert, true oder false
char Einzelner Buchstabe
string Datentyp aus der Standardlibrary; Zeichenkette beliebiger Länge

5 / 14

Beispiel: Ausdrücke

heron_const.cpp 6

#include <iostream>
#include <cmath>

using namespace std;

int main() {
double a = 3, b = 4, c = 5;
double s = (a + b + c) / 2;
cout << sqrt(s * (s - a) * (s - b) * (s - c)) << endl;
}

6 / 14

Seite 3 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026

Eigene Funktionen

⟨Fun'Vereinbarung⟩ → {⟨Dekl'Spez'⟩}+ ⟨Dekl'⟩ “(” [⟨Param'Dekl'⟩ {“,” ⟨Param'Dekl'⟩}] “)” ⟨Block⟩


⟨Anweisung⟩ → ⟨Block⟩
| [⟨Ausdruck⟩] “;”
| ⟨Einf'Vereinbarung⟩
| ⟨SprungAnweisung⟩
⟨SprungAnweisung⟩ → “return” [⟨Ausdruck⟩];
⟨Block⟩ → “{” {⟨Anweisung⟩} “}”
• Parameter und Rückgabewert jeweils
mit Typ sum
• Kein Rückgabewert entspricht void double sum(double a, double b) {
return a + b;
• return bricht ab & setzt
}
Rückgabewert

7 / 14

if-Abfragen

⟨Anweisung⟩ → . . . | ⟨Auswahlanw'⟩
⟨Auswahlanw'⟩ → “if” “(” ⟨Kondition⟩ “)” ⟨Anweisung⟩ [“else” ⟨Anweisung⟩]
⟨Kondition⟩ → ⟨Ausdruck⟩
| {⟨Dekl'Spez'⟩} ⟨Dekl'⟩ ⟨Init'⟩

even
bool even(int a) {
„Wenn ⟨Kondition⟩, dann ⟨Anweisung⟩, sonst if (a % 2)
⟨Anweisung⟩.“ return false;
else
return true;
}

8 / 14

Seite 4 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Quadratwurzel mit Überprüfung

[Link] x: 2
sqrt(x) = 1.41421
#include <iostream>
#include <cmath>
x: -1
using namespace std; sqrt(x) undefiniert

int main() {
cout << ”x: ”;
double x;
cin >> x;

if (x >= 0)
cout << ”sqrt(x) = ” << sqrt(x) << endl;
else
cout << ”sqrt(x) undefiniert” << endl;
}

9 / 14

while-Schleifen

⟨Anweisung⟩ → . . . | ⟨Wiederholanw'⟩
⟨Wiederholanw'⟩ → “while” “(” ⟨Kondition⟩ “)” ⟨Anweisung⟩

„Solange ⟨Kondition⟩, immer wieder ⟨Anweisung⟩.“

10 / 14

Seite 5 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Tabelle von Quadratzahlen

[Link] n: 10
1 1
#include <iostream> 2 4
#include <iomanip> 3 9
4 16
using namespace std; 5 25
6 36
int main() { 7 49
cout << ”n: ”; 8 64
int n; 9 81
cin >> n; 10 100

int i = 1;
while (i <= n) {
cout << setw(2) << i
<< ” ”
<< setw(4) << (i * i)
<< endl;
i += 1;
}
}

11 / 14

∑𝑛 𝑥𝑘
Beispiel: 𝑘=0 𝑘!
exp1_narrow.cpp return 0;
}
#include <iostream>
#include <iomanip>
#include <cmath> x = 1
exp(x) = 2.71828
using namespace std; n exp_reihe(x, n)
0 1
double exp_reihe(double x, int n) { 1 2
int k = 1; 2 2.5
double a = 1, s = 1; 3 2.66667
while (k <= n) { 4 2.70833
a *= x/k; 5 2.71667
s += a; 6 2.71806
k += 1; 7 2.71825
} 8 2.71828
return s; 9 2.71828
} 10 2.71828

int main() {
double x = 1;
cout << ”x = ” << x << endl;
cout << ”exp(x) = ” << exp(x) << endl;

cout << setw(3) << ”n” << ” ”


<< setw(15) << ”exp_reihe(x, n)” << endl;
int n = 0;
while (n <= 10) {
cout << setw(3) << n << ” ”
<< setw(15) << exp_reihe(x, n) << endl;
n += 1;
}
12 / 14

Seite 6 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026

Korrektheit von Programmen

• Hoare-Kalkül:
• Aussagen der Form {𝑃 } 𝐶 {𝑄 }
Programmfragment 𝐶, eine (zusammengesetzte) Anweisung
Vorbedingung 𝑃, logische Aussage die als vor Ausführung von 𝐶 wahr
angenommen wird
Nachbedingung 𝑄, logische Aussage die als nach Ausführung von 𝐶 wahr bewiesen
wird
• Feste Regeln zur Einführung und Umformung der Aussagen je nach Struktur von 𝐶
Analog zu Äquivalenzumformungen von Gleichungen
• Beliebige andere Beweistechniken auch möglich

13 / 14

Beispiel: Korrektheit von Schleifen


𝑖
𝑠𝑖 := 𝑗 𝑠 0 = 0, 𝑠𝑖 = 𝑠𝑖 −1 + 𝑖
𝑗=1

• Schleifeninvariante: s = 𝑠 i−1
• Invariante vor Schleife wahr (hier trivial) 1 i = 1; s = 0;
2
• Invariante (und Kondition) als Vorbedingung für 3 while (i <= n) {
Schleifen-Körper 4 s += i;
• Invariante als Nachbedingung für Schleifen-Körper 5 i += 1;
6 }
beweisbar wahr
• ⇒ Invariante auch nach Schleife wahr
• Noch zu zeigen: i = n + 1 nach Schleife
• Damit dann: Programm berechnet s = 𝑠 n

14 / 14

Seite 7 von 7
Programmieren I für Studierende der Mathematik SoSe 2026

Ein-/Ausgabe in C++

Operatoren in C++

• C++ unterscheidet fundamentale Typen und Klassen


• Erinnerung: Variablen (und Werte) haben stets genau einen Typ
• Operatoren (/, -=, <<, …) haben unterschiedliche Wirkung je nach Typ der
Operanden
Fundamentale Typen Arithmetik, aber z.B. / bei double anders als bei int

Klassen Teil der Implementierung der Klasse


• Werte cout, cin, cerr von Klassen-Typ aus Standardlibrary
Implementierungen von <<, >> bewirken Aus-/Eingabe als Seiteneffekt
• << und >> auf fundamentalen Typen logical shifts

1 / 10

Assoziativität von Operatoren

• << und >> links-Assoziativ

cout << arg1 << arg2 << . . .


((cout << arg1 ) << arg2 ) << . . .

• Konvention: << und >> auf Strömen∗ liefert linken Operand als Ergebnis
⇒ Auswertung nur für den Seiteneffekt

∗ cout, cin, cerr sind Ströme 2 / 10

Seite 1 von 5
Ein-/Ausgabe in C++ Programmieren I für Studierende der Mathematik — SoSe 2026

Ströme

• Strom ist Wert (z.B. cout, cin), als semantische Referenz auf
Ein-/Ausgabe-Möglichkeit (z.B. Konsole)
• Wert hat internen Zustand z.B. Hexadezimale/Dezimale Ausgabe, Anzahl
Nachkomma-/signifikante Stellen, …
• I/O-Manipulatoren: vordefinierte Werte, verändern bei Auswertung von << bzw. >>
den internen Zustand

3 / 10

Buffering/Zeilenumbrüche

• Ausgabe nicht immer sofort


• Betriebssystem präferiert Stücke von Ausgabe (oft Zeilen) anzusammeln und als
Ganzes auszugeben
• Spezielle Werte flush und endl umgehen das
• endl verursacht zudem Zeilenumbruch

4 / 10

Seite 2 von 5
Ein-/Ausgabe in C++ Programmieren I für Studierende der Mathematik — SoSe 2026

Ausgewählte I/O-Manipulatoren für Ausgabe

fixed
fixed Ausgabe von Dezimalzahlen nie
cout << fixed << 1e-5 << endl;
in Exponentialschreibweise;
Festpunktschreibweise
0.000010
scientific Ausgabe von Dezimalzahlen
immer in
Exponentialschreibweise
scientific
defaultfloat Voreinstellung: Ausgabe in
Festpunktschreibweise wenn cout << scientific << 3.14 << endl;
„sinnvoll“
3.140000e+00

5 / 10

Ausgewählte I/O-Manipulatoren für Ausgabe (Forts.)

prec
cout << setprecision(4)
<< 3.141593 << endl;

3.142
setprecision(𝑛 ) Setze Anzahl
Nachkommastellen bzw. max.
sig. Stellen (defaultfloat)
prec fixed
cout << setprecision(4)
<< fixed << 3.141593 << endl;

3.1416

6 / 10

Seite 3 von 5
Ein-/Ausgabe in C++ Programmieren I für Studierende der Mathematik — SoSe 2026

Ausgewählte I/O-Manipulatoren für Ausgabe (Forts.)

setw
cout << '|' << setw(3) << 3 << '|' << endl;

setw(𝑛 ) Setze min. Feldbreite


| 3|
left, right Links- bzw.
Rechtsbündig
setfill(𝑐 ) Setze Füllzeichen
setw min
(Voreinst.: Leerzeichen)
cout << '|' << setw(3) << 1337 << '|' << endl;

|1337|

7 / 10

Beispiel: Tabellarische Ausgabe


[Link] n: 10
* 0 1 2 3 4 5 6 7 8 9 10
#include <iostream> 0 0 0 0 0 0 0 0 0 0 0 0
#include <iomanip> 1 0 1 2 3 4 5 6 7 8 9 10
2 0 2 4 6 8 10 12 14 16 18 20
using namespace std; 3 0 3 6 9 12 15 18 21 24 27 30
4 0 4 8 12 16 20 24 28 32 36 40
int main() { 5 0 5 10 15 20 25 30 35 40 45 50
cout << ”n: ”; 6 0 6 12 18 24 30 36 42 48 54 60
int n; 7 0 7 14 21 28 35 42 49 56 63 70
cin >> n; 8 0 8 16 24 32 40 48 56 64 72 80
9 0 9 18 27 36 45 54 63 72 81 90
int i = 0, j = 0; 10 0 10 20 30 40 50 60 70 80 90 100
cout << setw(4) << ”*” << ” ”;
while (j <= n) {
cout << setw(4) << j << ” ”;
j += 1;
}
cout << endl;
while (i <= n) {
cout << setw(4) << i << ” ”;
j = 0;
while (j <= n) {
cout << setw(4) << i*j << ” ”;
j += 1;
}
cout << endl;
i += 1;
}
}

8 / 10

Seite 4 von 5
Ein-/Ausgabe in C++ Programmieren I für Studierende der Mathematik — SoSe 2026

Formatierte Eingabe

cin >> arg1 >> arg2 >> . . .

• „Richtung“ der shift-Operatoren als Pfeile symbolisieren Flussrichtung der Daten


• Ausdrücke arg1 , arg2 , . . . (zunächst) nur Variablen, nicht beliebige Ausdrücke
• Variable in Operand fungiert als Referenz auf Speicherstelle (Details später)
• Seiteneffekte von cin >> arg1 :
• Anhalten des Programms bis Eingabe „zur Verfügung steht“ (Buffering i.d.R.
Zeilenweise)
• Lesen von Zeichen von der Eingabe, solange bis Zeichen nicht Teil sein kann von
Darstellung von Wert vom Typ von arg1
• Gelesene Zeichen als Wert interpretieren (parsing; abhängig vom Typ von arg1 )
• Wert in Variable arg1 speichern
• Bei Fehler: Keine Änderung von arg1 , Programm fortführen (Möglichkeit(en) zur
Fehlerbehandlung später)
9 / 10

I/O-Manipulatoren für Eingabe

skipws
char c; cin >> skipws >> c;
cout << ”'” << c << ”'” << endl;
skipws, noskipws Leerzeichen/Zeilenumbrüche
vor Darstellung von Wert c: a
ignorieren? (Voreinst.: ja) 'a'

dec, oct, hex Einlesen von Zahlen zur Basis noskipws


10 (Voreinst.), Basis 8 oder
char c; cin >> noskipws >> c;
Basis 16 (/[0-9]/ | /[a-f]/)
cout << ”'” << c << ”'” << endl;

c: a
' '

10 / 10

Seite 5 von 5
Programmieren I für Studierende der Mathematik SoSe 2026

Arithmetik in C++

Ganzzahl- und Gleitpunktarithmetik

Ganzzahldatentypen • bool ⊆ char ⊆ short ⊆ int ⊆ long ⊆ long long


• Kleiner Zahlenbereich z.B. −2,1 · 109 . . . 2,1 · 109
• Nur ganze Zahlen
• Exakte Berechnungen
• Ganzzahldivision
• Verlassen des Zahlenbereichs:
Vorzeichenbehaftet Undefiniertes Verhalten!
Vorzeichenlos mod (max𝑇 + 1)
Gleitpunktdatentypen • float ⊆ double ⊆ long double
• Größerer Zahlenbereich bei gleichem Speicherbedarf z.B.
−3,4 · 1038 . . . 3,4 · 1038
• Nur beschränkte Genauigkeit (∃𝑁 ∀𝑥 > 𝑁 . 𝑥 + 1 = 𝑥)
• I.d.R. Ganzzahldatentyp ⊄ Gleitpunktdatentyp
• Berechnungen korrekt bis auf Rundung ⇒ nicht assoziativ!
• Spezielle Werte NaN, ±∞, −0
1 / 10

Beispiel: Bereichsüberlauf

[Link] a, b: 1000 2000


2000000
#include <iostream>

using namespace std; a, b: 1000000 2000000


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

cout << (a * b) << endl;


}

2 / 10

Seite 1 von 5
Arithmetik in C++ Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Ungenauigkeit v. Gleitpunktarithmetik

float_inc_narrow.cpp 16777213.0 16777214.0


16777214.0 16777215.0
#include <iostream> 16777215.0 16777216.0
#include <iomanip> 16777216.0 16777216.0

using namespace std;

int main() {
float prev = 0b11111111'11111111'11111100,
curr = prev + 1;
while (prev != curr) {
prev = curr; curr += 1;
cout << fixed << setprecision(1)
<< prev << ” ” << curr
<< endl;
}
}

3 / 10

Ganzzahldatentypen mit/ohne Vorzeichen

• Für 𝑇 Ganzzahldatentyp (vorzeichenbehaftet, z.B. int): unsigned 𝑇


• 𝑇 ∩ N ⊆ unsigned 𝑇
• Arithmetik auf nicht-vorzeichenbehaftetem Typ 𝑇 : mod (max𝑇 + 1)

4 / 10

Seite 2 von 5
Arithmetik in C++ Programmieren I für Studierende der Mathematik — SoSe 2026

Gleitpunktdatentypen

Darstellung einer Gleitpunktzahl 𝑥 als (𝑠, 𝑒, 𝑚) mit Basis 𝐵:

Õ
𝑡
𝑠 𝑒
𝑥 = (−1) · 𝑚 · 𝐵 𝑚= 𝑧𝑖 𝐵 −𝑖
𝑖=1

• (𝑧𝑖 )𝑖 ∈ {1,...,𝑡 } mit führenden Nullen: subnormal ⇒ weniger Präzision



• Zusätzlich: ±∞, NaN (z.B. −1), −0
• Math. korrektes Ergebnis von Operationen wird auf nächste Gleitpunktzahl
gerundet
𝑥 −rd(𝑥 )
• Rundungsfehler: 𝑥 ≤ 2·𝜀
• 𝜀 Maschinengenauigkeit

5 / 10

Auslöschung

Betrachte relativen Fehler 𝜀𝑥 −𝑦 von Näherung 𝑥 − 𝑦 an 𝑥 − 𝑦 in Abhängigkeit von


𝜀𝑥 := 𝑥 𝑥−𝑥 und 𝜀 𝑦 :

𝑥 − 𝑦 − (𝑥 − 𝑦) 𝑥 − 𝑦 − (𝑥 − 𝑦)
𝜀𝑥 −𝑦 = =
𝑥 −𝑦 𝑥 −𝑦
(1 + 𝜀𝑥 )𝑥 − (1 + 𝜀 𝑦 )𝑦 − (𝑥 − 𝑦)
=
𝑥 −𝑦
𝑥 𝑦
= 𝜀𝑥 − 𝜀𝑦
𝑥 −𝑦 𝑥 −𝑦

Beobachtung: 𝜀𝑥 −𝑦 beliebig groß für 𝑥 ≈ 𝑦


• Betrifft nicht nur Gleitpunktarithmetik sondern jede Berechnung mit
fehlerbehafteten Werten
• Effekt heißt Auslöschung
• Vermeiden durch Umformulierung des berechneten Problems (Vorkonditionierung)
6 / 10

Seite 3 von 5
Arithmetik in C++ Programmieren I für Studierende der Mathematik — SoSe 2026

Numerische Kenngrößen

• Header-Datei <limits>
• Implementierungsabhängig∗
• Konstante 𝑐 für arith. Typ 𝑇 (z.B. double): numeric_limits<𝑇 >::𝑐

Konstante float double int unsigned int

lowest() −3,4 · 1038 −1,8 · 10308


min() 1,18 · 10−38 2,23 · 10−308 −231 0
max() 3,4 · 1038 1,8 · 10308 231 − 1 232 − 1
digits10 6 15 9 9
epsilon() 1,19 · 10−7 2,22 · 10−16

numeric_limits<𝑇 >::infinity() →∞ numeric_limits<𝑇 >::quiet_NaN() → NaN


∗ g++-14.2.1 20241116, Linux (x86_64), CXXFLAGS: -std=c++14 7 / 10

Literale

Konstante Werte (v.A. Zahlen) im Programm: Literale

⟨Literal⟩ → ⟨Bool'Literal⟩ | ⟨Int'Literal⟩ | ⟨Float'Literal⟩


| ⟨Char'Literal⟩
| ⟨StringLiteral⟩
⟨Bool'Literal⟩ → “false” | “true”
⟨Char'Literal⟩ → “'” ⟨Zeichen⟩ “'”
⟨StringLiteral⟩ → “"” {⟨Zeichen⟩} “"”

8 / 10

Seite 4 von 5
Arithmetik in C++ Programmieren I für Studierende der Mathematik — SoSe 2026

Ganzzahlliterale

⟨Int'Literal⟩ → (⟨Dez'Literal⟩ | ⟨Okt'Literal⟩ | ⟨Hex'Literal⟩ | ⟨Bin'Literal⟩) [⟨Int'Suffix⟩]


⟨Dez'Literal⟩ → /[1-9]/ {[“'”] /[0-9]/}
⟨Okt'Literal⟩ → “0” {[“'”] /[0-7]/}
⟨Hex'Literal⟩ → (“0x” | “0X”) ⟨Hex'Ziffer⟩ {[“'”] ⟨Hex'Ziffer⟩}
⟨Hex'Ziffer⟩ → /[0-9]/ | /[a-f]/ | /[A-F]/
⟨Bin'Literal⟩ → (“0b” | “0B”) (“0” | “1”) {[“'”] (“0” | “1”)}
⟨Int'Suffix⟩ → (“u” | “U”) [(“l” | “L”) | (“ll” | “LL”)]
| ((“l” | “L”) | (“ll” | “LL”)) [“u” | “U”]
• Dezimale Ganzzahlliterale: keine führenden Nullen
• Stets Typ mit kleinstmöglichem Zahlenbereich; mindestens int
• Dezimal: immer mit Vorzeichen, außer mit “u”
• Andere Basen: vorzeichenlos wenn nötig
• “l” und “ll”: „längerer“ Ganzzahltyp

9 / 10

Gleitpunktliterale

⟨Float'Literal⟩ → ([⟨Ziffern⟩] “.” ⟨Ziffern⟩ | ⟨Ziffern⟩ “.”) [⟨Exponent⟩] [⟨Float'Suffix⟩]


| ⟨Ziffern⟩ ⟨Exponent⟩ [⟨Float'Suffix⟩]
⟨Exponent⟩ → (“e” | “E”) [“+” | “-”] ⟨Ziffern⟩
⟨Ziffern⟩ → /[0-9]/ {[“'”] /[0-9]/}
⟨Float'Suffix⟩ → (“f” | “F”) | (“l” | “L”)
• Immer Dezimal
• Zunächst: stets Typ double
• “f” → float
• “l” → long double

10 / 10

Seite 5 von 5
Programmieren I für Studierende der Mathematik SoSe 2026

Ausdrücke

Auswertung von Ausdrücken

• Auswertung eines Ausdrucks besteht aus:


• Berechnung des Werts
• Durchführen von Seiteneffekten
• Reihenfolge, insb. Auswertung von Teilausdrücken, i.d.R. nicht festgelegt
• Explizite Klammerung, Assoziativität und Bindungsstärke definieren Bedeutung von
Operatoren (Operatorvorrang)

1 / 17

Überblick: einige arith. Operatoren

Operatoren Sorte Beispiele Berechneter Wert Seiteneffekte

++ -- Postfix i++ i-- Urspr. Wert der Var. Inkr. bzw. Dekr.
++ -- Präfix ++i --i Neuer Wert der Var. Inkr. bzw. Dekr.
+ - Präfix +a -a Unverändert bzw.
Negation
* / % Zweistellig a*b a/b a%b Mult. bzw. Div. bzw.
Rest
+ - Zweistellig a+b a-b Add. bzw. Sub.
= Zweistellig a=b Neuer Wert Zuweisung

• Sortiert: Stärke Bindung oben ⇒ -a*b+c ≡ ((-a)*b)+c


2 / 17

Seite 1 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Auswertung i++ vs. ++i

Jeweils eine mögliche Auswertungsreihenfolge:


i = 5; k=i++ i = 5; k=++i
Anm.: k=i++ ≡ k=(i++) Anm.: k=++i ≡ k=(++i)

Teilausdruck Ber. Wert Seiteneffekt Teilausdruck Ber. Wert Seiteneffekt


i = 5 5 i←5 i = 5 5 i←5
i++ 5 i←6 ++i 6 i←6
k=i++ 5 k←5 k=++i 6 k←6

Danach: i = 6, k = 5 Danach: i = 6, k = 6

3 / 17

Implizite Typumwandlung von arith. Operanden

• Mind. ein Gleitpunkttyp → „längster“ beteiligter Gleitpunkttyp


• bool → int
• Beide mit Vorzeichen / Beide vorzeichenfrei → „längster“ beteiligter Ganzzahltyp
• Vorzeichenfrei ⊆ Mit Vorzeichen → Typ mit Vorzeichen
• Sonst: Vorzeichenfreie Variante von Typ mit Vorzeichen

Implizite Typumwandlung i.A. ist große Fehlerquelle

4 / 17

Seite 2 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026

Implizite Typumwandlung von Funktionsparametern/bei Zuweisungen

Argumente von Funktionsaufrufen bzw. Werte bei Zuweisungen werden implizit


konvertiert in Parametertyp den Funktion erwartet bzw. Typ des Ziels der Zuweisung
• Unverändert, wenn möglich

• positiver Wert v. Ganzzahltyp → vorzeichenloser Ganzzahltyp 𝑇 : modulo max𝑇 + 1


• true → 1, false → 0; {𝑛 | 𝑛 ≠ 0} → true, 0 → false

• Zahlentyp → Gleitpunkttyp: „nächster“ darstellbarer Wert


• Gleitpunkttyp → Ganzzahltyp: Nachkommastellen abschneiden (vgl. trunc)

Achtung: Konvertierung zwischen Gleitpunkt- und Ganzzahltypen können Präzision


verlieren in beide Richtungen

5 / 17

Explizite Typumwandlung

Explizite Typumwandlung
int i = 2, j = 3;
double x = i / j;
Mit static_cast
double y = static_cast<double>(i) / j;

x: 0.000000
y: 0.666667

6 / 17

Seite 3 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026

Logische Operatoren

Operatoren auf Typ bool (im Zweifel implizite Typkonvertierung):

&& false true || false true


! false true
false false false false false true
true false
true false true true true true

Bei &&, ||: rechte Seite wird nur ausgewertet, wenn nötig (Seiteneffekte!)

7 / 17

Beispiel: Häufige Fehler

Code-Fragment Bedeutung der Kondition Bemerkung

if (i == 1) {. . .} falls i den Wert 1 hat


if (i = 1) {. . .} immer weist i den Wert 1 zu
if (1 <= x && x <= 2) {. . .} wenn x in [1, 2]
if (1 <= x <= 2) {. . .} immer 1 <= x ist stets in {0, 1}
if (!(x > 0)) {. . .} wenn x ≤ 0
if (!x > 0) {. . .} wenn x = 0 !xliefert 0 für x ≠ 0
und 1 für x = 0

8 / 17

Seite 4 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026

Ternärer Operator

⟨Ausw'Ausdruck⟩ → ⟨Ausdruck⟩ “?” ⟨Ausdruck⟩ “:” ⟨Ausdruck⟩

Mit 𝑘 ? 𝑡 : 𝑓
• Falls 𝑘 zu true auswertet: identisch zu 𝑡
• Sonst: identisch zu 𝑓
• Seiteneffekte des jeweils anderen Teilausdrucks geschehen nicht

Collatz
while (n != 1) n = n%2 == 0 ? n/2 : 3*n + 1;

9 / 17

Komma-Operator

⟨KommaAusdruck⟩ → ⟨Ausdruck⟩ “,” ⟨Ausdruck⟩

• Linksassoziativ
• Mit 𝑎 , 𝑏
• Wertet 𝑎 aus (Seiteneffekte!)
• Verwirft Wert von 𝑎
• Dann identisch zu 𝑏

Häufiger Fehler: Verwechslung mit Semikolon “;”

10 / 17

Seite 5 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026

Binäroperatoren

Zu unterscheiden von logischen Operatoren

Ansatz: ganze Zahl 𝑘 hat Binärdarstellung als (𝑏𝑖 )𝑖=0,1,...,𝑡 −1 :


𝑡 −1
𝑘= 𝑏𝑖 2𝑖
𝑖=0

𝑏 31 𝑏 30 𝑏 29 𝑏 28 ··· 𝑏3 𝑏2 𝑏1 𝑏0

11 / 17

Binäroperatoren (Forts.)

Verknüpfungen &, |, ^, ~ agieren bit-weise:

& 0 1 | 0 1 ^ 0 1
~ 0 1
0 0 0 0 0 1 0 0 1
1 0
1 0 1 1 1 1 1 1 0

10 0 0 0 0 ··· 1 0 1 0
& & & & & & & &

12 0 0 0 0 ··· 1 1 0 0

0 0 0 0 ··· 1 0 0 0 8
12 / 17

Seite 6 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026

Shift-Operatoren

• << und >> berechnen Verschiebung der Binärdarstellung des linken Operanden
• Kein Seiteneffekt

𝑘 ≪ 0 := 𝑘 𝑘 ≫ 0 := 𝑘

𝑡 −2 ∑
𝑡 −1
𝑘 ≪ 1 := 𝑏𝑖 2𝑖+1 𝑘 ≫ 1 := 𝑏𝑖 2𝑖 −1
𝑖=0 𝑖=1
𝑘 ≪ (𝑛 + 1) := (𝑘 ≪ 1) ≪ 𝑛 𝑘 ≫ (𝑛 + 1) := (𝑘 ≫ 1) ≫ 𝑛

Oder, äquivalent:

𝑘 ≪ 𝑛 = 𝑘 · 2𝑛 mod 2𝑡 𝑘 ≫ 𝑛 = ⌊𝑘 · 2−𝑛 ⌋

13 / 17

Beispiel: 3 << 2

3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 12

14 / 17

Seite 7 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: 𝑏 3 von x? Bitmaske

1 0 0 0 0 ··· 0 0 0 1

1 << 3 0 0 0 0 ··· 1 0 0 0
& & & & & & & &

x 𝑏 31 𝑏 30 𝑏 29 𝑏 28 ··· 𝑏3 𝑏2 𝑏1 𝑏0

1 << 3 & x 0 0 0 0 ··· 𝑏3 0 0 0

(1 << 3 & x) >> 3 0 0 0 0 ··· 0 0 0 𝑏3

15 / 17

Beispiel: 𝑏 3 ←0

1 0 0 0 0 ··· 0 0 0 1

1 << 3 0 0 0 0 ··· 1 0 0 0
~ ~ ~ ~ ~ ~ ~ ~

~(1 << 3) 1 1 1 1 ··· 0 1 1 1


& & & & & & & &

x 𝑏 31 𝑏 30 𝑏 29 𝑏 28 ··· 𝑏3 𝑏2 𝑏1 𝑏0

~(1 << 3) & x 𝑏 31 𝑏 30 𝑏 29 𝑏 28 ··· 0 𝑏2 𝑏1 𝑏0

16 / 17

Seite 8 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: 𝑏 3 ←1

1 0 0 0 0 ··· 0 0 0 1

1 << 3 0 0 0 0 ··· 1 0 0 0
| | | | | | | |

x 𝑏 31 𝑏 30 𝑏 29 𝑏 28 ··· 𝑏3 𝑏2 𝑏1 𝑏0

1 << 3 | x 𝑏 31 𝑏 30 𝑏 29 𝑏 28 ··· 1 𝑏2 𝑏1 𝑏0

17 / 17

Seite 9 von 9
Programmieren I für Studierende der Mathematik SoSe 2026

Mathematische Standardfunktionen

Mathematische Standardfunktionen (<cmath>, Auszug)

abs(𝑥 ) fabs(𝑥 ) Liefert |𝑥 |


acos(𝑥 ) asin(𝑥 ) atan(𝑥 ) Trigonometrische Umkehrfunktionen
cos(𝑥 ) sin(𝑥 ) tan(𝑥 ) Trigonometrische Funktionen
acosh(𝑥 ) asinh(𝑥 ) Hyperbolische Umkehrfunktionen
atanh(𝑥 )
cosh(𝑥 ) sinh(𝑥 ) tanh(𝑥 ) Hyperbolische Funktionen
exp(𝑥 ) exp2(𝑥 ) Liefert 𝑒 𝑥 bzw. 2𝑥
log(𝑥 ) log2(𝑥 ) log10(𝑥 ) Liefert ln(𝑥) bzw. log2 (𝑥) bzw. log10 (𝑥)
ldexp(𝑥 , 𝑘) pow(𝑥 , 𝑦) Liefert 𝑥 · 2𝑘 bzw. 𝑥 𝑦
√ √
sqrt(𝑥 ) cbrt(𝑥 ) Liefert 𝑥 bzw. 3 𝑥
floor(𝑥 ) ceil(𝑥 ) trunc(𝑥 ) Liefert ⌊𝑥⌋ bzw. ⌈𝑥⌉ bzw. sign(𝑥) · ⌊|𝑥 |⌋
round(𝑥 ) Liefert Ergebnis kaufmännischer Rundung von 𝑥
fmod(𝑥 ,𝑦) Liefert Wert 𝑓 ∈ (−𝑦, 𝑦) sodass 𝑓 = 𝑥 − 𝑛𝑦 mit 𝑛 ∈ Z
copysign(𝑥 , 𝑦 ) Liefert sign(𝑦) · |𝑥 |
1/4

frexp und modf (<cmath>)

• 𝑥 Wert von Gleitpunkttyp 𝑇


• 𝑛 Name einer Variable vom Typ int
• 𝑧 Name einer Variable vom Typ 𝑇

frexp(𝑥 , &𝑛 ) Liefert Wert 𝑚 mit |𝑚| ∈ [ 1/2, 1) ∪ {0} und speichert
Wert 𝑒 in Variable 𝑛, sodass 𝑥 = 𝑚 · 2𝑒
modf(𝑥 , &𝑧 ) Liefert Wert 𝑓 ∈ Z und speichert Wert 𝑖 in Variable
𝑧 mit sign(𝑥) = sign(𝑖) = sign(𝑓 ), sodass 𝑓 + 𝑖 = 𝑥

2/4

Seite 1 von 2
Mathematische Standardfunktionen Programmieren I für Studierende der Mathematik — SoSe 2026

Klassifizierung von Werten von Gleitpunkttyp (<cmath>)

Liefern Wert vom Typ bool

isfinite(𝑥 ) 𝑥 ∉ {±∞}
isinf(𝑥 ) 𝑥 ∈ {±∞}
isnan(𝑥 ) 𝑥 = NaN
isnormal(𝑥 ) 𝑥 normalisiert

3/4

atan2 (<cmath>)

(𝑦)
• atan2(𝑦 , 𝑥 ) berechnet tan−1 .
𝑥 𝑦
• Vorzeichen von 𝑥, 𝑦 bestimmt
Quadranten des Ergebnis 𝜗
𝑥

𝜋
Hier 2 < 𝜗 < 𝜋, da 𝑥 < 0 und 𝑦 > 0

4/4

Seite 2 von 2
Programmieren I für Studierende der Mathematik SoSe 2026

Fehlerbehandlung bei Standardfunktionen

Fehlerbehandlung

• Programme werden oft „mit Lücken“ konzipiert, z.B.:


• Algorithmus zur Durchführung einer Division ignoriert Fall 𝑥/0
• Öffnen einer Datei zum Lesen prüft nicht ob Berechtigung hierzu besteht
• Zuweisen von Speicher für eine Datenstruktur prüft nicht ob genug Speicher zur
Verfügung steht
• Tradeoff: bequeme Interfaces (z.B. einfach / als Divisionsoperator) vs. korrektes
Verhalten in allen Situationen
• Üblicherweise „seltene“ Fehlerfälle out-of-band behandeln um Interfaces bequem zu
halten

1/6

Motivation zur Fehlerbehandlung mit errno

• sqrt(-1) liefert NaN


• Was soll isqrt(-1) liefern?
• Ansatz: Kommunikation der
Fehlerkondition außerhalb des
Rückgabewerts double sqrt(double x) { ... }
• globale Variable errno int isqrt(int x) { ... }
• isqrt(-1) verändert errno
• Aufrufender Code kann errno
überprüfen
• Exceptions in Pro2

2/6

Seite 1 von 3
Fehlerbehandlung bei Standardfunktionen Programmieren I für Studierende der Mathematik — SoSe 2026

Fehlerbehandlung mit errno (<cerrno>)

• Header-Datei <cerrno>
• Vereinbart globale Variable int errno
• Konvention: Standardfunktionen setzen bei Fehler Wert von errno
• Numerischer Wert kommuniziert Fehlersorte
• Wenn kein Fehler: errno wird nicht verändert ⇒ vorher auf 0 setzen

3/6

Fehler-Konstanten (<cerrno>, Auszug)

Numerischer Wert ist implementierungsabhängig∗ .

Konstante Wert Bedeutung

EDOM 33 Math. Funktion wurde mit Argument außerhalb ihres


Definitionsbereichs aufgerufen (Definitionsbereichsfehler)

ERANGE 34 Ergebnis der Funktion außerhalb ihres Wertebereichs oder nicht


darstellbar (Wertebereichsfehler)

EINVAL 22 Funktion wurde mit unzulässigem Argumentwert aufgerufen

∗ g++-14.2.1 20241116, Linux (x86_64), CXXFLAGS: -std=c++14 4/6

Seite 2 von 3
Fehlerbehandlung bei Standardfunktionen Programmieren I für Studierende der Mathematik — SoSe 2026

strerror (<cstring>)

• Header-Datei <cstring>
• Funktion strerror nimmt int-Argument (i.d.R. errno)
• strerror(𝑛 ) liefert Wert, der ausgegeben werden Kann
• Beschreibung der Fehlersorte

5/6

Beispiel: Fehlerbehandlung mit errno

errno_demo.cpp sqrt(-1) = -nan


33 Numerical argument out of domain
#include <iostream> exp(10'000) = inf
#include <iomanip> 34 Numerical result out of range
#include <cmath> log(0) = -inf
#include <cerrno> 34 Numerical result out of range
#include <cstring> atan2(0, 0) = 0
0 Success
using namespace std;

int main() {
cout << ”sqrt(-1) = ” << sqrt(-1.) << endl;
cout << setw(2) << errno << ” ” << strerror(errno) << endl;

errno = 0;
cout << ”exp(10'000) = ” << exp(10'000.) << endl;
cout << setw(2) << errno << ” ” << strerror(errno) << endl;

errno = 0;
cout << ”log(0) = ” << log(0.) << endl;
cout << setw(2) << errno << ” ” << strerror(errno) << endl;

errno = 0;
cout << ”atan2(0, 0) = ” << atan2(0., 0.) << endl;
cout << setw(2) << errno << ” ” << strerror(errno) << endl;
}

6/6

Seite 3 von 3
Programmieren I für Studierende der Mathematik SoSe 2026

Kontrollstrukturen

else-if-Kaskaden

Geschachtelte if-Abfragen:

if (𝐵 1 ) {

} else if (𝐵 2 ) {

} else if (𝐵 3 ) {

..
.

} else if (𝐵𝑛 ) {

} else {

}

1 / 10

Syntaktische Zweideutigkeit bei if mit else

Was bedeutet Folgendes?

if (𝐵 1 ) if (𝐵 2 ) 𝑆 1 else 𝑆 2

if (𝐵 1 ) if (𝐵 1 )
if (𝐵 2 ) if (𝐵 2 )
𝑆1 𝑆1
else else
𝑆2 𝑆2

Regel: else bezieht sich immer auf letztes if, das noch kein else hat

2 / 10

Seite 1 von 5
Kontrollstrukturen Programmieren I für Studierende der Mathematik — SoSe 2026

do-while-Schleifen

⟨Wiederholanw'⟩ → . . .
| “do” ⟨Anweisung⟩ “while” “(” ⟨Ausdruck⟩ “)” “;”

do { 𝑆 }
𝑆 while (𝐶 )
while (𝐶 ); 𝑆

3 / 10

Beispiel: euklidischer Algorithmus

[Link] a, b: 143 65
ggT(143, 65) = 13
#include <iostream>

using namespace std; a, b: 3528 3780


ggT(3528, 3780) = 252
unsigned int gcd(unsigned int a, unsigned int b) {
if (a == 0) return b;
if (b == 0) return a;

do {
int h = a % b;
a = b;
b = h;
} while (b != 0);

return a;
}

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

cout << ”ggT(” << a << ”, ” << b << ”) = ”


<< gcd(a, b) << endl;
}

4 / 10

Seite 2 von 5
Kontrollstrukturen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: wiederholte Eingabe

sqrt_check.cpp x: -4
x: -1
#include <iostream> x: 2
#include <cmath> sqrt(2) = 1.41421

using namespace std;

int main() {
double x;
do {
cout << ”x: ”;
cin >> x;
} while (x < 0);

cout << ”sqrt(” << x << ”) = ” << sqrt(x) << endl;


}

5 / 10

for-Schleifen

⟨Wiederholanw'⟩ → . . .
| “for” “(” [⟨Init'anw'⟩] “;” [⟨Kondition⟩] “;” [⟨Ausdruck⟩] “)”
⟨Init'anw'⟩ → ⟨Ausdruck⟩
| {⟨Dekl'Spez'⟩}+ ⟨Init'Dekl'⟩ {“,” ⟨Init'Dekl'⟩}

{
𝐼
while (𝐶 ) {
for (𝐼 ; 𝐶; 𝑈 )
𝑆
𝑆
𝑈 ;
}
}

6 / 10

Seite 3 von 5
Kontrollstrukturen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Potenz

[Link] a, n: 1.4142 0
pot(1.4142, 0) = 1
#include <iostream>

using namespace std; a, n: 1.4142 2


pot(1.4142, 2) = 1.99996
double pot(double a, unsigned int n) {
double pot = 1;
for (unsigned int i = 0; i < n; i++)
pot *= a;
return pot;
}

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

cout << ”pot(” << a << ”, ” << n << ”) = ” << pot(a, n)


<< endl;
}

7 / 10

„Rezept“: Iterationsverfahren mit for

// Vereinbarung itmax, x0, f

• Es seien: double eps


= numeric_limits<double>::epsilon();
• 𝐼 ⊂R
• 𝜑: 𝐼 → 𝐼
double xa = x0, xn = f(xa);
• 𝑥 0 ∈ 𝐼 der Startwert
for
• Betrachte (𝑥𝑖 )𝑖=0,1,... mit 𝑥𝑖+1 = 𝜑 (𝑥) ( int i = 0;
• Abbruch: abs(xn - xa) > eps && i <= itmax;
i++
• sobald 𝑥𝑛 nahe 𝑥𝑛+1 , oder
) {
• mehr als 𝑖 max Iterationen
xa = xn;
xn = f(xa);
}

8 / 10

Seite 4 von 5
Kontrollstrukturen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Binomialkoeffizient
[Link]

#include <iostream>

using namespace std;

unsigned int binom(unsigned int n, unsigned int k) {


( ) unsigned int b = 1;

𝑛 for (unsigned int i = 1; i <= k; n--, i++)


b = b*n/i;
=1 return b;
0
( ) ( )
}

𝑛 𝑛 𝑛 −𝑖 int main() {
= · unsigned int n, k;
𝑖 +1 𝑖 𝑖 +1 cout << ”n, k: ”;
cin >> n >> k;

cout << ”binom(” << n << ”, ” << k << ”) = ” << binom(n, k)


<< endl;
}

n, k: 6 3
binom(6, 3) = 20

n, k: 7 2
binom(7, 2) = 21

9 / 10

break- & continue-Anweisung

Innerhalb von Schleifenkörpern:

break Setze Ausführung fort, direkt nach der Schleife


continue Setze Ausführung fort, direkt vor Ende des Schleifenkörpers

10 / 10

Seite 5 von 5
Programmieren I für Studierende der Mathematik SoSe 2026

Arrays

Arrays

• Arrays sind Speicherbereiche; speichern ≥ 1 Wert von gleichem Typ


• Konstante Länge, festgelegt bei Deklaration
• Array-Typen „Array von 𝑁 𝑇 “, z.B. „Array von 3 int“

1/8

Vereinbarung von Arrays

Regel: wenn 𝑇 𝐷 Vereinbarung von Variable 𝐷 mit Typ 𝑇 , dann 𝑇 𝐷 [𝑁 ] Vereinbarung


von 𝐷 mit Typ „Array von 𝑁 𝑇 “

int a[3]; double b[50], c[2];

Initialisierung mit Initialisierungsliste mit geschweiften Klammern


Sonst (bzw. auch Rest) standard-initialisiert

int a[3] = {3, 2, 1}; int b[3] = {1, 2};

Länge kann bei Initialisierung autom. inferiert werden

int a[] = {3, 2, 1};

2/8

Seite 1 von 4
Arrays Programmieren I für Studierende der Mathematik — SoSe 2026

Zugriff auf Array-Elemente

• Wert von Typ „Array von 𝑁 𝑇 “ hat Elemente mit Indizes 0, . . . , 𝑁 − 1


• Zugriff auf Elemente mit Subscript-Operator []
• I.W. keine Operationen auf Array als Ganzes z.B. Zuweisung, Kopieren, etc.
• Zugriff mit invalidem Index ⇒ undefiniertes Verhalten

3/8

Beispiel: Zugriff auf Array-Elemente

array_access.cpp 9.42 20.25 21.56

#include <iostream>

using namespace std;

int main() {
double x[3] = { 3.14, 1.41, 2.72 };

for (unsigned int i = 0; i < 3; i++)


x[i] += x[0] * 2;

for (unsigned int i = 0; i < 3; i++)


cout << x[i] << ” ”;
cout << endl;
}

4/8

Seite 2 von 4
Arrays Programmieren I für Studierende der Mathematik — SoSe 2026

Arrays als Funktionsparameter

• Längenangaben von Array-Funktionsparametern möglich, werden aber ignoriert


• Übergabe ist mit Referenz-Semantik, d.h. Änderungen der Elemente innerhalb der
Funktion wirken sich außerhalb der Funktion aus

5/8

Beispiel: Arrays als Funktionsparameter

array_param.cpp 2
4
#include <iostream> 6

using namespace std;

void scalar_mult(unsigned int dim, double x, double vec[]) {


for (unsigned int i = 0; i < dim; i++)
vec[i] *= x;
}

int main() {
double vec[3] = {1, 2, 3};

scalar_mult(3, 2, vec);
for (unsigned int i = 0; i < 3; i++)
cout << vec[i] << endl;
}

6/8

Seite 3 von 4
Arrays Programmieren I für Studierende der Mathematik — SoSe 2026

Mehrdimensionale Arrays

Regel: wenn 𝑇 𝐷 Vereinbarung von Variable 𝐷 mit Typ 𝑇 , dann 𝑇 𝐷 [𝑁 ] Verein-


barung von 𝐷 mit Typ „Array von 𝑁 𝑇 “

• Regel auch rekursiv


• D.h. Typen der Form z.B. „Array von 𝑁 1 Array von 𝑁 2 𝑇 “ zulässig
• Vereinbarung dann z.B. 𝑇 𝐷 [𝑁 1 ][𝑁 2 ]

7/8

Beispiel: Mehrdimensionale Arrays


[Link] cout << ” ” << b[row] << endl;
}
#include <iostream>

using namespace std; m[0][0]: 1


m[0][1]: 2
int main() { m[0][2]: 3
double m[3][3] = { m[1][0]: 4
{ 0.0, 0.0, 0.0 }, m[1][1]: 5
{ 0.0, 0.0, 0.0 }, m[1][2]: 6
{ 0.0, 0.0, 0.0 } m[2][0]: 7
}; m[2][1]: 8
double a[3], b[3] = {0.0, 0.0, 0.0}; m[2][2]: 9
a[0]: 1
for (unsigned int row = 0; row < 3; row++) a[1]: 2
for (unsigned int col = 0; col < 3; col++) { a[2]: 3
cout << ”m[” << row << ”][” << col << ”]: ”; m*a:
cin >> m[row][col]; 14
} 32
50
for (unsigned int row = 0; row < 3; row++) {
cout << ”a[” << row << ”]: ”;
cin >> a[row];
}

for (unsigned int i = 0; i < 3; i++)


for (unsigned int j = 0; j < 3; j++) {
b[i] += m[i][j] * a[j];
}

cout << ”m*a:” << endl;


for (unsigned int row = 0; row < 3; row++)
8/8

Seite 4 von 4
Programmieren I für Studierende der Mathematik SoSe 2026

Vektoren

Vektoren

• Header-Datei <vector>
• Für Typ 𝑇 ist vector<𝑇 > Behälter (container) mit beliebig vielen (auch null)
Elementen vom Typ 𝑇
• Indizes der Elemente 0, . . . vom Typ vector<𝑇 >::size_type vorzeichenlos

1/6

„Vektor“ vs. „Array“

• Arrays aus C übernommen ⇒ historisch


• Vektor erlauben Anpassen der Länge zur Laufzeit
• Viel mehr Methoden und Operationen auf Vektoren, auch als Ganzes
• Arrays nicht konsistent Werte-Semantik

• Vektoren i.A. höherer Speicherbedarf (Reservierung in Zweierpotenzen für spätere


Vergrößerung)
• Matrixelemente nicht direkt hintereinander gespeichert (schlechtere cache
coherence)
• Zweifach indirekter Zugriff auf Matrixelemente i.d.R. nicht optimiert

Für effiziente Numerik (später) auch bessere Vektor-artige Datenstrukturen vorhanden


⇒ Arrays nicht in neuem Code

2/6

Seite 1 von 3
Vektoren Programmieren I für Studierende der Mathematik — SoSe 2026

Operationen auf Vektoren (Auszug)

𝑇 ein Datentyp; 𝑛, 𝑘 < 𝑛 Werte vom Typ vector<𝑇 >::size_type; 𝑡, 𝑡 0, . . . , 𝑡𝑛−1 Werte
vom Typ 𝑇 ; 𝑎, 𝑎 ′ Werte vom Typ vector<𝑇 >

vector<𝑇 > 𝑎 Vereinbart Variable 𝑎 vom Typ vector<𝑇 >, Länge 0


vector<𝑇 > 𝑎 (𝑛 ) , Länge 𝑛 und allen Elementen
standard-initialisiert
vector<𝑇 > 𝑎 (𝑛 , 𝑡 ) , Länge 𝑛 und allen Elementen Kopien von 𝑡
vector<𝑇 > 𝑎 = {𝑡 0 , . . ., 𝑡𝑛−1 } , Länge 𝑛 und Elem' Kopien von 𝑡 0, . . . , 𝑡𝑛−1
vector<𝑇 > 𝑎 {𝑡 0 , . . ., 𝑡𝑛−1 }
𝑎 .at(𝑘 ) Element mit Index 𝑘; 𝑘 invalide: bricht Programm ab
𝑎 [𝑘 ] : undefiniertes Verhalten

3/6

Operationen auf Vektoren (Auszug)

𝑎 = 𝑎 ′ 𝑎 = {𝑡 0 , . . ., 𝑡𝑛−1 } Zuweisung; kopiert


𝑎 == 𝑎 ′ 𝑎 != 𝑎 ′ 𝑎 > 𝑎 ′ Lexikographisch
𝑎 < 𝑎 ′ 𝑎 <= 𝑎 ′ 𝑎 >= 𝑎 ′
𝑎 .size() Liefert Länge von 𝑎
𝑎 .resize(𝑛 ) 𝑎 .resize(𝑛 , 𝑡 ) Setzt Länge von 𝑎 auf 𝑛; standard-initialisiert bzw.
Kopie(n) von 𝑡
𝑎 .push_back(𝑡 ) Hängt Kopie von 𝑡 hinten an
𝑎 .pop_back() Löscht letztes Element
𝑎 .clear() Setzt Länge von 𝑎 auf 0, löscht alle Elemente

4/6

Seite 2 von 3
Vektoren Programmieren I für Studierende der Mathematik — SoSe 2026

Besipiel: Euklidische Norm

[Link] n: 3
a[0] ... a[2]: 3 2 1
#include <iostream> ||a|| = 3.74166
#include <vector>
#include <cmath>
n: 2
using namespace std; a[0] ... a[1]: 4 3
||a|| = 5
double norm(vector<double> x) {
double s = 0;
for (vector<double>::size_type i = 0; i < [Link](); i++)
s += x[i] * x[i];
return sqrt(s);
}

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

vector<double> a(n);
cout << ”a[0] ... a[” << n - 1 << ”]: ”;
for (vector<double>::size_type i = 0; i < [Link](); i++)
cin >> a[i];

cout << ”||a|| = ” << norm(a) << endl;


}

5/6

Beispiel: Mehrdimensionale Vektoren


matvec_stl.cpp cin >> x[j];

#include <iostream> vector<double> y = matvecmult(a, x);


#include <vector> cout << ”a*x: ” << endl;
for (unsigned int i = 0; i < m; i++)
using namespace std; cout << ” ” << y[i] << endl;
}
vector<double> matvecmult( vector<vector<double>> a
, vector<double> x
) { m, n: 2 3
unsigned int m = [Link](), n = [Link](); a:
vector<double> y(m, 0.0); 1 2 3
for (unsigned int i = 0; i < m; i++) 4 5 6
for (unsigned int j = 0; j < n; j++) x:
y[i] += a[i][j] * x[j]; -1
return y; 0
} 2
a*x:
int main() { 5
unsigned int m, n; 8
cout << ”m, n: ”;
cin >> m >> n;

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


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

vector<double> x(n);
cout << ”x: ”;
for (unsigned int j = 0; j < n; j++)
6/6

Seite 3 von 3
Programmieren I für Studierende der Mathematik SoSe 2026

Zeichen

Zeichen

• Zeichen sind Werte vom Typ char


• Code implementierungsabhängig; Minimalzeichensatz (≥ASCII) immer positiv

30 40 50 60 70 80 90 100 110 120


0 ( 2 < F P Z d n x
1 ) 3 = G Q [ e o y
2 ␠ * 4 > H R \ f p z
3 ! + 5 ? I S ] g q {
4 " , 6 @ J T ^ h r |
5 # - 7 A K U _ i s }
6 $ . 8 B L V ` j t ~
7 % / 9 C M W a k u ␡
8 & 0 : D N X b l v
9 ' 1 ; E O Y c m w
ASCII
1/4

Zeichen-Literale

⟨Char'Literal⟩ → “'” ⟨Zeichen⟩ “'”


⟨Zeichen⟩ → ⟨Einf'Zeichen⟩ | ⟨Einf'Ersatz⟩ | ⟨Okt'Zeichen⟩ | ⟨Hex'Zeichen⟩
⟨Einf'Ersatz⟩ → “\'” | “\"” | “\?” | “\\” | “\a” | “\b” | “\f” | “\n” | “\r” | “\t” | “\v”
⟨Okt'Zeichen⟩ → “\” /[0-7]/ [/[0-7]/] [/[0-7]/]
⟨Hex'Zeichen⟩ → “\x” {/[0-9a-fA-F]/}+

“\'” “\"” Einfaches Anführungszeichen bzw. doppeltes Anführungszeichen bzw.


“\?” “\\” Fragezeichen bzw. Backslash
“\n” Zeilenumbruch
“\r” „Wagenrücklauf“; manchmal “\r\n” Darstellung eines Zeilenumbruchs
“\t” Tabulator

char c; c = '\n'; c = '\''; c = '\\';


c = 'A'; c = '5'; c = '*'; c = ' '; c = '\0'; c = '\40'; c = '\x20';

2/4

Seite 1 von 2
Zeichen Programmieren I für Studierende der Mathematik — SoSe 2026

Klassifizierung/Umwandlung von Zeichen

• Header-Datei <locale>
• locale() liefert „default“-Wert vom Typ locale
• Klassifizierungsfunktionen nehmen 𝑙 vom Typ locale als zweiten Parameter

isspace(𝑐 , 𝑙 ) Liefert bool, ob 𝑐 Zwischenraumzeichen


isprint(𝑐 , 𝑙 ) druckbares Zeichen
isupper(𝑐 , 𝑙 ) Großbuchstabe
islower(𝑐 , 𝑙 ) Kleinbuchstabe
isalpha(𝑐 , 𝑙 ) Buchstabe
isdigit(𝑐 , 𝑙 ) Dezimalziffer (/[0-9]/)
tolower(𝑐 , 𝑙 ) Liefert char, zu 𝑐 korrespondierer Klein- bzw. Großbuchstabe
toupper(𝑐 , 𝑙 )

3/4

Beispiel: Klassifizierung von Zeichen

classify_char.cpp Zeichen: ⇆
Code: 9, Steuerzeichen, Zwischenraumzeichen
#include <iostream> Zeichen:
#include <iomanip> Code: 32, Zwischenraumzeichen
#include <locale> Zeichen: .
Code: 46, Interpunktion
using namespace std; Zeichen: 0
Code: 48, Ziffer
int main() { Zeichen: A
char c, newl; Code: 65, Buchstabe
while (cout << ”Zeichen: ”, cin >> noskipws >> c >> newl) { Zeichen: a
cout << ”Code: ” << setw(3) << static_cast<int>(c); Code: 97, Buchstabe
Zeichen:
if (iscntrl(c, locale()))
cout << ”, Steuerzeichen”;
if (isspace(c, locale()))
cout << ”, Zwischenraumzeichen”;
if (ispunct(c, locale()))
cout << ”, Interpunktion”;
if (isdigit(c, locale()))
cout << ”, Ziffer”;
if (isalpha(c, locale()))
cout << ”, Buchstabe”;

cout << endl;


}
}

4/4

Seite 2 von 2
Programmieren I für Studierende der Mathematik SoSe 2026

Zeichenketten

C-Zeichenketten

• Werte vom Typ „Array von char“ unspezifizierter Länge


• Null-Byte \0 ist Marker für Ende der Zeichenkette (Fehleranfällig!)

⟨StringLiteral⟩ → “"” {⟨Zeichen⟩} “"”


String-Literale sind korrekt \0-terminierte C-Zeichenketten
Wegen Nachteilen von Arrays zzgl. Nachteilen von \0-Terminierung
⇒ Vermeiden

1/7

Strings

• Header-Datei <string>
• Typ string analog zu vector ebenfalls Behälter
• Längen/Indizes vom Typ string::size_type
• Automatische Konvertierung von/nach C-Zeichenketten, insb. Literale

2/7

Seite 1 von 4
Zeichenketten Programmieren I für Studierende der Mathematik — SoSe 2026

Operationen auf Strings (Auszug)

𝑠 Wert vom Typ string; 𝑡 Wert vom Typ string oder C-Zeichenkette; c Wert vom Typ
char; 𝑖, 𝑛 Werte vom Typ string::size_type; 𝑖, 𝑜 Ein- bzw. Ausgabestrom

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


string 𝑠 (𝑡 ) , Inhalt Kopie von 𝑡 bzw. 𝑠𝐶
string 𝑠 (𝑡 , 𝑖 ) , Inhalt Kopie von 𝑡 ab Index 𝑖 bis Ende
string 𝑠 (𝑡 , 𝑖 , 𝑛 ) , Inhalt Kopie von 𝑡 ab Index 𝑖 bis 𝑖 + 𝑛 − 1
(Länge maximal 𝑛)
𝑠 .at(𝑖 ) char mit Index 𝑘; 𝑘 invalide: bricht Programm ab
𝑠 [𝑖 ] : undefiniertes Verhalten

3/7

Operationen auf Strings (Auszug)

𝑠 =𝑡 𝑠 =𝑐 Zuweisung; kopiert
𝑠 +𝑡 𝑠 +𝑐 Liefert neuen String mit Kopie von 𝑠 konkateniert
mit 𝑡 bzw. 𝑐
𝑠 += 𝑡 𝑠 += 𝑐 Konkatenation
𝑠 == 𝑡 𝑠 != 𝑡 𝑠 > 𝑡 𝑠 < 𝑡 Lexikographisch
𝑠 >= 𝑡 𝑠 <= 𝑡
𝑠 .size() 𝑠 .length() Liefert Länge von 𝑠 mit Typ string::size_type
𝑠 .substr(𝑖 ) 𝑠 .substr(𝑖 , 𝑛 ) Liefert neuen string mit Kopie von 𝑠 ab Index 𝑖,
optional mit maximaler Länge 𝑛
𝑠 .find(𝑡 ) 𝑠 .find(𝑐 ) Sucht erstes bzw. letztes von Vorkommen von 𝑡, bzw.
𝑠 .find(𝑡 , 𝑖 ) 𝑠 .find(𝑐 , 𝑖 ) 𝑐 in 𝑠, optional erst ab Index 𝑖, nimmt index vom Typ
𝑠 .rfind(𝑡 ) 𝑠 .rfind(𝑐 ) string::size_type an, wenn gefunden, sonst
𝑠 .rfind(𝑡 , 𝑖 ) 𝑠 .rfind(𝑐 , 𝑖 ) string::npos

4/7

Seite 2 von 4
Zeichenketten Programmieren I für Studierende der Mathematik — SoSe 2026

Operationen auf Strings (Auszug)

𝑜 << 𝑠 𝑖 >> 𝑠 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 Kopie von 𝑖
als Wert an

5/7

Beispiel: Eingabe von Strings

string_io.cpp Methode? a
wort1 wort2 wort3
#include <iostream> »wort1«
#include <string>
#include <sstream>
Methode? b
using namespace std; wort1 wort2 wort3
»«
int main() {
char m, newl;
cout << ”Methode? ”; Methode? c
cin >> noskipws >> m >> newl; wort1 wort2 wort3
» wort1 wort2 wort3 «
string s;
if (m == 'a')
cin >> skipws >> s;
else if (m == 'b')
cin >> noskipws >> s;
else
getline(cin, s);

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

return 0;
}

6/7

Seite 3 von 4
Zeichenketten Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Zahldarstellung zur Basis 𝐵


[Link] }

#include <iostream>
#include <string> n: 1
Binaer: 1
using namespace std; Oktal: 1
Hex: 1
string itos(unsigned int base, unsigned int n) {
string s;
unsigned int digit; n: 3
Binaer: 11
do { Oktal: 3
digit = n % base; n /= base; Hex: 3
s += digit < 10 ? '0' + digit : 'A' + digit - 10;
} while (n != 0);
n: 15
Binaer: 1111
for (string::size_type i = 0; i < [Link]() / 2; i++) {
Oktal: 17
char c = s[i];
Hex: F
s[i] = s[[Link]() - (i + 1)];
s[[Link]() - (i + 1)] = c;
}
n: 17
Binaer: 10001
return s; Oktal: 21
} Hex: 11

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

cout << ”Binaer: ” << itos(2, n) << endl;


cout << ”Oktal: ” << itos(8, n) << endl;
cout << ”Hex: ” << itos(16, n) << endl;
7/7

Seite 4 von 4
Programmieren I für Studierende der Mathematik SoSe 2026

Ein-/Ausgabe Ströme

Buffering

• Ausgabe nicht immer sofort sondern gesammelt für Effizienz


• Eingabe hat Möglichkeit zum Zurückstellen von Zeichen
• Implementierung hiervon durch Buffer (Puffer):
Speicherbereich mit Platz für Zeichen zum später lesen/schreiben
• cin, cout, cerr zu Start des Programms synchronisiert
⇒ Ein-/Ausgabe umgeht buffer; Betriebssystem kontrolliert direkt
• Betriebssystem (nicht C++) kennt line-buffering
• cin, cout line-buffered gdw. Terminal
• Ein-/Ausgabe geschieht bei Zeilenumbrüchen (\n)

1 / 14

Unformatierte Ein-/Ausgabe

𝑠 ein Strom; 𝑐 Wert vom Typ char; 𝑣 Name einer Variable vom Typ char; 𝑖, 𝑜 ein Eingabe-
bzw. Ausgabestrom; 𝑠𝐶 eine hinreichend lange C-Zeichenkette; 𝑝 vorzeichenlose
Ganzzahl

𝑖 .get() Lese einzelnes Zeichen und liefere als int bzw. speichere in 𝑣
𝑖 .get(𝑣 )
𝑖 .putback(𝑐 ) Stelle 𝑐 vorne an buffer
𝑖 .unget() Setze „cursor-Position im Strom“ ein Zeichen zurück
𝑖 .peek() Liefert einzelnes Zeichen als int; schaltet cursor nicht weiter
𝑖 .read(𝑠𝐶 , 𝑛 ) Lese höchstens 𝑛 Zeichen und speichere in 𝑠𝐶
𝑖 .gcount() Liefert Anzahl von Zeichen gelesen von letzter unformatierter
Eingabefunktion (i.d.R. read)
𝑠 .tellg() Liefert aktuelle cursor-Position
𝑠 .seekg(p) Setzt aktuelle cursor-Position
𝑜 .put(𝑐 ) Schreibe einzelnes Zeichen
2 / 14

Seite 1 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026

Stromzustand

𝑠 .good() Keines der bits gesetzt?


𝑠 .eof() Strom „zuende“?
𝑠 .fail() Ist Fehler aufgetreten?
𝑠 .bad() War Fehler hinreichend schwerwiegend, dass Datenverlust?
𝑠 .clear() Setze Zustand, sodass danach 𝑠 .good()
if (𝑠 ) … Identisch zu if (!𝑠 .fail()) …

3 / 14

Beispiel: Stromzustand

cin >> ... int n; cin >> n; cout << ...


Eingabe: keine mehr Eingabe: abc Ausgabe nicht möglich
⇒ [Link]() == true ⇒ [Link]() == true ⇒ [Link]() == true

4 / 14

Seite 2 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026

Stromzustand (Forts.)

Stromzustand ist von Bitmaskentyp∗ ios::iostate:


• Bitmasken ios::badbit, ios::failbit, ios::eofbit
• Wert ios::goodbit, per Definition identisch zu 0

𝑠 .rdstate() Liefert aktuellen Zustand von 𝑠; im Folgenden 𝑧


𝑠 .setstate(𝑧 ′ ) Setze Zustand auf 𝑧 | 𝑧 ′
𝑠 .good() !s
𝑠 .eof() static_cast<bool>(𝑠 .rdstate() & ios::eofbit)
𝑠 .fail() static_cast<bool>(𝑠 .rdstate() & (ios::badbit | ios::failbit))
𝑠 .bad() static_cast<bool>(𝑠 .rdstate() & ios::badbit)
𝑠 .clear() Setze Zustand auf 0

∗ Verhalten
bzgl. Binäroperatoren wie vorzeichenloser Ganzzahldatentyp, int Wert 0 konvertierbar, sonst
implementierungsabhängig 5 / 14

Demo: Eingabe lesen

string gesamt; string gesamt;


for (string zeile; getline(cin, zeile); ) { for (char c; [Link](c); )
if ([Link]()) break; gesamt += c;
gesamt += zeile + '\n';
} • Abbruch bei „Ende der Eingabe“
• Auf Konsole: ctrl + D
• Abbruch bei erster leeren Zeile

6 / 14

Seite 3 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026

Ein-/Ausgabe auf Dateien

• Header-Datei <fstream>
• Strom-Datentypen ifstream, ofstream und fstream
• Öffnen von Datei (anhand ihres Namens) i.d.R. bei Initialisierung von Variable von
Datei-Stromtyp
• Konstruktoren der Datei-Stromtypen verwenden errno-Mechanismus um Fehler
beim Öffnen von Dateien zu kommunizieren
• Danach Ein-/Ausgabe genau wie bei anderen Strömen, also wie von cin/cout
bereits bekannt

7 / 14

Ein-/Ausgabe auf Dateien

Modus beeinflusst Verhalten beim Öffnen und ob lesbar bzw. schreibbar

ios::in Datei zum Lesen öffnen


ios::out Datei zum Schreiben öffnen
ios::app Beim Schreiben hinten an Datei anhängen
ios::ate Nach Öffnen cursor am Ende der Datei positionieren
ios::trunc Inhalt der Datei beim Öffnen löschen
ios::binary Binärmodus; keine auto. Übersetzung von Zeilenumbrüchen

in, binary
und ate dürfen frei miteinander und mit den folgenden ansonsten zulässigen
Modi kombiniert werden:

out trunc app Bedeutung


1 ∗ 0 Inhalt beim Öffnen löschen
∗ 0 1 Hinten anhängen
8 / 14

Seite 4 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026

Ein-/Ausgabe auf Dateien

𝑓 Dateiname vom Typ string (oder C-Zeichenkette); 𝑚 ein Zugriffsmodus vom Typ
ios::mode; 𝑖, 𝑜, 𝑏
Eingabe- bzw. Ausgabe- bzw. Ein-Ausgabe-Strom; 𝑠 beliebiger
Datei-Strom

ifstream 𝑖 { 𝑓 } Vereinbare 𝑖 vom Typ ifstream, öffne dabei 𝑓 zum Lesen (Modus
ifstream 𝑖 { 𝑓 , 𝑚} ios::in bzw. ios::in | 𝑚)
ofstream 𝑜 {𝑓 } Vereinbare 𝑜 vom Typ ofstream, öffne dabei 𝑓 zum Schreiben
ofstream 𝑜 { 𝑓 , 𝑚 } (Modus ios::out bzw. ios::out | 𝑚)
fstream 𝑏{𝑓 } Vereinbare 𝑏 vom Typ fstream, öffne dabei 𝑓 zum Schreiben und
fstream 𝑏 { 𝑓 , 𝑚} Lesen (Modus ios::out | ios:in bzw. 𝑚)

9 / 14

Beispiel: Dateien kopieren

[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};
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);
}

10 / 14

Seite 5 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Zeichen in Datei zählen

[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;


}

11 / 14

Ein-/Ausgabe auf Strings

• Problemstellung: besonders formatierte Ein-/Ausgabe-Operatoren sind gut


verstanden und bequem; wie benutzen um damit mit Texten zu interagieren die
nicht von Konsole oder aus einer Datei stammen?
• Ansatz: Behandle Wert vom Typ string wie eine Datei als Strom
• Header-Datei <sstream>
• Strom-Datentypen istringstream, ostringstream und stringstream
• Jedes Objekt dieser Typen unterliegt ein string; Zugriff auf diesen String durch
Methode .str()

12 / 14

Seite 6 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026

Ein-/Ausgabe auf Strings

𝑠 ein String; 𝑖, 𝑜, 𝑏 ein Eingabe- bzw. Ausgabe- bzw. Ein-/Ausgabestrom; 𝑎 ein beliebiger
Strom

istringstream 𝑖 Vereinbare 𝑖 vom Typ istringstream, Buffer initial leer bzw.


istringstream 𝑖 {𝑠 } Kopie von 𝑠, Modus ios::in

ostringstream 𝑜 Vereinbare 𝑜 vom Typ ostringstream, Buffer initial leer bzw.


ostringstream 𝑜 {𝑠 } Kopie von 𝑠, Modus ios::out

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

13 / 14

Beispiel: istringstream & ostringstream

demo_sstream.cpp i: 123
s: 7b
#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;
}

14 / 14

Seite 7 von 7
Programmieren I für Studierende der Mathematik SoSe 2026

Zeiger

Speichermodell von C++


• Speicher besteht aus Folgen von Bytes
• „Byte ≥ unsigned char“
• Byte ist kleinste addressierbare Einheit ⇒ jedes Byte hat eindeutige Adresse
• Darstellung eines Wertes oft mehrere Bytes (int ist oft 4 Bytes lang)
• sizeof(int) liefert 4

int x = 17, y = 378;

··· 0 0 1 122 ··· 0 0 0 17 ···


99 100 101 102 103 104 199 200 201 202 203 204
y x

Byte, i.d.R. 8 bit


103 Speicheradresse
1/8

Zeiger

• Ansatz: speichere Adresse von Anfang eines Werts in anderer Variable


• Für 𝑇 𝐷 Vereinbarung von Variable 𝐷 mit Typ 𝑇 , dann 𝑇 *𝐷 Vereinbarung von 𝐷
mit Typ „Zeiger auf 𝑇 “
• Für 𝑥 Wert vom Typ 𝑇 : &𝑥 liefert Adresse von erstem Byte der Darstellung von Wert
von 𝑥 im Speicher
• Für 𝑎 Wert vom Typ „Zeiger auf 𝑇 “: *𝑥 liefert Wert vom Typ 𝑇 dessen Darstellung
an Stelle 𝑎 im Speicher beginnt
• Zeiger vergleichbar mit == und !=
• Verwendung von * auf linker Seite von Zuweisung ist zulässig

*
Adresse Wert
&

2/8

Seite 1 von 4
Zeiger Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Zeiger

[Link] 17

#include <iostream>

using namespace std;

int main() {
int x = 3;
int *xp;

xp = &x;

x = 17;

cout << (*xp) << endl;


}

3/8

Beispiel: Zeiger (Forts.)

··· 0 0 0 0 0 0 0 200 ··· 0 0 0 17 ···


99 100 101 102 103 104 105 106 107 108 199 200 201 202 203 204

xp x

Byte
103 Speicheradresse

4/8

Seite 2 von 4
Zeiger Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Schreibzugriff durch Zeiger

pointer_write.cpp Davor: 4
Danach: 6
#include <iostream>

using namespace std;

void modify(double* xp) {


*xp += 2;
}

int main() {
double x = 4;
cout << ”Davor: ” << x << endl;
modify(&x);
cout << ”Danach: ” << x << endl;
}

5/8

Null-Pointer

• Spezieller Wert nullptr


• nullptr konvertierbar zu Wert vom Typ „Zeiger auf 𝑇 “ für alle 𝑇
• Resultierender Wert ist ein Null-Pointer
• Zeiger implizit konvertierbar zu bool: false gdw. Null-Pointer

6/8

Seite 3 von 4
Zeiger Programmieren I für Studierende der Mathematik — SoSe 2026

Pointer auf C-Arrays / C-Arrays von Pointern

• xps ist „Array von 3 Zeigern auf


double“ double *xps[3];
• xsp ist „Zeiger auf Array von 3 double (*xsp)[3];
double“
⇒ Zeiger „bindet schwächer“ als C-Array

7/8

C-Arrays sind Zeiger

• C-Arrays im Speicher zusammenhängend


• Wert vom Typ „Array von 𝑁 𝑇 “ kann implizit konvertiert werden zu Wert vom Typ
„Zeiger auf 𝑇 “
⇒ 𝑥 konvertiert zu &(𝑥 [0])
• Parameter von Array-Typ in Wirklichkeit von Zeiger-Typ; Länge wird ignoriert
• Subscript-Operator [] anwendbar sowohl auf C-Arrays, wie auch auf Werte von
Zeiger-Typ
• 𝑥 [𝑖 ] per Definition identisch zu *(𝑥 +𝑖 )
⇒ Addition/Subtraktion von Zeiger mit Zahl 𝑥 +𝑛 liefert 𝑥 ′ + 𝑠 · 𝑛 mit 𝑠 Anzahl Bytes
in Darstellung von *𝑥

··· ···
99 100 101 102 103 104 105 106 107 108 109 110 111 112

x[0] x[1] x[2]


8/8

Seite 4 von 4
Programmieren I für Studierende der Mathematik SoSe 2026

Kommandozeilenargumente

Kommandozeilenargumente

echo_noendl.cpp

• Vereinbare Hauptprogramm als: #include <iostream>


int main(int argc, char *argv[])
using namespace std;
• Rufe übersetztes Programm auf als:
./[Link] arg1 arg2 . . . arg𝑛−1 int main(int argc, char *argv[]) {
• argc: 𝑛 for (int i = 1; i < argc; i++)
• argv: {"[Link]", arg1 , arg2 , . . ., arg𝑛−1 } cout << ”Arg ” << i << ”: ”
<< argv[i] << ” ”;
}

./[Link] arg1 arg2 arg3

Arg 1: arg1 Arg 2: arg2 Arg 3: arg3

1/2

Beispiel: Numerische Kommandozeilenargumente

cli_parse.cpp ./[Link] 12 0.25

#include <iostream>
n=12 m=0.25
#include <sstream>
n*m=3
#include <string>

using namespace std;

int main(int argc, char* argv[]) {


if (argc != 3) {
cerr << ”Usage: ./[Link] <N> <M>” << endl;
return 2;
}

double n, m;
istringstream cli_arg{argv[1]};
cli_arg >> n;
cli_arg.str(argv[2]);
cli_arg.seekg(0);
cli_arg >> m;

cout << ”n=” << n


<< ” m=” << m << endl
<< ”n*m=” << n*m << endl;
}

2/2

Seite 1 von 1
Programmieren I für Studierende der Mathematik SoSe 2026

Dynamische Speicherzuweisung

Lebensdauer von Variablen

• Variablen haben Lebensdauer


• Bisher immer: bis Ende des syntaktischen Gültigkeitsbereichs
• Effekt bei Erreichen des Endes der Lebensdauer: zugewieser Speicher wird
freigegeben, i.A. sofort wiederverwendet und überschrieben
• Zudem bisher immer: Zuweisen von Speicher für Variablen auf dem Stack
Stack hat begrenzte Größe, z.B. 8MB – oft zu klein
• Dynamische Speicherzuweisung stattdessen auf dem Heap – i.W. aller dem Rechner
zur Verfügung stehender Speicher
• Lebensdauer unabhängig von Variablen; stattdessen manuelle Zuweisung und
Freigabe
• Manuelle Freigabe kann vergessen werden∗ → Speicherleck

∗ Später: smart pointer 1/4

Speicher zuweisen mit new

⟨NewExpr'⟩ → [“::”] “new” (⟨Typ⟩ | “(” ⟨Typ⟩ “)”) [⟨Init'⟩]

• new 𝑇 für 𝑇 ein Typ, weist dynamisch genug Speicher zu für Wert vom Typ 𝑇
• Liefert Zeiger vom Typ „Zeiger auf 𝑇 “ auf Anfang des zugewiesen Bereichs
• new kann überladen sein; wenn Aufruf mit :: immer eingebaute Variante

2/4

Seite 1 von 2
Dynamische Speicherzuweisung Programmieren I für Studierende der Mathematik — SoSe 2026

Speicher freigeben mit delete

⟨Del'Expr'⟩ → [“::”] “delete” [“ [] ”] ⟨Ausdruck⟩


• delete 𝑥 für 𝑥 Wert vom Typ „Zeiger auf 𝑇 “, gibt Speicher wieder frei
• Wenn 𝑇 ein C-Array, dann delete[]
• Wenn ::new, dann später auch ::delete

3/4

Beispiel: dynamische Speicherzuweisung

alloc_double.cpp 17.3

#include <iostream>

using namespace std;

double* alloc() {
double *xp;
xp = new double{17.3};
return xp;
}

int main() {
double *xp = alloc();
cout << *xp << endl;
delete xp;
}

4/4

Seite 2 von 2
Programmieren I für Studierende der Mathematik SoSe 2026

Referenzen

Referenzen

• Intuition: Referenz ist zusätzlicher Name für Variable


• Für 𝑇 𝐷 Vereinbarung von Variable mit Typ 𝑇 , dann 𝑇 & 𝐷 Vereinbarung von 𝐷 mit
Typ „Referenz auf 𝑇 “
• Ähnlich zu Zeigern∗ , aber:
• Referenzen nicht veränderbar – nach Initialisierung keine Veränderung der „Adresse“
• Keine „Null-Referenzen“
• Referenzen sind keine Werte
⇒ Keine C-Arrays, Vektoren, Zeiger auf, Referenzen von, … Referenzen
• Implizite Typkonvertierung Referenz ↔ Wert
⇒ Zugriff „durch“ und Bilden von Referenzen ist transparent

∗ Implementierung von Referenzen mit konstanten Zeigern (später) möglich 1/5

Beispiel: Referenz vs. Zeiger

[Link] [Link]

#include <iostream> #include <iostream>

using namespace std; using namespace std;

int main() { int main() {


int x = 3; int x = 3;
int& xr = x; int *xp = &x;

x = 17; x = 17;

cout << xr << endl; cout << (*xp) << endl;


} }

17 17

2/5

Seite 1 von 3
Referenzen Programmieren I für Studierende der Mathematik — SoSe 2026

Referenzparameter/Rückgabe von Referenzen

• Referenz als Funktionsparameter ist zulässig


Man sagt Referenzparameter (statt Werteparameter)
• Ebenso als Rückgabetyp einer Funktion zulässig

3/5

Beispiel: Referenzparameter vs. Zeiger

reference_write.cpp pointer_write.cpp

#include <iostream> #include <iostream>

using namespace std; using namespace std;

void modify(double& xr) { void modify(double* xp) {


xr += 2; *xp += 2;
} }

int main() { int main() {


double x = 4; double x = 4;
cout << ”Davor: ” << x << endl; cout << ”Davor: ” << x << endl;
modify(x); modify(&x);
cout << ”Danach: ” << x << endl; cout << ”Danach: ” << x << endl;
} }

Davor: 4 Davor: 4
Danach: 6 Danach: 6

4/5

Seite 2 von 3
Referenzen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: swap

[Link] x=3 y=7


x=7 y=3
#include <iostream>

using namespace std;

void swap(double& x, double& y) {


double h = y;
y = x; x = h;
}

int main() {
double x = 3, y = 7;

cout << ”x=” << x << ” y=” << y << endl;

swap(x, y);

cout << ”x=” << x << ” y=” << y << endl;


}

5/5

Seite 3 von 3
Programmieren I für Studierende der Mathematik SoSe 2026

Konstanten

Konstanten

• Markierung const an Variablen (Teil des Typs)


⇒ Variable darf∗ nicht verändert werden
• Für 𝑇 ein Typ, const 𝑇 der Typ „Konstante 𝑇 “
• Empfehlung: von Anfang const, wo immer möglich
Entfernen von const ist einfach, Hinzufügen schwierig

∗ mit etwas Aufwand trotzdem möglich; jedoch immer undefiniertes Verhalten 1/9

Beispiel: Konstanten können nicht verändert werden

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


const_change.cpp:4:5: error: assignment of read-only variable
1 int main() { ↩→ ‘x’
2 const double x = 7; // Initialisation 4 | x = 3; // Attempt to change constant
3 | ~~^~~
4 x = 3; // Attempt to change constant
5 }

2/9

Seite 1 von 5
Konstanten Programmieren I für Studierende der Mathematik — SoSe 2026

Zeiger auf Konstante

• Vereinbarung als Zeiger (also *) „bindet stärker“ als const


• const 𝑇 *𝑥 ist also „Zeiger auf Konstante 𝑇 “
• Zeiger auf Konstante dürfen selbst verändert werden,
• Speicher auf den sie zeigen jedoch nicht
• Zeiger auf Konstante darf auch zeigen auf Variable, die selbst nicht const ist

3/9

Beispiel: Zeiger auf Konstante

pointer_const_change.cpp pointer_const_redirect.cpp

1 int main() { #include <iostream>


2 double x = 3, y = 7;
3 using namespace std;
4 const double *xp = &x;
5 int main() {
6 *xp = 4; double x = 3, y = 7;
7 }
const double *xp = &x;

xp = &y;
pointer_const_change.cpp: In function ‘int main()’:
pointer_const_change.cpp:6:7: error: assignment of read-only
cout << *xp << endl;
↩→ location ‘* xp’ }
6 | *xp = 4;
| ~~~~^~~
7

4/9

Seite 2 von 5
Konstanten Programmieren I für Studierende der Mathematik — SoSe 2026

Konstante Zeiger

• 𝑇 * const 𝑥 ist „Konstanter Zeiger auf 𝑇 “


• Konstante Zeiger dürfen nicht selbst verändert werden,
• Speicher auf den sie zeigen jedoch schon

5/9

Beispiel: Konstante Zeiger

const_pointer_redirect.cpp const_pointer_change.cpp

1 int main() { #include <iostream>


2 double x = 3, y = 7;
3 using namespace std;
4 double * const xp = &x; // Initialisation
5 int main() {
6 xp = &y; // Attempt to change constant double x = 3;
7 }
double * const xp = &x;

*xp = 4;
const_pointer_redirect.cpp: In function ‘int main()’:
const_pointer_redirect.cpp:6:6: error: assignment of read-only
cout << *xp << endl;
↩→ variable ‘xp’ }
6 | xp = &y; // Attempt to change constant
| ~~~^~~~
4

6/9

Seite 3 von 5
Konstanten Programmieren I für Studierende der Mathematik — SoSe 2026

Konstante Referenzen

• Unterscheidung „Zeiger auf Konstante“ vs. „Konstanter Zeiger“ existiert bei


Referenzen nicht
• Konstante Referenz entspricht Zeiger auf Konstante
• Referenzen sind, im Sinne von „Konstanter Zeiger“, ja immer konstant
• 𝑇 𝐷 Vereinbarung Variable 𝐷 vom Typ 𝑇 , dann const 𝑇 & 𝐷 Vereinbarung von
Referenz vom Typ „Konstante Referenz auf 𝑇 “
• Konstante Referenz darf auch Variable referenzieren, die selbst nicht const ist

7/9

Beispiel: Konstante Referenzen

reference_const_change.cpp pointer_const_change.cpp: In function ‘int main()’:


pointer_const_change.cpp:6:7: error: assignment of read-only
1 int main() { ↩→ location ‘* xp’
2 double x = 3; 6 | *xp = 4;
3 | ~~~~^~~
4 const double& xr = x;
5
6 xr = 4;
7 }

8/9

Seite 4 von 5
Konstanten Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Konstante Referenzen zur Vermeidung unnötiger Kopien

const_reference_param.cpp 0

#include <iostream>
#include <vector>

using namespace std;

double f(const vector<double>& xs) {


return [Link](0);
}

int main() {
vector<double> xs(500'000'000, 0.0);

cout << f(xs) << endl;


}

9/9

Seite 5 von 5
Programmieren I für Studierende der Mathematik SoSe 2026

Einfache Klassen

Zusammengesetzte Datentypen

C++ hat zusätzlich zu eingebauten auch zusammengesetzte Datentypen

⟨KlassenDefinition⟩ → (“class” | “struct”) [⟨Identifier⟩] “{” {⟨KlassenKomponente⟩} “}”


⟨KlassenKomponente⟩ → [(“private” | “public”) “:”] (⟨Fun'Vereinbarung⟩ | ⟨Einf'Vereinbarung⟩)

• Nach Definition ist ⟨Identifier⟩ neuer Typ, eine Klasse bzw. ein struct
• Wert von Klassentyp auch Objekt
• Objekte bestehen aus ihren Attributen – „Variablen“ vereinbart als
Klassenkomponenten
• Achtung: ⟨KlassenDefinition⟩ ist ein Ausdruck; es muss Semikolon ; folgen für
Anweisung

1 / 16

Zugriffsattribute

• Schlüsselwörter private/public definieren Sichtbarkeit von Klassenkomponenten


außerhalb von Klasse
• class ⇒ Voreinstellung private; struct ⇒ Voreinstellung public

2 / 16

Seite 1 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026

Attributszugriff

Für 𝑐 Objekt von Klasse 𝑇 , 𝑎 Attribut von 𝑇 :


• Zugriff mit 𝑐 .𝑎
• 𝑐𝑝 ->𝑎 ist identisch zu (*𝑐𝑝 ).𝑎

3 / 16

Aggregat-Initialisierung

Voraussetzung: alle Attribute public


Initialisierung mit Initialisierungsliste in geschweiften Klammern weißt Ausdrücke den
Attributen in Definitionsreihenfolge zu

4 / 16

Seite 2 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Coordinate

[Link] x=3 y=7

#include <iostream>

using namespace std;

class Coordinate {
public:
double x;
double y;
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”x=” << c.x << ” y=” << c.y << endl;
}

5 / 16

Methoden

⟨KlassenDefinition⟩ → (“class” | “struct”) [⟨Identifier⟩] “{” {⟨KlassenKomponente⟩} “}”


⟨KlassenKomponente⟩ → [(“private” | “public”) “:”] (⟨Fun'Vereinbarung⟩ | ⟨Einf'Vereinbarung⟩)

• „Funktions“-Vereinbarungen als Klassenkomponenten möglich


• Deklarieren keine Funktionen, sondern Methoden
• Methoden 𝑓 werden aufgerufen auf Objekten 𝑐: 𝑐 . 𝑓 (arg1 , arg2 , . . .)
• Körper von Methode hat Zugriff auf Attribute von 𝑐
• 𝑐𝑝 -> 𝑓 (arg1 , arg2 , . . .) ist identisch zu (*𝑐𝑝 ). 𝑓 (arg1 , arg2 , . . .)

6 / 16

Seite 3 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Methode

[Link] ||c||=7.61577

#include <iostream>
#include <cmath>

using namespace std;

class Coordinate {
public:
double x;
double y;

double norm() {
return sqrt(x*x + y*y);
}
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”||c||=” << [Link]() << endl;


}

7 / 16

Statische Komponentenfunktionen

• Funktionsdeklaration innerhalb von Klasse mit static vor Typ


• Statische Komponentenfunktion 𝑓 wird aufgerufen auf Klasse 𝑇 :
𝑇 :: 𝑓 (arg1 , arg2 , . . .)
• Kein Zugriff auf Attribute (welche auch?)

8 / 16

Seite 4 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Statische Komponentenfunktion

static_norm.cpp ||c||=7.61577

#include <iostream>
#include <cmath>

using namespace std;

class Coordinate {
public:
double x;
double y;

static double norm(double p, Coordinate c) {


return pow(pow(c.x, p) + pow(c.y, p), 1.0/p);
}
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”||c||=” << Coordinate::norm(2, c) << endl;


}

9 / 16

Befreundete Funktionen

• Wie statische Komponentenfunktionen, aber friend statt static


• Unterschied ist Aufruf direkt mit Namen 𝑓 ohne 𝑇

10 / 16

Seite 5 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Befreundete Funktion

friend_norm.cpp ||c||=7.61577

#include <iostream>
#include <cmath>

using namespace std;

class Coordinate {
public:
double x;
double y;

friend double norm(double p, Coordinate c) {


return pow(pow(c.x, p) + pow(c.y, p), 1.0/p);
}
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”||c||=” << norm(2, c) << endl;


}

11 / 16

Konstruktoren

• Aggregat-Initialisierung funktioniert nicht mit private Attributen und ist auch so


sehr einschränkend
• Stattdessen: Programmierer legt fest wie Initialisierung ablaufen soll
• Konstruktor ist syntaktisch wie Methode mit Name identisch zu Klasse aber ohne
Rückgabetyp (nicht einmal void)
• Bei Initialisierung enthält Konstruktor die gegebenen Ausdrücke, Konstruktor
belegt Attribute mit Werten

12 / 16

Seite 6 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Konstruktor

coord_constructor.cpp x=3 y=7

#include <iostream>

using namespace std;

class Coordinate {
double y, x;

public:
Coordinate(double x_, double y_) {
x = x_; y = y_;
}

double getX() {
return x;
}
double getY() {
return y;
}
};

int main() {
Coordinate c{3.0, 7.0};

cout << ”x=” << [Link]() << ” y=” << [Link]() << endl;
}

13 / 16

Destruktoren

• Destruktor syntaktisch wie Methode mit, für 𝑇 name der Klasse, ~𝑇 als Name
Ebenfalls komplett ohne Rückgabetyp (nicht einmal void).
Keine Parameter
• Sobald Lebensdauer von Wert von Klassentyp endet (i.d.R. Ende des syntaktischen
Gültigkeitsbereichs eine Variable):
Automatische Ausführung des Destruktors
• Übliche Anwendung: Konstruktor weist Speicher zu, Destruktor gibt ihn wieder frei

14 / 16

Seite 7 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026

Beispiel: Destruktor

[Link] Vor Vereinbarung von c


Konstruktor
#include <iostream> Nach Vereinbarung von c
Destruktor
using namespace std; Nach Ende Gültigkeitsbereich von c

class C {
public:
C() {
cerr << ”Konstruktor” << endl;
}
~C() {
cerr << ”Destruktor” << endl;
}
};

int main() {
{
cerr << ”Vor Vereinbarung von c” << endl;
C c;
cerr << ”Nach Vereinbarung von c” << endl;
}
cerr << ”Nach Ende Gültigkeitsbereich von c” << endl;
}

15 / 16

Beispiel: Komplexe Zahlen


[Link] << endl
<< ”Re z2 = ” << [Link]() << ” Im z2 = ” << [Link]()
#include <iostream> << endl
<< ”Re z3 = ” << [Link]() << ” Im z3 = ” << [Link]()
using namespace std; << endl;
}
class Complex{
private:
double re, im; Re z1 = 1 Im z1 = 2
Re z2 = 1 Im z2 = -2
public: Re z3 = 3 Im z3 = 4
Complex(double re_, double im_) {
re = re_; im = im_;
}

double real() { return re; }


double imag() { return im; }

friend Complex conj(Complex z) {


[Link] = -[Link];
return z;
}
};

int main() {
Complex z1{1.0, 2.0}, z2{0.0, 0.0};
z2 = conj(z1);

double x3 = 3, y3 = 4;
Complex z3{x3, y3};

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


16 / 16

Seite 8 von 8
Programmieren I für Studierende der Mathematik SoSe 2026

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
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 1 – Lösungsvorschlag

Verwenden Sie zum Entwickeln und Testen von Programmen für die hypothetische Maschine 𝑀 ′ die online-
Implementierung:
[Link]/~gkleen/eval-m

Aufgabe 1 Für (𝑥𝑘 )𝑘 ∈ {1,...,𝑛} mit 𝑥𝑘 ≥ 0 eine Folge von nichtnegativen Zahlen soll das quadratische Mittel
berechnet werden: √
𝑥 12 + 𝑥 22 + . . . + 𝑥𝑛2
𝑛

Die Folge von Werten soll von der Eingabe gelesen werden. Um dem laufenden Programm zu kommunizieren,
dass keine weitere Eingabe folgt, soll als Endkennung nach der letzten Zahl 𝑥𝑛 eine negative Zahl eingegeben
werden.
Erstellen Sie ein Programm für 𝑀 ′ , das das beschriebene Verfahren implementiert. Nach Einlesen der End-
kennung soll das Programm sowohl das berechnete quadratische Mittel, wie auch die Anzahl von gelesenen
Werten 𝑛 ausgeben.
Hinweis (Kontrollergebnis). Das quadratische Mittel von {1, 2, 3, 4, 5} ist ungefähr 3,316.

quad_mean.m

1 JUMP(5)
2 # k
3 # laufende Summe
4 # tmp
5 CONST(0)
6 STORE(2)
7 STORE(3)
8 READ
9 NEGATIVE(18)
10 STORE(4)
11 MULT(4)
12 ADD(3)
13 STORE(3)
14 CONST(1)
15 ADD(2)
16 STORE(2)
17 JUMP(8)
18 RECALL(3)
19 DIV(2)
20 SQRT
21 PRINT
22 STOP

Aufgabe 2 Übersetzen Sie das Programm [Link] und führen Sie es aus. Für eine Anleitung hierzu, siehe
Anhang A:

Seite 1 von 9
Aufgabe 3 Programmieren I für Studierende der Mathematik — SoSe 2026

[Link]

#include <iostream>

using namespace std;

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

Aufgabe 3 Die Collatz-Funktion Col : N → N ist:


{
𝑛/2 𝑛 gerade
Col(𝑛) :=
3𝑛 + 1 sonst

Der Collatz-Orbit ColN (𝑛) einer Zahl 𝑛 ∈ N ist:


{ }
ColN (𝑛) := Col𝑘 (𝑛) | 𝑘 ∈ N

Hierbei bezeichnen wir mit Col𝑘 (𝑛) für 𝑘 ∈ N die 𝑘-fache Anwendung von Col:
( )
Col0 (𝑛) := 𝑛 Col𝑘+1 (𝑛) := Col Col𝑘 (𝑛)

Mit ColN
max (𝑛) bezeichnen wir den maximalen Wert, der im Collatz-Orbit einer Zahl 𝑛 vorkommt:
( )
ColN
max (𝑛) := max Col N
(𝑛)

Beispiel. Der Collatz-Orbit der Zahl 3 ist:

ColN (3) = {3, 10, 5, 16, 8, 4, 2, 1}

Es ist ColN
max (3) = 16.

Implementieren Sie ein Programm für 𝑀 ′ , das, für beliebiges aber festes 𝑁 ∈ N, ausgehend von 𝑛 = 1 suk-
zessive ColN N
max (𝑛) berechnet für 𝑛 = 1, 2, . . . bis Colmax (𝑛) > 𝑁 . Ihr Programm soll anhalten und das erste 𝑛
ausgeben für welches ColN max (𝑛) > 𝑁 .
Gehen Sie vor, wie folgt:

a) Implementieren Sie ein Unterprogramm zur Berechnung von Col


b) Implementieren Sie ein Hauptprogramm welches einen Wert von der Eingabe einliest, ihr Unterpro-
gramm für Col mit diesem Wert aufruft und das Ergebnis ausgibt
Testen Sie Ihr Programm mit mehreren Werten von 𝑛.
c) Passen Sie Ihr Hauptprogramm an, sodass dieses nun einen Wert von der Eingabe einliest, ihr Unter-
programm für Col darauf anwendet, das Ergebnis ausgibt und dann auf das Ergebnis wiederum Col
anwendet. Dies soll wiederholt werden, solange bis Ihr Programm die Zahl 1 als Ergebnis erhält1 .
Ihr Hauptprogramm sollte also den Collatz-Orbit einer auf der Eingabe gegebenen Zahl 𝑛 ausgeben.
Testen Sie dies für mehrere Werte von 𝑛.

1 Diesog. Collatz-Vermutung: ∀𝑛 ∈ N \ {0} ∃𝑟 ∈ N. Col𝑟 (𝑛) = 1. Sobald die 1 erreicht ist, werden wir wegen ColN (1) = {1, 4, 2}
keine neuen Elemente von ColN (𝑛) mehr finden. Vgl.: [Link]/wiki/Collatz-Problem

Seite 2 von 9
Aufgabe 3 Programmieren I für Studierende der Mathematik — SoSe 2026

d) Passen Sie Ihr Hauptprogramm an, sodass dieses nun einen Wert 𝑁 von der Eingabe einliest. Es soll
dann Col immer wieder zunächst auf 𝑛 = 1 angewandt werden, bis Ihr Programm die Zahl 1 als Ergebnis
erhält, oder das Ergebnis größer ist als 𝑁 . In letzterem Fall soll das Programm an dieser Stelle angehalten
und 𝑛 ausgegeben werden. Ansonsten erhöhen Sie 𝑛 um eins und wenden das selbe Verfahren auf das
neue 𝑛 an.

Hinweis (Kontrollergebnisse). Ihr Programm sollte für gegebenes 𝑁 jeweils die folgende Zahl berechnen und
ausgeben:

𝑁 Ergebnis2
0, 1, 2, 3 1
4, . . . , 15 3
16, . . . , 51 7
52, . . . , 159 15
160, . . . , 9 231 27
9 232, . . . , 13 119 255

collatz.m

1 JUMP(15)
2 # stack pointer
3 # length of subroutine call (lsc)
4 # arg
5 # ret
6 # return pointer (rp)
7 # 1
8 # N
9 # n
10 # n'
11
12
13
14
15 CONST(100)
16 STORE(2)
17 CONST(1)
18 STORE(7)
19 STORE(9)
20 STORE(10)
21 CONST(8)
22 STORE(3)
23 READ
24 STORE(8)
25 RECALL(10)
26 STORE(4)
27 POS
28 ADD(3)
29 STORE(6)
30 RECALL(2)
31 ADD(7)
32 STORE(2)
33 COPY(6)
34 JUMP(55)

2 Vgl.: [Link]/A006884

Seite 3 von 9
Aufgabe 3 Programmieren I für Studierende der Mathematik — SoSe 2026

35 RECALL(5)
36 SUB(8)
37 POSITIVE(39)
38 JUMP(42)
39 RECALL(9)
40 PRINT
41 STOP
42 RECALL(5)
43 SUB(7)
44 ZERO(48)
45 RECALL(5)
46 STORE(10)
47 JUMP(25)
48 RECALL(9)
49 ADD(7)
50 STORE(9)
51 STORE(10)
52 JUMP(25)
53
54
55 CONST(2)
56 STORE(11)
57 RECALL(4)
58 DIV(11)
59 STORE(11)
60 FLOOR
61 SUB(11)
62 ZERO(69)
63 CONST(3)
64 STORE(11)
65 RECALL(4)
66 MULT(11)
67 ADD(7)
68 JUMP(70)
69 RECALL(11)
70 STORE(5)
71 RECALL(2)
72 RECALL
73 STORE(12)
74 RECALL(2)
75 SUB(7)
76 STORE(2)
77 RECALL(12)
78 JUMP

Seite 4 von 9
A. Übersetzung von C++-Programmen Programmieren I für Studierende der Mathematik — SoSe 2026

Anhang

A. Übersetzung von C++-Programmen

Das Vorgehen ist wie folgt:

1) Öffnen Sie einen Texteditor. Genauer, einen Texteditor welcher in der Lage ist reine Textdateien (Da-
teiendung z.B. .txt) zu speichern und nicht nur Dateien in einem editorspezifischen Format, welches
sich mit der graphischen Darstellung des Textes befasst (Dateiendungen z.B. .docx, .odt, uvw.).

Abbildung 1: Eine Verknüpfung zum Öffnen eines geeigneten Texteditors

2) Schreiben Sie den Quelltext für ein C++-Programm.

Abbildung 2: Quelltext für ein C++-Programm in einem Texteditor

Seite 5 von 9
A. Übersetzung von C++-Programmen Programmieren I für Studierende der Mathematik — SoSe 2026

3) Speichern Sie die Textdatei in einem beliebigen Ordner mit einem beliebigen Namen, solange letzterer
in .cpp endet.
Manche Betriebssysteme verstecken standardmäßig die Dateiendung. Achten Sie darauf, dass der Datei-
name wirklich mit .cpp endet und nicht etwa mit .[Link] o.Ä. Bei manchen Texteditoren gibt es beim
Speichern hierfür nicht nur ein Feld für den Dateinamen, sondern auch Einstellungen zum Dateityp.

Abbildung 3: Dialog des Texteditors zum Speichern der Datei als [Link] im Ordner ~/pro1

4) Öffnen Sie ein Konsolenfenster.

Abbildung 4: Eine Verknüpfung zum Öffnen eines Konsolenfensters

Seite 6 von 9
A. Übersetzung von C++-Programmen Programmieren I für Studierende der Mathematik — SoSe 2026

5) In das Konsolenfenster können Sie Befehle eintippen.


Sobald Sie Ihre Eingabe mit bestätigen, wird der eingegebene Befehl ausgeführt. Etwaige Ausgabe,
die der Befehl produziert, erscheint ebenfalls im Konsolenfenster.
Per Konvention produzieren die meisten Befehle keine Ausgabe gdw. sie erfolgreich ausgeführt wurden.
Zu jedem Zeitpunkt hat die Konsole ein current working directory (cwd, aktuelles Arbeitsverzeichnis). D.h.
Dateinamen wie z.B. die Argumente von Befehle oder ggf. auch Befehle selbst werden relativ zu einem
gewissen Verzeichnis interpretiert.
Wenn das current working directory also z.B. das persönliche home-Verzeichnis Ihrer Benutzerkennung
ist (standardmäßig der Anfangszustand eines neu geöffneten Konsolenfensters), wird der Befehl cd 𝑑
nach dem Verzeichnis Namens 𝑑 direkt innerhalb Ihres home-Verzeichnisses suchen und das current
working directory in jenes Verzeichnis wechseln, wenn es existiert.
Das current working directory ist i.d.R. Bestandteil der Eingabeaufforderung. Hierbei wird das persön-
liche home-Verzeichnis Ihrer Benutzerkennung i.d.R. abgekürzt als ~.
Die Dateien und Ordner, auf denen Sie im Konsolenfenster agieren können, liegen in einer Verzeichnis-
struktur. Sie können diese Verzeichnisstruktur einsehen und verändern im Dateimanager (File Manager)
Ihres Betriebssystem, z.B. Dolphin. Dies kann hilfreich sein um sich im Konsolenfenster in der Verzeich-
nisstruktur „zurecht zu finden“.
Insb. Dolphin bietet auch an über ö Open Terminal ein Konsolenfenster zu öffnen, dessen Arbeitsver-
zeichnis direkt der aktuell angezeigte Ordner ist.

Abbildung 5: Ein soeben geöffnetes Konsolenfenster im initialen Zustand

Seite 7 von 9
A. Übersetzung von C++-Programmen Programmieren I für Studierende der Mathematik — SoSe 2026

6) Wechseln Sie durch eine geeignete Serie von Befehlen der Form cd 𝑑 mit 𝑑 dem Namen eines Verzeich-
nisses, in das Verzeichnis, in dem Sie die Datei gespeichert haben.

Abbildung 6: Das Konsolenfenster nach Wechseln in das Verzeichnes ~/pro1

7) Übersetzen Sie die Quelltext-Datei mit einem Befehl g++ 𝑓 mit 𝑓 dem Namen der Datei in die Sie den
Quelltext abgespeichert haben.

Abbildung 7: Das Konsolenfenster nach Übersetzen der Quelltext-Datei ~/pro1/[Link]

Seite 8 von 9
A. Übersetzung von C++-Programmen Programmieren I für Studierende der Mathematik — SoSe 2026

8) Führen Sie das übersetzte Programm mit dem Befehl ./[Link] aus.

Abbildung 8: Das Konsolenfenster nach Ausführen des übersetzten Programms

Seite 9 von 9
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 2 – Lösungsvorschlag

Verwenden Sie zum Entwickeln und Testen von Programmen für die hypothetische Maschine 𝑀 ′ die online-
Implementierung:
[Link]/~gkleen/eval-m

Aufgabe 4 Wir betrachten die wechselseitig rekursiven Funktionen 𝐹, 𝑀 : N → N:


{ {
1 𝑛 =0 0 𝑛 =0
𝐹 (𝑛) = 𝑀 (𝑛) =
𝑛 − 𝑀 (𝐹 (𝑛 − 1)) sonst 𝑛 − 𝐹 (𝑀 (𝑛 − 1)) sonst

Implementieren Sie ein Programm für 𝑀 ′ mit jeweils einem Unterprogramm zur Berechnung von 𝐹 (𝑛) bzw.
𝑀 (𝑛). Verwenden Sie zur Berechnung die obige Rekursionsbeziehung.
Lesen Sie im Hauptprogramm einen Wert 𝑛 von der Eingabe ein, rufen Sie das Unterprogramm zur Berechnung
von 𝐹 (𝑛) auf und geben Sie das Ergebnis aus.
Hinweis. Für ein Beispiel einer Implementierung eines Unterprogramms inkl. indirektem Sprung „zurück“ mit
der Rücksprungadresse auf dem Stack, siehe Anhang A.
Hinweis (Kontrollergebnisse). Sie sollten die folgenden Werte berechnen können:

𝑛 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
𝐹 (𝑛) 1 1 2 2 3 3 4 5 5 6 6 7 8 8 9 9

hofstadter.m

1 NOP # 1
2 NOP # sp
3 NOP # lsr
4 NOP # n
5 NOP # ret
6 NOP
7 NOP
8 NOP
9 NOP
10 CONST(1) # Initialisierung
11 STORE(1)
12 CONST(1000)
13 STORE(2)
14 CONST(8)
15 STORE(3)
16 READ # Lies n
17 STORE(4)
18 POS # Beginn: Unterprogramm-Aufruf
19 ADD(3)
20 STORE(7)
21 RECALL(2)
22 ADD(1)
23 STORE(2)
24 COPY(7)

Seite 1 von 7
Aufgabe 4 Programmieren I für Studierende der Mathematik — SoSe 2026

25 JUMP(40) # Ende: Unterprogramm-Aufruf


26 RECALL(5)
27 PRINT
28 STOP
29
30
31
32
33
34
35
36
37
38
39
40 RECALL(4) # Unterprogramm F
41 ZERO(43)
42 JUMP(46)
43 CONST(1)
44 STORE(5)
45 JUMP(80) # to return
46 RECALL(2) # Beginn: n auf stack
47 ADD(1)
48 STORE(2)
49 COPY(4) # Ende: n auf stack
50 RECALL(4)
51 SUB(1)
52 STORE(4) # n = n - 1
53 POS # Beginn: Unterprogramm-Aufruf
54 ADD(3)
55 STORE(7)
56 RECALL(2)
57 ADD(1)
58 STORE(2)
59 COPY(7)
60 JUMP(40) # Ende: Unterprogramm-Aufruf
61 RECALL(5)
62 STORE(4)
63 POS # Beginn: Unterprogramm-Aufruf
64 ADD(3)
65 STORE(7)
66 RECALL(2)
67 ADD(1)
68 STORE(2)
69 COPY(7)
70 JUMP(90) # Ende: Unterprogramm-Aufruf
71 RECALL(2) # Beginn: n von stack
72 RECALL
73 STORE(4)
74 RECALL(2)
75 SUB(1)

Seite 2 von 7
Aufgabe 4 Programmieren I für Studierende der Mathematik — SoSe 2026

76 STORE(2) # Ende: n von stack


77 RECALL(4)
78 SUB(5)
79 STORE(5)
80 RECALL(2) # Beginn: return
81 RECALL
82 STORE(6)
83 RECALL(2)
84 SUB(1)
85 STORE(2)
86 RECALL(6)
87 JUMP
88
89
90 RECALL(4) # Unterprogramm M
91 ZERO(93)
92 JUMP(96)
93 CONST(0)
94 STORE(5)
95 JUMP(130) # to return
96 RECALL(2) # Beginn: n auf stack
97 ADD(1)
98 STORE(2)
99 COPY(4) # Ende: n auf stack
100 RECALL(4)
101 SUB(1)
102 STORE(4) # n = n - 1
103 POS # Beginn: Unterprogramm-Aufruf
104 ADD(3)
105 STORE(7)
106 RECALL(2)
107 ADD(1)
108 STORE(2)
109 COPY(7)
110 JUMP(90) # Ende: Unterprogramm-Aufruf
111 RECALL(5)
112 STORE(4)
113 POS # Beginn: Unterprogramm-Aufruf
114 ADD(3)
115 STORE(7)
116 RECALL(2)
117 ADD(1)
118 STORE(2)
119 COPY(7)
120 JUMP(40) # Ende: Unterprogramm-Aufruf
121 RECALL(2) # Beginn: n von stack
122 RECALL
123 STORE(4)
124 RECALL(2)
125 SUB(1)
126 STORE(2) # Ende: n von stack
127 RECALL(4)
128 SUB(5)
129 STORE(5)
130 RECALL(2) # Beginn: return
131 RECALL
132 STORE(6)
133 RECALL(2)

Seite 3 von 7
Aufgabe 5 Programmieren I für Studierende der Mathematik — SoSe 2026

134 SUB(1)
135 STORE(2)
136 RECALL(6)
137 JUMP

Aufgabe 5 Seien 𝑎, 𝑏, 𝑐, 𝑑, 𝑒, 𝑓 ∈ R. Wir betrachten das folgende Gleichungssystem:


( )( ) ( )
𝑎 𝑏 𝑥 𝑒
=
𝑐 𝑑 𝑦 𝑓

Falls 𝑎𝑑 − 𝑏𝑐 ≠ 0 lässt sich eine Lösung 𝑥, 𝑦 ∈ R berechnen als:

𝑑𝑒 − 𝑏 𝑓 𝑎𝑓 − 𝑐𝑒
𝑥= 𝑦=
𝑎𝑑 − 𝑏𝑐 𝑎𝑑 − 𝑏𝑐

Implementieren Sie ein Programm in C++ zur Berechnung von 𝑥, 𝑦.


Testen Sie Ihr Programm mit den folgenden Werten:

a) 𝑎 = 3 𝑏 = 4 𝑐 = 2 𝑑 = 3 𝑒 = 5 𝑓 = 6
b) 𝑎 = 1 𝑏 = 3 𝑐 = 5 𝑑 = 2 𝑒 = −3 𝑓 = 7
c) 𝑎 = 2 𝑏 = 3 𝑐 = 5 𝑑 = 1 𝑒 = 2 𝑓 = −5

Hinweis (Kontrollergebnisse). Es ist:


( )( ) ( ) ( )( ) ( ) ( )( ) ( )
3 4 −9 5 1 3 2,08 −3 2 3 −1,31 2
a) = b) ≈ c) ≈
2 3 8 6 5 2 −1,69 7 5 1 1,54 −5

[Link]

#include <iostream>
#include <iomanip>

using namespace std;

void cramer(double a, double b, double c, double d, double e, double f) {


cout << ”a = ” << setw(2) << a << ”, ”
<< ”b = ” << setw(2) << b << ”, ”
<< ”c = ” << setw(2) << c << ”, ”
<< ”d = ” << setw(2) << d << ”, ”
<< ”e = ” << setw(2) << e << ”, ”
<< ”f = ” << setw(2) << f << endl;

double denom = a * d - b * c;
double x = (d * e - b * f) / denom;
double y = (a * f - c * e) / denom;

cout << ”x = ” << setw(8) << x << ”, ”


<< ”y = ” << setw(8) << y << endl;
}

int main() {
cramer(3, 4, 2, 3, 5, 6);
cout << endl;
cramer(1, 3, 5, 2, -3, 7);

Seite 4 von 7
Aufgabe 5 Programmieren I für Studierende der Mathematik — SoSe 2026

cout << endl;


cramer(2, 3, 5, 1, 2, -5);
}

a = 3, b = 4, c = 2, d = 3, e = 5, f = 6
x = -9, y = 8

a = 1, b = 3, c = 5, d = 2, e = -3, f = 7
x = 2.07692, y = -1.69231

a = 2, b = 3, c = 5, d = 1, e = 2, f = -5
x = -1.30769, y = 1.53846

Seite 5 von 7
A. Rekursive Fakultätsfunktion Programmieren I für Studierende der Mathematik — SoSe 2026

Anhang

A. Rekursive Fakultätsfunktion

Für die Fakultätsfunktion −! : N → N gilt:


{
1 𝑛 =0
𝑛! =
𝑛 · (𝑛 − 1)! sonst

Wir implementieren die Fakultätsfunktion, wie obig definiert, mit einem Unterprogramm für 𝑀 ′ :

fac_recursive.m

1 CONST(1) # Initialisierung, speichere Konstanten; S_1 = 1


2 STORE(1) # stack-pointer, initial 99
3 CONST(99) # temporaere Ablage fuer stack push/pop
4 STORE(2)
5 JUMP(20) # Sprung zum Hauptprogramm
6
7
8
9
10 # Argument
11 # Rueckgabewert
12
13
14
15
16
17
18
19
20 READ # start: Hauptprogramm
21 STORE(10) # start: Unterprogramm-Aufruf; Argument in S_10
22 CONST(29) # Ruecksprungadresse
23 STORE(3) # start: stack push; Ruecksprungadresse auf stack
24 RECALL(2)
25 ADD(1)
26 STORE(2)
27 COPY(3) # end: stack push
28 JUMP(40) # end: Unterprogramm-Aufruf; Sprung in F
29 RECALL(11) # Rueckgabewert aus S_11
30 PRINT
31 STOP # end: Hauptprogramm
32
33
34
35
36
37
38
39
40 RECALL(10) # start: Unterprogramm F; Argument aus S_10
41 ZERO(66) # n = 0?
42 STORE(3) # start: stack push; n auf stack
43 RECALL(2)
44 ADD(1)

Seite 6 von 7
A. Rekursive Fakultätsfunktion Programmieren I für Studierende der Mathematik — SoSe 2026

45 STORE(2)
46 COPY(3) # end: stack push
47 RECALL(3)
48 SUB(1) # R = n - 1
49 STORE(10) # start: Unterprogramm-Aufruf; n-1 in S_10
50 CONST(57) # Ruecksprungadresse
51 STORE(3) # start: stack push
52 RECALL(2)
53 ADD(1)
54 STORE(2)
55 COPY(3) # end: stack push
56 JUMP(40) # end: Unterprogramm-Aufruf; Sprung in F
57 RECALL(2) # start: stack pop
58 RECALL
59 STORE(3)
60 RECALL(2)
61 SUB(1)
62 STORE(2)
63 RECALL(3) # end: stack pop; n von stack
64 MULT(11) # R = n * F(n - 1)
65 JUMP(67)
66 CONST(1) # wenn n = 0, F(n) = 1
67 STORE(11) # F(n) in S_11
68 RECALL(2) # start: stack pop
69 RECALL
70 STORE(3)
71 RECALL(2)
72 SUB(1)
73 STORE(2)
74 RECALL(3) # end: stack pop; Ruecksprungadresse von stack
75 JUMP # end: Unterprogramm F; indirekter Sprung zurueck

Seite 7 von 7
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 3 – Lösungsvorschlag

Aufgabe 6 Zur näherungsweisen Berechnung der Sinusfunktion an der Stelle 𝑥 ∈ R soll, mit 𝑛 ∈ N, die
folgende Summe verwendet werden:
Õ
𝑛
𝑥 2𝑘+1
𝑠𝑛 (𝑥) := (−1)𝑘
(2𝑘 + 1)!
𝑘=0

Implementieren Sie ein Programm welches ausgehend von 𝑠 0 in einer Schleife jeweils, unter Verwendung von
insb. 𝑠𝑘 −1 aus der vorherigen Iteration, 𝑠𝑘 berechnet. Brechen Sie die Schleife ab, sobald gilt:

𝑥 2𝑘+1
≤ 𝜀 · |𝑠𝑘 (𝑥)|
(2𝑘 + 1)!

Nehmen Sie hierbei 𝜀 = 1 · 10−15 .


Vergleichen Sie den mit diesem Verfahren berechneten Wert auch mit dem Wert, den sie mit der eingebauten
Funktion sin (Header-Datei <cmath>) berechnen können.
Hinweis. Es ist hilfreich den Summanden von 𝑠𝑛 (𝑥) einen Namen zu geben, z.B. (𝑎𝑘 )𝑘 ∈ {0,...,𝑛} und zu betrachten
wie sich 𝑎𝑘+1 aus 𝑎𝑘 berechnen lässt.
Hinweis. Den Absolutbetrag einer Zahl können Sie mit der Funktion abs aus <cmath> berechnen.
Die Zahl 1 · 10−15 können Sie als das Literal 1e-15 schreiben.
Hinweis (Eingabe). Eine Variable x vom Typ double können Sie mit den folgenden Anweisungen vereinbaren
und einen Wert für x von der Konsole einlesen:

Eingabe

cout << ”x: ”;


double x;
cin >> x;

Es muss hierfür die Header-Datei <iostream> eingebunden werden.


Hinweis (Ausgabe). Einen in einer Variable y gespeicherten Wert können Sie mit den folgenden Anweisungen
in einem Exponentialformat (e𝑛 steht in der Ausgabe für ·10𝑛 ) mit 15 signifikanten Stellen ausgeben lassen:

cout << setprecision(15) << scientific << y << endl;

Es müssen hierfür die Header-Dateien <iostream> und <iomanip> eingebunden werden.


Rechnen Sie die folgenden Beispiele:
a) 1 b) −0,43 c) 3,141 592 653 5 d) 50

Seite 1 von 4
Aufgabe 6 Programmieren I für Studierende der Mathematik — SoSe 2026

Verwenden Sie zur Verbesserung Ihrer Resultate die folgenden Identitäten:



 − sin(−𝑥) 𝑥<0





 sin(𝑥 − 2𝑘𝜋) 𝑥 > 2𝜋 (∀𝑥 ∈ R ≥0 ∃𝑘 ∈ N. 0 ≤ 𝑥 − 2𝑘𝜋 < 2𝜋)



sin(𝑥) = − sin(𝑥 − 𝜋) 𝜋 ≤ 𝑥 < 2𝜋

 𝜋

 sin(𝜋 − 𝑥) 2 ≤𝑥 <𝜋

 q

 
 1 − sin 𝜋2 − 𝑥 2 𝜋
<𝑥 ≤ 𝜋
 4 2


Hinweis. Es ist 𝜋 = 4 arctan(1). Die Funktionen atan (entspricht arctan) und sqrt (entspicht −) stehen in
<cmath> zur Verfügung.

Hinweis (Kontrollergebnisse). Die folgenden Werte haben in etwa die volle zu erwartende Präzision:

𝑥 sin(𝑥)
1 0,841 470 984 807 896 6
−0,43 −0,416 870 802 429 210 8
3,141 592 653 5 8,979 306 187 484 326 · 10−11
50 −0,262 374 853 703 926 8

Zunächst bestimmen wir Rekursionsbeziehungen für die Folgen der Summanden (𝑎𝑘 )𝑘 ∈ {0,...,𝑛} und der
Partialsummen (𝑠𝑘 )𝑘 ∈ {0,...,𝑛} :

𝑥 2𝑘+1 Õ
0
𝑎 0 = (−1)𝑘 𝑠0 = 𝑎𝑘 = 𝑎 0
(2𝑘 + 1)! 𝑘=0 𝑘=0
𝑥 2·0+1
= (−1) 0
(2 · 0 + 1)!
=𝑥

2(𝑘+1) +1
𝑘+1 𝑥
𝑎𝑘+1 (−1) (2(𝑘+1)+1)!
=
𝑎𝑘 𝑥 2𝑘+1
(−1)𝑘 (2𝑘+1)!
(2𝑘 + 1)!
= −𝑥 2
(2𝑘 + 3)!
(2𝑘 + 1)!
= −𝑥 2
(2𝑘 + 1)! · (2𝑘 + 2) (2𝑘 + 3)
𝑥2
=−
(2𝑘 + 2)(2𝑘 + 3)

𝑥2
𝑎𝑘+1 = − 𝑎𝑘 𝑠𝑘+1 = 𝑠𝑘 + 𝑎𝑘+1
(2𝑘 + 2)(2𝑘 + 3)

In der Implementierung verwenden wir dann Variablen step für das aktuelle 𝑎𝑘 und res für das aktuelle
𝑠𝑘 . Wir achten darauf die Variablen k, step und res korrekt zu initialisieren für 𝑘 = 0, 𝑎 0 und 𝑠 0 . Im
Körper der Schleife berechnen wir dann jeweils 𝑎𝑘+1 und 𝑠𝑘+1 gemäß obiger Rekursionsbeziehungen.

Seite 2 von 4
Aufgabe 6 Programmieren I für Studierende der Mathematik — SoSe 2026

[Link]

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

using namespace std;

double PI = 4 * atan(1), EPS = 1e-15;

double mysin(double x) {
if (x < 0)
return - mysin(-x);
if (x >= 2 * PI) {
while (x >= 2 * PI)
x -= 2 * PI;
return mysin(x);
}
if (PI <= x)
return - mysin(x - PI);
if (PI / 2 <= x)
return mysin(PI - x);
if (PI / 4 <= x) {
double r = mysin(PI / 2 - x);
return sqrt(1 - r * r);
}

double step = x, res = step;


int k = 0;
while (abs(step) > EPS * abs(res)) {
step *= -x * x / (2 * k + 2) / (2 * k + 3);
res += step;
k += 1;
}

return res;
}

int main() {
cout << ”x: ”;
double x;
cin >> x;

cout << setprecision(15) << scientific


<< ”sin(x) = ” << sin(x) << endl
<< ”mysin(x) = ” << mysin(x) << endl;
}

x: 1
sin(x) = 8.414709848078965e-01
mysin(x) = 8.414709848078966e-01

x: -0.43
sin(x) = -4.168708024292108e-01
mysin(x) = -4.168708024292108e-01

Seite 3 von 4
Aufgabe 6 Programmieren I für Studierende der Mathematik — SoSe 2026

x: 3.1415926535
sin(x) = 8.979318433952318e-11
mysin(x) = 8.979306187484326e-11

x: 50
sin(x) = -2.623748537039288e-01
mysin(x) = -2.623748537039268e-01

Seite 4 von 4
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 4 – Lösungsvorschlag

Aufgabe 7 Wir betrachten 𝐴𝑛 und 𝐵𝑛 , die Flächen des dem Einheitskreises einbeschriebenen bzw. umbe-
schriebenen regulären 2𝑛 -Ecks mit 𝑛 ≥ 2 ∈ N. Die Seitenlänge 𝑠𝑛 des einbeschriebenen 2𝑛 -Ecks folgt der
Rekursionsbeziehung:

𝑠2 = 2
√ √
𝑠𝑛+1 = 2 − 4 − 𝑠𝑛2

Es ist des Weiteren:



𝑛−1 𝑠𝑛2 1
𝐴𝑛 = 2 𝑠𝑛 1− 𝐵𝑛 = 2𝑛−1𝑠𝑛 √
4 𝑠𝑛2
1− 4

Berechnen Sie für 𝑛 = 2, 3, . . . Näherungen an die Kreiszahl 𝜋 und geben Sie diese in einer Tabelle aus. Geben
Sie in weiteren Spalten jeweils auch 𝑛, die Eckenanzahl, die Seitenlänge 𝑠𝑛 im Exponentialformat und die
berechneten Flächen 𝐴𝑛 , 𝐵𝑛 in einem der Genauigkeit gut angepassten Festpunktformat aus.
𝑠2
Brechen Sie die Iteration ab, sobald 4𝑛 kleiner wird als die Maschinengenauigkeit des verwendeten Gleitpunkt-
datentyps.
Verwenden Sie für die Berechnung von Ausdrücken der Form 𝑥 · 2𝑛 die Funktion ldexp(𝑥 , 𝑛 ) aus der Stan-
dardlibrary.
Hinweis. Die Maschinengenauigkeit für den Gleitpunktdatentyp double und die Anzahl von Dezimalstellen
die sich vom Gleitpunktdatentyp double zuverlässig darstellen lassen stehen in der Header-Datei <limits>
zur Verfügung als numeric_limits<double>::epsilon() bzw. numeric_limits<double>::digits10.
Führen Sie das obige Verfahren auch mit folgender, mathematisch äquivalenten, Rekursionsbeziehung durch:
𝑠𝑛
𝑠𝑛+1 = √ √
2 + 4 − 𝑠𝑛2

Fügen Sie die so berechnete Seitenlänge, die aus ihr berechneten Flächen und die sich aus ihnen ergebende
Näherung an 𝜋 der tabellarischen Ausgabe als weitere Spalten hinzu.
Hinweis (Kontrollergebnis). 𝜋 ≈ 3,141 592 653 589 793

[Link]

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

using namespace std;

double eps = numeric_limits<double>::epsilon();


int p = numeric_limits<double>::digits10;
double pi = 4 * atan(1);

Seite 1 von 4
Aufgabe 7 Programmieren I für Studierende der Mathematik — SoSe 2026

int main() {
cout
<< setw(3) << ”n”
<< ” ”
<< setw(9) << ”2^n”
<< ” ”
<< setw(p + 7) << ”s”
<< setw(p + 3) << ”A”
<< setw(p + 3) << ”B”
<< setw(p + 3) << ”PI”
<< ” ”
<< setw(p + 7) << ”s_2”
<< setw(p + 3) << ”A_2”
<< setw(p + 3) << ”B_2”
<< setw(p + 3) << ”PI_2”
<< endl;

double s = sqrt(2), s2 = s;
int n = 2;

while (s * s / 4 >= eps || s2 * s2 / 4 >= eps) {


double A = ldexp(s * sqrt(1 - s*s/4), n - 1);
double B = ldexp(s / sqrt(1 - s*s/4), n - 1);
double A2 = ldexp(s2 * sqrt(1 - s2*s2/4), n - 1);
double B2 = ldexp(s2 / sqrt(1 - s2*s2/4), n - 1);

cout
<< fixed << setw(3) << n
<< ” ”
<< fixed << setw(9) << setprecision(0) << ldexp(1, n)
<< ” ”
<< scientific << setw(p + 7) << setprecision(p) << s
<< fixed << setw(p + 3) << A
<< setw(p + 3) << B
<< setw(p + 3) << ((A + B) / 2)
<< ” ”
<< scientific << setw(p + 7) << s2
<< fixed << setw(p + 3) << A2
<< setw(p + 3) << B2
<< setw(p + 3) << ((A2 + B2) / 2)
<< endl;

n += 1;
s = sqrt(2 - sqrt(4 - s*s));
s2 = s2 / sqrt(2 + sqrt(4 - s2*s2));
}

cout
<< setw(3) << ””
<< ” ”
<< setw(9) << ””

Seite 2 von 4
Aufgabe 7 Programmieren I für Studierende der Mathematik — SoSe 2026

<< ” ”
<< setw(p + 7) << ””
<< setw(p + 3) << ””
<< setw(p + 3) << ””
<< fixed << setw(p + 3) << setprecision(p) << pi
<< ” ”
<< setw(p + 7) << ””
<< setw(p + 3) << ””
<< setw(p + 3) << ””
<< setw(p + 3) << pi
<< endl;

return 0;
}

Seite 3 von 4
Aufgabe 7
n 2^n s A B PI s_2 A_2 B_2 PI_2
2 4 1.414213562373095e+00 2.000000000000000 4.000000000000001 3.000000000000000 1.414213562373095e+00 2.000000000000000 4.000000000000001 3.000000000000000
3 8 7.653668647301797e-01 2.828427124746191 3.313708498984761 3.071067811865476 7.653668647301796e-01 2.828427124746190 3.313708498984761 3.071067811865476
4 16 3.901806440322566e-01 3.061467458920719 3.182597878074529 3.122032668497623 3.901806440322566e-01 3.061467458920718 3.182597878074528 3.122032668497623
5 32 1.960342806591213e-01 3.121445152258054 3.151724907429257 3.136585029843656 1.960342806591212e-01 3.121445152258052 3.151724907429256 3.136585029843654
6 64 9.813534865483560e-02 3.136548490545926 3.144118385245890 3.140333437895908 9.813534865483603e-02 3.136548490545939 3.144118385245904 3.140333437895922
7 128 4.908245704582433e-02 3.140331156954737 3.142223629942441 3.141277393448589 4.908245704582458e-02 3.140331156954753 3.142223629942457 3.141277393448605
8 256 2.454307657143864e-02 3.141277250932617 3.141750369168811 3.141513810050714 2.454307657143985e-02 3.141277250932773 3.141750369168967 3.141513810050870
9 512 1.227176929831204e-02 3.141513801145092 3.141632080703973 3.141572940924533 1.227176929830895e-02 3.141513801144301 3.141632080703182 3.141572940923742
10 1024 6.135913525937424e-03 3.141572940369893 3.141602510259610 3.141587725314751 6.135913525931953e-03 3.141572940367092 3.141602510256809 3.141587725311950
11 2048 3.067960372563120e-03 3.141587725270595 3.141595117743024 3.141591421506810 3.067960372569531e-03 3.141587725277160 3.141595117749589 3.141591421513374
12 4096 1.533980637505409e-03 3.141591421552159 3.141593269670266 3.141592345611213 1.533980637485409e-03 3.141591421511200 3.141593269629308 3.141592345570254
13 8192 7.669903751330577e-04 3.141592345530249 3.141592807559776 3.141592576545013 7.669903751427912e-04 3.141592345570118 3.141592807599645 3.141592576584881
14 16384 3.834951945145567e-04 3.141592575709558 3.141592691216940 3.141592633463249 3.834951946214067e-04 3.141592576584873 3.141592692092255 3.141592634338564
15 32768 1.917475985600335e-04 3.141592640369167 3.141592669246012 3.141592654807590 1.917475981919547e-04 3.141592634338564 3.141592663215409 3.141592648776986
16 65536 9.587379899051560e-05 3.141592641711610 3.141592648930821 3.141592645321215 9.587379920613378e-05 3.141592648776986 3.141592655996198 3.141592652386592
17 131072 4.793689891625549e-05 3.141592606473318 3.141592608278121 3.141592607375720 4.793689961683645e-05 3.141592652386592 3.141592654191395 3.141592653288993
18 262144 2.396845177413691e-05 3.141592910714072 3.141592911165273 3.141592910939673 2.396844981013942e-05 3.141592653288993 3.141592653740194 3.141592653514594
19 524288 1.198423051908566e-05 3.141594125138791 3.141594125251591 3.141594125195191 1.198422490528486e-05 3.141592653514594 3.141592653627394 3.141592653570994
20 1048576 5.992119891557349e-06 3.141596553690719 3.141596553718920 3.141596553704820 5.992112452669323e-06 3.141592653570994 3.141592653599194 3.141592653585094
21 2097152 2.996059945778675e-06 3.141596553701294 3.141596553708345 3.141596553704820 2.996056226338024e-06 3.141592653585094 3.141592653592145 3.141592653588619

Programmieren I für Studierende der Mathematik — SoSe 2026


22 4194304 1.498067028532866e-06 3.141674265020876 3.141674265022639 3.141674265021758 1.498028113169432e-06 3.141592653588619 3.141592653590382 3.141592653589501
23 8388608 7.490705685351375e-07 3.141829681888981 3.141829681889422 3.141829681889202 7.490140565847686e-07 3.141592653589501 3.141592653589941 3.141592653589721
24 16777216 3.746093836419742e-07 3.142451272494079 3.142451272494189 3.142451272494134 3.745070282923909e-07 3.141592653589721 3.141592653589831 3.141592653589776
25 33554432 1.873046918209871e-07 3.142451272494120 3.142451272494148 3.142451272494134 1.872535141461962e-07 3.141592653589776 3.141592653589804 3.141592653589790
26 67108864 9.424321830774485e-08 3.162277660168376 3.162277660168383 3.162277660168380 9.362675707309823e-08 3.141592653589790 3.141592653589797 3.141592653589794
27 134217728 4.712160915387242e-08 3.162277660168379 3.162277660168380 3.162277660168380 4.681337853654913e-08 3.141592653589794 3.141592653589795 3.141592653589794
3.141592653589793 3.141592653589793
Seite 4 von 4
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 5 – Lösungsvorschlag

Aufgabe 8 Formulieren Sie die folgenden beiden Verfahren zur Berechnung des Umfangs 𝑈 einer Ellipse
mit den Halbachsen 𝑎 0 ≥ 𝑏 0 > 0 jeweils als Funktion.
Lesen Sie 𝑎 0, 𝑏 0 im Hauptprogramm ein und berechnen Sie den Ellipsenumfang mit beiden Funktionen. Ge-
ben Sie 𝑎 0, 𝑏 0 im Festpunktformat mit 4 Nachkommastellen und die berechneten Umfangswerte in einem der
Genauigkeit gut angepassten Exponentialformat aus.

a) Wir betrachten die beiden Folgen (𝑎𝑖 )𝑖 ∈N0 , (𝑏𝑖 )𝑖 ∈N0 :

𝑎𝑖 + 𝑏 𝑖 p
𝑎𝑖+1 = 𝑏𝑖+1 = 𝑎𝑖 𝑏𝑖
2

Die beiden Folgen konvergieren gegen einen gemeinsamen Grenzwert, das arithmetisch-geometrische
Mittel von 𝑎 0 und 𝑏 0 .
Es gilt für den Umfang 𝑈 der Ellipse mit den Halbachsen 𝑎 0, 𝑏 0 :

𝑈 = lim 𝑈𝑛
𝑛→∞
2𝜋 (𝑎 20 − 𝑠𝑛 ) Õ
𝑛
𝑈𝑛 := 𝑠𝑛 := 2𝑖 −1 (𝑎𝑖2 − 𝑏𝑖2 )
𝑎𝑛 𝑖=0

Programmieren Sie dieses Verfahren. Brechen Sie die Berechnung ab wenn 2𝑛−1 (𝑎𝑛2 − 𝑏𝑛2 ) < 𝜀𝑎 20 .
𝜀 bezeichnet hierbei die Maschinengenauigkeit.
Berechnen Sie zur Vermeidung der Auslöschung die Größe 𝑐𝑖 := 𝑎𝑖2 − 𝑏𝑖2 über:

𝑐𝑖2−1
𝑐𝑖 =
16 · 𝑎𝑖2

Hinweis. Achten Sie bei der Implementierung der Rekursionsvorschriften für (𝑎𝑖 )𝑖 ∈N0 und (𝑏𝑖 )𝑖 ∈N0 auf
folgenden Umstand:

a = a0; b = b0;
while (a > b + eps) {
a = (a + b)/2.;
b = sqrt(a*b);
}

Die obige Implementierung ist nicht korrekt. Sie implementiert stattdessen fälschlicherweise die
folgende Beziehung:

𝑎𝑖 + 𝑏 𝑖 p
𝑎𝑖+1 = 𝑏𝑖+1 = 𝑎𝑖+1𝑏𝑖
2

Sie werden innerhalb des Schleifenkörpers eine Hilfsvariable einführen müssen, die den vorherigen
Wert von a „zwischenspeichert“ damit er für die Berechnung des neuen Werts von b weiterhin zur
Verfügung steht.

Seite 1 von 8
Aufgabe 8 Programmieren I für Studierende der Mathematik — SoSe 2026

b) Programmieren Sie zum Vergleich die langsamer konvergierende Potenzreihenmethode zur Bestim-
mung des Ellipsenumfangs:
 2 2  2  2 ! s
1 𝑒 1 · 3 𝑒4 1 · 3 · 5 𝑒6 𝑏2
𝑈 = 2𝜋𝑎 0 1 − − − −... 𝑒 := 1 − 02
2 1 2·4 3 2·4·6 5 𝑎0

Die Größe 𝑒 bezeichnet man als die numerische Exzentrizität der Ellipse.
Leiten Sie hierfür eine geeignete Rekursionsbeziehung her und brechen Sie die Berechnung der Potenz-
reihe mit einem sinnvollen Kriterium ab.

Rechnen Sie zwei eigene Testbeispiele und zusätzlich auch:

i) 𝑎 0 = 2 𝑏 0 = 2 ii) 𝑎 0 = 5 𝑏 0 = 3 iii) 𝑎 0 = 10 𝑏 0 = 9,999 99

Hinweis (Kontrollergebnisse). Die folgenden Werte haben in etwa die volle zu erwartende Präzision:

i) 12,566 370 614 359 17 ii) 25,526 998 863 398 13 iii) 62,831 821 655 873 26

[Link]

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

using namespace std;

double eps = numeric_limits<double>::epsilon();


double pi = 4*atan(1);

double umfang_arithgeo(double a0, double b0) {


int n = 0;
double a = a0, b = b0, c = a*a - b*b, s = c / 2;

while (ldexp(c, n-1) >= eps * a0 * a0) {


double h = a;

a = (a + b) / 2;
b = sqrt(h * b);
c = c * c / (16 * a * a);
s += ldexp(c, n);
n++;
}

return 2 * pi * (a0 * a0 - s) / a;
}

double umfang_potreihe(double a0, double b0) {


int n = 3;
double es = 1 - b0 * b0 / (a0 * a0), s = 1, x = 0.25 * es;

while (x >= eps) {


s -= x;

x *= es * (n - 2) / n * (n * n) / ((n + 1) * (n + 1));

Seite 2 von 8
Aufgabe 8 Programmieren I für Studierende der Mathematik — SoSe 2026

n += 2;
}

return 2 * pi * a0 * s;
}

int main() {
double a0, b0;

cout << ”a0, b0: ”;


cin >> a0 >> b0;

if (a0 < b0) {


double h = a0;
a0 = b0;
b0 = h;
}

cout << ”a0 = ” << fixed << setprecision(4) << a0 << endl
<< ”b0 = ” << fixed << setprecision(4) << b0 << endl;

cout << ”umfang_arithgeo(a0, b0) = ”


<< scientific << setprecision(numeric_limits<double>::digits10)
<< umfang_arithgeo(a0, b0) << endl
<< ”umfang_potreihe(a0, b0) = ”
<< scientific << setprecision(numeric_limits<double>::digits10)
<< umfang_potreihe(a0, b0) << endl;

return 0;
}

a0, b0: 2 2
a0 = 2.0000
b0 = 2.0000
umfang_arithgeo(a0, b0) = 1.256637061435917e+01
umfang_potreihe(a0, b0) = 1.256637061435917e+01

a0, b0: 5 3
a0 = 5.0000
b0 = 3.0000
umfang_arithgeo(a0, b0) = 2.552699886339813e+01
umfang_potreihe(a0, b0) = 2.552699886339815e+01

a0, b0: 10 9.99999


a0 = 10.0000
b0 = 10.0000
umfang_arithgeo(a0, b0) = 6.283182165587326e+01
umfang_potreihe(a0, b0) = 6.283182165587326e+01

Seite 3 von 8
A. Arithm.-Geom. Mittel Programmieren I für Studierende der Mathematik — SoSe 2026

Anhang

A. Arithm.-Geom. Mittel

Lemma A.1. Sei 𝑎 0 ≥ 𝑏 0 > 0.


𝑎𝑛 + 𝑏 𝑛 p
𝑎𝑛+1 := 𝑏𝑛+1 := 𝑎𝑛𝑏𝑛
2
Dann gilt:

i) ∀𝑛 ∈ N0 . 𝑎𝑛 ≥ 𝑎𝑛+1 ≥ 𝑏𝑛+1 ≥ 𝑏𝑛
ii) lim𝑛→∞ 𝑎𝑛 = lim𝑛→∞ 𝑏𝑛 ≕ 𝑀 (𝑎 0, 𝑏 0 ) existiert.
iii) 𝑐𝑛 := 𝑎𝑛2 − 𝑏𝑛2 konvergiert quadratisch gegen 0, insb.:

𝑐𝑛2
0 ≤ 𝑐𝑛+1 ≤
16 · (𝑀 (𝑎 0, 𝑏 0 )) 2
Beweis von Lemma A.1.

i) Per Induktion
ii) Es ist lim𝑛→∞ 𝑎𝑛 ≥ lim𝑛→∞ 𝑏𝑛 wegen der Monotonie der Folgen
Es ist:
𝑎𝑛 + 𝑏 𝑛 𝑎𝑛 − 𝑏 𝑛
0 ≤ 𝑎𝑛+1 − 𝑏𝑛+1 ≤ 𝑎𝑛+1 − 𝑏𝑛 = − 𝑏𝑛 =
2 2
Daher ist 𝑎𝑛 − 𝑏𝑛 ≤ 2−𝑛 (𝑎 0 − 𝑏 0 ).
iii)

𝑐𝑛+1 = 𝑎𝑛+1
2
− 𝑏𝑛+1
2
 2
𝑎𝑛 + 𝑏 𝑛
= − 𝑎𝑛 𝑏 𝑛
2
𝑎 2 − 2𝑎𝑛𝑏𝑛 + 𝑏𝑛2
= 𝑛
4
 2
𝑎𝑛 − 𝑏 𝑛
=
2
 2
𝑎𝑛2 − 𝑏𝑛2
=
2 · (𝑎𝑛 + 𝑏𝑛 )
 2
𝑐𝑛
=
4 · 𝑎𝑛+1
𝑐𝑛2 𝑐𝑛2
= ≤
16 · 𝑎𝑛+1
2
16 · (𝑀 (𝑎 0, 𝑏 0 )) 2

Satz A.1. Mit Lemma A.1 gilt:


𝜋/2
𝑀 (𝑎 0, 𝑏 0 ) = ∫ 𝜋/2
1
q d𝜑
0 𝑎 0 cos 𝜑 + 𝑏 0 sin 𝜑
2 2 2 2

| {z }
≕ 𝐼 (𝑎 0 ,𝑏 0 )

Seite 4 von 8
A. Arithm.-Geom. Mittel Programmieren I für Studierende der Mathematik — SoSe 2026

 √ 
𝑎+𝑏
Beweis von Satz A.1. Sei 𝑎 ≥ 𝑏 > 0. Wir zeigen zunächst 𝐼 (𝑎, 𝑏) = 𝐼 2 , 𝑎𝑏 :
∫ 𝜋/2
1
𝐼 (𝑎, 𝑏) = p d𝜑
0 𝑎 2 cos2 𝜑 + 𝑏 2 sin2 𝜑
∫ 𝜋/2
1
= p d𝜑
0 cos 𝜑 𝑎 2 + 𝑏 2 tan2 𝜑
∫ ∞
1
= p d𝑡 Substitution 𝑡 = 𝑏 tan 𝜑
0 (𝑎 2 + 𝑡 2 )(𝑏 2 + 𝑡 2 )
  ∫
𝑎 +𝑏 √ 1 ∞ 1
𝐼 , 𝑎𝑏 = s d𝑡
2 2 −∞   2 
𝑎+𝑏
2 + 𝑡 2 (𝑎𝑏 + 𝑡 2 )
 
∫ ∞
1
1+ 𝑎𝑏  
1 2 𝑠2 1 𝑎𝑏
= √   d𝑠 Substitution 𝑡 = 𝑠−
2 0 (𝑎 2 +𝑠 2 ) (𝑏 2 +𝑠 2 ) 1 𝑎𝑏 2 𝑠
2𝑠 𝑠2 1+ 𝑠2
∫ ∞
1
= p d𝑠 = 𝐼 (𝑎, 𝑏)
0 (𝑎 2 + 𝑏 2 ) (𝑏 2 + 𝑠 2 )

Es ist daher 𝐼 (𝑎 0, 𝑏 0 ) = 𝐼 (𝑎𝑛 , 𝑏𝑛 ) für alle 𝑛 ∈ N. Daher:

𝐼 (𝑎 0, 𝑏 0 ) = lim 𝐼 (𝑎𝑛 , 𝑏𝑛 )
𝑛→∞
∫ 𝜋/2
1
= lim p d𝜑
𝑛→∞ 0 𝑎𝑛2 cos2 𝜑 + 𝑏𝑛2 sin2 𝜑
∫ 𝜋/2
1
= d𝜑 𝑎𝑛2 −−−−→ 𝑀 (𝑎 0, 𝑏 0 ) 𝑏𝑛2 −−−−→ 𝑀 (𝑎 0, 𝑏 0 )
0 𝑀 (𝑎 0, 𝑏 0 ) 𝑛→∞ 𝑛→∞
𝜋/2
=
𝑀 (𝑎 0, 𝑏 0 )

Satz A.2. Mit Lemma A.1 gilt:


∫ q Í∞
𝜋 𝑎 20 − 𝑛=0 2𝑛−1 (𝑎𝑛2 − 𝑏𝑛2 )
𝜋/2

𝐽 (𝑎 0, 𝑏 0 ) := 𝑎 0 cos 𝜑 + 𝑏 0 sin 𝜑 d𝜑 =
2 2 2 2
0 2 lim𝑛→∞ 𝑎𝑛
| {z }
= 1/4 𝑈

U ist hierbei der Umfang der Ellipse mit Halbachsen 𝑎 0, 𝑏 0 , vgl. Gleichung 9 in Unterabschnitt B.

Lemma A.2. Sei 𝑎 ≥ 𝑏 > 0. Es gilt:


 
𝑎 +𝑏 √
2·𝐽 , 𝑎𝑏 − 𝐽 (𝑎, 𝑏) = 𝑎𝑏 · 𝐼 (𝑎, 𝑏)
2
Beweis von Satz A.2. Wir wenden Lemma A.2 an:

2𝐽 (𝑎𝑛+1, 𝑏𝑛+1 ) − 𝐽 (𝑎𝑛 , 𝑏𝑛 ) = 𝑎𝑛𝑏𝑛 𝐼 (𝑎𝑛 , 𝑏𝑛 )


𝑛+1
⇒ 2 𝐽 (𝑎𝑛+1, 𝑏𝑛+1 ) − 2𝑛 𝐽 (𝑎𝑛 , 𝑏𝑛 ) = 2𝑛 𝑎𝑛𝑏𝑛 𝐼 (𝑎𝑛 , 𝑏𝑛 )

Seite 5 von 8
A. Arithm.-Geom. Mittel Programmieren I für Studierende der Mathematik — SoSe 2026

Wegen lim𝑛→∞ 2𝑛 𝐽 (𝑎𝑛 , 𝑏𝑛 ) = ∞ betrachten wir 2𝑛 𝐽 (𝑎𝑛 , 𝑏𝑛 ) − 2𝑛 𝑎𝑛2 𝐼 (𝑎𝑛 , 𝑏𝑛 ) mit:

lim |2𝑛 𝐽 (𝑎𝑛 , 𝑏𝑛 ) − 2𝑛 𝑎𝑛2 𝐼 (𝑎𝑛 , 𝑏𝑛 )| = 0 (1)


𝑛→∞

Mit 𝐼 (𝑎𝑛 , 𝑏𝑛 ) = 𝐼 (𝑎 0, 𝑏 0 ):
 
2𝑛+1 𝐽 (𝑎𝑛+1, 𝑏𝑛+1 ) − 2𝑛+1𝑎𝑛+1
2
𝐼 (𝑎𝑛+1, 𝑏𝑛+1 ) − 2𝑛 𝐽 (𝑎𝑛 , 𝑏𝑛 ) − 2𝑛 𝑎𝑛2 𝐼 (𝑎𝑛 , 𝑏𝑛 ) = 2𝑛−1 (𝑎𝑛2 − 𝑏𝑛2 )𝐼 (𝑎 0, 𝑏 0 )

Aufsummieren beider Seiten und Anwenden von Gleichung 1 liefert:

 Õ∞
− 𝐽 (𝑎 0, 𝑏 0 ) − 𝑎 20 𝐼 (𝑎 0, 𝑏 0 ) = 2𝑛−1 (𝑎𝑛2 − 𝑏𝑛2 )𝐼 (𝑎 0, 𝑏 0 )
𝑛=0

Mit Satz A.1 ergibt sich damit die Aussage.


Beweis von Lemma A.2. Wir definieren:
∫ 𝜋/2
cos2 𝜑
𝐿(𝑎, 𝑏) := p d𝜑
0 𝑎 2 cos2 𝜑 + 𝑏 2 sin2 𝜑
Es ist:
q
𝑏 2 + (𝑎 2 − 𝑏 2 ) cos2 𝜑
𝑎 2 cos2 𝜑 + 𝑏 2 sin2 𝜑 = p (2)
𝑎 2 cos2 𝜑 + 𝑏 2 sin2 𝜑
𝑠2 𝑎2 1 𝑏2 1
= − (3)
(𝑎 + 𝑠 ) (𝑏 + 𝑠 ) 𝑎 − 𝑏 𝑎 + 𝑠
2 2 2 2 2 2 2 2 𝑎 − 𝑏 𝑏 + 𝑠2
2 2 2

Damit:

𝐽 (𝑎, 𝑏) = 𝑏 2 𝐼 (𝑎, 𝑏) + (𝑎 2 − 𝑏 2 )𝐿(𝑎, 𝑏) Mit (2) (4)


∫ ∞
1
𝐿(𝑎, 𝑏) = 𝑏 2
√ √ d𝑡 Substitution: 𝑡 = 𝑏 tan 𝜑 (5)
0 𝑎2 + 𝑡 2 𝑏 2 + 𝑡 2
𝐿(𝑎, 𝑏) + 𝐿(𝑏, 𝑎) = 𝐼 (𝑎, 𝑏) sin2 𝜑 + cos2 𝜑 = 1 (6)
  ∫ ∞  
√ 𝑎 +𝑏 𝑠2 1 𝑎𝑏
𝐿 𝑎𝑏, = (𝑎 + 𝑏) 2 p d𝑠 Mit (5) und Subst. 𝑡 = 𝑠− (7)
2 −∞ (𝑎 2 + 𝑠 2 )(𝑏 2 + 𝑠 2 ) 2 𝑠
 
√ 𝑎 +𝑏 𝑎 +𝑏
𝐿 𝑎𝑏, = (𝐿(𝑏, 𝑎) − 𝐿(𝑎, 𝑏)) Mit (7), (5) und (3) (8)
2 𝑎 −𝑏
     2  
𝑎 +𝑏 √ 𝑎 +𝑏 √ 𝑎 −𝑏 𝑎 +𝑏 √
𝐽 , 𝑎𝑏 = 𝑎𝑏𝐼 , 𝑎𝑏 + 𝐿 , 𝑎𝑏 Mit (4)
2 2 2 2
 2  2  
𝑎 +𝑏 𝑎 −𝑏 √ 𝑎 +𝑏
= 𝐼 (𝑎, 𝑏) − 𝐿 𝑎𝑏, Mit (6)
2 2 2
 2
𝑎 +𝑏 𝑎 −𝑏
2 2
= 𝐼 (𝑎, 𝑏) − (𝐿(𝑏, 𝑎) − 𝐿(𝑎, 𝑏)) Mit (8)
2 4
𝑎𝑏 1
= 𝐼 (𝑎, 𝑏) + 𝐽 (𝑎, 𝑏) Mit (6) und (4)
2 2

Mit Satz A.2 berechnet das Verfahren aus der Aufgabe in der Tat den Ellipsenumfang. Im Folgenden betrachten
wir die Konvergenzgeschwindigkeit des Verfahrens.

Satz A.3.
lim |2𝑛 · 𝐽 (𝑎𝑛 , 𝑏𝑛 ) − 2𝑛 𝑎𝑛2 · 𝐼 (𝑎𝑛 , 𝑏𝑛 )| ≤ 2𝑛𝑐𝑛 · 2 · 𝐼 (𝑎 0, 𝑏 0 ) −−−−→ 0
𝑛→∞ 𝑛→∞

Seite 6 von 8
A. Arithm.-Geom. Mittel Programmieren I für Studierende der Mathematik — SoSe 2026

Beweis von Satz A.3. Wir zeigen zunächst:

lim |2𝑛 𝐽 (𝑎𝑛 , 𝑏𝑛 ) − 2𝑛 𝑎𝑛2 𝐼 (𝑎𝑛 , 𝑏𝑛 )| = 0


𝑛→∞

Aus Gleichung 4 folgt:

2𝑛 𝐽 (𝑎𝑛 , 𝑏𝑛 ) = 2𝑛𝑏𝑛2 𝐼 (𝑎𝑛 , 𝑏𝑛 ) + 2𝑛 (𝑎𝑛2 − 𝑏𝑛2 )𝐿(𝑎𝑛 , 𝑏𝑛 )


= 2𝑛 𝑎𝑛2 𝐼 (𝑎𝑛 , 𝑏𝑛 ) + 2𝑛 (𝑎𝑛2 − 𝑏𝑛2 ) (𝐿(𝑎𝑛 , 𝑏𝑛 ) − 𝐼 (𝑎𝑛 , 𝑏𝑛 ))

Und mit dem Beweis von Satz A.1 und cos2 𝜑 ≤ 1:

|𝐿(𝑎𝑛 , 𝑏𝑛 )| ≤ 𝐼 (𝑎𝑛 , 𝑏𝑛 ) = 𝐼 (𝑎 0, 𝑏 0 )

Zusammen mit 2𝑛𝑐𝑛 −−−−→ 0 ergibt sich die Aussage.


𝑛→∞

Seite 7 von 8
B. Potenzreihenentwicklung Programmieren I für Studierende der Mathematik — SoSe 2026

B. Potenzreihenentwicklung

Eine Ellipse mit Halbachsen 𝑎 0 ≥ 𝑏 0 > 0 wird beschrieben durch die Ellipsengleichung:

𝑥 2 𝑦2
+ =1
𝑎 20 𝑏 02

Wir können sie stattdessen auch nach dem Winkel 𝜋 ∈ [0, 2𝜋] parametrisieren und erhalten als Parameter-
darstellung:    
#⇀ 𝑥 (𝜑) 𝑎 0 cos 𝜑
𝑥 = =
𝑦 (𝜑) 𝑏 0 sin 𝜑

Wir bestimmen den Umfang der Ellipse durch Integration über die Parameterdarstellung:
∫ 2𝜋
d #⇀𝑥
𝑈 = d𝜑
0 d𝜑
s
∫ 2𝜋  2  
d𝑥 d𝑦 2
= + d𝜑
0 d𝜑 d𝜑
∫ 2𝜋 q
= 𝑎 20 sin2 𝜑 + 𝑏 02 cos2 𝜑 d𝜑
0
∫ 𝜋/2 q
=4 𝑎 20 sin2 𝜑 + 𝑏 02 cos2 𝜑 d𝜑 Symmetrie
0
∫ 𝜋/2 q
𝜋
=4 𝑎 20 cos2 𝜑 + 𝑏 02 sin2 𝜑 d𝜑 Substitution 𝜑 ↦→ −𝜑 (9)
0 2
∫ 𝜋/2 q
=4 𝑎 20 + (𝑏 02 − 𝑎 20 ) sin2 𝜑 d𝜑 sin2 𝜑 + cos2 𝜑 = 1
0
∫ 𝜋/2 s  
𝑏 02
= 4𝑎 0 1 − 1 − 2 sin2 𝜑 d𝜑
0 𝑎0
∫ 𝜋/2 q s
𝑏 02
= 4𝑎 0 1 − 𝑒 2 sin2 𝜑 d𝜑 𝑒 := 1−
0 𝑎 20
∫ 𝜋/2  1 1 1 
= 4𝑎 0 1− 2 𝑒 2 sin2 𝜑 + 2 𝑒 4 sin4 𝜑 − 2 𝑒 6 sin6 𝜑 + . . . d𝜑 Taylorreihe der Wurzelfunktion
0 1 2 3
∫ 𝜋/2  
1 𝑒2 1 · 3 𝑒4 1 · 3 · 5 𝑒6
= 4𝑎 0 1− sin 𝜑 +
2
sin 𝜑 −
4
sin 𝜑 + . . . d𝜑 Binomialkoeffizient
6
0 21 2·4 3 2·4·6 5
 2 2  2  2 ! ∫ 𝜋/2
1 𝑒 1 · 3 𝑒4 1 · 3 · 5 𝑒6
= 2𝜋𝑎 0 1 − − − −... sin2𝑘 𝜑 d𝜑
2 1 2·4 3 2·4·6 5 0
(2𝑘 − 1) · (2𝑘 − 3) · . . . · 1 𝜋
= ·
2𝑘 · (2𝑘 − 2) · . . . · 2 2

Seite 8 von 8
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 6 – Lösungsvorschlag

Aufgabe 9 Wir erstellen ein Programm zur Multiplikation zweier natürlicher Zahlen vom Datentyp
unsigned int nur durch Binäroperationen und Vergleiche.

Anmerkung. Wir teilen die Erstellung des Programms kleinschrittig auf. Die hier präsentierte spezielle Auftei-
lung ist top-down, d.h. ausgehend von einer groben Zielsetzung für das ganze Programm werden Lösungen
von Teilproblemen durch Platzhalter oder approximative Lösungen schrittweise durch tatsächliche Imple-
mentierungen ersetzt.
Es wäre auch denkbar die grobe Struktur des Programms zunächst nur theoretisch zu entwickeln und
dann, ohne Zwischenschritte mit Platzhaltern oder approximativen Lösungen, einzelne Teile der ange-
dachten Lösung direkt zu implementieren. Sobald hinreichend viele dieser Lösungen von Teilproblemen
implementiert wurden, können sie dann zu einer Gesamtlösung zusammengesetzt werden. Diesen Ansatz
nennt man bottom-up.
In der modernen Softwareentwicklung laufen die Design-Phase top-down und die eigentlich Entwicklung
bottom-up i.d.R. gleichzeitig.
Gehen Sie vor, wie folgt:

1) Erstellen Sie eine Funktion printbits ohne Rückgabewert (Rückgabetyp void) mit einem Parameter
u vom Typ unsigned int. Die Funktion soll alle numeric_limits<unsigned int>::digits viele bits
von u ausgeben.
Testen Sie die Funktion an vorzeichenlosen ganzen Zahlen, welche Sie im Hauptprogramm hexadezimal
einlesen.
2) Erstellen Sie eine Funktion mult mit zwei Parametern a und b jeweils vom Datentyp unsigned int. Die
Funktion soll als Rückgabewert das Ergebnis der Multiplikation der beiden Zahlen a · b liefern.
Gehen Sie vor wie folgt:

i) Implementieren Sie die Multiplikation innerhalb von mult durch Addition, shift-Operatoren und
Vergleiche (siehe Unterabschnitt B):


𝑡 −1
𝑎 ·𝑏 ≡ 𝑏𝑖 · (𝑎 ≪ 𝑖) mod 2𝑡
𝑖=0

{
𝑥 𝑏𝑖 = 1
Hinweis. Für 𝑏𝑖 ∈ {0, 1}, 𝑥 ∈ N ist 𝑏𝑖 · 𝑥 =
0 sonst
ii) Passen Sie ihr Hauptprogramm an um die Funktion mult zu testen.
Geben Sie für hexadezimal eingelesene vorzeichenlose ganzen Zahlen das Ergebnis der Berech-
nung sowohl binär (mit printbits), wie auch hexadezimal und dezimal aus.
iii) Ersetzen Sie den herkömmlichen Additions-Operator in mult durch eine hierfür implementier-
te Funktion add, welche jedoch wiederum zunächst nur den herkömmlichen Additions-Operator
verwendet.
iv) Stellen Sie sicher, dass ihre Funktion mult weiterhin korrekte Ergebnisse liefert.

Seite 1 von 5
Aufgabe 9 Programmieren I für Studierende der Mathematik — SoSe 2026

3) Ersetzen Sie die bisher verwendete Addition mit eingebautem Additionsoperator durch eine eigene
Implementierung:

i) Passen Sie ihr Hauptprogramm an um die Funktion add zu testen.


ii) Implementieren Sie die Addition in add statt mit dem eingebauten Additionsoperator mit der von-
Neumann-Addition (siehe Unterabschnitt A):
Für Parameter 𝑎, 𝑏 seien 𝑎 (0) := 𝑎, 𝑏 (0) := 𝑏 und:
( )
𝑎 (𝑘+1) := 𝑎 (𝑘 ) ^ 𝑏 (𝑘 ) 𝑏 (𝑘+1) := 𝑎 (𝑘 ) & 𝑏 (𝑘 ) ≪ 1

Berechnen Sie in einer Schleife stets das nächste Glied dieser Folgen solange bis 𝑏 (𝑛) = 0.
Danach speichert 𝑎 (𝑛) das Ergebnis der Addition.
iii) Testen Sie die Funktion add ausgiebig.

4) Ändern Sie ihr Hauptprogramm „wieder zurück“ sodass dort mult aufgerufen wird.

Rechnen Sie folgende Beispiele:

𝑎 𝑏

fa7 a8bd

ffff 10001

777777 888

Hinweis (Kontrollergebnisse). Ihr Programm könnte sich verhalten, wie folgt:

a, b: fa7 a8bd a, b: 777777 888


a: 00000000000000000000111110100111 a: 00000000011101110111011101110111
b: 00000000000000001010100010111101 b: 00000000000000000000100010001000
a * b: 00001010010100010010011001001011 a * b: 11111011001100110010111100111000
= a51264b = fb332f38
= 173090379 = 4214435640

a, b: ffff 10001
a: 00000000000000001111111111111111
b: 00000000000000010000000000000001
a * b: 11111111111111111111111111111111
= ffffffff
= 4294967295

[Link]

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

using namespace std;

unsigned int add(unsigned int a, unsigned int b) {

Seite 2 von 5
Aufgabe 9 Programmieren I für Studierende der Mathematik — SoSe 2026

while (b != 0) {
unsigned int h = a ^ b;

b &= a;
b <<= 1;

a = h;
}

return a;
}

unsigned int mult(unsigned int a, unsigned int b) {


unsigned int res = 0;

while (b != 0) {
if (b & 1)
res = add(res, a);

b >>= 1;
a <<= 1;
}

return res;
}

int uid = numeric_limits<unsigned int>::digits;

void printbits(unsigned int u) {


int shift = uid - 1;
while (shift >= 0)
cout << (u >> shift-- & 1 ? '1' : '0');
}

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

cout << ”a: ”;


printbits(a);
cout << endl;
cout << ”b: ”;
printbits(b);
cout << endl;

unsigned int res = mult(a, b);


cout << ”a * b: ”;
printbits(res);
cout << endl
<< ” = ” << hex << setw(uid) << res << endl
<< ” = ” << dec << setw(uid) << res << endl;
}

Seite 3 von 5
A. Von-Neumann-Addition Programmieren I für Studierende der Mathematik — SoSe 2026

Anhang

A. Von-Neumann-Addition

Wir betrachten die Addition zweier bits 𝑎𝑖 , 𝑏𝑖 ∈ {0, 1}, hier dargestellt zur Basis 2:

+ 0 1
0 002 012
1 012 102

Half-Adder

Wir beobachten, dass die beiden Binärstellen des Ergebnisses sich mit den Binäroperatoren ^ (hier im Folgen-
den ⊻) und & (hier im Folgenden ∧) berechnen lassen:

⊻ 0 1 ∧ 0 1
0 0 1 0 0 0
1 1 0 1 0 1

Die mit ∧ berechnete Binärstelle nennen wir das carry-bit.


∑𝑡 −1 𝑖 ∑𝑡 −1 𝑖
Die Addition zweier 𝑡-bit Binärzahlen 𝑎 = 𝑖=0 𝑎𝑖 2 , 𝑏 = 𝑖=0 𝑏𝑖 2 kann nur durch Binäroperatoren erfolgen,
wie folgt:


𝑡 −1
𝑎 +𝑏 = (𝑎𝑖 + 𝑏𝑖 )2𝑖
𝑖=0
𝑡∑−1 ∑
𝑡 −1
= (𝑎𝑖 ⊻ 𝑏𝑖 )2𝑖 + 2 · (𝑎𝑖 ∧ 𝑏𝑖 )2𝑖
𝑖=0 𝑖=0
𝑡∑−1 𝑡∑−1
≡ (𝑎𝑖 ⊻ 𝑏𝑖 )2𝑖 + (𝑎𝑖 −1 ∧ 𝑏𝑖 −1 )2𝑖 mod 2𝑡 (1)
𝑖=0 𝑖=1

Die hier in Gleichung 1 weiterhin benötigte Addition muss erneut mit dem hier beschrieben Additionsverfah-
ren realisiert werden.
Hierfür betrachten wir ausgehend von 𝑎 (0) := 𝑎, 𝑏 (0) := 𝑏 die Binärstellen zweier Folgen:

𝑏 0(𝑘+1) := 0
𝑎𝑖(𝑘+1) := 𝑎𝑖(𝑘 ) ⊻ 𝑏𝑖(𝑘 ) 𝑏𝑖(𝑘+1) := 𝑎𝑖(𝑘−1) ∧ 𝑏𝑖(𝑘−1)

Per Induktion ist 𝑏𝑖(𝑡 ) = 0.


Bei der von-Neumann-Addition (ein carry-save-adder) berechnen wir in jedem Schritt 𝑎 (𝑖 ) und 𝑏 (𝑖 ) vollständig.
Sobald 𝑏 (𝑘 ) = 0 können wir die Iteration abbrechen.
Wir identifizieren zudem: ( )
𝑏 (𝑘+1) = 𝑎 (𝑘 ) ∧ 𝑏 (𝑘 ) ≪ 1

Seite 4 von 5
B. Binäre Multiplikation Programmieren I für Studierende der Mathematik — SoSe 2026

B. Binäre Multiplikation
∑𝑡 −1
Wir betrachten die Multiplikation zweier 𝑡-bit Binärzahlen 𝑎 und 𝑏 = 𝑖=0 𝑏𝑖 2𝑖 :
( 𝑡 −1 )

𝑎 ·𝑏 =𝑎 · 𝑏𝑖 2𝑖
𝑖=0

𝑡 −1
= (𝑏𝑖 · 𝑎)2𝑖
𝑖=0
𝑡∑−1
≡ 𝑏𝑖 · (𝑎 ≪ 𝑖) mod 2𝑡
𝑖=0

Wir beobachten zudem für 𝑏𝑖 ∈ {0, 1} eine Binärstelle:


{
𝑥 𝑏𝑖 = 1
𝑏𝑖 · 𝑥 =
0 sonst

Seite 5 von 5
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 7 – Lösungsvorschlag

Aufgabe 10 Für 𝑎 ∈ R+ gilt:


 √ 
2𝑛
ln(𝑎) = lim 2𝑛 𝑎−1 (1)
𝑛→∞
𝑎−1
= lim 2𝑛 Î  √  (2)
𝑛→∞ 𝑛 2𝑘
𝑘=1 1 + 𝑎

a) Implementieren Sie sowohl das Gleichung 1, wie auch das Gleichung 2 entsprechende Verfahren. Geben
Sie für beide Verfahren und jede Iteration die jeweilige berechnete Näherung an ln(𝑎) in einer gemein-
samen Tabelle aus und zum Vergleich am Ende auch den mit der Standardfunktion berechneten Wert
von ln(𝑎).
√𝑛
Brechen Sie die Berechnung ab, sobald 2 𝑎 − 1 ≤ 𝜀.
Verwenden Sie hier und im Folgenden für alle auszugebenden Größen ein der zur Verfügung stehenden
Genauigkeit gut angepasstes Exponentialformat.
qp

2𝑛

Die Berechnung von 𝑎 = · · · 𝑎 soll hierbei stets durch wiederholtes Ziehen der Quadratwurzel
erfolgen und nicht etwa mithilfe von pow, exp oder log. Für die Berechnung von 2𝑛 können Sie entweder
ldexp verwenden oder wiederholt mit 2 multiplizieren.

b) Erstellen Sie eine Funktion mylog mit einem Parameter 𝑎. Lagern Sie Ihre Implementierung der obigen
Verfahren in diese Funktion aus und geben Sie für 𝑎 > 0 den von Gleichung 2 berechneten Wert aus
der Funktion zurück.
Für 𝑎 = 0 soll der Funktionswert −∞ und für 𝑎 < 0 der Funktionswert NaN zurückgegeben werden.
Zusätzlich soll für 𝑎 = 0 ein Wertebereichsfehler und für 𝑎 < 0 ein Definitionsbereichsfehler mittels
des errno-Mechanismus kommuniziert werden.
Hinweis. Das Kommunizieren eines Fehlers mittels des errno-Mechanismus geschieht durch das Setzen
der in der Header-Datei <cerrno> vereinbarten globalen Variable errno auf einen der Fehlerkondi-
tion entsprechenden Wert. Ein Wertebereichsfehler entspricht dem Wert der ebenfalls in <cerrno>
vereinbarten Konstante ERANGE und ein Definitionsbereichsfehler dem Wert der Konstante EDOM.
Geben Sie in Ihrem Hauptprogramm zusätzlich und durch eine Leerzeile von der Ausgabe der ersten
Teilaufgabe abgetrennt Folgendes aus:

• 𝑎,
• den Wert der Standardfunktion log für 𝑎,
• den Wert Ihrer Funktion mylog für 𝑎 und
• jeweils für log und mylog den, nach Aufrufen der Funktion, in errno gespeicherten Wert und ggf.
die entsprechende Fehlermeldung.

Das Verhalten von mylog sollte, bis auf kleinere numerische Abweichungen und evtl. dem Vorzeichen
von NaN (welches bedeutungslos ist) genau dem Verhalten von log entsprechen.
Hinweis. Beachten Sie, dass, wenn kein Fehler auftritt, der in errno gespeicherte Wert nicht verändert
wird.
Die Funktion strerror aus der Header-Datei <cstring> liefert, gegeben einen int-Wert als Para-
meter, einen Wert den Sie mit dem üblichen cout-Mechanismus ausgeben können. Der gelieferte

Seite 1 von 8
Aufgabe 10 Programmieren I für Studierende der Mathematik — SoSe 2026

Wert entspricht einer Fehlermeldung die der Bedeutung des gegebenen int-Wertes anhand der in
<cerrno> vereinbarten Konstanten entspricht.

Nicht alle Implementierungen der C++-Standardlibrary unterstützen das Melden von Fehlerkondi-
tionen bei arithmetischen Funktionen mit dem errno-Mechanismus. Falls log in der von Ihnen ver-
wendeten Implementierung für keines der unten aufgeführten Beispiele errno setzt, implementieren
Sie das Melden von Fehlerkonditionen mit dem errno-Mechanismus in mylog trotzdem. In diesem
Fall ist natürlich eine Abweichung im Verhalten ggÜ. log zu erwarten.

Rechnen Sie die folgenden Beispiele:

i) 2 iii) 109 v) 1 vii) −0,0


ii) 0,367 89 iv) 0,999 999 99 vi) 0,0 viii) −1

Hinweis (Kontrollergebnisse). Die folgenden Werte haben in etwa die volle zu erwartende Präzision:

𝑥 log(𝑥)
2 0,693 147 180 559 946 2
0,367 89 −0,999 971 298 540 092 5
109 20,723 265 836 946 45
0,999 999 99 −1,000 000 010 024 761 · 10−08
1 0

[Link]

#include <cerrno>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>
#include <cstring>

using namespace std;

double eps = numeric_limits<double>::epsilon(),


inf = numeric_limits<double>::infinity(),
mynan = numeric_limits<double>::quiet_NaN();
int d = numeric_limits<double>::digits10;

double mylog(double a) {
if (a == 0) {
errno = ERANGE;
return -inf;
} else if (a < 0) {
errno = EDOM;
return mynan;
}

double res = a - 1, r = a;
int n = 0;
while (abs(r - 1) >= eps) {
r = sqrt(r);
res *= 2 / (1 + r);

Seite 2 von 8
Aufgabe 10 Programmieren I für Studierende der Mathematik — SoSe 2026

n++;

cout << ” ” << setw(d + 7) << ldexp(r - 1, n) << ” ”


<< setw(d + 7) << res << endl;
}

{
int h = errno;
double res = log(a);
errno = h;
cout << ”log(a) = ” << setw(d + 7) << res << endl;
}

return res;
}

int main() {
double a;

cout << ”a: ”;


cin >> a;

cout << scientific << setprecision(d);

errno = 0;
double myres = mylog(a);
int myerrno = errno;

cout << endl;

cout << ”a = ” << setw(d + 7) << a << endl;


errno = 0;
double x = log(a);
cout << ”log(a) = ” << setw(d + 7) << x
<< ” (errno = ” << errno << ”; ” << strerror(errno) << ”)” << endl;
cout << ”mylog(a) = ” << setw(d + 7) << myres
<< ” (errno = ” << myerrno << ”; ” << strerror(myerrno) << ”)” << endl;
}

a: 2
8.284271247461903e-01 8.284271247461902e-01
7.568284600108841e-01 7.568284600108844e-01
7.240618613220615e-01 7.240618613220613e-01
7.083805188386201e-01 7.083805188386214e-01
7.007087569317321e-01 7.007087569317336e-01
6.969143073088304e-01 6.969143073088294e-01
6.950273424387490e-01 6.950273424387613e-01
6.940864128518456e-01 6.940864128518364e-01
6.936165847594111e-01 6.936165847594016e-01
6.933818297000016e-01 6.933818296999493e-01
6.932644918933875e-01 6.932644918933770e-01
6.932058329175561e-01 6.932058329179386e-01
6.931765059107420e-01 6.931765059118138e-01
6.931618430280651e-01 6.931618430291042e-01
6.931545117404312e-01 6.931545117428318e-01
6.931508461420890e-01 6.931508461384654e-01
6.931490133574698e-01 6.931490133459747e-01

Seite 3 von 8
Aufgabe 10 Programmieren I für Studierende der Mathematik — SoSe 2026

6.931480969651602e-01 6.931480969521523e-01
6.931476388126612e-01 6.931476387558468e-01
6.931474097073078e-01 6.931474096578456e-01
6.931472951546311e-01 6.931472951088828e-01
6.931472383439541e-01 6.931472378344109e-01
6.931472104042768e-01 6.931472091971772e-01
6.931471973657608e-01 6.931471948785609e-01
6.931471899151802e-01 6.931471877192530e-01
6.931471824645996e-01 6.931471841395991e-01
6.931471824645996e-01 6.931471823497721e-01
6.931471824645996e-01 6.931471814548587e-01
6.931471824645996e-01 6.931471810074019e-01
6.931471824645996e-01 6.931471807836735e-01
6.931471824645996e-01 6.931471806718092e-01
6.931467056274414e-01 6.931471806158773e-01
6.931457519531250e-01 6.931471805879114e-01
6.931457519531250e-01 6.931471805739283e-01
6.931457519531250e-01 6.931471805669368e-01
6.931457519531250e-01 6.931471805634410e-01
6.931457519531250e-01 6.931471805616932e-01
6.931152343750000e-01 6.931471805608194e-01
6.931152343750000e-01 6.931471805603824e-01
6.931152343750000e-01 6.931471805601638e-01
6.928710937500000e-01 6.931471805600545e-01
6.923828125000000e-01 6.931471805600000e-01
6.914062500000000e-01 6.931471805599728e-01
6.914062500000000e-01 6.931471805599593e-01
6.875000000000000e-01 6.931471805599525e-01
6.875000000000000e-01 6.931471805599492e-01
6.875000000000000e-01 6.931471805599475e-01
6.875000000000000e-01 6.931471805599466e-01
6.250000000000000e-01 6.931471805599463e-01
5.000000000000000e-01 6.931471805599462e-01
5.000000000000000e-01 6.931471805599462e-01
0.000000000000000e+00 6.931471805599462e-01
log(a) = 6.931471805599453e-01

a = 2.000000000000000e+00
log(a) = 6.931471805599453e-01 (errno = 0; Success)
mylog(a) = 6.931471805599462e-01 (errno = 0; Success)

a: 0.36789
-7.869212721344092e-01 -7.869212721344091e-01
-8.847745149147346e-01 -8.847745149147346e-01
-9.399994503283322e-01 -9.399994503283325e-01
-9.693640324338446e-01 -9.693640324338453e-01
-9.845086783356933e-01 -9.845086783356936e-01
-9.921997751625113e-01 -9.921997751625102e-01
-9.960754245822585e-01 -9.960754245822632e-01
-9.980208260847974e-01 -9.980208260848002e-01
-9.989954275146715e-01 -9.989954275146784e-01
-9.994832042117423e-01 -9.994832042117003e-01
-9.997272116575004e-01 -9.997272116574348e-01
-9.998492451672973e-01 -9.998492451673300e-01
-9.999102693709574e-01 -9.999102693706255e-01
-9.999407833347504e-01 -9.999407833345592e-01
-9.999560407813988e-01 -9.999560407821222e-01

Seite 4 von 8
Aufgabe 10 Programmieren I für Studierende der Mathematik — SoSe 2026

-9.999636696229572e-01 -9.999636696223060e-01
-9.999674840655643e-01 -9.999674840714987e-01
-9.999693912977818e-01 -9.999693913033704e-01
-9.999703449429944e-01 -9.999703449211250e-01
-9.999708217801526e-01 -9.999708217304570e-01
-9.999710600823164e-01 -9.999710601352367e-01
-9.999711792916059e-01 -9.999711793376550e-01
-9.999712388962507e-01 -9.999712389388711e-01
-9.999712686985731e-01 -9.999712687394811e-01
-9.999712817370892e-01 -9.999712836397865e-01
-9.999712854623795e-01 -9.999712910899393e-01
-9.999712854623795e-01 -9.999712948150157e-01
-9.999713003635406e-01 -9.999712966775538e-01
-9.999713301658630e-01 -9.999712976088230e-01
-9.999713897705078e-01 -9.999712980744576e-01
-9.999713897705078e-01 -9.999712983072749e-01
-9.999713897705078e-01 -9.999712984236836e-01
-9.999713897705078e-01 -9.999712984818880e-01
-9.999713897705078e-01 -9.999712985109901e-01
-9.999732971191406e-01 -9.999712985255411e-01
-9.999771118164062e-01 -9.999712985328166e-01
-9.999847412109375e-01 -9.999712985364545e-01
-1.000000000000000e+00 -9.999712985382735e-01
-1.000000000000000e+00 -9.999712985391830e-01
-1.000000000000000e+00 -9.999712985396377e-01
-1.000000000000000e+00 -9.999712985398651e-01
-1.000000000000000e+00 -9.999712985399788e-01
-1.000000000000000e+00 -9.999712985400356e-01
-1.000000000000000e+00 -9.999712985400641e-01
-1.000000000000000e+00 -9.999712985400783e-01
-1.000000000000000e+00 -9.999712985400854e-01
-1.000000000000000e+00 -9.999712985400889e-01
-1.000000000000000e+00 -9.999712985400907e-01
-1.000000000000000e+00 -9.999712985400916e-01
-1.000000000000000e+00 -9.999712985400920e-01
-1.000000000000000e+00 -9.999712985400923e-01
-1.000000000000000e+00 -9.999712985400925e-01
-1.000000000000000e+00 -9.999712985400925e-01
log(a) = -9.999712985400927e-01

a = 3.678900000000000e-01
log(a) = -9.999712985400927e-01 (errno = 0; Success)
mylog(a) = -9.999712985400925e-01 (errno = 0; Success)

a: 1000000000
6.324355320336758e+04 6.324355320336759e+04
7.073117640155691e+02 7.073117640155692e+02
9.868171457306592e+01 9.868171457306593e+01
4.242786036077403e+01 4.242786036077403e+01
2.915049519905410e+01 2.915049519905410e+01
2.447182255090557e+01 2.447182255090557e+01
2.249513803785101e+01 2.249513803785100e+01
2.158514130871578e+01 2.158514130871579e+01
2.114837024990049e+01 2.114837024990048e+01
2.093438180193698e+01 2.093438180193698e+01
2.082846748122984e+01 2.082846748122979e+01
2.077577791239855e+01 2.077577791239857e+01

Seite 5 von 8
Aufgabe 10 Programmieren I für Studierende der Mathematik — SoSe 2026

2.074949973008370e+01 2.074949973008359e+01
2.073637725261869e+01 2.073637725261890e+01
2.072982016271271e+01 2.072982016271444e+01
2.072654265438905e+01 2.072654265439532e+01
2.072490415931679e+01 2.072490415932233e+01
2.072408497653669e+01 2.072408497654852e+01
2.072367540129926e+01 2.072367540135117e+01
2.072347061778419e+01 2.072347061779973e+01
2.072336822701618e+01 2.072336822703581e+01
2.072331703174859e+01 2.072331703190680e+01
2.072329143434763e+01 2.072329143440553e+01
2.072327863425016e+01 2.072327863567070e+01
2.072327223420143e+01 2.072327223630724e+01
2.072326903045177e+01 2.072326903662650e+01
2.072326743602753e+01 2.072326743678638e+01
2.072326666116714e+01 2.072326663686638e+01
2.072326624393463e+01 2.072326623690639e+01
2.072326612472534e+01 2.072326603692640e+01
2.072326612472534e+01 2.072326593693641e+01
2.072326564788818e+01 2.072326588694141e+01
2.072326469421387e+01 2.072326586194391e+01
2.072326278686523e+01 2.072326584944516e+01
2.072325897216797e+01 2.072326584319579e+01
2.072325134277344e+01 2.072326584007110e+01
2.072323608398438e+01 2.072326583850876e+01
2.072320556640625e+01 2.072326583772759e+01
2.072314453125000e+01 2.072326583733700e+01
2.072314453125000e+01 2.072326583714171e+01
2.072314453125000e+01 2.072326583704407e+01
2.072265625000000e+01 2.072326583699525e+01
2.072265625000000e+01 2.072326583697084e+01
2.072265625000000e+01 2.072326583695863e+01
2.071875000000000e+01 2.072326583695253e+01
2.071875000000000e+01 2.072326583694948e+01
2.071875000000000e+01 2.072326583694795e+01
2.068750000000000e+01 2.072326583694719e+01
2.062500000000000e+01 2.072326583694681e+01
2.050000000000000e+01 2.072326583694662e+01
2.050000000000000e+01 2.072326583694653e+01
2.000000000000000e+01 2.072326583694649e+01
2.000000000000000e+01 2.072326583694646e+01
2.000000000000000e+01 2.072326583694645e+01
1.600000000000000e+01 2.072326583694645e+01
1.600000000000000e+01 2.072326583694645e+01
0.000000000000000e+00 2.072326583694645e+01
log(a) = 2.072326583694641e+01

a = 1.000000000000000e+09
log(a) = 2.072326583694641e+01 (errno = 0; Success)
mylog(a) = 2.072326583694645e+01 (errno = 0; Success)

a: 0.99999999
-1.000000016126990e-08 -1.000000007524759e-08
-1.000000038331450e-08 -1.000000008774759e-08
-1.000000082740371e-08 -1.000000009399760e-08
-1.000000082740371e-08 -1.000000009712260e-08
-1.000000082740371e-08 -1.000000009868510e-08

Seite 6 von 8
Aufgabe 10 Programmieren I für Studierende der Mathematik — SoSe 2026

-1.000000082740371e-08 -1.000000009946635e-08
-1.000000793283107e-08 -1.000000009985697e-08
-1.000000793283107e-08 -1.000000010005229e-08
-1.000000793283107e-08 -1.000000010014994e-08
-1.000000793283107e-08 -1.000000010019877e-08
-1.000012161966879e-08 -1.000000010022318e-08
-1.000034899334423e-08 -1.000000010023539e-08
-1.000080374069512e-08 -1.000000010024150e-08
-1.000080374069512e-08 -1.000000010024455e-08
-1.000080374069512e-08 -1.000000010024607e-08
-1.000444171950221e-08 -1.000000010024684e-08
-1.001171767711639e-08 -1.000000010024722e-08
-1.001171767711639e-08 -1.000000010024741e-08
-1.001171767711639e-08 -1.000000010024751e-08
-1.001171767711639e-08 -1.000000010024756e-08
-1.001171767711639e-08 -1.000000010024758e-08
-1.024454832077026e-08 -1.000000010024759e-08
-1.024454832077026e-08 -1.000000010024760e-08
-1.117587089538574e-08 -1.000000010024761e-08
-1.117587089538574e-08 -1.000000010024761e-08
-1.490116119384766e-08 -1.000000010024761e-08
-1.490116119384766e-08 -1.000000010024761e-08
log(a) = -1.000000010024759e-08

a = 9.999999899999999e-01
log(a) = -1.000000010024759e-08 (errno = 0; Success)
mylog(a) = -1.000000010024761e-08 (errno = 0; Success)

a: 1
log(a) = 0.000000000000000e+00

a = 1.000000000000000e+00
log(a) = 0.000000000000000e+00 (errno = 0; Success)
mylog(a) = 0.000000000000000e+00 (errno = 0; Success)

a: 0.0

a = 0.000000000000000e+00
log(a) = -inf (errno = 34; Numerical result out of range)
mylog(a) = -inf (errno = 34; Numerical result out of range)

a: -0.0

a = -0.000000000000000e+00
log(a) = -inf (errno = 34; Numerical result out of range)
mylog(a) = -inf (errno = 34; Numerical result out of range)

a: -1

a = -1.000000000000000e+00
log(a) = -nan (errno = 33; Numerical argument out of domain)
mylog(a) = nan (errno = 33; Numerical argument out of domain)

Seite 7 von 8
A. Logarithmus durch Quadratwurzel Programmieren I für Studierende der Mathematik — SoSe 2026

Anhang

A. Logarithmus durch Quadratwurzel

Es sei 𝑎 ∈ R+ . Es ist:
d ℎ d ln(𝑎) ·ℎ
𝑎 = 𝑒
dℎ ℎ=0 dℎ ℎ=0

= ln(𝑎) · 𝑒 ln(𝑎) ·ℎ
ℎ=0

= ln(𝑎) · 𝑎
ℎ=0
= ln(𝑎)

Mit dem Differentialquotient:

d ℎ 𝑎ℎ − 𝑎 0
𝑎 := lim
dℎ ℎ=0 ℎ→0 ℎ
𝑎ℎ − 1
= lim
ℎ→0 ℎ

Insgesamt also:
𝑎ℎ − 1
ln(𝑎) = lim
ℎ→0 ℎ

Durch Wahl der Folge (ℎ𝑛 )𝑛=0,1,... = (2−𝑛 )𝑛=0,1,... ergibt sich direkt:
 √
𝑛

ln(𝑎) = lim 2𝑛 2 𝑎 − 1
𝑛→∞

Es ist für 𝑥 ∈ R+, 𝑛 ∈ N:


(𝑥 − 1)(𝑥 + 1)
𝑥 −1=
(𝑥 + 1)
𝑥 −1
2
=
(𝑥 + 1)
(𝑥 2 − 1) (𝑥 2 + 1)
=
(𝑥 + 1) (𝑥 2 + 1)
𝑥4 − 1
=
(𝑥 + 1)(𝑥 2 + 1)
...
𝑛
𝑥2 − 1
= 𝑛−1 
(𝑥 + 1)(𝑥 2 + 1) · · · 𝑥 2 + 1
𝑛
𝑥2 − 1
=Î  
𝑛−1 2𝑘 + 1
𝑘=0 𝑥


2𝑛
Mit 𝑥 = 𝑎 ergibt sich so:
𝑎−1
ln(𝑎) = lim 2𝑛 Î  √ 
𝑛→∞ 𝑛 2𝑘
𝑘=1 1 + 𝑎

Seite 8 von 8
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 8 – Lösungsvorschlag

Aufgabe 11 Seien 𝑛 ∈ N \ {0}, 𝑙 > 1 ∈ N und 𝑘 1, 𝑘 2, . . . , 𝑘𝑙 ∈ N0 mit 𝑙𝑖=1 𝑘𝑖 = 𝑛. Der Multinomialkoeffizient
ist: ( ) ( )( ) ( )
𝑛 𝑛! 𝑛 𝑛 − 𝑘1 𝑛 − 𝑘 1 − . . . − 𝑘𝑙 −2
:= = ···
𝑘 1 𝑘 2 . . . 𝑘𝑙 𝑘 1 !𝑘 2 ! · · · 𝑘𝑙 ! 𝑘1 𝑘2 𝑘𝑙 −1

Hinweis (Binomialkoeffizient). In der Vorlesung hatten Sie folgende Funktion zur Berechnung des Binomial-
koeffizienten gesehen:

unsigned int binom(unsigned int n, unsigned int k) {


unsigned int b = 1;
for (unsigned int i = 1; i <= k; n--, i++)
b = b*n/i;
return b;
}

Erstellen Sie eine Funktion mit Ergebnistyp double und Parametern vom Typ unsigned int bzw.
vector<unsigned int> zur Berechnung des Multinomialkoeffizienten.
Die Funktion soll 0 zurück geben und mithilfe des errno-Mechanismus einen Definitionsbereichsfehler
kommunizieren, falls unzulässige Argumente übergeben werden.
Verwenden Sie ein Verfahren zur Berechnung des Binomialkoeffizienten, welches analog ist zu dem im Hin-
weis angegebenen, jedoch mit Rückgabetyp
(𝑛 ) ( 𝑛 )double aufgrund der Größe der zu erwartenden Ergebnisse. Ver-
wenden Sie zudem die Beziehung 𝑘 = 𝑛−𝑘 , wenn 𝑛 ≥ 𝑘 > 𝑛/2. Achten Sie darauf, dass auch die Fälle 𝑘 = 𝑛
und 𝑘 = 0 korrekt behandelt werden.

a) Schreiben Sie ein Hautprogramm in dem Sie 𝑛, 𝑙 und 𝑘 1, 𝑘 2, . . . , 𝑘𝑙 einlesen und hierfür den Multinomi-
alkoeffizienten berechnen.
Rechnen Sie die folgenden Beispiele:
( ) ( ) ( ) ( )
8 50 100 500
i) ii) iii) iv)
233 20 20 10 25 11 37 27 200 150 20 80 50

Hinweis (Kontrollergebnisse). Die folgenden Werte haben in etwa die volle zu erwartende Präzision:

i) 560 iii) 1,005 732 752 932 468 · 1054


ii) 1,415 997 888 807 962 · 1021 iv) 5,113 276 435 474 374 · 10294

b) Verändern Sie Ihr Hauptprogramm, sodass dieses 𝑥, 𝑦, 𝑧 ∈ R und 𝑛 ∈ N einliest. Berechnen Sie die
folgende Größe:
∑ ∑1 (
𝑛 𝑛−𝑘
𝑛
)
𝑥 𝑘1 𝑦𝑘2 𝑧𝑛−𝑘1 −𝑘2 (1)
𝑘 1 𝑘 2 (𝑛 − 𝑘 1 − 𝑘 2 )
𝑘 1 =0 𝑘 2 =0

Hinweis (Kontrollergebnis). Mit 𝑛 = 7 und 𝑥 = 4, 𝑦 = 5, 𝑧 = 6 ergibt sich für Gleichung 1: 170 859 375.
Zu welchem anderen Ausdruck mit den Größen 𝑥, 𝑦, 𝑧 und 𝑛 sollten die berechneten Ergebnisse mathe-
matisch identisch sein? Zu (𝑥 + 𝑦 + 𝑧)𝑛 .

Seite 1 von 4
Aufgabe 11 Programmieren I für Studierende der Mathematik — SoSe 2026

multinom

double binom(unsigned int n, unsigned int k) {


if (n >= k && k > n/2)
return binom(n, n - k);

double res = 1;

for (unsigned int i = 1; i <= k; --n, ++i)


res *= static_cast<double>(n)/i;

return res;
}

double multinom(unsigned int n, vector<unsigned int> k) {


double res = 1;

for (vector<unsigned int>::size_type i = 0; i < [Link](); i++) {


res *= binom(n, k[i]);
if (k[i] > n) {
errno = EDOM;
return 0;
}
n -= k[i];
}

if (n != 0) {
errno = EDOM;
return 0;
}

return res;
}

[Link]

#include <iostream>
#include <iomanip>
#include <cerrno>
#include <cstring>
#include <vector>
#include <limits>

using namespace std;

multinom

int main() {
unsigned int n;
vector<unsigned int>::size_type l;

Seite 2 von 4
Aufgabe 11 Programmieren I für Studierende der Mathematik — SoSe 2026

cout << ”n, l: ”;


cin >> n >> l;

vector<unsigned int> k(l);


for (vector<unsigned int>::size_type i = 0; i < [Link](); i++) {
cout << ”k[” << i << ”]: ”;
cin >> k[i];
}

cout << scientific << setprecision(numeric_limits<double>::digits10 - 1)


<< ”multinom(n, k): ” << multinom(n, k) << endl;
}

n, l: 8 3
k[0]: 2
k[1]: 3
k[2]: 3
multinom(n, k): 5.60000000000000e+02

n, l: 50 3
k[0]: 20
k[1]: 20
k[2]: 10
multinom(n, k): 1.41599788880796e+21

n, l: 100 4
k[0]: 25
k[1]: 11
k[2]: 37
k[3]: 27
multinom(n, k): 1.00573275293247e+54

n, l: 500 5
k[0]: 200
k[1]: 150
k[2]: 20
k[3]: 80
k[4]: 50
multinom(n, k): 5.11327643547437e+294

multinom_theorem.cpp

#include <iostream>
#include <iomanip>
#include <cerrno>
#include <cstring>
#include <vector>
#include <limits>
#include <cmath>

using namespace std;

Seite 3 von 4
Aufgabe 11 Programmieren I für Studierende der Mathematik — SoSe 2026

multinom

int main() {
double x, y, z;
unsigned int n;

cout << ”n: ”;


cin >> n;

cout << ”x, y, z: ”;


cin >> x >> y >> z;

cout << scientific << setprecision(numeric_limits<double>::digits10 - 1)


<< ”n = ” << n << endl
<< ”x = ” << x << endl
<< ”y = ” << y << endl
<< ”z = ” << z << endl;

double res;
for (unsigned int k1 = 0; k1 <= n; k1++)
for (unsigned int k2 = 0; k2 <= n - k1; k2++) {
vector<unsigned int> k = {k1, k2, n - k1 - k2};
res += multinom(n, k) * pow(x, k1) * pow(y, k2) * pow(z, n - k1 - k2);
}

cout << ”res = ” << res << endl;


}

n: 7
x, y, z: 4.0 5.0 6.0
n = 7
x = 4.00000000000000e+00
y = 5.00000000000000e+00
z = 6.00000000000000e+00
res = 1.70859375000000e+08

Seite 4 von 4
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 9 – Lösungsvorschlag

Aufgabe 12 Das Vigenère-Chiffre ist ein historisches Verschlüsselungsverfahren, bei dem, anhand der nume-
rischen Werte der Buchstaben eines Schlüsselwortes, jeder Buchstabe des Klartextes im Alphabet zyklisch ver-
schoben wird. Traditionell werden im Klartext vorkommende Zwischenraumzeichen ignoriert, ausschließlich
Großbuchstaben verwendet und im Geheimtext Leerzeichen-separierte Blöcke fester Größe (oft 5) gebildet.
Wir ordnen zunächst den Buchstaben numerische Werte zu: 𝐴 ≡ 0, 𝐵 ≡ 1, . . . , 𝑍 ≡ 25.
Hinweis. Für einen Großbuchstaben 𝑐 vom Typ char können Sie den hier geforderten numerischen Wert
berechnen mit 𝑐 - 'A'.
Die genannte „zyklische Verschiebung“ entspricht einer Berechnung auf den, den Buchstaben zugeordne-
ten, numerischen Werten modulo 26.
Beispiel. Für die manuelle Verschlüsselung (mit Stift und Papier) werden Klartext und wiederholtes Schlüssel-
wort gerne untereinander geschrieben. Es wird dann jeweils für jede Stelle des Klartextes der numerische
Wert des Zeichens des „Schlüssel-Stroms“ an dieser Stelle abgelesen und das korrespondierende Zeichen
im Geheimtext durch die zyklische Verschiebung bestimmt:

TEXTZEILEN
ABCABCABCA
TFZTAGIMGN

Es entspricht z.B. der Schlüssel-Buchstabe B einer Verschiebung des Klartextbuchstabens Z um 1 (dem B


zugeordneten numerischen Wert), sodass sich im verschlüsselten Text an dieser Stelle A ergibt.

a) Erstellen Sie ein Programm, das im Hautprogramm zunächst einen Schlüssel und einen Klartext ein-
liest. Der Klartext soll, zur Kontrolle, in Großbuchstaben umgewandelt und ohne Leerzeichen wieder
ausgegeben werden.
Verschlüsseln Sie den Klartext dann mit einer Funktion encrypt und geben Sie den zurückgegebenen
Wert, also den verschlüsselten Text, im Hauptprogramm in mit Leerzeichen getrennten Blöcken von
jeweils 5 Zeichen aus.
Übergeben Sie den eingegebenen Schlüssel und den soeben verschlüsselten Text zudem einer Funktion
decrypt, welche den Text wieder entschlüsselt. Geben Sie den zurückgegebenen Wert, also den wieder
entschlüsselten Text, ebenfalls im Hauptprogramm aus.
b) Zum Entschlüsseln von Texten ohne Kenntnis des Schlüssel (dem Brechen der Verschlüsselung) können
vergleichsweise einfache Methoden verwendet werden. Ein erster Schritt ist es hier die Länge des ver-
wendeten Schlüssels mit hoher Sicherheit zu bestimmen.
Hierfür wird im Geheimtext nach gleichen Teilstrings beliebiger aber fester Länge (oft 2 oder 3) gesucht
und deren Positionen bestimmt.
Beispiel. Wir finden gleiche Teilstrings der Länge 3 im Geheimtext und markieren diese:

DIESETEXTEBLEIBENNICHTGEHEIMWEILDIEVIGENEREVERSCHLUESSELUNGKEINSICHERESVERFAHRENIST
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABC
DJGVEUGATFDOEJDHNOKFHUIHHFKPWFKODJGYIHGQESGYESUFHMWHSTGOUOINEJPVIDJHRFUYESHDHSGQITV

Wir beobachten, dass die gleichen Teilstrings im Geheimtext zu gleichen Teilstrings sowohl im Klar-
text, wie auch im Schlüssel korrespondieren.

Seite 1 von 5
Aufgabe 12 Programmieren I für Studierende der Mathematik — SoSe 2026

Erstellen Sie ein Programm, das einen Geheimtext einliest und für alle Teilstrings der Länge 3 alle
Positionen der jeweiligen Vorkommen bestimmt. Geben Sie dann für alle Vorkommen des am häufigsten
vorkommenden Teilstrings jeweils die Abstände zu allen folgenden Vorkommen des gleichen Teilstrings
aus.
Hinweis. Es empfiehlt sich einen Vektor als endliche Abbildung zu verwenden.
Das Ziel wäre es für eine endliche Menge 𝐾 von Schlüsseln jeweils jedem 𝑘 ∈ 𝐾 einen Wert 𝑓 (𝑘)
zuzuordnen.
Wir wählen hierfür eine Bijektion 𝜋 : 𝐾 → {0, 1, . . . , |𝐾 | − 1}. Wir speichern dann für jedes 𝑘 ∈ 𝐾
den Wert 𝑓 (𝑘) an Index 𝜋 (𝑘) in einem Vektor.
Hier konkret betrachten wir als 𝐾 alle Strings (bestehend nur aus Großbuchstaben) der Länge 3.
Es genügen also Indizes {0, 1, . . . , 17 575}. Als Bijektion können wir eine Art Stellenwertsystem zur
Basis 26 verwenden z.B. ABC ≡ 0 · 260 + 1 · 261 + 2 · 262 = 1 378
Wenden Sie Ihr Programm an auf den Inhalt der Datei [Link].
Können Sie anhand der Ausgabe Ihres Programms eine Aussage über die Länge des Schlüssels machen?
Hinweis. Um den Inhalt der Datei [Link] nicht kopieren oder gar abtippen zu müssen, können
Sie ihr Programm auch ausführen, wie folgt:
./[Link] < [Link]

Die Konsole wird dann die Datei [Link] auslesen und als Eingabe an ihr Programm über
den gewöhnlichen cin-Mechanismus zur Verfügung stellen.

Da wiederholte Teilstrings im Geheimtext i.d.R. nur dann auftreten, wenn eine Wiederholung des
Schlüssels an gleicher Stelle auftritt wie eine Wiederholung im Klartext, würden wir erwarten, dass
die Abstände in aller Regel ein Vielfaches der Länge des Schlüssels sind. Wir sehen, dass die Abstände
nur 5 als gemeinsamen Teiler haben. Der Schlüssel wird also vmtl. Länge 5 haben.
Anmerkung. Ein möglicher nächster Schritt ist es nun die Texte bestehend aus den Zeichen des Geheim-
textes mit Index 0, 1, . . . mod 5 zu betrachten.
Diese Teil-Texte verwenden mutmaßlich jeweils die gleiche zyklische Verschiebung.
Gleichzeitig haben die zu diesen Teil-Texten korrespondierenden Klartexte die selben statistischen
Eigenschaften wie der unterliegende Text gesamtheitlich.
Wir können nun also für jeden Teil-Text das am häufigsten vorkommende Zeichen bestimmen und,
unter der Annahme, dass es sich um einen Text in einer der meisten europäischen Sprachen handelt,
annehmen, dass dieses Zeichen eine Verschlüsselung des Buchstabens „E“ ist. So können wir dann
den Schlüssel buchstabenweise ablesen.

conv

unsigned int ord(char c) {


return toupper(c, locale()) - 'A';
}
char chr(unsigned int i) {
return i + 'A';
}

string canon_clear(string in) {


string out;
for (string::size_type i = 0; i < [Link](); i++) {
if (isspace(in[i], locale()))
continue;
out += toupper(in[i], locale());

Seite 2 von 5
Aufgabe 12 Programmieren I für Studierende der Mathematik — SoSe 2026

}
return out;
}
string canon_cipher(string in) {
string out;
bool first = true;
for (string::size_type i = 0; i < [Link](); i += 5) {
if (!first)
out += ' ';
else
first = false;

out += [Link](i, 5);


}
return out;
}

vigenere_test.cpp

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <locale>

using namespace std;

conv

string encrypt(string key, string text) {


for (string::size_type i = 0; i < [Link](); i++) {
text[i] = chr((ord(text[i]) + ord(key[i % [Link]()])) % 26);
}
return text;
}

string decrypt(string key, string text) {


for (string::size_type i = 0; i < [Link](); i++) {
text[i] = chr((ord(text[i]) - ord(key[i % [Link]()]) + 26) % 26);
}
return text;
}
int main() {
string key;
cout << ”Schluessel: ”; cin >> key;
key = canon_clear(key);

string input;
cout << ”Klartext: ”;
{ char c; cin >> c; input += c; }
cin >> noskipws;
for (char c; cin >> c; ) input += c;
input = canon_clear(input);

cout << ”Klartext: ” << input << endl;


string ciphertext = encrypt(key, input);

Seite 3 von 5
Aufgabe 12 Programmieren I für Studierende der Mathematik — SoSe 2026

cout << ”Geheimtext: ” << canon_cipher(ciphertext) << endl;

cout << ”Klartext: ” << decrypt(key, ciphertext) << endl;


}

Schluessel: ABCD
Klartext: Dieser Text bleibt nicht lange geheim
Klartext: DIESERTEXTBLEIBTNICHTLANGEGEHEIM
Geheimtext: DJGVE SVHXU DOEJD WNJEK TMCQG FIHHF KP
Klartext: DIESERTEXTBLEIBTNICHTLANGEGEHEIM

vigenere_ana.cpp

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

using namespace std;

conv

unsigned int to_index(string cs) {


unsigned int res = 0;
unsigned int mul = 1;
for (string::size_type i = 0; i < [Link](); i++) {
res += ord(cs[i]) * mul;
mul *= 26;
}
return res;
}
string from_index(unsigned int ix) {
string res;
for (string::size_type i = 0; i < 3; i++) {
res += chr(ix % 26);
ix /= 26;
}
return res;
}

int main() {
string input;
cout << ”Geheimtext: ”;
cin >> noskipws;
for (char c; cin >> c; ) input += c;
input = canon_clear(input);

vector<vector<string::size_type>> occurrences(26 * 26 * 26);


int longest = -1;
for (string::size_type i = 0; i + 2 < [Link](); i++) {
unsigned int ix = to_index([Link](i, 3));
occurrences[ix].push_back(i);

if (longest < 0 || occurrences[ix].size() > occurrences[longest].size())


longest = ix;

Seite 4 von 5
Aufgabe 12 Programmieren I für Studierende der Mathematik — SoSe 2026

vector<string::size_type> distances;
string::size_type max_distance = -1;
cout << from_index(longest) << ”: ” << endl;
for ( vector<string::size_type>::size_type b = 0;
b < occurrences[longest].size();
b++
) {
cout << ” ”;
for ( vector<string::size_type>::size_type x = b + 1;
x < occurrences[longest].size();
x++
) {
string::size_type distance = occurrences[longest][x] - occurrences[longest][b];
distances.push_back(distance);
if (max_distance < 0 || distance > max_distance)
max_distance = distance;
cout << ” ” << distance;
}
cout << endl;
}
}

Geheimtext: CTABX CUGGU JBFTY QATVA OLLJF FLRRW PCNRV KLEFV GDAFO WLSHN PKLSW TCNRW UDAFX
KUSHW THUTX FLRHA GMFSN PKDSJ ILIGL . . .
JFF:
125 140 205 465 685 865 1095 1165 1190 1255 1450 1470 1835 1895 1935 1980 2140 2525 2600
↩→ 2655 2885 2935
15 80 340 560 740 970 1040 1065 1130 1325 1345 1710 1770 1810 1855 2015 2400 2475 2530 2760
↩→ 2810
65 325 545 725 955 1025 1050 1115 1310 1330 1695 1755 1795 1840 2000 2385 2460 2515 2745 2795
260 480 660 890 960 985 1050 1245 1265 1630 1690 1730 1775 1935 2320 2395 2450 2680 2730
220 400 630 700 725 790 985 1005 1370 1430 1470 1515 1675 2060 2135 2190 2420 2470
180 410 480 505 570 765 785 1150 1210 1250 1295 1455 1840 1915 1970 2200 2250
230 300 325 390 585 605 970 1030 1070 1115 1275 1660 1735 1790 2020 2070
70 95 160 355 375 740 800 840 885 1045 1430 1505 1560 1790 1840
25 90 285 305 670 730 770 815 975 1360 1435 1490 1720 1770
65 260 280 645 705 745 790 950 1335 1410 1465 1695 1745
195 215 580 640 680 725 885 1270 1345 1400 1630 1680
20 385 445 485 530 690 1075 1150 1205 1435 1485
365 425 465 510 670 1055 1130 1185 1415 1465
60 100 145 305 690 765 820 1050 1100
40 85 245 630 705 760 990 1040
45 205 590 665 720 950 1000
160 545 620 675 905 955
385 460 515 745 795
75 130 360 410
55 285 335
230 280
50

Seite 5 von 5
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 10 – Lösungsvorschlag

Aufgabe 13 Es ist oft nützlich, z.B. zur Übertragung als E-Mail Anhang, beliebige Binärdaten (welche i.A.
auch nicht druckbare Zeichen enthalten) darstellen zu können als eine Folge von druckbaren Zeichen. Als
Satz von druckbaren Zeichen zu diesem Zweck wählt die base64-Kodierung 1 das folgende Alphabet und das
padding-Zeichen =:

Wert 0 ··· 25 26 ··· 51 52 ··· 61 62 63


Kodierung A ··· Z a ··· z 0 ··· 9 + /

Im Folgenden bezeichnen wir diese Abbildung von Wert auf Kodierung mit −−−−−→.
base64

Wir fassen die zu kodierenden Binärdaten auf als Folge von 8-bit Bytes. Da wir nur 64 = 26 viele Zeichen
verwenden wollen, wird es uns nicht gelingen jedes Byte der Binärdaten in genau ein druckbares Zeichen zu
kodieren. Stattdessen nehmen wir eine Vergrößerung der Datenmenge um den Faktor 4⁄3 in Kauf und fassen
jede Teilfolge von drei 8-bit Bytes auf als vier 6-bit base64-Zeichen.
Beispiel. Die base64-Kodierung des Strings ABC ist QUJD. Schematisch dargestellt:

ˆ ’A’
010000012 = 65 = ˆ ’B’
010000102 = 66 = ˆ ’C’
010000112 = 67 =
ASCII ASCII ASCII

0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 1

0100002 = 16 −−−−−→ ’Q’ 0101002 = 20 −−−−−→ ’U’ 0010012 = 9 −−−−−→ ’J’ 0000112 = 3 −−−−−→ ’D’
base64 base64 base64 base64

Ist die Anzahl von 8-bit Bytes in der Eingabe nicht durch drei teilbar, so füllen wir die noch „unvollständigen“
6-bit base64-Zeichen am Ende geeignet mit Nullbits auf. Hierbei werden dann nicht genügend 6-bit Zeichen
zur Verfügung stehen um die letzte Vierergruppe von Zeichen in der Ausgabe zu vervollständigen.
Insb. um auch konkatenierte kodierte Daten eindeutig dekodieren zu können, spezifiziert die base64-
Kodierung in diesen Fällen padding zu verwenden. Es werden ein oder zwei =-Zeichen an die Ausgabe
angehängt, sodass die Länge der Ausgabe zunächst stets durch vier teilbar ist2 .
Beispiel (Padding). Einige korrekte base64-Kodierungen inkl. padding sind wie folgt:

Eingabe ”” ”A” ”AB” ”ABC” ”ABCD” ”ABCDE” ”ABCDEF”


base64 ”” ”QQ==” ”QUI=” ”QUJD” ”QUJDRA==” ”QUJDREU=” ”QUJDREVG”
In der Kodierung soll jede Zeile jeweils maximal 76 base64-Zeichen (19 Vierergruppen) enthalten. Es sollen
entsprechend Zeilenumbrüche eingefügt werden.
a) Erstellen Sie ein Programm, das von der Kommandozeile die Namen zweier Dateien einliest. Lesen Sie
die Daten aus der ersten Datei im Binärmodus, kodieren Sie sie in base64 und schreiben Sie das Ergebnis
in die zweite Datei. Testen Sie Ihr Programm an den obigen und eigenen Beispielen.
b) Erstellen Sie analog ein weiteres Programm für die Dekodierung. Testen Sie ihr Programm an den obigen
und eigenen Beispielen und schließlich an [Link].b64.

1 Nach RFC4648 Sektion 4; Siehe [Link]/doc/html/rfc4648#section-4


2 Es wird die Anzahl von 8-bit Bytes in der Eingabe modulo drei durch die Anzahl von =-Zeichen in der Ausgabe kodiert

Seite 1 von 4
Aufgabe 13 Programmieren I für Studierende der Mathematik — SoSe 2026

[Link]

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

using namespace std;

string b64{”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/”};

int main() {
string fname_in, fname_out;
cout << ”Eingabedatei: ”; getline(cin, fname_in);
cout << ”Ausgabedatei: ”; getline(cin, fname_out);

ifstream in{fname_in, ios::binary};


if (!in) {
cerr << fname_in << ”: ” << strerror(errno) << endl; return 1;
}
ofstream out{fname_out};
if (!out) {
cerr << fname_out << ”: ” << strerror(errno) << endl; return 1;
}

unsigned int out_chars = 0;


string in_buffer(3, '\0');
string::size_type nread;
while (true) {
for (nread = 0; in && nread < 3;) {
char c;
if ([Link](c))
in_buffer[nread++] = c;
}

if (nread == 0) break;

for (string::size_type i = nread; i < 3; i++)


in_buffer[i] = '\0';

unsigned char c0 = in_buffer[0], c1 = in_buffer[1], c2 = in_buffer[2];


in_buffer.erase(0, 3);

out << b64[c0 >> 2]


<< b64[(c0 & 0b11) << 4 | c1 >> 4];

if (nread > 1)
out << b64[(c1 & 0b1111) << 2 | c2 >> 6];
else
out << '=';

Seite 2 von 4
Aufgabe 13 Programmieren I für Studierende der Mathematik — SoSe 2026

if (nread > 2)
out << b64[(c2 & 0b111111)];
else
out << '=';

out_chars += 4;

if ((out_chars %= 76) == 0)
out << endl;
}

if (out_chars % 76 != 0)
out << endl;
}

[Link]

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

using namespace std;

bool is_b64(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c ==
↩→ '+') || (c == '/') || (c == '=');
}
unsigned char b64_inv(char c) {
if (c >= 'A' && c <= 'Z') {
return c - 'A';
} else if (c >= 'a' && c <= 'z') {
return c - 'a' + 26;
} else if (c >= '0' && c <= '9') {
return c - '0' + 52;
} else if (c == '+') {
return 62;
} else if (c == '/') {
return 63;
} else {
return -1;
}
}

int main() {
string fname_in, fname_out;
cout << ”Eingabedatei: ”; getline(cin, fname_in);
cout << ”Ausgabedatei: ”; getline(cin, fname_out);

ifstream in{fname_in};
if (!in) {
cerr << fname_in << ”: ” << strerror(errno) << endl; return 1;
}
ofstream out{fname_out, ios::binary};
if (!out) {
cerr << fname_out << ”: ” << strerror(errno) << endl; return 1;

Seite 3 von 4
Aufgabe 13 Programmieren I für Studierende der Mathematik — SoSe 2026

string in_buffer(4, '\0');


string::size_type nread;
while (true) {
for (nread = 0; in && nread < 4;) {
char c;
if ([Link](c) && is_b64(c))
in_buffer[nread++] = c;
}

if (nread != 4) break;

out << static_cast<char>(b64_inv(in_buffer[0]) << 2 | b64_inv(in_buffer[1]) >> 4);


if (in_buffer[2] != '=')
out << static_cast<char>((b64_inv(in_buffer[1]) & 0b1111) << 4 | b64_inv(in_buffer[2])
↩→ >> 2);
if (in_buffer[3] != '=')
out << static_cast<char>((b64_inv(in_buffer[2]) & 0b11) << 6 | b64_inv(in_buffer[3]));
}

return nread == 0 ? 0 : 1;
}

Ergebnis der Dekodierung von [Link].b64

Seite 4 von 4
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 11 – Lösungsvorschlag

Aufgabe 14 Für 𝑎 = 𝑏 · 4𝑛 mit 𝑎, 𝑏 ∈ R, 𝑛 ∈ Z, 1/4 ≤ 𝑏 < 1 und 𝑥 einer Nullstelle der Funktion 𝑓 (𝑧) = 1
𝑧2
−𝑏
ist: √
𝑎 = 𝑏 · 𝑥 · 2𝑛

a) Implementieren Sie unter Verwendung der Funktion frexp aus der Standardlibrary eine Funktion
frexp4. frexp4 soll, analog zu frexp, als Parameter einen Wert vom Typ double und einen Wert vom
Typ „Zeiger auf int“ akzeptieren.
Es soll eine Zerlegung 𝑎 = 𝑏 · 4𝑛 bestimmt werden. 𝑏 soll als Rückgabewert der Funktion zurückgegeben
werden, 𝑛 an der Speicherstelle abgelegt werden auf die der übergebene Zeiger zeigt.
Hinweis. Die Funktion frexp liefert 𝑎 = 𝑐 · 2𝑚 . Unterscheiden Sie zwei Fälle:

1. Wenn 𝑚 gerade, also 𝑎 = 𝑐 · 2𝑚 :


𝑚
𝑏 =𝑐 𝑛=
2
2. Wenn 𝑚 ungerade, also 𝑎 = 𝑐 · 2𝑚 = 𝑐/2 · 2𝑚+1 :
𝑐 𝑚+1
𝑏= 𝑛=
2 2
b) Erstellen Sie eine Funktion mysqrt die für ein Argument 𝑎 vom Typ double die Quadratwurzel nach
folgendem Verfahren berechnet:

1. Zerlegen Sie mit frexp4 𝑎 in 𝑎 = 𝑏 · 4𝑛 .


2. Berechnen Sie 𝑥 = √1 mit dem Newtonverfahren (siehe Anhang A), also folgender Iteration:
𝑏

𝑥0 = 1
𝑥𝑘
𝑥𝑘+1 = (3 − 𝑏𝑥𝑘2 )
2

Brechen Sie die Iteration ab, sobald |𝑥𝑘+1 − 𝑥𝑘 | ≤ 10𝜀𝑥 0 .



3. Berechnen Sie 𝑎 = 𝑏 · 𝑥 · 2𝑛 mit der Funktion ldexp aus der Standardlibrary.

Für 𝑎 = 0 soll stattdessen direkt 0 zurück gegeben werden. Für 𝑎 < 0 soll die Funktion NaN liefern und
mit dem errno-Mechanismus einen Definitionsbereichsfehler kommunizieren.
Implementieren Sie ein Hauptprogramm, das 𝑎 von von der Konsole einliest, mysqrt mit dem eingelese-
nen Wert aufruft und sowohl den mit mysqrt, wie auch, zum Vergleich, einen mit der Funktion sqrt aus

der Standardlibrary berechneten Wert für 𝑎 ausgibt. Verwenden Sie zur Ausgabe ein gut an die erwar-
tete Genauigkeit angepasstes Exponentialformat. Falls mysqrt oder sqrt jeweils einen Fehler mittels
des errno-Mechanismus kommunizieren, soll jeweils eine entsprechende Fehlermeldung ausgegeben
werden.

Rechnen Sie die folgenden Beispiele:

Seite 1 von 4
Aufgabe 14 Programmieren I für Studierende der Mathematik — SoSe 2026

i) 12,9 ii) 1,98 · 1011 iii) 2 · 10−308 iv) 1,79 · 10308 v) −3,3

Hinweis (Kontrollergebnisse). Die folgenden Werte haben in etwa die volle zu erwartende Präzision:

i) 3,591 656 999 213 594 iii) 1,414 213 562 373 095 · 10−154
ii) 444 971,909 225 739 8 iv) 1,337 908 816 025 965 · 10154

frexp4

double frexp4(double a, int *n) {


int m;
double c = frexp(a, &m);
if (m % 2 == 0) {
*n = m / 2;
return c;
} else {
*n = (m + 1) / 2;
return c / 2;
}
}

[Link]

#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>
#include <cerrno>
#include <cstring>

using namespace std;

frexp4

double mysqrt(double a) {
if (a == 0) {
return 0;
} else if (a < 0) {
errno = EDOM;
return numeric_limits<double>::quiet_NaN();
}

int n;
a = frexp4(a, &n);
double a2 = a / 2;

double x = 1, x_old;
do {
x_old = x;
x *= 1.5 - a2 * x * x;
} while (abs(x - x_old) >= 10 * numeric_limits<double>::epsilon());

return ldexp(a * x, n);


}

Seite 2 von 4
Aufgabe 14 Programmieren I für Studierende der Mathematik — SoSe 2026

int main() {
cout << ”a: ”;

double a;
cin >> a;

errno = 0;
double mysqrt_a = mysqrt(a);
int errno_mysqrt = errno;

errno = 0;
double sqrt_a = sqrt(a);
int errno_sqrt = errno;

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


cout << ”mysqrt(a) = ” << mysqrt_a << endl;
if (errno_mysqrt != 0)
cout << ”errno=” << setw(3) << errno_mysqrt << ” ” << strerror(errno_mysqrt) << endl;
cout << ”sqrt(a) = ” << sqrt_a << endl;
if (errno_sqrt != 0)
cout << ”errno=” << setw(3) << errno_sqrt << ” ” << strerror(errno_sqrt) << endl;

return 0;
}

a: 12.9
mysqrt(a) = 3.591656999213594e+00
sqrt(a) = 3.591656999213594e+00

a: 1.98e11
mysqrt(a) = 4.449719092257398e+05
sqrt(a) = 4.449719092257398e+05

a: 2e-308
mysqrt(a) = 1.414213562373095e-154
sqrt(a) = 1.414213562373095e-154

a: 1.79e308
mysqrt(a) = 1.337908816025965e+154
sqrt(a) = 1.337908816025965e+154

a: -3.3
mysqrt(a) = nan
errno= 33 Numerical argument out of domain
sqrt(a) = -nan
errno= 33 Numerical argument out of domain

Seite 3 von 4
A. Newtonverfahren Programmieren I für Studierende der Mathematik — SoSe 2026

Anhang

A. Newtonverfahren

Das Newtonverfahren ist ein Verfahren zur Bestimmung einer Nullstelle einer Funktion 𝑓 : R → R ausgehend
von einem Ansatz 𝑎.
Sei 𝛼 eine Nullstelle von 𝑓 , d.h. 𝑓 (𝛼) = 0.
Wenn 𝑓 stetig differenzierbar ist und 𝑓 ′ (𝛼) ≠ 0, so existiert eine Umgebung von 𝛼 sodass das Verfahren
ausgehend von 𝑎 innerhalb dieser Umgebung konvergiert gegen 𝛼. Ist 𝑓 in 𝛼 zweimal stetig differenzierbar,
so ist die Konvergenz mindestens quadratisch.
Wir betrachten für 𝑥 0 = 𝑎 die Folge gegeben durch:

𝑓 (𝑥𝑛 )
𝑥𝑛+1 = 𝑥𝑛 −
𝑓 ′ (𝑥𝑛 )

𝑓 (𝑥) := 1
𝑥2
−1
Tangenten an 𝑓 (𝑥)

𝑥0 𝑥1 𝑥2 𝑥3 𝑥4 · · · 𝛼

Graphische Darstellung des Newtonverfahrens

Speziell für 𝑓 (𝑥) := 𝑥 𝛼 − 𝑎 mit festem 𝑎 > 0 und 𝛼 ≠ 0:


( )
1 1 𝑎
𝑥𝑛+1 = 1 − 𝑥𝑛 + · 𝛼 −1
𝛼 𝛼 𝑥𝑛

Und für 𝛼 = ±2:


( )
1 𝑎 √
𝑥𝑘+1 = 𝑥𝑘 + −−−−→ 𝑎 (Heron-Verfahren)
2 𝑥𝑘 𝑘→∞
3 1 1
𝑥𝑘+1 = 𝑥𝑘 − 𝑎𝑥𝑘3 −−−−→ √
2 2 𝑘→∞ 𝑎

Seite 4 von 4
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Blatt 12 – Lösungsvorschlag

Aufgabe 15 Ziel ist es ein Programm zu erstellen, dass einen String von der Kommandozeile einliest, prüft
ob er der folgenden Grammatik genügt und, wenn ja, als arithmetischen Ausdruck auswertet und das Ergebnis
ausgibt:
⟨Ausdruck⟩ → [“+” | “-”] ⟨Term⟩ {(“+” | “-”) ⟨Term⟩}
⟨Term⟩ → ⟨Faktor⟩ {(“*” | “/”) ⟨Faktor⟩}
⟨Faktor⟩ → ⟨Operand⟩ [“^” ⟨Operand⟩]
⟨Operand⟩ → ⟨Zahl⟩
| [“sin” | “cos” | “exp” | “ln”] “(” ⟨Ausdruck⟩ “)”
⟨Zahl⟩ → /[1-9]/ {/[0-9]/}
Die gegebene Grammatik ist eine sog. LL(1)-Grammatik. Ein Programm, das für eine gegebene Grammatik
eine Eingabe syntaktisch analysiert und insb. prüft ob die Eingabe ein Wort der beschriebenen Sprache ist,
nennt man einen Parser. Wir entwickeln für die gegebene LL(1)-Grammatik eine spezielle Sorte von Parser,
einen LL(1) recursive descent parser.
Gehen Sie vor, wie folgt:

a) Wir wollen im Folgenden für jedes der Non-Terminale der Grammatik (⟨Ausdruck⟩, ⟨Term⟩, ⟨Faktor⟩,
⟨Operand⟩ und ⟨Zahl⟩) eine Funktion implementieren. Diese Funktionen werden sich später rekursiv
gegenseitig aufrufen. Damit das in C++ möglich ist, müssen Name und Typ der Funktionen vor ihrer
Definition bereits bekannt gemacht werden.
Sie werden hierfür am Anfang ihrer Übersetzungseinheit, direkt nach ihren #includes und ggF. using
namespace std am Ende Deklarationen für die später definierten Funktionen benötigen, wie folgt:

double Ausdruck(istringstream&);
double Term(istringstream&);
double Faktor(istringstream&);
double Operand(istringstream&);
double Zahl(istringstream&);

void fehler(istringstream&);

Fügen Sie einzelne dieser Deklarationen hinzu, wann immer sie benötigt werden.
b) Lesen Sie in Ihrem Hauptprogramm eine einzelne Zeile vollständig von der Standardeingabe ein und
legen Sie den gelesenen Inhalt in einem istringstream ab.
c) Implementieren Sie eine Funktion fehler, welche, gegeben eine Referenz auf einen Wert vom Typ
istringstream, den gesamten Inhalt des Stroms ausgibt und geeignet die Stelle markiert, bis zu der im
Strom bereits gelesen wurde (siehe tellg()).
Dies wird später sehr nützlich sein um syntaktische Fehler in der Eingabe zu lokalisieren.
Nach Ausgabe der Fehlermeldung sollten Sie die Ausführung Ihres Programms aus der Funktion fehler
heraus beenden. Hierfür können Sie die Anweisung exit(1); verwenden.
d) Implementieren Sie für jedes der Non-Terminale der Grammatik jeweils eine Funktion, die ebenfalls
eine Referenz auf einen Wert vom Typ istringstream als einzigen Parameter akzeptiert und einen
berechneten Wert vom Typ double als Rückgabewert liefert.

Seite 1 von 6
Aufgabe 15 Programmieren I für Studierende der Mathematik — SoSe 2026

Um schrittweise vorgehen zu können, vereinfachen Sie hierfür zunächst die Grammatik und implemen-
tieren als ersten Schritt z.B. nur:
⟨Ausdruck⟩ → [“+” | “-”] ⟨Zahl⟩ {(“+” | “-”) ⟨Zahl⟩}
⟨Zahl⟩ → /[1-9]/ {/[0-9]/}
Verwenden Sie innerhalb ihrer Funktionen jeweils die Funktion peek auf den gegebenen Strom um das
nächste noch nicht von der Eingabe verarbeitete Zeichen zu bestimmen. Entscheiden Sie anhand dieses
Vorausschauens in die Eingabe, wie die Eingabe weiter verarbeitet werden soll.
Die LL(1)-Eigenschaft der betrachteten Grammatik bedeutet, dass Sie stets maximal ein Zeichen vor-
ausschauen werden müssen.
Wenn das nächste Zeichen nicht von der Funktion direkt verarbeitet werden sollte (also nicht z.B. +),
sondern zu einem anderen Non-Terminal gehört (z.B. 0 zu ⟨Zahl⟩), dann rufen Sie die zu diesem Non-
Terminal gehörige Funktion auf und übergeben ihr die Referenz auf den Eingabestrom.
Das Ziel ist es am Ende der Funktion den numerischen Wert (vom Typ double) zurückzugeben, der dem
verarbeiteten Teil der Eingabe entspricht.
Beispiel. Es folgt Teil einer Implementierung dieser Sorte von Parser für folgende Grammatik:
⟨Ausdruck⟩ → ⟨Zahl⟩ [(“+” | “-”) ⟨Zahl⟩]
⟨Zahl⟩ → . . .

double Ausdruck(istringstream& ein) {


double summe = Zahl(ein);

char z = [Link]();
if (z == '+') {
[Link](z);
summe += Zahl(ein);
} else if (z == '-') {
[Link](z);
summe -= Zahl(ein);
}

return summe;
}

e) Rufen Sie aus Ihrem Hauptprogramm die Funktion Ausdruck auf, prüfen Sie, dass sämtliche Eingabe
auch verarbeitet wurde und geben Sie dann das von Ausdruck zurückgegebene Ergebnis aus.

Worauf müssten Sie, zum Zwecke des Beibehaltens der LL(1)-Eigenschaft, achten, wenn Sie zusätzliche Funk-
tionen anbieten wollen, zusätzlich zu sin, cos, exp und ln?

Da nur der Anfangsbuchstabe des Namens der Funktion verwendet wird um die Funktion auszuwählen,
dürfen keine zwei Funktionen mit dem selben Zeichen beginnen.
Stattdessen den ganzen Namen der Funktion zu betrachten würde die Grammatik aus der Klasse von
LL(1)-Grammatiken heraus heben. Gibt es eine beliebige aber feste Grenze 𝑘 wieviele Zeichen voraus-
geschaut werden muss, so spricht man von einer LL(𝑘)-Grammatik.

Rechnen Sie die folgenden Beispiele:


i) 17 v) 24 + 1
ii) 14 + 3 vi) exp(log(4) · 2) + 1
iii) 14 − (−3) vii) 11 +√22 + 33 + 44
iv) 7 · 2 + 3 viii) log( 3 𝑒)

Seite 2 von 6
Aufgabe 15 Programmieren I für Studierende der Mathematik — SoSe 2026

(( √ ) 16 ( √ ) 16 ) 3 (1)
1 1+ 5 1− 5 16sin · 2sin(3)
ix) √ − x)
5 2 2 8sin(1)
Hinweis (Kontrollergebnisse). Es ergibt sich jeweils ca.:
i) 17 iii) 17 v) 17 vii) 288 ix) 987
ii) 17 iv) 17 vi) 17 viii) 0,333 333 x) 1

[Link]

#include <iostream>
#include <sstream>
#include <cmath>

using namespace std;

double Ausdruck(istringstream&);
double Term(istringstream&);
double Faktor(istringstream&);
double Operand(istringstream&);
double Zahl(istringstream&);

void fehler(istringstream&);

int main() {
string s;
cout << ”Arithm. Ausdruck: ”;
getline(cin, s);
istringstream ein(s);

double ergebnis = Ausdruck(ein);


if (![Link]())
fehler(ein);

cout << ”Ergebnis: ” << ergebnis << endl;


}

double Ausdruck(istringstream& ein) {


double summe;

char z;
z = [Link]();
if (z == '+') {
[Link](z);
summe = Term(ein);
} else if (z == '-') {
[Link](z);
summe = - Term(ein);
} else
summe = Term(ein);

while (true) {
z = [Link]();
if (z == '+') {
[Link](z);
summe += Term(ein);
} else if (z == '-') {
[Link](z);

Seite 3 von 6
Aufgabe 15 Programmieren I für Studierende der Mathematik — SoSe 2026

summe -= Term(ein);
} else
break;
}

return summe;
}

double Term(istringstream& ein) {


double produkt = Faktor(ein);

char z;
while (true) {
z = [Link]();
if (z == '*') {
[Link](z);
produkt *= Faktor(ein);
} else if (z == '/') {
[Link](z);
produkt /= Faktor(ein);
} else
break;
}

return produkt;
}

double Faktor(istringstream& ein) {


double exponential = Operand(ein);

char z = [Link]();
if (z == '^') {
[Link](z);
exponential = pow(exponential, Operand(ein));
}

return exponential;
}

double Operand(istringstream& ein) {


double operand;

char z = [Link]();
if (z >= '1' && z <= '9') {
operand = Zahl(ein);
} else if (z == '(' || z == 's' || z == 'c' || z == 'e' || z == 'l') {
string expected;
if (z == 's')
expected = ”sin”;
else if (z == 'c')
expected = ”cos”;
else if (z == 'e')
expected = ”exp”;
else if (z == 'l')
expected = ”ln”;
expected += '(';

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

Seite 4 von 6
Aufgabe 15 Programmieren I für Studierende der Mathematik — SoSe 2026

[Link](z);
if (z != expected[i])
fehler(ein);
}

operand = Ausdruck(ein);
[Link](z);
if (z != ')')
fehler(ein);

if (expected[0] == 's')
operand = sin(operand);
else if (expected[0] == 'c')
operand = cos(operand);
else if (expected[0] == 'e')
operand = exp(operand);
else if (expected[0] == 'l')
operand = log(operand);
} else
fehler(ein);

return operand;
}

double Zahl(istringstream& ein) {


string zahl_str;
char z = [Link]();
if (z >= '1' && z <= '9') {
[Link](z);
zahl_str += z;
} else
fehler(ein);

while (true) {
z = [Link]();
if (z >= '0' && z <= '9') {
[Link](z);
zahl_str += z;
} else
break;
}

double zahl;
istringstream zahl_inp(zahl_str);
zahl_inp >> zahl;
return zahl;
}

void fehler(istringstream& ein) {


string s = [Link]();
cout << ”Fehler:” << endl;
cout << ” ” << s << endl << ” ”;
unsigned int fehlerpos = [Link]()
? [Link]()
: static_cast<string::size_type>([Link]());
for (unsigned int i = 0; i < fehlerpos; i++)
cout << ' ';
cout << '^' << endl;

Seite 5 von 6
Aufgabe 15 Programmieren I für Studierende der Mathematik — SoSe 2026

exit(1);
}

Arithm. Ausdruck: 17
Ergebnis: 17

Arithm. Ausdruck: 14+3


Ergebnis: 17

Arithm. Ausdruck: 14-(-3)


Ergebnis: 17

Arithm. Ausdruck: 7*2+3


Ergebnis: 17

Arithm. Ausdruck: 2^4+1


Ergebnis: 17

Arithm. Ausdruck: exp(ln(4)*2)+1


Ergebnis: 17

Arithm. Ausdruck: 1^1+2^2+3^3+4^4


Ergebnis: 288

Arithm. Ausdruck: ln(exp(1)^(1/3))


Ergebnis: 0.333333

Arithm. Ausdruck: (1/5^(1/2))*(((1+5^(1/2))/2)^16-((1-5^(1/2))/2)^16)


Ergebnis: 987

Arithm. Ausdruck: (16^((sin(1))^3)*2^(sin(3)))/(8^(sin(1)))


Ergebnis: 1

Seite 6 von 6
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen

Programmieren I für Studierende der Mathematik


Beispielklausur — Lösungsvorschlag

Aufgabe 1 2 3 4 5 Σ
Punkte 8 6 14 26 42 96

Bearbeitungszeit 120 Minuten


Beispielklausur — Lösung Programmieren I für Studierende der Mathematik — SoSe 2026

1 (8 Punkte) Welche Ausgabe produziert das folgende Programm?

#include <iostream>
#include <iomanip>

using namespace std;

double f1(int n, double x) { return n*n/2+x*x/2; }


double f2(int n, double x) { return n+(n<=x); }
double f3(int n, double x) { return n/2*x+x/2*n; }
double f4(int n, double x) { return n++*--x; }

int main() {
cout
<< setw(8) << f1(3, 3) << setw(8) << f1(4, 4) << endl
<< setw(8) << f2(3, 3) << setw(8) << f2(4, 4) << endl
<< setw(8) << f3(3, 3) << setw(8) << f3(4, 4) << endl
<< setw(8) << f4(3, 3) << setw(8) << f4(4, 4) << endl;
}

8.5 16
4 5
7.5 16
6 12

2 (6 Punkte) Formulieren Sie für die folgenden 𝑀 ′ -Programme jeweils einen äquivalenten C++-Ausdruck.
D.h. jeweils einen Ausdruck welcher zu dem vom 𝑀 ′ -Programm ausgegebenen Wert auswertet.
Gehen Sie davon aus, dass die Werte 𝑎 und 𝑏, die in den 𝑀 ′ -Programmen eingelesen werden, in den
Variablen a und b, jeweils vom Typ double, zur Verfügung stehen.
Geben Sie jeweils nur genau den Ausdruck an.

(a) (b)
1 JUMP(4)
1 JUMP(4) 2 # a
2 # a 3 # b
3 # b 4 READ
4 READ 5 STORE(2)
5 STORE(2) 6 READ
6 READ 7 STORE(3)
7 STORE(3) 8 RECALL(2)
8 DIV(2) 9 MULT(2)
9 STORE(3) 10 MULT(2)
10 RECALL(2) 11 STORE(2)
11 ADD(2) 12 RECALL(3)
12 SUB(3) 13 ADD(3)
13 PRINT 14 MULT(2)
14 STOP 15 PRINT
16 STOP

a+a-b/a a*a*a*(b+b)

Seite 1 von 6
Beispielklausur — Lösung Programmieren I für Studierende der Mathematik — SoSe 2026

3 (14 Punkte) Die Funktion log1p : (−1, ∞) → R, 𝑥 ↦→ ln(1 + 𝑥) besitzt für |𝑥 | < 1 die Potenzreihenent-
wicklung:
Õ

𝑥𝑘
log1p(𝑥) = (−1)𝑘+1
𝑘
𝑘=1 | {z }
:=𝑎𝑘
Í𝑛
(a) Leiten Sie für 𝑎𝑘 und 𝑠𝑛 := 𝑘=1 𝑎𝑘 Rekursionsbeziehungen her, die sich zur Programmierung eignen.

Es ist: 𝑘+1
𝑥
𝑎𝑘+1 (−1)𝑘+2 𝑘+1 𝑘
= = −𝑥
𝑎𝑘 (−1)𝑘+1 𝑥𝑘
𝑘
𝑘 +1

Insgesamt also:

𝑎1 = 𝑥 𝑠1 = 𝑎1 = 𝑥
𝑘
𝑎𝑘+1 = −𝑥 𝑎𝑘 𝑠𝑛+1 = 𝑠𝑛 + 𝑎𝑛+1
𝑘 +1

(b) Erstellen Sie eine C++-Funktion log1p zur Berechnung von log1p(𝑥) unter Verwendung Ihrer Rekur-
sionsbeziehung aus Aufgabenteil a. Die Bedingung |𝑥 | < 1 dürfen Sie ohne gesonderte Überprüfung
annehmen. Die Iteration soll abgebrochen werden, sobald |𝑎𝑛 | ≤ 𝜀 |𝑠𝑛 |.

double log1p(double x) {
double a = x;
double s = a;
for (int n = 1; abs(a) > numeric_limits<double>::epsilon()*abs(s); n++) {
a *= -x*n/(n+1);
s += a;
}
return s;
}

4 (26 Punkte) Für 𝑥 ∈ R ∩ [1, ∞) konvergiert die durch (𝑠𝑛 )𝑛∈N\{0} gegebene Folge gegen arcosh(𝑥):
𝑥 −2
𝑎1 = 𝑠 1 = ln(2𝑥) − 𝑎 1
4
(2𝑘) (2𝑘 + 1) · 𝑥 −2
𝑎𝑘+1 = 𝑎𝑘 𝑠𝑘+1 = 𝑠𝑘 − 𝑎𝑘+1
(2𝑘 + 2) 2
(a) Implementieren Sie diese Iteration als Funktion arccosh mit einem Parameter und Rückgabetyp je-
weils sinnvoll. Verwenden Sie hierbei aus den arithmetischen Funktionen der Standardlibrary höchs-
tens die Implementierungen des natürlichen Logarithmus und des Absolutbetrags. Brechen Sie die
Iteration ab, sobald 𝑎𝑘 ≤ 𝜀 |𝑠𝑘 |.

double arccosh(double x) {

Fehlerbehandlung

double a = 1/(4*x*x);
double s = log(2*x) - a;
for (int n = 1; a > numeric_limits<double>::epsilon()*abs(s); n++) {
a *= 2*n*(2*n + 1)/(x*x*(2*n + 2)*(2*n + 2));
s -= a;
}

Seite 2 von 6
Beispielklausur — Lösung Programmieren I für Studierende der Mathematik — SoSe 2026

return s;
}

(b) Fügen Sie zu Ihrer Implementierung die folgenden Spezialfälle hinzu:


i. Für 𝑥 < 1 soll NaN zurückgegeben werden und ein Definitionsbereichsfehler mit dem errno-
Mechanismus kommuniziert werden.
ii. Für 𝑥 = 1 soll direkt, ohne Iteration, 0 zurückgegeben werden.

Fehlerbehandlung

if (x < 1) {
errno = EDOM;
return numeric_limits<double>::quiet_NaN();
} else if (x == 1)
return 0;

(c) Implementieren Sie ein Hauptprogramm welches, ausgehend von 𝑥 = 0,9, für 𝑥 und danach in Schrit-
ten der Größe 0,1 aufsteigend bis inklusive 𝑥 = 4,1 jeweils Ihre Funktion arccosh mit 𝑥 aufruft. Geben
Sie die Ergebnisse tabellarisch aus mit jeweils einer Spalte für 𝑥 und arcosh(𝑥).
Geben Sie die Werte für arcosh(𝑥) jeweils in Exponentialschreibweise und einer an die zur Verfügung
stehende Genauigkeit gut angepasster Anzahl von Stellen aus. Verwenden Sie für die Ausgabe von
𝑥 jeweils die automatische Auswahl des Ausgabeformat mit 6 signifikanten Stellen, so wie es der
Voreinstellung zu Programmstart entspräche.
Falls bei einer Auswertung Ihrer Funktion arccosh ein Fehler kommuniziert wird, fügen Sie in Ihre
Tabelle eine zusätzliche Zeile ein, die den numerischen Wert der übermittelten Fehlerkondition und
eine zur tatsächlich aufgetreten Fehlerkondition passenden Text enthält.

const int d10 = numeric_limits<double>::digits10;

int main() {
cout << setw(5) << ”x” << setw(d10 + 8) << ”arccosh(x)” << endl;
for (int i = 9; i <= 41; i++) {
double x = i * 0.1;
cout << setw(5) << setprecision(6) << defaultfloat << x;

errno = 0;
double val = arccosh(x);
cout << setw(d10 + 8) << setprecision(d10) << scientific << val << endl;
if (errno != 0)
cout << ”errno=” << errno << ” ” << strerror(errno) << endl;
}
}

5 (42 Punkte) Ziel ist es eine effiziente Kodierung einer beliebig langen Folge von jeweils beliebig großen
positiven ganzen Zahlen zu implementieren, die Fibonacci-Kodierung.
(a) Implementieren Sie eine memoisierte Version der Fibonacci-Funktion.
Ihre Funktion soll zwei Parameter akzeptieren. Der erste Parameter soll eine Referenz auf einen Vek-
tor von ganzen Zahlen vom Typ unsigned long sein. Der zweite Parameter soll der Index n der zu
berechnenden Fibonacci-Zahl 𝐹 (𝑛) sein:
𝐹 (0) := 0 𝐹 (1) := 1 𝐹 (2) := 1 𝐹 (𝑛 + 2) = 𝐹 (𝑛) + 𝐹 (𝑛 + 1)
Verwenden Sie den referenzierten Vektor um berechnete Fibonacci-Zahlen zu speichern, bevor Sie
sie zurück geben.

Seite 3 von 6
Beispielklausur — Lösung Programmieren I für Studierende der Mathematik — SoSe 2026

Verwenden Sie zur Berechnung noch nicht bekannter Fibonacci-Zahlen die im referenzierten Vektor
zur Verfügung stehenden Werte. Tragen Sie hierfür wenn nötig zunächst die obigen Basisfälle in den
Vektor ein. Achten Sie darauf keine Annahmen über die Länge des referenzierten Vektors zu machen.

unsigned long fib(vector<unsigned long>& memo, unsigned int n) {


while ([Link]() <= n)
if ([Link]() < 3)
memo = {0, 1, 1};
else
memo.push_back(memo[[Link]() - 2] + memo[[Link]() - 1]);

return memo[n];
}

(b) Implementieren Sie zwei Hilfsfunktionen bits_to_uchar und uchar_to_bits die jeweils einen Vek-
tor von bool zu einem unsigned char konvertieren bzw. von einem unsigned char zu einem Vektor
von bool. Der Vektor von bool soll hierbei der Binärdarstellung des jeweiligen Wertes entsprechen.
Achten Sie darauf unnötige Vektorkopien zu vermeiden.

unsigned char bits_to_uchar(const vector<bool>& bits) {


unsigned char out = 0;
for (vector<bool>::size_type i = 0; i < [Link](); i++)
if (bits[i])
out += 1 << ([Link]() - (i + 1));
return out;
}

vector<bool> uchar_to_bits(unsigned char n) {


unsigned char mask = 1 << (numeric_limits<unsigned char>::digits - 1);
vector<bool> out;
while (mask != 0) {
out.push_back(n & mask);
mask >>= 1;
}
return out;
}

(c) Implementieren Sie eine Funktion decode, die, gegeben eine Referenz auf einen Vektor von unsigned
long (durchzureichen an Aufrufe von fib) und eine Referenz auf einen Eingabestrom einen Vektor
von unsigned long zurück liefert. Gehen Sie hierfür vor, wie folgt:
1) Legen Sie eine Variable an um erfolgreich dekodierte Werte anzusammeln bis Sie sie zurück
geben können.
2) Legen Sie einen „Eingabepuffer“ vom Typ Vektor von bool an.
3) Prüfen Sie ob Sie in Ihrem Eingabepuffer eine Teilfolge von zwei aufeinanderfolgenden Werten
true finden.
4) Falls Sie keine derartige Teilfolge finden, dann lesen Sie ein Zeichen vom Eingabestrom ein,
konvertieren es, mithilfe von uchar_to_bits, in einen Vektor von bool und hängen dessen Ele-
mente an Ihren Eingabepuffer hinten an. Falls Sie kein Zeichen einlesen konnten, geben Sie die
angesammelten dekordierten Werte zurück. Ansonsten fahren Sie wieder bei Schritt 3 fort.
5) Falls Sie eine derartige Teilfolge finden, dekodieren Sie das Anfangsstück bis inklusive der Teil-
folge zu einem Wert vom Typ unsigned char, wie folgt:
Sei 𝑏 0, 𝑏 1, . . . , 𝑏𝑙 −1, 𝑏𝑙 das gefundene Anfangsstück bis inkl. der Teilfolge. D.h. insb. 𝑏𝑙 −1 = 𝑏𝑙 =
true.

Seite 4 von 6
Beispielklausur — Lösung Programmieren I für Studierende der Mathematik — SoSe 2026

Mit 𝐹 der Fibonacci-Funktion fib ergibt sich für den dekodierten Wert w:

Õ
𝑙 −1
𝑤= 𝑏𝑖 · 𝐹 (𝑖 + 2)
𝑖=0

6) Hängen Sie den dekodierten Wert hinten an ihre Sammlung dekodierter Werte an.
7) Entfernen Sie das verarbeitete Anfangsstück 𝑏 0, . . . , 𝑏𝑙 vom Anfang ihres Eingabepuffers. Hier-
durch verkürzt sich der Eingabepuffer um 𝑙 + 1.
8) Fahren Sie wieder bei Schritt 3 fort.

vector<unsigned long> decode(vector<unsigned long>& memo, istream& in) {


vector<unsigned long> outs;

vector<bool> inp;
while (true) {
vector<bool>::size_type end = 0;

for (vector<bool>::size_type i = 1; i < [Link](); i++)


if (inp[i - 1] && inp[i]) {
end = i;
break;
}

if (end == 0) {
char z;
if (![Link](z))
break;
vector<bool> byte = uchar_to_bits(static_cast<unsigned char>(z));
for (vector<bool>::size_type i = 0; i < [Link](); i++)
inp.push_back(byte[i]);
continue;
}

unsigned long out = 0;


for (vector<bool>::size_type i = end; i > 0; i--)
if (inp[i - 1])
out += fib(memo, i + 1);
outs.push_back(out);
for (vector<bool>::size_type i = end + 1; i < [Link](); i++)
inp[i - end - 1] = inp[i];
[Link]([Link]() - end - 1);
}

return outs;
}

(d) Implementieren Sie eine Funktion encode, die gegeben eine Referenz auf einen Vektor von unsigned
long (durchzureichen an Aufrufe von fib), eine Referenz auf einen Ausgabestrom und einen Vektor
von unsigned long von zu kodierenden Werten die Werte in den Ausgabestrom kodiert, wie folgt:
1) Legen Sie einen „Ausgabepuffer“ vom Typ Vektor von bool an.
2) Für jedes Element 𝑤 der zu kodierenden Werte, kodieren Sie es zunächst zu einem Vektor von
bool, wie folgt:
i) Finden Sie die größte Fibonacci-Zahl 𝑓 kleiner oder gleichgroß zu 𝑤.
ii) Für 𝑓 = 𝐹 (𝑖), 𝑖 ≥ 2, setzen Sie das bit mit Index 𝑖 − 2 in Ihrer Kodierung.
iii) Ersetzen Sie 𝑤 durch 𝑤 − 𝑓 .
iv) Wiederholen Sie die obigen drei Schritte bis 𝑤 = 0.

Seite 5 von 6
Beispielklausur — Lösung Programmieren I für Studierende der Mathematik — SoSe 2026

v) Hängen Sie einen zusätzlichen Wert true hinten an Ihre Kodierung an. Es sollte sich hier-
durch ergeben, dass die letzten zwei Werte in ihrem kodierten Vektor von bool stets true
sind.
3) Hängen Sie dann jeweils die Kodierung Ihrer Werte hinten an Ihren Ausgabepuffer an.
4) Wann immer dabei ihr Ausgabepuffer acht oder mehr bits enthält, kodieren Sie die ersten acht
bit des Ausgabepuffers in einen Wert vom Typ unsigned char, geben Sie diesen Wert auf dem
Ausgabestrom aus und entfernen Sie acht bit vom Anfang des Ausgabepuffers.
5) Wenn Ihr Ausgabepuffer nach Verarbeitung aller zu kodierender Werte nicht leer ist, so füllen
Sie ihn auf Länge Acht mit Nullen auf und geben Sie ein finales Byte aus.

const int ucd2 = numeric_limits<unsigned char>::digits;

void encode(vector<unsigned long>& memo, ostream& out, const vector<unsigned long>


↩→ ins) {
vector<bool> bits;
vector<bool>::size_type off = 0;
for (vector<unsigned long>::size_type k = 0; k < [Link](); k++) {
unsigned long rem = ins[k];
while (rem != 0) {
unsigned int n, i = 0;
unsigned long f_prev, f = fib(memo, i);
do {
f_prev = f; n = i;
f = fib(memo, ++i);
} while (f <= rem);
if ([Link]() < off + n - 1)
[Link](off + n - 1, false);
bits[off + n - 2] = true;
rem -= f_prev;
}
bits.push_back(true);
off = [Link]();

while ([Link]() > ucd2) {


vector<bool> byte;
for (vector<bool>::size_type i = 0; i < [Link](); i++)
if (i < ucd2)
byte.push_back(bits[i]);
else
bits[i - ucd2] = bits[i];
[Link]([Link]() - ucd2);
off = [Link]();
out << static_cast<char>(bits_to_uchar(byte));
}
}
if (![Link]()) {
while ([Link]() < ucd2)
bits.push_back(false);
out << static_cast<char>(bits_to_uchar(bits));
}
}

Seite 6 von 6

Das könnte Ihnen auch gefallen