pro1
pro1
Mathematisches Institut
Gregor Kleen
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
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]
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
[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
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.
𝑀 ′ 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
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
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.
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
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
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
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 Σ:
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.
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++
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>
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:
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.
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:
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⟩}
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
Seite 7 von 12
Skript 02 Programmieren I für Studierende der Mathematik — SoSe 2026
heron_const.cpp
#include <iostream>
#include <cmath>
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:
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
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>
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>
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
Im Folgenden bezeichne 𝑖 einen Eingabestrom (z.B. cin) und 𝑜 einen Ausgabestrom (z.B. cout, cerr).
Vordefinierte Ströme
Formatierte Ausgabe
Die Ausgabe auf einen Strom erfolgt durch geeignet überladene shift-Operatoren <<, wie folgt:
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)
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
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:
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.
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>
int main() {
int a, b;
cout << ”a, b: ”;
cin >> a >> b;
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_inc.cpp
#include <iostream>
#include <iomanip>
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
Seite 2 von 14
Skript 04 Programmieren I für Studierende der Mathematik — SoSe 2026
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:
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
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
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
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
(𝑎 ◦ 𝑏) ∗ 𝑐.
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.
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.
Anmerkung. Insb. bei Konvertierungen zwischen Gleitpunkt- und Ganzzahldatentypen kann in beide Richtun-
gen Präzision verloren werden.
Explizite Typumwandlung
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
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>
int main() {
int i = 5, j = 3;
j = i += j -= 1;
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 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>
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
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:
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>
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
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.
:: links
() [] -> . ++ -- links ++, -- postfix
typeid static_cast const_cast rechts Bindungsstärke ident. mit vorheriger Zeile
dynamic_cast reinterpret_cast
.* ->* 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
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.
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
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 .
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
[Link]
#include <iostream>
#include <iomanip>
#include <cmath>
#include <cerrno>
#include <cstring>
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>
int main() {
errno = 0;
ifstream inp{”/nonexistent”};
if (!inp)
cout << setw(2) << errno << ” ” << strerror(errno) << endl;
}
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
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>
gcd
int main() {
unsigned int a, b;
cout << ”a, b: ”;
cin >> a >> b;
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>
int main() {
double x;
do {
cout << ”x: ”;
cin >> x;
} while (x < 0);
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 (𝐶 ) {
𝑆
𝑈 ;
}
}
pot
[Link]
#include <iostream>
pot
int main() {
double a;
unsigned int n;
cout << ”a, n: ”;
cin >> a >> n;
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
[Link]
#include <iostream>
binom
int main() {
unsigned int n, k;
cout << ”n, k: ”;
cin >> n >> k;
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>
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
⟨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.
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.
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.
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.
Seite 1 von 4
Skript 06 Programmieren I für Studierende der Mathematik — SoSe 2026
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>
int main() {
double x[3] = { 3.14, 1.41, 2.72 };
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>
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>
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};
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
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>
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];
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
int main() {
unsigned int m, n;
cout << ”m, n: ”;
cin >> m >> n;
vector<double> x(n);
cout << ”x: ”;
for (unsigned int j = 0; j < n; j++)
cin >> x[j];
m, n: 2 3
a:
1 2 3
4 5 6
x:
-1
0
2
a*x:
5
8
• 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
Hexadezimal Dezimal
Zeichen-Literale
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';
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.
Seite 2 von 6
Skript 08 Programmieren I für Studierende der Mathematik — SoSe 2026
Beispiel. Wir implementieren ein kleines Programm, das eingegebene Buchstaben klassifiziert.
classify_char.cpp
#include <iostream>
#include <iomanip>
#include <locale>
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”;
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:
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.
Seite 5 von 6
Skript 08 Programmieren I für Studierende der Mathematik — SoSe 2026
string_io.cpp
#include <iostream>
#include <string>
#include <sstream>
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);
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.
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:
𝑠 .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.
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:
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:
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 𝑠
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
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>
int main() {
string s;
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>
int main() {
string s;
Seite 7 von 9
Skript 09 Programmieren I für Studierende der Mathematik — SoSe 2026
char c;
in >> noskipws;
while (in >> c) count++;
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
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>
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
Seite 1 von 5
Skript 10 Programmieren I für Studierende der Mathematik — SoSe 2026
17
Byte
103 Speicheradresse
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>
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>
int main() {
double x;
cout << ”x: ”; cin >> x;
double y;
int n;
y = frexp(x, &n);
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
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>
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>
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;
./[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>
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>
int main() {
int x = 3;
int& xr = x;
x = 17;
17
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>
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>
int main() {
double x = 3, y = 7;
swap(x, y);
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
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
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;
*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>
int main() {
double x = 3, y = 7;
xp = &y;
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;
Beispiel. Das Verändern eines Wertes einer Variablen „durch“ einen konstanten Zeiger ist zulässig.
const_pointer_change.cpp
#include <iostream>
int main() {
double x = 3;
*xp = 4;
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;
}
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>
int main() {
vector<double> xs(500'000'000, 0.0);
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 (*𝑐𝑝 ).𝑎.
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>
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
[Link]
#include <iostream>
#include <cmath>
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};
||c||=7.61577
static_norm.cpp
#include <iostream>
#include <cmath>
class Coordinate {
public:
double x;
double y;
int main() {
Coordinate c{3.0, 7.0};
||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>
class Coordinate {
public:
double x;
double y;
int main() {
Coordinate c{3.0, 7.0};
||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>
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
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>
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;
}
Beispiel (Komplexe Zahlen). Wir vereinbaren eine, sehr rudimentäre, Klasse zur Darstellung von komplexen
Zahlen.
[Link]
#include <iostream>
class Complex{
private:
double re, im;
public:
Complex(double re_, double im_) {
re = re_; im = im_;
}
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};
Seite 13 von 14
Skript 11 Programmieren I für Studierende der Mathematik — SoSe 2026
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
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) {}
z = Complex{x, y};
return stream;
}
};
Seite 1 von 2
Skript 12 Programmieren I für Studierende der Mathematik — SoSe 2026
operators_demo.cpp
#include <iostream>
Klasse
int main()
{
Complex z1, z2;
return 0;
}
Seite 2 von 2
Skript 13 Programmieren I für Studierende der Mathematik — SoSe 2026
Seite 1 von 1
Programmieren I für Studierende der Mathematik SoSe 2026
Organisatorisches
Organisatorisches
Veranstaltungs-Website
[Link]
2/2
Seite 1 von 1
Programmieren I für Studierende der Mathematik SoSe 2026
Inhalt
Inhalt
1/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
Seite 2 von 3
Inhalt Programmieren I für Studierende der Mathematik — SoSe 2026
5/6
∗ 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-
Seite 3 von 3
Programmieren I für Studierende der Mathematik SoSe 2026
Computer
Eine Rechenmaschine
1/2
von-Neumann Architektur
Prozessor
Befehlszähler Befehlsadresse
Anweisung
... Steuereinheit
Hauptspeicher
Register
Eingabe
...
... Recheneinheit
Ausgabe
Seite 1 von 1
Programmieren I für Studierende der Mathematik SoSe 2026
1 / 14
Speicherinhalt/Programm
2 / 14
Seite 1 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026
Ausgewählte Anweisungen
3 / 14
4 / 14
Seite 2 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026
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
6 / 14
Seite 3 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026
⇒ 𝑆 𝑦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)
8 / 14
Seite 4 von 7
Ein hypothetischer Rechner Programmieren I für Studierende der Mathematik — SoSe 2026
Unterprogramme: Forts. 2
9 / 14
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
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
···
𝑅 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
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
1/5
Programmiersprachen
2/5
Seite 1 von 3
Programmiersprachen Programmieren I für Studierende der Mathematik — SoSe 2026
Der Standard
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
5/5
Seite 3 von 3
Programmieren I für Studierende der Mathematik SoSe 2026
Grammatik
1/4
∗ 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
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
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
4 / 14
Seite 2 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026
Datentypen
5 / 14
Beispiel: Ausdrücke
heron_const.cpp 6
#include <iostream>
#include <cmath>
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
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
[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⟩
10 / 14
Seite 5 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026
[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;
Seite 6 von 7
Programmaufbau Programmieren I für Studierende der Mathematik — SoSe 2026
• 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
∑
𝑖
𝑠𝑖 := 𝑗 𝑠 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++
1 / 10
• Konvention: << und >> auf Strömen∗ liefert linken Operand als Ergebnis
⇒ Auswertung nur für den Seiteneffekt
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
4 / 10
Seite 2 von 5
Ein-/Ausgabe in C++ Programmieren I für Studierende der Mathematik — SoSe 2026
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
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
setw
cout << '|' << setw(3) << 3 << '|' << endl;
|1337|
7 / 10
8 / 10
Seite 4 von 5
Ein-/Ausgabe in C++ Programmieren I für Studierende der Mathematik — SoSe 2026
Formatierte 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'
c: a
' '
10 / 10
Seite 5 von 5
Programmieren I für Studierende der Mathematik SoSe 2026
Arithmetik in C++
Beispiel: Bereichsüberlauf
2 / 10
Seite 1 von 5
Arithmetik in C++ Programmieren I für Studierende der Mathematik — SoSe 2026
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
4 / 10
Seite 2 von 5
Arithmetik in C++ Programmieren I für Studierende der Mathematik — SoSe 2026
Gleitpunktdatentypen
Õ
𝑡
𝑠 𝑒
𝑥 = (−1) · 𝑚 · 𝐵 𝑚= 𝑧𝑖 𝐵 −𝑖
𝑖=1
5 / 10
Auslöschung
𝑥 − 𝑦 − (𝑥 − 𝑦) 𝑥 − 𝑦 − (𝑥 − 𝑦)
𝜀𝑥 −𝑦 = =
𝑥 −𝑦 𝑥 −𝑦
(1 + 𝜀𝑥 )𝑥 − (1 + 𝜀 𝑦 )𝑦 − (𝑥 − 𝑦)
=
𝑥 −𝑦
𝑥 𝑦
= 𝜀𝑥 − 𝜀𝑦
𝑥 −𝑦 𝑥 −𝑦
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<𝑇 >::𝑐
Literale
8 / 10
Seite 4 von 5
Arithmetik in C++ Programmieren I für Studierende der Mathematik — SoSe 2026
Ganzzahlliterale
9 / 10
Gleitpunktliterale
10 / 10
Seite 5 von 5
Programmieren I für Studierende der Mathematik SoSe 2026
Ausdrücke
1 / 17
++ -- 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
Seite 1 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026
Danach: i = 6, k = 5 Danach: i = 6, k = 6
3 / 17
4 / 17
Seite 2 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026
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
Bei &&, ||: rechte Seite wird nur ausgewertet, wenn nötig (Seiteneffekte!)
7 / 17
8 / 17
Seite 4 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026
Ternärer Operator
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
• Linksassoziativ
• Mit 𝑎 , 𝑏
• Wertet 𝑎 aus (Seiteneffekte!)
• Verwirft Wert von 𝑎
• Dann identisch zu 𝑏
10 / 17
Seite 5 von 9
Ausdrücke Programmieren I für Studierende der Mathematik — SoSe 2026
Binäroperatoren
∑
𝑡 −1
𝑘= 𝑏𝑖 2𝑖
𝑖=0
𝑏 31 𝑏 30 𝑏 29 𝑏 28 ··· 𝑏3 𝑏2 𝑏1 𝑏0
11 / 17
Binäroperatoren (Forts.)
& 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
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
15 / 17
Beispiel: 𝑏 3 ←0
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
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
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
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
1/6
2/6
Seite 1 von 3
Fehlerbehandlung bei Standardfunktionen Programmieren I für Studierende der Mathematik — SoSe 2026
• 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
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
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
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
[Link] a, b: 143 65
ggT(143, 65) = 13
#include <iostream>
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;
4 / 10
Seite 2 von 5
Kontrollstrukturen Programmieren I für Studierende der Mathematik — SoSe 2026
sqrt_check.cpp x: -4
x: -1
#include <iostream> x: 2
#include <cmath> sqrt(2) = 1.41421
int main() {
double x;
do {
cout << ”x: ”;
cin >> x;
} while (x < 0);
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>
int main() {
double a;
unsigned int n;
cout << ”a, n: ”;
cin >> a >> n;
7 / 10
8 / 10
Seite 4 von 5
Kontrollstrukturen Programmieren I für Studierende der Mathematik — SoSe 2026
Beispiel: Binomialkoeffizient
[Link]
#include <iostream>
𝑛 𝑛 𝑛 −𝑖 int main() {
= · unsigned int n, k;
𝑖 +1 𝑖 𝑖 +1 cout << ”n, k: ”;
cin >> n >> k;
n, k: 6 3
binom(6, 3) = 20
n, k: 7 2
binom(7, 2) = 21
9 / 10
10 / 10
Seite 5 von 5
Programmieren I für Studierende der Mathematik SoSe 2026
Arrays
Arrays
1/8
2/8
Seite 1 von 4
Arrays Programmieren I für Studierende der Mathematik — SoSe 2026
3/8
#include <iostream>
int main() {
double x[3] = { 3.14, 1.41, 2.72 };
4/8
Seite 2 von 4
Arrays Programmieren I für Studierende der Mathematik — SoSe 2026
5/8
array_param.cpp 2
4
#include <iostream> 6
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
7/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
2/6
Seite 1 von 3
Vektoren Programmieren I für Studierende der Mathematik — SoSe 2026
𝑇 ein Datentyp; 𝑛, 𝑘 < 𝑛 Werte vom Typ vector<𝑇 >::size_type; 𝑡, 𝑡 0, . . . , 𝑡𝑛−1 Werte
vom Typ 𝑇 ; 𝑎, 𝑎 ′ Werte vom Typ vector<𝑇 >
3/6
4/6
Seite 2 von 3
Vektoren Programmieren I für Studierende der Mathematik — SoSe 2026
[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];
5/6
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-Literale
2/4
Seite 1 von 2
Zeichen Programmieren I für Studierende der Mathematik — SoSe 2026
• Header-Datei <locale>
• locale() liefert „default“-Wert vom Typ locale
• Klassifizierungsfunktionen nehmen 𝑙 vom Typ locale als zweiten Parameter
3/4
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”;
4/4
Seite 2 von 2
Programmieren I für Studierende der Mathematik SoSe 2026
Zeichenketten
C-Zeichenketten
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
𝑠 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
3/7
𝑠 =𝑡 𝑠 =𝑐 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
5/7
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);
return 0;
}
6/7
Seite 3 von 4
Zeichenketten Programmieren I für Studierende der Mathematik — SoSe 2026
#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;
Seite 4 von 4
Programmieren I für Studierende der Mathematik SoSe 2026
Ein-/Ausgabe Ströme
Buffering
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
3 / 14
Beispiel: Stromzustand
4 / 14
Seite 2 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026
Stromzustand (Forts.)
∗ Verhalten
bzgl. Binäroperatoren wie vorzeichenloser Ganzzahldatentyp, int Wert 0 konvertierbar, sonst
implementierungsabhängig 5 / 14
6 / 14
Seite 3 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026
• 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
in, binary
und ate dürfen frei miteinander und mit den folgenden ansonsten zulässigen
Modi kombiniert werden:
Seite 4 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026
𝑓 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
[Link]
#include <string>
#include <iostream>
#include <fstream>
#include <cerrno>
#include <cstring>
int main() {
string s;
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
[Link]
#include <string>
#include <iostream>
#include <fstream>
#include <cerrno>
#include <cstring>
int main() {
string s;
11 / 14
12 / 14
Seite 6 von 7
Ein-/Ausgabe Ströme Programmieren I für Studierende der Mathematik — SoSe 2026
𝑠 ein String; 𝑖, 𝑜, 𝑏 ein Eingabe- bzw. Ausgabe- bzw. Ein-/Ausgabestrom; 𝑎 ein beliebiger
Strom
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
demo_sstream.cpp i: 123
s: 7b
#include <iostream>
#include <string>
#include <sstream>
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
Zeiger
*
Adresse Wert
&
2/8
Seite 1 von 4
Zeiger Programmieren I für Studierende der Mathematik — SoSe 2026
Beispiel: Zeiger
[Link] 17
#include <iostream>
int main() {
int x = 3;
int *xp;
xp = &x;
x = 17;
3/8
xp x
Byte
103 Speicheradresse
4/8
Seite 2 von 4
Zeiger Programmieren I für Studierende der Mathematik — SoSe 2026
pointer_write.cpp Davor: 4
Danach: 6
#include <iostream>
int main() {
double x = 4;
cout << ”Davor: ” << x << endl;
modify(&x);
cout << ”Danach: ” << x << endl;
}
5/8
Null-Pointer
6/8
Seite 3 von 4
Zeiger Programmieren I für Studierende der Mathematik — SoSe 2026
7/8
··· ···
99 100 101 102 103 104 105 106 107 108 109 110 111 112
Seite 4 von 4
Programmieren I für Studierende der Mathematik SoSe 2026
Kommandozeilenargumente
Kommandozeilenargumente
echo_noendl.cpp
1/2
#include <iostream>
n=12 m=0.25
#include <sstream>
n*m=3
#include <string>
double n, m;
istringstream cli_arg{argv[1]};
cli_arg >> n;
cli_arg.str(argv[2]);
cli_arg.seekg(0);
cli_arg >> m;
2/2
Seite 1 von 1
Programmieren I für Studierende der Mathematik SoSe 2026
Dynamische Speicherzuweisung
• 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
3/4
alloc_double.cpp 17.3
#include <iostream>
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
[Link] [Link]
x = 17; x = 17;
17 17
2/5
Seite 1 von 3
Referenzen Programmieren I für Studierende der Mathematik — SoSe 2026
3/5
reference_write.cpp pointer_write.cpp
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
int main() {
double x = 3, y = 7;
swap(x, y);
5/5
Seite 3 von 3
Programmieren I für Studierende der Mathematik SoSe 2026
Konstanten
Konstanten
∗ mit etwas Aufwand trotzdem möglich; jedoch immer undefiniertes Verhalten 1/9
2/9
Seite 1 von 5
Konstanten Programmieren I für Studierende der Mathematik — SoSe 2026
3/9
pointer_const_change.cpp pointer_const_redirect.cpp
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
5/9
const_pointer_redirect.cpp const_pointer_change.cpp
*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
7/9
8/9
Seite 4 von 5
Konstanten Programmieren I für Studierende der Mathematik — SoSe 2026
const_reference_param.cpp 0
#include <iostream>
#include <vector>
int main() {
vector<double> xs(500'000'000, 0.0);
9/9
Seite 5 von 5
Programmieren I für Studierende der Mathematik SoSe 2026
Einfache Klassen
Zusammengesetzte Datentypen
• 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
2 / 16
Seite 1 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026
Attributszugriff
3 / 16
Aggregat-Initialisierung
4 / 16
Seite 2 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026
Beispiel: Coordinate
#include <iostream>
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
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>
class Coordinate {
public:
double x;
double y;
double norm() {
return sqrt(x*x + y*y);
}
};
int main() {
Coordinate c{3.0, 7.0};
7 / 16
Statische Komponentenfunktionen
8 / 16
Seite 4 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026
static_norm.cpp ||c||=7.61577
#include <iostream>
#include <cmath>
class Coordinate {
public:
double x;
double y;
int main() {
Coordinate c{3.0, 7.0};
9 / 16
Befreundete Funktionen
10 / 16
Seite 5 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026
friend_norm.cpp ||c||=7.61577
#include <iostream>
#include <cmath>
class Coordinate {
public:
double x;
double y;
int main() {
Coordinate c{3.0, 7.0};
11 / 16
Konstruktoren
12 / 16
Seite 6 von 8
Einfache Klassen Programmieren I für Studierende der Mathematik — SoSe 2026
Beispiel: Konstruktor
#include <iostream>
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
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
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};
Seite 8 von 8
Programmieren I für Studierende der Mathematik SoSe 2026
Operatorüberladung
z = Complex{x, y};
return stream;
2/2
Seite 1 von 1
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen
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>
int main() {
cout << ”Hello, World!” << endl;
}
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
(𝑛)
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:
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
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.).
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
Seite 6 von 9
A. Übersetzung von C++-Programmen Programmieren I für Studierende der Mathematik — SoSe 2026
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.
7) Übersetzen Sie die Quelltext-Datei mit einem Befehl g++ 𝑓 mit 𝑓 dem Namen der Datei in die Sie den
Quelltext abgespeichert haben.
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.
Seite 9 von 9
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen
Verwenden Sie zum Entwickeln und Testen von Programmen für die hypothetische Maschine 𝑀 ′ die online-
Implementierung:
[Link]/~gkleen/eval-m
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
Seite 2 von 7
Aufgabe 4 Programmieren I für Studierende der Mathematik — SoSe 2026
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
𝑑𝑒 − 𝑏 𝑓 𝑎𝑓 − 𝑐𝑒
𝑥= 𝑦=
𝑎𝑑 − 𝑏𝑐 𝑎𝑑 − 𝑏𝑐
a) 𝑎 = 3 𝑏 = 4 𝑐 = 2 𝑑 = 3 𝑒 = 5 𝑓 = 6
b) 𝑎 = 1 𝑏 = 3 𝑐 = 5 𝑑 = 2 𝑒 = −3 𝑓 = 7
c) 𝑎 = 2 𝑏 = 3 𝑐 = 5 𝑑 = 1 𝑒 = 2 𝑓 = −5
[Link]
#include <iostream>
#include <iomanip>
double denom = a * d - b * c;
double x = (d * e - b * f) / denom;
double y = (a * f - c * e) / denom;
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
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
Wir implementieren die Fakultätsfunktion, wie obig definiert, mit einem Unterprogramm für 𝑀 ′ :
fac_recursive.m
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
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)!
Eingabe
Seite 1 von 4
Aufgabe 6 Programmieren I für Studierende der Mathematik — SoSe 2026
√
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>
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);
}
return res;
}
int main() {
cout << ”x: ”;
double x;
cin >> x;
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
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
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>
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;
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
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.
𝑎𝑖 + 𝑏 𝑖 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.
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>
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;
}
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 = ” << fixed << setprecision(4) << a0 << endl
<< ”b0 = ” << fixed << setprecision(4) << 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
Seite 3 von 8
A. Arithm.-Geom. Mittel Programmieren I für Studierende der Mathematik — SoSe 2026
Anhang
A. Arithm.-Geom. Mittel
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
| {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 )
𝐼 (𝑎 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 )
𝐽 (𝑎 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.
Seite 5 von 8
A. Arithm.-Geom. Mittel Programmieren I für Studierende der Mathematik — SoSe 2026
Mit 𝐼 (𝑎𝑛 , 𝑏𝑛 ) = 𝐼 (𝑎 0, 𝑏 0 ):
2𝑛+1 𝐽 (𝑎𝑛+1, 𝑏𝑛+1 ) − 2𝑛+1𝑎𝑛+1
2
𝐼 (𝑎𝑛+1, 𝑏𝑛+1 ) − 2𝑛 𝐽 (𝑎𝑛 , 𝑏𝑛 ) − 2𝑛 𝑎𝑛2 𝐼 (𝑎𝑛 , 𝑏𝑛 ) = 2𝑛−1 (𝑎𝑛2 − 𝑏𝑛2 )𝐼 (𝑎 0, 𝑏 0 )
Õ∞
− 𝐽 (𝑎 0, 𝑏 0 ) − 𝑎 20 𝐼 (𝑎 0, 𝑏 0 ) = 2𝑛−1 (𝑎𝑛2 − 𝑏𝑛2 )𝐼 (𝑎 0, 𝑏 0 )
𝑛=0
Damit:
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
|𝐿(𝑎𝑛 , 𝑏𝑛 )| ≤ 𝐼 (𝑎𝑛 , 𝑏𝑛 ) = 𝐼 (𝑎 0, 𝑏 0 )
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
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:
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.
𝑎 𝑏
fa7 a8bd
ffff 10001
777777 888
a, b: ffff 10001
a: 00000000000000001111111111111111
b: 00000000000000010000000000000001
a * b: 11111111111111111111111111111111
= ffffffff
= 4294967295
[Link]
#include <iostream>
#include <iomanip>
#include <limits>
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;
}
while (b != 0) {
if (b & 1)
res = add(res, a);
b >>= 1;
a <<= 1;
}
return res;
}
int main() {
unsigned int a, b;
cout << ”a, b: ”;
cin >> hex >> a >> b;
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
∑
𝑡 −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)
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
Seite 5 von 5
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen
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.
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>
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++;
{
int h = errno;
double res = log(a);
errno = h;
cout << ”log(a) = ” << setw(d + 7) << res << endl;
}
return res;
}
int main() {
double a;
errno = 0;
double myres = mylog(a);
int myerrno = errno;
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
Es sei 𝑎 ∈ R+ . Es ist:
d ℎ d ln(𝑎) ·ℎ
𝑎 = 𝑒
dℎ ℎ=0 dℎ ℎ=0
= ln(𝑎) · 𝑒 ln(𝑎) ·ℎ
ℎ=0
ℎ
= ln(𝑎) · 𝑎
ℎ=0
= ln(𝑎)
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
𝑛→∞
√
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
Hinweis (Binomialkoeffizient). In der Vorlesung hatten Sie folgende Funktion zur Berechnung des Binomial-
koeffizienten gesehen:
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:
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 res = 1;
return res;
}
if (n != 0) {
errno = EDOM;
return 0;
}
return res;
}
[Link]
#include <iostream>
#include <iomanip>
#include <cerrno>
#include <cstring>
#include <vector>
#include <limits>
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
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>
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;
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);
}
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
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
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
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;
vigenere_test.cpp
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <locale>
conv
string input;
cout << ”Klartext: ”;
{ char c; cin >> c; input += c; }
cin >> noskipws;
for (char c; cin >> c; ) input += c;
input = canon_clear(input);
Seite 3 von 5
Aufgabe 12 Programmieren I für Studierende der Mathematik — SoSe 2026
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>
conv
int main() {
string input;
cout << ”Geheimtext: ”;
cin >> noskipws;
for (char c; cin >> c; ) input += c;
input = canon_clear(input);
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
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 =:
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:
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>
string b64{”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/”};
int main() {
string fname_in, fname_out;
cout << ”Eingabedatei: ”; getline(cin, fname_in);
cout << ”Ausgabedatei: ”; getline(cin, fname_out);
if (nread == 0) break;
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>
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
if (nread != 4) break;
return nread == 0 ? 0 : 1;
}
Seite 4 von 4
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen
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:
𝑥0 = 1
𝑥𝑘
𝑥𝑘+1 = (3 − 𝑏𝑥𝑘2 )
2
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.
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
[Link]
#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>
#include <cerrno>
#include <cstring>
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());
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;
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 · · · 𝛼
Seite 4 von 4
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen
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⟩ → . . .
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.
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>
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);
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;
}
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;
}
char z = [Link]();
if (z == '^') {
[Link](z);
exponential = pow(exponential, Operand(ein));
}
return exponential;
}
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 += '(';
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;
}
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;
}
Seite 5 von 6
Aufgabe 15 Programmieren I für Studierende der Mathematik — SoSe 2026
exit(1);
}
Arithm. Ausdruck: 17
Ergebnis: 17
Seite 6 von 6
Ludwig-Maximilians-Universität München SoSe 2026
Mathematisches Institut
Gregor Kleen
Aufgabe 1 2 3 4 5 Σ
Punkte 8 6 14 26 42 96
#include <iostream>
#include <iomanip>
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;
}
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.
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.
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.
(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<bool> inp;
while (true) {
vector<bool>::size_type end = 0;
if (end == 0) {
char z;
if ()
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;
}
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.
Seite 6 von 6