Sie sind auf Seite 1von 502

Elias Posoukidis

Klassische
Mechanik mit C++
Basics und Anwendungen
Klassische Mechanik mit C++
Elias Posoukidis

Klassische
Mechanik mit C++
Basics und Anwendungen
Elias Posoukidis
Essen, Deutschland

Ergänzendes Material zu diesem Buch finden Sie auf http://www.springer.com/978-3-662-60904-0.

ISBN 978-3-662-60904-0 ISBN 978-3-662-60905-7 (eBook)


https://doi.org/10.1007/978-3-662-60905-7

Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detail-
lierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Jede Verwertung, die nicht
ausdrücklich vom Urheberrechtsgesetz zugelassen ist, bedarf der vorherigen Zustimmung des Verlags.
Das gilt insbesondere für Vervielfältigungen, Bearbeitungen, Übersetzungen, Mikroverfilmungen und die
Einspeicherung und Verarbeitung in elektronischen Systemen.
Die Wiedergabe von allgemein beschreibenden Bezeichnungen, Marken, Unternehmensnamen etc. in diesem
Werk bedeutet nicht, dass diese frei durch jedermann benutzt werden dürfen. Die Berechtigung zur Benutzung
unterliegt, auch ohne gesonderten Hinweis hierzu, den Regeln des Markenrechts. Die Rechte des jeweiligen
Zeicheninhabers sind zu beachten.
Der Verlag, die Autoren und die Herausgeber gehen davon aus, dass die Angaben und Informationen in
diesem Werk zum Zeitpunkt der Veröffentlichung vollständig und korrekt sind. Weder der Verlag, noch
die Autoren oder die Herausgeber übernehmen, ausdrücklich oder implizit, Gewähr für den Inhalt des
Werkes, etwaige Fehler oder Äußerungen. Der Verlag bleibt im Hinblick auf geografische Zuordnungen und
Gebietsbezeichnungen in veröffentlichten Karten und Institutionsadressen neutral.

Planung/Lektorat: Lisa Edelhäuser


Springer Spektrum ist ein Imprint der eingetragenen Gesellschaft Springer-Verlag GmbH, DE und ist ein Teil
von Springer Nature.
Die Anschrift der Gesellschaft ist: Heidelberger Platz 3, 14197 Berlin, Germany
Vorwort
Möchte man ein physikalisches Problem quantitativ beschreiben, bedeutet dies, seine
charakteristischen Eigenschaften zu identifizieren und diese als Parameter von Gleichun-
gen in einem mathematischen Modell darzustellen. Ist eine analytische Lösung der Glei-
chungen nicht möglich, werden numerische Methoden eingesetzt und Näherungslösun-
gen berechnet. Wie gut diese Lösungen bzw. das Modell sind, hängt davon ab, wie gut
die Vorhersagen des Modells mit den experimentellen Ergebnissen übereinstimmen. Ein
Computermodell ist die Abbildung des mathematischen Modells mithilfe von Computer-
programmen.
Betrachtet man Physikaufgaben als Beschreibungen von stark idealisierten physikali-
schen Prozessen, tragen sie dazu bei, ein Gespür dafür zu entwickeln, welche Parameter
für die Modellierung eines Prozesses relevant sind, was Voraussetzung für die Lösung
eines Problems ist. Sie bilden deshalb eine ideale Basis, um die Konstruktion von Model-
len zu üben. Ein Lehrbuch über Computerphysik muss Methoden vorstellen, die zeigen,
wie Computeralgorithmen zur Lösung physikalischer Probleme entworfen werden. Hier
gibt es zwei Alternativen bezüglich der Themenauswahl: Eine ist, aus dem Spektrum
der Aufgaben diejenigen herauszusuchen, für die keine analytische Lösung existiert oder
bekannt ist und diese dann mittels numerischer Methoden zu lösen. Eine andere ist,
Aufgaben, so wie sie in den meisten Lehrbüchern zu finden sind, zu nehmen und diese
neben den analytischen auch mit numerischen Methoden zu untersuchen. An dieser Stelle
setzt dieses Buch an. Es zeigt, wie aus analytisch hergeleiteten Formeln ein Computer-
modell entworfen wird und wie neben den analytischen Lösungen der Gleichungen auch
mittels numerischer Verfahren Daten generiert werden. Jede der behandelten Aufgaben
wird detailliert gelöst. Dies soll Zeit und Nerven sparen und, was noch wichtiger ist, den
Übergang zum Computermodell durch die Gegenüberstellung von Lösung und Quelltext
verständlich machen. Die Auswahl der Aufgaben wurde so getroffen, dass ein gewisser
Grad an Komplexität vorhanden ist, sodass die dazugehörigen Algorithmen nicht mit
wenigen Zeilen umzusetzen sind. Am Ende soll der Leser in der Lage sein, das Erlernte
in eigene Arbeiten einfließen zu lassen.
Der vorliegende erste Band ist in drei Teile gegliedert und beginnt mit einer Tour durch
C++. Auf eine allgemeine Einführung in C++ wurde wegen des Umfangs der Sprache
verzichtet. Stattdessen werden anhand von Beispielen ausgewählte Elemente der Sprache
vorgestellt, welche für das Verständnis der Programme im weiteren Verlauf des Buches
wichtig sind. Jedes Kapitel wird mit Beispielen aus der Mathematik oder Physik begleitet.
Der zweite Teil des Buches befasst sich mit numerischen Algorithmen. Hier wurde der
Versuchung widerstanden, die Algorithmen selbst zu programmieren, denn dies hätte eine
weitere Hürde für den Leser bedeutet und die Einarbeitungszeit verlängert. Statt dessen
wird die GNU Scientific Library (in diesem Buch auch gsl abgekürzt) eingesetzt. Es
handelt sich hierbei um eine in C geschriebene Bibliothek numerischer Algorithmen, die
fast alles auf diesem Gebiet abdeckt. Die Verwandtschaft von C++ und C wird genutzt und
gezeigt, wie durch das Kapseln der C-Funktionen in C++-Klassen das Programmieren mit
vi Vorwort

der Bibliothek vereinfacht wird. Nicht die ganze Funktionalität der gsl wird abgedeckt,
sondern nur die Teile, die für das Lösen der Aufgaben des Buches notwendig sind. Der
Aufbau der Klassen orientiert sich an Beispielen, die auf der Webseite der GNU Scientific
Library zu finden sind. Am Anfang von jedem Kapitel wird gezeigt, wie der Übergang von
einem solchen Beispiel zu einer C++-Klasse durchgeführt wird. Am Ende sind Beispiele
aus der Physik zu finden. Der dritte und letzte Teil baut auf das Wissen der beiden ersten
Teile auf und zeigt, wie Computermodelle auf Basis von Physikaufgaben konstruiert
werden, was im zweiten Band mit weiteren Beispielen fortgesetzt wird.
Die Entscheidung modernes C++ einzusetzen folgt nicht dem derzeitigen Trend, Pro-
gramme mit Skript-Sprachen zu erstellen. C++ wurde und wird immer noch als schwer
zu erlernen betrachtet. Die Sprache hat sich in den letzten Jahren jedoch gewandelt.
Ideen aus anderen Programmiersprachen sind in die neuen Versionen eingeflossen und
das Programmieren mit C++ ist einfacher geworden. Die Länge der Quelltexte ist deut-
lich kürzer geworden und unterscheidet sich nicht wesentlich vom Umfang der Quelltex-
te anderer Sprachen, wenn diese nicht nur zur Demonstration eines Konzepts gedacht
sind. Besonderer Wert wurde darauf gelegt, dass die Programme in diesem Buch nicht
monolithisch aufgebaut sind, sondern aus wiederverwendbaren Komponenten bestehen.
Gleichzeitig wurde auf tiefe Vererbungshierarchien verzichtet, um das Verständnis der
Quelltexte zu vereinfachen. Die Quelltexte sind ausführlich kommentiert und werden in
kleinen Abschnitten im Text eingebunden. Sie sollen genau wie mathematische Formeln
als Fortsetzung des Textes gesehen werden. Die Kommentare in den Quelltexten ergänzen
den laufenden Text.
Das Buch richtet sich an Studierende der Physik und verwandter naturwissenschaftli-
cher Fächer, die bereits einen Grundkurs in klassischer Mechanik absolviert haben und
mit der Differenzial- und Integralrechnung vertraut sind. Der Leser sollte über Grund-
kenntnisse in C++ verfügen oder bereit sein, sich diese anzueignen.
Für die Übersetzung der Quelltexte ist ein C++-Compiler erforderlich, der den Stan-
dard C++17 unterstützt. Als IDE wurde der Qt Creator und als Compiler clang
in der Version 6.0 unter Linux Mint 19 eingesetzt. Alle Quelltexte stehen unter
www.springer.com/978-3-662-60904-0 zum Download bereit. Dieser Text wurde mit LATEX
und dem TeXstudio-Editor erstellt. Obwohl dieses Buch mit höchster Sorgfalt geschrie-
ben wurde, kann es trotzdem Fehler enthalten. Verbesserungsvorschläge, Bemerkungen
und Hinweise sind stets willkommen.
Dem Springer Verlag und insbesondere Frau Dr. Lisa Edelhäuser und Frau Anja
Dochnal möchte ich für die freundliche Unterstützung danken. Mein besonderer Dank
gilt meiner Familie, die während der Entstehung dieses Buches große Geduld bewiesen
hat.

Dezember 2019, Elias Posoukidis


Inhaltsverzeichnis
Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v

I C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1 Eine Tour durch C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Standardein- und -ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Fehlerbehandlung durch Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4 STL-Container und Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.5 Teilaufgaben mit Funktionen lösen . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.6 λ-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.7 Rechnen mit komplexen Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.8 Benutzerdefinierte Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.9 Statische Variablen und Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . 34
1.10 Präprozessor-Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
1.11 Schreiben und Lesen von Daten in Dateien . . . . . . . . . . . . . . . . . . 47
1.12 Aufzählungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
1.13 Zeichenketten in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
1.14 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
2 Klassen in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.2 Einfache Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.3 Klassentemplates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
2.4 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
2.5 Programmieren mit Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . 111
2.6 Schnittstellen für Klassen mithilfe von Templates definieren . . . . 119
2.7 Eigene Werkzeuge bauen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
2.8 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
3 Tabellen und Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
3.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
3.2 Eine Klasse für Zahlentabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
3.3 Eine Klasse für Sichten auf Tabellen . . . . . . . . . . . . . . . . . . . . . . . . 152
3.4 Erstellen von Views auf Zahlentabellen . . . . . . . . . . . . . . . . . . . . . . 157
3.5 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
3.6 Beispiele aus der Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
3.7 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
viii Inhaltsverzeichnis

4 Klassen zur Modellierung von physikalischen Systemen . 177


4.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
4.2 Eine Basisklasse für Computermodelle . . . . . . . . . . . . . . . . . . . . . . 178
4.3 Eine Komponente zur Speicherung von Rechenergebnissen . . . . . 180
4.4 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
4.5 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
II Schnittstellen zur GNU Scientific Library . . . . . . . . . . . . . . 195
5 Die Vektor-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
5.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
5.2 Vom gsl-Programm zur Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
5.3 Entwurf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
5.4 Kommunikation zwischen gsl und Komponenten . . . . . . . . . . . . . . 201
5.5 Komponente zum Lesen und Schreiben von Vektoren . . . . . . . . . . 204
5.6 Komponente zum Rechnen mit Vektoren . . . . . . . . . . . . . . . . . . . . 206
5.7 Schnittstellen zur stl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
5.8 Minima und Maxima . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
5.9 Eigenschaften von Vektoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
5.10 BLAS-Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
5.11 Sicht auf einen Vektor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
5.12 Die Vektor-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
5.13 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
5.14 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
6 Die Matrix-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
6.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
6.2 Ein C-ähnliches Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
6.3 Basisfunktionalität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
6.4 Rechnen mit Matrizen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
6.5 Minimales und maximales Element einer Matrix . . . . . . . . . . . . . . 252
6.6 Austausch von Reihen und Spalten . . . . . . . . . . . . . . . . . . . . . . . . . 254
6.7 Sichten und Kopien von Reihen und Spalten . . . . . . . . . . . . . . . . . 255
6.8 Schnittstelle zur BLAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
6.9 Eigenschaften von Matrizen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
6.10 Die Matrix-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
6.11 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
6.12 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
Inhaltsverzeichnis ix

7 Lösung von linearen Gleichungssystemen . . . . . . . . . . . . . . . 285


7.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
7.2 LU-Zerlegung mit der GNU Scientific Library . . . . . . . . . . . . . . . . 287
7.3 Entwurf einer C++-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
7.4 Beispielanwendungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
7.5 Beispiele aus der Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
7.6 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
8 Nullstellenberechnung reeller Funktionen . . . . . . . . . . . . . . . 321
8.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
8.2 Zwei Beispielprogramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
8.3 Eine Klasse für das Bisektionsverfahren . . . . . . . . . . . . . . . . . . . . . 327
8.4 Eine Klasse für das Newton-Verfahren . . . . . . . . . . . . . . . . . . . . . . 331
8.5 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
8.6 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
9 Numerische Integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
9.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
9.2 Numerische Integration mit der GNU Scientific Library . . . . . . . . 342
9.3 Klassen als Schnittstellen zur GNU Scientific Library . . . . . . . . . 346
9.4 Berechnung von Beispielintegralen . . . . . . . . . . . . . . . . . . . . . . . . . . 353
9.5 Beispiele aus der Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
9.6 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
10 Anfangswertprobleme für gewöhnliche Differenzialglei-
chungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
10.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
10.2 Ein gsl-Programm zur numerischen Lösung einer Differenzial-
gleichung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
10.3 Die Schnittstelle zur GNU Scientific Library . . . . . . . . . . . . . . . . . 382
10.4 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
10.5 Beispiele aus der Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
10.6 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
11 Interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
11.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
11.2 Interpolation mit der GNU Scientific Library . . . . . . . . . . . . . . . . . 414
11.3 Die Schnittstelle zur GNU Scientific Library . . . . . . . . . . . . . . . . . 416
11.4 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
11.5 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
x Inhaltsverzeichnis

III Klassische Mechanik mit C++ . . . . . . . . . . . . . . . . . . . . . . . . . . 425


12 Eindimensionale Bewegungen . . . . . . . . . . . . . . . . . . . . . . . . . . 427
12.1 Geradlinige Bewegung mit konstanter äußerer Kraft und Gleit-
reibung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
12.2 Teilchen bewegt sich unter dem Einfluss einer ortsabhängigen
Kraft . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434
12.3 Teilchen bewegt sich unter dem Einfluss einer zeitabhängigen
Kraft . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
12.4 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446
13 Bewegung in einem Medium mit Reibung . . . . . . . . . . . . . . 449
13.1 Bewegung mit Reibung nach Stokes . . . . . . . . . . . . . . . . . . . . . . . . . 449
13.2 Bewegung mit Reibung nach Newton . . . . . . . . . . . . . . . . . . . . . . . 456
13.3 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464
14 Schwingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
14.1 Harmonische Schwingung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
14.2 Schwingung in vertikaler Richtung eines Systems aus zwei Massen475
14.3 Harmonische Schwingungen mit Dämpfung . . . . . . . . . . . . . . . . . . 482
14.4 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496
Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
Teil I

C++
1 Eine Tour durch C++

Übersicht
1.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Standardein- und -ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Fehlerbehandlung durch Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4 STL-Container und Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.5 Teilaufgaben mit Funktionen lösen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.6 λ-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.7 Rechnen mit komplexen Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.8 Benutzerdefinierte Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.9 Statische Variablen und Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
1.10 Präprozessor-Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
1.11 Schreiben und Lesen von Daten in Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
1.12 Aufzählungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
1.13 Zeichenketten in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
1.14 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

1.1 Einleitung
Eine so mächtige Programmiersprache wie C++ in wenigen Seiten vorzustellen, ist unmög-
lich. Das Angebot an Funktionen und Programmiermethoden ist so groß, dass zwangs-
läufig eine Auswahl stattfinden muss. Doch welche Teile der Sprache sind wichtig und
welche nicht? Eine eindeutige Antwort auf diese Frage gibt es nicht, denn die Sprache
wird von einem Systemprogrammierer anders als von einem Naturwissenschaftler und
nochmal anders von einem Spielentwickler genutzt. Es macht deswegen Sinn, sich auf
Themen zu konzentrieren, die für eigene Projekte nützlich sind, was Gegenstand die-
ses Kapitels ist. Ziel des Buchs ist, mithilfe von C++ eine Sammlung von Komponenten
zu programmieren, welche die Erstellung von physikalischen Modellen vereinfacht. Dies
ist dann erreicht, wenn z.B. die Lösung eines linearen Gleichungssystems mit 3-4 Pro-
grammzeilen implementiert ist. Idealerweise entsteht am Ende keine lose Sammlung von
Komponenten, sondern nur solche die Wiederholungen von Quelltexten minimieren. Wir
wollen dabei nicht das Rad neu erfinden, sondern nur die eigene Kreativität in den Auf-
bau von Programmen einfließen lassen. (Seit C++17 können Funktionen, Klassen usw.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_1
4 1 Eine Tour durch C++

direkt in Header-Dateien geschrieben werden, ohne die One Definition Rule zu verletzen,
wovon wir Gebrauch machen werden. Der Begriff Programm wird oft als Synonym für
eine Funktion gebraucht, wenn diese durch Kompilierung eine ausführbare Datei erzeu-
gen kann. Die Verwendung von englischen Begriffen in der Computerliteratur hat sich
so durchgesetzt, das es sich teilweise nicht vermeiden lässt, bestimmte Wörter im Text
einfließen zu lassen, wie z.B. Compiler an Stelle von Übersetzer oder View für Sicht
usw.)

1.2 Standardein- und -ausgabe


Eine typische Aufgabe von Computerprogrammen ist, Daten über die Tastatur einzule-
sen, diese zu verarbeiten und anschließend auf dem Bildschirm anzuzeigen. Die Ein- und
Ausgabe der Daten basiert auf der Idee, eine Folge von Bytes von einem Eingabemedium,
hier die Tastatur, in einem Ausgabemedium wie den Bildschirm «fließen» zu lassen. In
C++ wird dies mit Elementen der iostream-Bibliothek umgesetzt. Die Kombination aus
std::cout, einem globalen Objekt dieser Bibliothek, und des überladenen Operators <<
erlaubt es, Daten auf dem Bildschirm anzuzeigen, wobei std::cout für die Ausgabe auf
dem Bildschirm zuständig ist und der Operator << die Daten an dieses Objekt weiterlei-
tet. Das Einlesen der Daten über die Tastatur erfolgt mit dem globalen Objekt std::cin,
das mittels des überladenen Operators >> Daten an Variablen weiterleitet.

1.2.1 Beispielanwendungen

Ausgabe von Zahlen auf dem Bildschirm

Wir wollen in diesem Abschnitt mit einem Programm beginnen, welches statt eines Hello
World Zahlen und ihre Quadrate in die Standardausgabe schreibt. Wir durchlaufen in
einer Schleife die Werte von -2 bis 2 und geben diese und ihre Quadrate in jeweils einer
neuen Zeile aus. Die Breite jeder Ausgabe wird über std::setw festgelegt.

/**
* @brief ex1 Zahlen und ihre Quadrate werden in die Standardausgabe
* geschrieben
*/
inline void ex1 () {
for ( double x = -2; x <= 2; x++) {
// setze mit std :: setw die Breite der Ausgabe
std :: cout << std :: setw (2) << x //
<< std :: setw (5) << std :: pow(x, 2) //
<< std :: endl;
}
}

Listing 1.1 cppsrc/apps-x001/hpp/x001–01


1.2 Standardein- und -ausgabe 5

Die Funktion produziert folgendes Ergebnis:

-2 4
-1 1
0 0
1 1
2 4

Listing 1.2

Berechnung der Hypotenuse eines rechtwinkligen Dreiecks, Version 1.0

Mit einem interaktiven Programm soll die Hypotenuse eines rechtwinkligen Dreiecks,
dessen Katheten der Benutzer eingibt, berechnet werden.

Theorie Wir werden zur Berechnung der Hypotenuse den Satz vom Pythagoras c =

x2 + y 2 anwenden.

Quelltext und Daten Zur Ein- und Ausgabe der Daten werden wir die iostream-
Bibliothek einsetzen. Die Deklaration der zwei double-Variablen hat zur Folge, dass
std::cin die Eingabe von Zahlen erwartet. Nach der Eingabe wird die Hypotenuse be-
rechnet und ausgegeben.

/**
* @brief ex2 Berechnung der Hypotenuse eines rechtwinkligen Dreiecks
* Version 1.0
*/
inline void ex2 () {
double x, y;
// erste Kathete
std :: cout << " input x=?\t";
std :: cin >> x;
// zweite Kathete
std :: cout << " input y=?\t";
std :: cin >> y;
// Berechnung und Ausgabe der Hypotenuse
std :: cout << " output :" << sqrt(std :: pow(x, 2) + std :: pow(y, 2));
}

Listing 1.3 cppsrc/apps-x001/hpp/x001–02

Die Funktion wurde erfolgreich implementiert, wie folgende Ausgabe zeigt.

input x=? 3
input y=? 4
output :5

Listing 1.4

Wir stellen jedoch fest, dass die Implementierung einige Schwachstellen aufweist. Eine
wäre, dass das Programm keine Möglichkeit hat, auf falsche Eingaben seitens des Be-
6 1 Eine Tour durch C++

nutzers zu reagieren. Wird statt einer Zahl eine Zeichenkette eingegeben, werden die
nachfolgenden Eingaben nicht ausgeführt und ihre Werte auf 0 gesetzt. Das Programm
würde mit der Anzeige eines falschen Ergebnisses beendet werden.

input x=? 4
input y=? y
output :4

Listing 1.5

Der Benutzer könnte für x oder y einen negativen Wert eingeben. Das Programm würde
die Rechnung zu Ende führen. Die Ergebnisse wären mathematisch korrekt, obwohl die
Eingaben des Benutzers keinen Sinn machen.

input x=? -3
input y=? 4
output :5

Listing 1.6

Die Software muss in der Lage sein, Benutzereingaben bezüglich ihrer Gültigkeit zu
testen. Wir werden in den kommenden Abschnitten das Programm überarbeiten und
diese Schwachstellen beheben.

Bewegungsdaten einer geradlinigen Bewegung mit konstanter


Geschwindigkeit

Wir fahren mit einem Beispiel aus der Physik fort und betrachten ein Teilchen, das sich
geradlinig mit konstanter Geschwindigkeit v = 10 m/s bewegt. Zum Zeitpunkt t0 = 0
befindet es sich am Ort x0 = 0. Es soll der Ort als Funktion der Zeit für 0 ≤ t ≤ 5 s, in
Schritten ∆t = 1 s auf dem Bildschirm ausgegeben werden.

Theorie Die Formel für den Ort als Funktion der Zeit lautet

⃗x = (x0 + vt)⃗ex , (1.1)

wobei x0 = 0 und v = 10 m/s ist und der Einheitsvektor ⃗ex in Richtung der Bewegung
des Teilchens zeigt.

Quelltext und Daten Das Programm besteht aus einer Schleife, die den Ort x für die
Zeiten t durch Anwendung der Formel (Gl. 1.1) berechnet und auf dem Bildschirm mittels
std::cout ausgibt.

/**
* @brief ex3 Geradlinige Bewegung mit konstanter Geschwindigkeit
*/
inline void ex3 () {
// Ort und Geschwindigkeit zum Zeitpunkt t=0
const double x0 = 0, v0 = 10;
1.3 Fehlerbehandlung durch Exceptions 7

for ( double t = 0; t <= 5; t++) {


const double x = x0 + v0 * t;
std :: cout << "t=" << t << " s" << std :: setw (5) //
<< "x=" << x << " m" << std :: endl;
}
}

} // namespace nmx :: apps :: x001

Listing 1.7 cppsrc/apps-x001/hpp/x001–03

Nach Ausführung des Programms erhalten wir pro Reihe ein Wertepaar (t, x) mit den
dazugehörigen Einheiten.

t=0 s x=0 m
t=1 s x=10 m
t=2 s x=20 m
t=3 s x=30 m
t=4 s x=40 m
t=5 s x=50 m

Listing 1.8

1.3 Fehlerbehandlung durch Exceptions


Eines der größten Probleme in der Softwareentwicklung sind Fehler, die sich während
des Programmierens in den Quelltext einschleichen können. Neben den syntaktischen
Fehlern, die beim Übersetzungsvorgang vom Compiler erkannt werden, existieren auch
logische Fehler. Es handelt sich hierbei um syntaktisch korrekte Anweisungen, die sich
jedoch nicht mit dem vom Entwickler vorgesehenen Verhalten decken. Solche Fehler müs-
sen nicht immer auftreten, sondern können vielleicht nur unter bestimmten Bedingungen
erscheinen. Die Idee ist, an Stellen im Quelltext, wo Fehler auftreten können, Überprü-
fungen einzubauen, um mögliches Fehlverhalten abzufangen und den Programmverlauf
zu beeinflussen. C++ verfügt mit der Ausnahmebehandlung (exception handling) über
einen cleveren Mechanismus, um genau das zu tun. Tritt ein Fehler auf oder verhält sich
ein Programm nicht wie gewünscht, kann mit einer throw-Anweisung der Programmfluss
unterbrochen werden und an den übergeordneten Block eine Ausnahme, die ein Objekt
eines beliebigen Datentyps sein kann, weitergereicht werden. Dies geschieht so lange,
bis die Ausnahme mittels einer geeigneten catch-Anweisung aufgefangen wird. Im Bei-
spiel Listing 1.9 sehen wir drei Funktionen. Eine Ausnahme wird in der Funktion f00
ausgeworfen. f00 wird von f01 innerhalb eines try-Blocks aufgerufen. Der dazugehöri-
ge catch-Block wird die Ausnahme nicht auffangen, da dieser nur auf Ausnahmen vom
Typ double reagiert. Die Ausnahme wird deswegen an den nächst übergeordneten Block
weitergereicht, der in diesem Fall f02 ist. Sie wird hier vom zuständigen catch-Block
aufgefangen, der eine Fehlermeldung ausgibt.
8 1 Eine Tour durch C++

/**
* @brief f00 try -catch - Beispiel
* @param x
*/
inline void f00(int x) {
// ....
if (x == 0) {
// Ausnahme vom Typ int wird ausgeworfen
throw 0;
}
// ....
}

// Die Ausnahme wird innerhalb dieser Funktion ausgelöst


// aber nicht aufgefangen
inline void f01 () {
// ...
try {
// ...
f00 (0);
// ...
} catch ( double w) {
std :: cout << " error double :" << w << std :: endl;
}
}

inline void f02 () {


// ...
try {
// ...
f01 ();
// ..
} catch (int n) { // Ausnahme wird hier aufgefangen
std :: cout << " error int:" << n << std :: endl;
} catch (...) {
std :: cout << " error " << std :: endl;
}
// ...
}

Listing 1.9 cppsrc/src–cppxbase/cppxbase–02.cpp

Durch diese Strategie wird eine Trennung von Fehlererkennung und Fehlerbehandlung
erreicht. Zusätzlich kann ein Programm durch die Möglichkeit, Objekte vom unter-
schiedlichem Typ auszuwerfen, gezielt auf Fehler reagieren. Dies wird durch eine Rei-
he von vordefinierten Ausnahmeobjekten, die alle von einer Basisklasse std::exception
abgeleitet werden, unterstützt. Wird einer Funktion ein falsches Eingabeargument
übergeben oder erfolgt bei einem interaktiven Programm eine falsche Eingabe sei-
tens des Benutzers, kann ein Programm mit dem Auswerfen einer Instanz der Klasse
std::invalid_argument reagieren. Ein Objekt vom Typ std::domain_error kann ausge-
worfen werden, wenn sich Daten außerhalb eines Definitionsbereichs befinden. Schließ-
lich erwähnen wir std::runtime_error. Diese Ausnahme kann ausgeworfen werden, wenn
während der Ausführung eines Programms unerwartet Fehler auftreten, die nicht einer
spezifischen Gruppe zugeordnet werden können.
1.3 Fehlerbehandlung durch Exceptions 9

1.3.1 Beispielanwendungen

Berechnung der Hypotenuse eines rechtwinkligen Dreiecks, Version 2.0

Das Beispielprogramm (Listing 1.3) soll weiterentwickelt werden, sodass es auf fehlerhafte
Benutzereingaben reagieren kann. Das Objekt std::cin erwartet in diesem Programm
immer die Eingabe von Zahlen. Dies haben wir durch die Deklaration der Variablen x
und y so festgelegt. In der neuen Version des Programms (Listing 1.10) testen wir mit
der Elementfunktion cin::fail(), ob die eingegebenen Werte tatsächlich vom erwarteten
Typ sind. Wenn nicht, werfen wir eine Ausnahme vom Typ std::invalid_argument aus.
Damit hätten wir den Fall der Eingabe eines Werts vom falschen Typ berücksichtigt.
Wir testen zusätzlich, ob die Eingabe auch logisch einen Sinn macht, indem wir negative
Zahlen und 0 ebenfalls als Fehler betrachten und darauf mit dem Auswerfen einer weiteren
Ausnahme vom Typ std::domain_error reagieren. Die Art des Fehlers wird anhand des
Ausnahmetyps identifiziert und es wird mit einer entsprechenden Fehlermeldung reagiert.

/**
* @brief ex1 Berechnung der Hypotenuse eines rechtwinkligen Dreiecks
* Version 2
*/
inline void ex1 () {
try {
double x = 0, y = 0;

// Eingabe der ersten Kathete


std :: cout << "x =?\t";
std :: cin >> x;

// prüfe erste Eingabe


if (std :: cin.fail ()) {
throw std :: invalid_argument ("x");
}
if (x <= 0) {
throw std :: domain_error ("x");
}

// Eingabe der zweiten Kathete


std :: cout << "y =?\t";
std :: cin >> y;

// prüfe zweite Eingabe


if (std :: cin.fail ()) {
throw std :: invalid_argument ("y");
}
if (x <= 0) {
throw std :: domain_error ("y");
}

// Berechnung der Hypotenuse mit Funktion aus der std - Bibliothek


std :: cout << " hypotenuse :" << std :: hypot(x, y) << std :: endl;
} catch (std :: invalid_argument & invarg ) {
// wenn die Eingabe keine Zahl ist
std :: cout << " invalid argument for:" << invarg .what () <<
std :: endl;
10 1 Eine Tour durch C++

} catch (std :: domain_error & domerr ) {


// wenn die Eingabe <= 0 ist
std :: cout << " input parameter must be >0:" << domerr .what () <<
std :: endl;
}
}

} // namespace nmx :: apps :: x025

Listing 1.10 cppsrc/apps-x025/hpp/x025–01

Es folgt die Reaktion des Programms, wenn statt einer positiven Zahl für die Länge der
zweiten Kathete eine negative Zahl eingegeben wird (Listing 1.11).

x=? 1
y=? -3
input parameter must be >0:y

Listing 1.11

Wird statt einer Zahl ein Buchstabe eingegeben, wird das Programm, mit der Anzeige
einer Fehlermeldung, beendet (Listing 1.12).

x=? 4
y=? a
invalid argument for:y

Listing 1.12

Das Programm ist besser geworden, denn es reagiert auf nicht vorgesehene Situationen
in einer geordneten Weise. Wir können uns aber mit dem Ergebnis nicht zufrieden geben,
denn identische Teile des Quelltexts wie z.B. die Eingabeüberprüfung erscheinen zweimal
innerhalb der Funktion. Zusätzlich fällt auf, dass das Programm nach Auftreten eines
Fehlers beendet wird. Der Benutzer muss es in diesem Fall neu starten, um ein Ergebnis
zu berechnen. In den nächsten Abschnitten werden wir diese Schwachstellen beheben.

1.4 STL-Container und Iteratoren


Die stl (Standard Template Library) ist Teil der C++-Standardbibliothek. Sie besteht
aus Datenstrukturen und Algorithmen. Datenstrukturen sind Container, die Objekte
verwalten (Hinzufügen, Entfernen usw.) und für die Bereitstellung und Freigabe des nö-
tigen Speichers sorgen. Algorithmen sind für die Verarbeitung der Daten von Containern
zuständig. Durch die Trennung von Algorithmen und Datenstrukturen verfolgt die stl ei-
nen Ansatz, der dem der objektorientierten Programmierung genau entgegengesetzt ist.
Grund ist das Schreiben derselben Algorithmen für jeden Containertyp zu vermeiden.
Die Kommunikation zwischen Algorithmen und Containern findet über Iteratoren statt
(Abb. 1.1).
1.4 STL-Container und Iteratoren 11

Algorithmen Iteratoren Datenstrukturen


z.B. std::max_element z.B. begin(),end() z.B. std::vector

std::vector v;
...
std::max_element(v.begin(),v.end())

Abb. 1.1: Die stl besteht aus Algorithmen und Datenstrukturen, die über Iteratoren
miteinander kommunizieren.

Iteratoren sind Objekte, die auf Elemente eines Containers zeigen und per Anweisung
sich von einem Element zu einem anderen innerhalb desselben Containers bewegen kön-
nen. Die Algorithmen der stl machen von ihnen massiv Gebrauch. Iteratoren machen
es möglich, entweder über alle oder über eine Teilmenge der Elemente eines Containers
einen Algorithmus anzuwenden. Der Bereich innerhalb dessen iteriert werden soll, wird
durch zwei Iteratoren festgelegt. Der erste Iterator zeigt auf das erste Element und der
zweite auf eine Position nach dem letzten Element des Bereichs. Nehmen wir als Beispiel
einen sequenziellen Container (Abb. 1.2). Der Anfang und das Ende des Containers wer-
den durch zwei feste, an die Instanz gebundene Iteratoren begin() und end() angegeben.
Der erste der beiden Iteratoren zeigt auf das erste Element des Containers, der zweite
aber auf eine Position nach dem letzten Element und somit auf eine ungültige Position.
Ein dritter Iterator durchläuft alle Elemente des Containers (Abb. 1.2).

... ... itr

auto itr = begin();


0 1 2 3 4 6 7 8 9
while( itr != end())
end() { fn(*itr); ++itr; }
begin()

Abb. 1.2: Ein Iterator (itr) durchläuft alle Elemente. Das Element, auf das der Iterator
zeigt, wird einer Funktion fn übergeben.

Alternativ kann durch zwei andere, nicht mit dem Container mitgelieferten Iteratoren,
ein Bereich festgelegt werden (Abb. 1.3). In beiden Fällen endet die Iteration eine Position
vor dem zweiten Iterator.
12 1 Eine Tour durch C++

... ... itr

auto itr = itr1;


0 1 2 3 4 6 7 8 9
while( itr != it2)
{ fn(*itr); ++itr; }
itr1 itr2
Abb. 1.3: Ein Iterator (itr) durchläuft die Elemente 2 bis einschließlich 7 eines Contai-
ners. Das Element auf das der Iterator zeigt, wird einer Funktion fn übergeben.

1.4.1 Beispiele

Speicherung von Daten in einem Container

Es soll eine Funktion geschrieben werden, die eine Zahlenfolge von x = 0, . . . 1 mit
∆x = 0.23 in einem Container speichert und anschließend die Werte auf dem Bildschirm
ausgibt.

Kurzbeschreibung Da wir nicht vorher die Anzahl der zu speichernden Elemente be-
rechnen wollen, wählen wir als Container einen std::vector, denn dieser stellt automa-
tisch den notwendigen Speicher zur Verfügung.

Quelltext und Daten Mit einer Schleife und der Elementfunktion push_back werden
die Zahlen immer am Ende des Containers hinzugefügt. Wir geben die gespeicherten
Elemente zweimal auf den Bildschirm aus: Einmal mithilfe von Iteratoren und einer for-
Schleife und dann mit einer Variante einer for-Schleife (range-based for-loop), die mit
einer kompakten Syntax dasselbe tut.

/**
* @brief ex0 Speicherung von Daten in einem Container
*/
inline void ex0 () {
// erzeuge leeren Vektor
std :: vector <double > v;

// fülle Vektor
for ( double x = 0; x < 1; x += 0.23) {
v. push_back (x);
}

// gebe Elemente mittels Iteratoren


// des Vektors auf dem Bildschirm aus
for (auto itr = v. begin (); itr != v.end (); itr ++) {
std :: cout << *itr << ",";
}
std :: cout << std :: endl;

// gebe Elemente mit range -based for -loop


// auf dem Bildschirm aus
for (const auto &x : v) {
1.4 STL-Container und Iteratoren 13

std :: cout << x << ",";


}

std :: cout << std :: endl;


}

Listing 1.13 cppsrc/apps-x031/hpp/x031–01

0 ,0.23 ,0.46 ,0.69 ,0.92 ,


0 ,0.23 ,0.46 ,0.69 ,0.92 ,

Listing 1.14

Anwendung von Algorithmen auf einen Container

Das Füllen und Kopieren von std::vector-Containern sowie die Anwendung einer Reihe
von Algorithmen soll mit diesem Programm demonstriert werden. Die Iteratoren durch-
laufen immer alle Elemente des Containers. Zur Ausgabe auf dem Bildschirm wird ein
std::ostream_iterator eingesetzt. Dieser benutzt den <<-Operator, um die Elemente der
Container an einem Ausgabestrom weiterzuleiten.

/**
* @brief ex1 Anwendung von Algorithmen auf einen Container
*/
inline void ex1 () {
// Speicher wird reserviert
std :: vector <double > v1 (9);

// fülle Vektor mit Werten von -4 ,...4


std :: iota(v1. begin () , v1.end () , -4);
// gebe die Werte aus
for (const auto &x : v1) {
std :: cout << x << ",";
}
std :: cout << std :: endl;

// kopiere Vektor
auto v2 = v1;

// drehe die Reihenfolge um


std :: reverse (v2. begin () , v2.end ());

// gebe Elemente von v1 und v2 auf dem Bildschirm aus


auto outitr = std :: ostream_iterator <double >( std :: cout , ",");
std :: copy(v1. begin () , v1.end () , outitr );
std :: cout << std :: endl;
std :: copy(v2. begin () , v2.end () , outitr );
std :: cout << std :: endl;

// addiere alle Elemente des Vektors mit Startwert 0


auto val = std :: accumulate (v1. begin (), v1.end (), 0);
std :: cout << val << std :: endl;
14 1 Eine Tour durch C++

// Skalarprodukt v1 * v2
auto val1 = std :: inner_product (v1. begin (), v1.end (), v2.begin (), 0);
std :: cout << val1 << std :: endl;
}

} // namespace nmx :: apps :: x031

Listing 1.15 cppsrc/apps-x031/hpp/x031–02

-4,-3,-2,-1,0,1,2,3,4,
-4,-3,-2,-1,0,1,2,3,4,
4,3,2,1,0,-1,-2,-3,-4,
0
-60

Listing 1.16

1.5 Teilaufgaben mit Funktionen lösen


Mit wachsenden Anforderungen wächst auch der Quelltext eines Programms mit dem
Ergebnis, dass er sehr schnell unübersichtlich werden kann. Immer wieder benötigte Ar-
beitsvorgänge sollten in Form von Funktionen nur einmal programmiert und immer wie-
der benutzt werden. Eine Funktion ist ein Unterprogramm, welches für eine Anzahl von
Eingabeparametern einen Rückgabewert liefert. Ein Argument, dass für diese Arbeitsauf-
teilung spricht, ist nicht nur, dass der Quelltext kürzer und übersichtlicher wird, sondern
dass bei notwendigen Änderungen nur an einer statt an mehreren Stellen im Quelltext
eingegriffen werden muss.

1.5.1 Beispielanwendungen

Berechnung der Hypotenuse eines rechtwinkligen Dreiecks, Version 3.0

Wir entwickeln das Programm aus Abschnitt 1.3.1 (Listing 1.10) weiter mit dem Ziel,
es modularer zu gestalten. Teile des Quelltextes werden in eine Funktion (Listing 1.17)
ausgelagert, die ab jetzt für die Benutzereingaben zuständig sein wird.

/**
* @brief read_input lese eine double -Zahl von der Standardeingabe
* @param c Name der aktuellen Variablen
* @return die eingelesene Variable
*/
inline double read_input ( const char *c) {
double x;
std :: cout << c << "=?\t";
std :: cin >> x;
1.5 Teilaufgaben mit Funktionen lösen 15

// x ist double , std :: cin erwartet double


if (std :: cin.fail ()) {
throw std :: invalid_argument (c);
}
// die Seite eines Dreiecks muss immer eine positive Zahl sein
if (x <= 0) {
throw std :: domain_error (c);
}
return x;
};

Listing 1.17 cppsrc/apps-x004/hpp/x004–01

Bei jeder Eingabe seitens des Benutzers wird getestet, ob diese eine positive Zahl war.
Die neue Funktion wird, wie im nächsten Quelltextabschnitt zu sehen ist, innerhalb
eines try-Blocks aufgerufen, der zusammen mit den catch-Blöcken in eine while-Schleife
eingebettet ist. Im Fall einer fehlerhaften Eingabe wird eine Ausnahme ausgeworfen,
von einem catch-Block aufgefangen und eine Fehlermeldung ausgegeben. Der Benutzer
wird jedes Mal aufgefordert, durch die Eingabe eines Befehls das Programm entweder zu
beenden oder eine neue Berechnung zu starten.

/**
* @brief ex1 Berechnung der Hypotenuse eines rechtwinkligen Dreiecks ,
* Version 3.0 ( Benutzerschnittstelle )
*/
inline void ex1 () {
constexpr auto maxstreamsize =
std :: numeric_limits <std :: streamsize >:: max ();
char cmd = 'y';

// rechne bis der Benutzer per Befehl das Programm beendet


while (cmd == 'y') {
try {
// Benutzereingabe : zwei senkrechte Seiten
double x = read_input ("x");
double y = read_input ("y");
// Ausgabe
std :: cout << " hypotenuse :" << std :: hypot (x, y) << std :: endl;
} catch (std :: invalid_argument & invarg ) {
// Buchstabe statt einer Zahl wurde eingegeben
std :: cin. clear ();
// ignoriere alle restlichen Eingaben
std :: cin. ignore ( maxstreamsize , '\n');
std :: cout << " invalid argument for:" //
<< invarg .what () << std :: endl;
} catch (std :: domain_error & domerr ) {
// negative Zahl wurde eingegeben
std :: cin. clear ();
// ignoriere alle restlichen Eingaben
std :: cin. ignore ( maxstreamsize , '\n');
std :: cout << " input parameter must be >0:" //
<< domerr .what () << std :: endl;
}

// Programm beenden ?
do {
16 1 Eine Tour durch C++

std :: cout << "new claculation ? y[yes] / n[no]" << std :: endl;
std :: cin >> cmd;
} while (cmd != 'y' && cmd != 'n');
}
}

Listing 1.18 cppsrc/apps-x004/hpp/x004–02

Auto bremst, um einem Hindernis ausweichen

Wir wollen anhand eines Beispiels aus der Physik die Aufteilung zwischen Programm-
logik und Darstellung der Daten demonstrieren. Dies ist sinnvoll, denn die Berechnung
von Daten und ihre Präsentation, sind zwei voneinander unabhängige Aufgaben. Die
Ergebnisse könnten in einem Terminal oder in einer grafischen Benutzeroberfläche ange-
zeigt werden. In beiden Fällen würde aber die Berechnung der Ergebnisse durch dasselbe
Unterprogramm erfolgen.
Ein Auto fährt mit einer konstanten Geschwindigkeit v0 = 50 km/h auf gerader Straße.
Der Fahrer erkennt ein Hindernis, welches sich in einem Abstand von 30 m befindet.
Die Reaktionszeit tR des Fahrers beträgt ungefähr 0.7 s und das Auto kann mit einer
Bremsverzögerung bis 6 m/s2 bremsen.

x1
x0
a
v0 v

d
O A B
Abb. 1.4: Ein Auto bremst, um einen Zusammenprall mit einem Hindernis, dass sich in
einem Abstand d befindet, zu verhindern. OA: Das Auto fährt mit konstanter Geschwin-
digkeit. AB: Das Auto bremst mit konstanter Beschleunigung ⃗a.

1. Es soll ein Computerprogramm geschrieben werden, welches den Bremsvorgang simu-


liert und die optimale Bremsverzögerung berechnet, sodass der Aufprall des Autos auf
das Hindernis vermieden wird.
2. Wie muss das Programm reagieren, wenn sich ein Aufprall nicht vermeiden lässt?

Theorie Wir beginnen mit der Herleitung der Formeln für die notwendige Beschleuni-
gung, damit das Auto gerade noch vor dem Hindernis zum Stillstand kommt. Das Auto
fährt vom Zeitpunkt, in dem der Autofahrer das Hindernis sieht, bis er die Gefahr er-
kennt und bremst, mit konstanter Geschwindigkeit. Danach bremst er kontinuierlich mit
konstanter Beschleunigung (Abb. 1.4). Für den ersten Abschnitt der Bewegung gilt

x 0 = v 0 tR (1.2)
1.5 Teilaufgaben mit Funktionen lösen 17

und für den zweiten



 v = v0 − at (1.3a)
1
 x = x0 + v0 t − at2 , (1.3b)
2
wobei v0 die Geschwindigkeit des Autos an der Stelle A, x0 den Ort angibt, an dem der
Fahrer anfängt zu bremsen, und a der Betrag der Beschleunigung ist. Wenn das Auto
zum Stillstand kommt, gilt
v0
v = 0 ⇒ v0 − at = 0 ⇒ t = .
a
Dieses Ergebnis setzen wir in (Gl. 1.3b) mit x = d ein und erhalten die gesuchte Be-
schleunigung:
v2 v02
d = x0 + 0 ⇒ a = (1.4)
2a 2 (d − x0 )
Wir haben die nötigen Formeln, um zur Realisierung des Programms überzugehen.

Kurzbeschreibung Die Berechnung der Beschleunigung und die Darstellung der Er-
gebnisse werden in zwei Funktionen aufgeteilt. Das Programm soll die kleinstmögliche
Beschleunigung berechnen und für den Fall, dass es nicht mehr möglich ist dem Hin-
dernis auszuweichen, über eine Fehlermeldung in Form einer Ausnahme dies dem Fahrer
melden.

Quelltext Innerhalb einer Funktion implementieren wir (Gl. 1.2) und (Gl. 1.4) und
berechnen a für gegebene d, v0 und tR . Sollte die Distanz bis zum Hindernis kleiner als
|⃗x0 | (Abb. 1.4) oder aber eine Beschleunigung a erforderlich sein, die nicht vom Auto
erreicht werden kann, werden Ausnahmen ausgeworfen.

/**
* @brief brake_assist Berechnung der Beschleunigung damit das Auto
* noch vor dem Hindernis zum Stehen kommt
* @param v0 Geschwindigkeit (der Fahrer sieht das Hindernis )
* @param d Abstand des Autos zum Hindernis
* @param tR Reaktionszeit des Fahrers
* @return Beschleunigung
*/
inline double brake_assist ( double v0 , double d, double tR) {
double x0 = v0 * tR;

if (x0 > d) {
throw std :: range_error (std :: to_string (0));
}

double a = 0.5 * std :: pow(v0 , 2) / (d - x0);


if (a > 6) {
throw std :: range_error (std :: to_string (a));
}
return a;
};

Listing 1.19 cppsrc/apps-x004/hpp/x004–03


18 1 Eine Tour durch C++

Es folgt die Funktion zur Anzeige der Daten.

/**
* @brief break_assist_ui Benutzerschnittstelle
* @param v0 Geschwindigkeit (der Fahrer sieht das Hindernis )
* @param d Abstand des Autos zum Hindernis
* @param rtime Reaktionszeit des Fahrers
*/
void break_assist_ui ( double v0 , double d, double rtime) {
std :: cout << "d=" << d << " m,";
std :: cout << "v0=" << v0 << " m/s,";
std :: cout << "tR=" << rtime << " m/s" << std :: endl;
std :: cout << std :: setprecision (2);
try {
const double a = brake_assist (v0 , d, rtime);
double x0 = v0 * rtime ;
double x = x0 + 0.5 * std :: pow(v0 , 2) / a;
std :: cout << "need a=" << a << " m/s^2" << std :: endl;
std :: cout << "use a=" << std :: ceil(a) << " m/s^2" << std :: endl;
std :: cout << " distance =" << x << std :: endl;
} catch (std :: range_error err) {
std :: cout << "need : " << err.what () << " m/s^2" << std :: endl;
std :: cout << "car will crash " << std :: endl;
}
}

Listing 1.20 cppsrc/apps-x004/hpp/x004–04

Auf Basis der in der Aufgabe vorgegebenen Zahlenwerte erstellen wir ein Szenario und
ergänzen dies durch ein zusätzliches für eine zweifache Anfangsgeschwindigkeit des Autos.

/**
* @brief ex2 Auto bremst , um einem Hindernis ausweichen
*/
void ex2 () {
const double d = 30.0;
const double rtime = 0.7;
const double v0 = 14; //m/s;
break_assist_ui (v0 , d, rtime );
// zweiter Versuch mit Geschwindigkeit 2* v0
std :: cout << " -------------------" << std :: endl;
break_assist_ui (2 * v0 , d, rtime );
}

} // namespace nmx :: apps :: x004

Listing 1.21 cppsrc/apps-x004/hpp/x004–05

Daten Folgende Ausgabe, zeigt, dass das Auto es im ersten Fall schaffen wird, recht-
zeitig vor dem Hindernis zum Stehen zu kommen, während dies beim zweiten Mal nicht
mehr möglich ist.

d=30 m,v0 =14 m/s,tR =0.7 m/s


need a=4.9 m/s^2
use a=5 m/s^2
1.6 λ-Funktionen 19

distance =30
-------------------
d=30 m,v0 =28 m/s,tR =0.7 m/s
need : 37.692308 m/s^2
car will crash

Listing 1.22

1.6 λ-Funktionen
Eine λ-Funktion ist ein Objekt, das sich genau wie eine Funktion verhält und direkt an
der Stelle im Quelltext platziert werden kann, wo es benötigt wird. Genauer handelt
es sich um Instanzen von Klassen, die vom Compiler generiert werden und über einen
überladenen Klammeroperator verfügen. λ-Funktionen haben eine kompakte Syntax und
können in Variablen gespeichert oder direkt einer anderen Funktion übergeben werden
(Abb. 1.5). Über die Erfassungsliste können der λ-Funktion Variablen entweder als Kopie

Parameterliste
Funktionskörper
Erfassungsliste
double b = 9;
...
auto fn = [b]( double x){
Variable
return x+b;
}

Abb. 1.5: Eine λ-Funktion wird in einer Variablen gespeichert. Über die Erfassungsliste
wird der Parameter b als Kopie übergeben.

oder als Referenz übergeben werden. Diese Variablen stammen aus dem Block, in dem die
λ-Funktion definiert ist. Die Parameterliste und der Funktionskörper spielen die gleiche
Rolle wie auch in ganz normalen Funktionen.

1.6.1 Beispiele

λ-Funktionen und Container

Im folgenden Beispiel soll das Zusammenspiel zwischen Containern, Algorithmen, Itera-


toren und λ-Funktionen demonstriert werden. Ein std::vector v1 wird mit den Zahlen
von 0 bis 19 gefüllt. Ein zweiter std::vector v2 wird mit Elementen von v1 gefüllt, die
größer als 6 sind. Mit std::for_each und einer λ-Funktion werden beide Container auf
dem Bildschirm ausgegeben. Anschließend werden alle Elemente von v1 gesucht, die grö-
20 1 Eine Tour durch C++

ßer als ein vorgegebenes xmin und kleiner als ein xmax sind. λ-Funktionen, die mehrmals
angewandt werden, werden in Variablen gespeichert.

/**
* @brief ex5 lambda - Funktionen und Container
*/
inline void ex5 () {
// reserviere Speicher für 20 double Zahlen
std :: vector <double > v1 (20);

// fülle Vektor mit Zahlen 0 ,1 ,2 ,.... ( insgesamt 20 Elemente )


std :: iota(std :: begin (v1), std :: end(v1), 0);

// initialisiere zweiten Vektor


std :: vector <double > v2;

// kopiere alle Elemente aus v1 mit x > 6


// in den Zielvektor v2
// lambda - Funktion wird direkt übergeben
std :: copy_if (std :: begin (v1), //
std :: end(v1),
std :: back_inserter (v2),
[]( double x) { return x > 6; });

// lambda Hilfsfunktion , wird in Variable gespeichert


auto print = []( double x) { std :: cout << x << ","; };

// schreibe jedes Element von v1 in die Standardausgabe


std :: for_each (std :: begin (v1), std :: end(v1), print);
std :: cout << std :: endl;
// schreibe jedes Element von v1 in die Standardausgabe
std :: for_each (std :: begin (v2), std :: end(v2), print);
std :: cout << std :: endl;

const double xmin = 10, xmax = 16;


// finde Position des ersten Elements aus v1 , das > xmin ist
// lambda - Funktion wird direkt übergeben
auto itr1 = std :: find_if (std :: begin (v1),
std :: end(v1), //
[xmin ]( auto x) { return x > xmin; });
// finde Position des ersten Elements aus v1 , das > xmax ist
// lambda - Funktion wird direkt übergeben
auto itr2 = std :: find_if (std :: begin (v1),
std :: end(v1), //
[xmax ]( auto x) { return x > xmax; });
// gebe Zahlen zwischen itr1 (zeigt auf 11) und itr2 (zeigt auf 17)
std :: for_each (itr1 , itr2 , print );
}
} // namespace nmx :: apps :: x008

Listing 1.23 cppsrc/apps-x008/hpp/x008–05

0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 ,19 ,
7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,18 ,19 ,
11 ,12 ,13 ,14 ,15 ,16 ,

Listing 1.24
1.6 λ-Funktionen 21

Bahngeschwindigkeit als Funktion des Abstands von der Rotationsachse

Eine Scheibe mit Radius R = 1 m rotiert mit einer konstanten Winkelgeschwindigkeit


ω = 0.5 rad/s um eine Achse, die senkrecht durch ihren Mittelpunkt geht.

1. Wie lauten die Beträge der Bahngeschwindigkeit v und der Zentripetalbeschleunigung


az einzelner Punkte der Scheibe als Funktion des Abstands r von der Drehachse?
2. Erstellen Sie Beispieldaten und geben Sie diese auf dem Bildschirm aus. Trennen
Sie die Spalten innerhalb einer Zeile einmal durch ein Komma und dann durch ein
Semikolon.

Lösung Die Beträge für Bahngeschwindigkeit und Zentripetalbeschleunigung werden


durch die Formeln
v2
v = rω, az = (1.5)
r
gegeben.

Kurzbeschreibung Wir werden das Programm mit drei unterschiedlichen Ansätzen rea-
lisieren, wobei in allen drei Varianten folgende Anforderungen umgesetzt werden sollen:

Implementierung der Formeln (Gl. 1.5)


Speicherung der Daten in einem std::vector<double> wegen der automatischen Spei-
cherverwaltung
Ausgabe des Containers auf dem Bildschirm

Die erste Variante wird die Anforderungen ohne Verwendung von Funktionen umsetzen,
so wie dies bei der Erstellung eines Prototyps der Fall sein würde. Die zweite Version
wird mithilfe von Funktionen eine Aufgabetrennung enthalten und die dritte neben der
Aufgabeteilung zusätzlich stl-Algorithmen einsetzen.

Quelltext und Daten Die erste Version des Programms ist mit drei Schleifen, eine für
die Wertezuweisung und zwei für die Ausgabe, erstellt.

/**
* @brief ex1 Die Bahngeschwindigkeit als Funktion
* des Abstands von der Rotationsachse , Version 1.0
*/
inline void ex1 () {
// Synonym für std :: vector
using Array = std :: vector <double >;
// Winkelgeschwindigkeit und Radius
const double omega = 0.5;
const double R = 1.0;
// Felder speichern
// Radius , Bahngeschwindigkeit , Zentripetalbeschleunigung
Array radii , velocities , accelerations ;
// Berechnung der Werte
for ( double r = 0.1; r < R; r += 0.2) {
// Radius wird hinzugefügt
radii. push_back (r);
// berechne Bahngeschwindigkeit
22 1 Eine Tour durch C++

const double v = omega * r;


// Wert wird hinzugefügt
velocities . push_back (v);
// berechne Zentripetalbeschleunigung
const double a = pow(v, 2) / r;
// Wert wird hinzugefügt
accelerations . push_back (a);
}
// Ausgabe der Daten Trennzeichen ,
for ( size_t idx = 0; idx < radii .size (); idx ++) {
std :: cout << radii [idx] << "," //
<< velocities [idx] << "," //
<< accelerations [idx] << std :: endl; //
}
std :: cout << " -------------------------------" << std :: endl;
// Ausgabe der Daten Trennzeichen ;
for ( size_t idx = 0; idx < radii .size (); idx ++) {
std :: cout << radii [idx] << ";" //
<< velocities [idx] << ";" //
<< accelerations [idx] << std :: endl; //
}
}

Listing 1.25 x008.h

Wir führen die Funktion aus und erhalten folgende Ausgabe auf dem Bildschirm.

0.1 ,0.05 ,0.025


0.3 ,0.15 ,0.075
0.5 ,0.25 ,0.125
0.7 ,0.35 ,0.175
0.9 ,0.45 ,0.225
-------------------------------
0.1;0.05;0.025
0.3;0.15;0.075
...

Listing 1.26

Quelltext, Version 2.0 Wir formulieren das Beispielprogramm neu und zeigen, wie
dasselbe Problem mit λ-Funktionen gelöst werden kann. Nach der Definition der Felder
zur Speicherung von r, v und a, folgt die Implementierung der Formeln (Gl. 1.5) in
Form von λ-Funktionen. Eine weitere λ-Funktion regelt die Ausgabe auf dem Bildschirm.
Wir können so, ohne zusätzliche Schleifen zu programmieren, die Daten mit anderen
Trennzeichen ausgeben.

/**
* @brief ex7 Die Bahngeschwindigkeit als Funktion
* des Abstands von der Rotationsachse , Version 2.0
*/
inline void ex2 () {
using Array = std :: vector <double >;
const double omega = 0.5;
const double R = 1.0;
1.6 λ-Funktionen 23

// Radius , Bahngeschwindigkeit , Zentripetalbeschleunigung


Array radii , velocities , accelerations ;

// Rotationsgeschwindigkeit wird den lambda - Funktionen übergeben


auto v = [omega ]( double r) { return omega * r; };
auto a = [omega ]( double r) { return pow(omega , 2) * r; };

// Funktion zur Ausgabe der drei Felder als Tabelle ,


// wenn nichts anderes angegeben , wird ein Komma als
// Trennzeichen benutzt
auto print = []( const auto &v1 ,
const auto &v2 , //
const auto &v3 ,
char sep = ',') {
for ( size_t idx = 0; idx < v1.size (); idx ++) {
std :: cout << v1[idx] << sep //
<< v2[idx] << sep //
<< v3[idx] << std :: endl;
}
};

// Berechnung der Werte


for ( double r = 0.1; r < R; r += 0.2) {
radii. push_back (r);
velocities . push_back (v(r));
accelerations . push_back (a(r));
}

// Ausgabe der berechneten Werte


print(radii , velocities , accelerations );
print(radii , velocities , accelerations , ';');
}

Listing 1.27 x008.h

Quelltext, Version 3.0 Es folgt eine dritte Version des Programms, diesmal unter Ver-
wendung von stl-Algorithmen. Wir generieren die Werte für die Abstände von der Dreh-
achse mittels std::generate_n und einer anonymen λ-Funktion, der wir eine Referenz auf
einen Startwert übergeben. Dieser wird um 0.2 innerhalb der λ-Funktion inkrementiert.
Die Formeln (Gl. 1.5) erscheinen innerhalb der Funktion std::transform. Zur Ausgabe
der Felder wird eine variadische Funktion eingesetzt. Sie erlaubt es mit einer beliebigen
Anzahl von Feldern zu arbeiten.

/**
* @brief ex8 Die Bahngeschwindigkeit als Funktion
* des Abstands von der Rotationsachse , Version 3.0
*/
inline void ex3 () {
using Array = std :: vector <double >;
const double omega = 0.5;

// Radius , Bahngeschwindigkeit , Zentripetalbeschleunigung


Array radii , velocities , accelerations ;
// Startwert für anonyme lambda - Funktion
24 1 Eine Tour durch C++

double startval = -0.1;


// generiere ein Feld mit fünf Werten beginnend mit 0.1
std :: generate_n (std :: back_inserter ( radii ), 5, [& startval ]() { //
return startval += 0.2;
});

// Rotationsgeschwindigkeit wird den lambda - Funktionen übergeben


auto v = [omega ]( double r) { return omega * r; };
auto a = [omega ]( double r) { return pow(omega , 2) * r; };

// fülle Feld mit den Werten für die Bahngeschwindigkeit


std :: transform ( radii . begin () , radii .end (),
std :: back_inserter ( velocities ), v);

// fülle Feld mit den Werten für die Zentripetalbeschleunigung


std :: transform ( radii . begin () , radii .end (),
std :: back_inserter ( accelerations ), a);

// variadische Funktion zur Ausgabe von beliebig vielen Feldern


// als Tabelle , erstes Argument ist das Trennzeichen ,
// es folgen beliebig viele Felder
auto print = []( char sep , const auto &v1 , const auto &... v) {
for ( size_t idx = 0; idx < v1.size (); idx ++) {
std :: cout << v1[idx ];
(( std :: cout << sep << v[idx ]), ...);
std :: cout << std :: endl;
}
};

// Ausgabe der berechneten Werte


print(',', radii , velocities , accelerations );
print(';', radii , velocities , accelerations );
}

Listing 1.28 x008.h

Gleichförmige Kreisbewegung von zwei Teilchen

Zwei Teilchen mit den Massen m1 und m2 rotieren gleichförmig auf zwei Kreisbahnen um
einen gemeinsamen Mittelpunkt mit Radien R1 und R2 . Die Umlaufzeit von m1 beträgt
T1 = 20 s und die von m2 , T2 = 15 s.

1. Wenn zum Zeitpunkt t0 = 0 der Winkel ∆φ zwischen den zwei Radiusvektoren 0 ist,
wie viel Zeit verstreicht bis dieser Winkel 10◦ , 30◦ , 60◦ , 90◦ wird?
2. Wenn φ1 der vom Radiusvektor R1 innerhalb eines Zeitintervalls überstrichene Winkel
bezüglich seiner Anfangsposition und φ2 der entsprechende Winkel für R2 ist, erstellen
Sie Daten für φ1 und φ2 als Funktion der Zeit und ermitteln Sie durch einfache Suche
Näherungswerte für die Zeiten, zu denen sich die Winkeldifferenzen ∆φ einstellen.
Vergleichen Sie die Werte mit den exakten Resultaten.
1.6 λ-Funktionen 25

m2
ϕ2
R2 ϕ1
m1 m1

R1 R1 ∆ϕ
R2 m2
0 0

(a) (b)

Abb. 1.6: Gleichförmige Kreisbewegung von zwei Teilchen (a) Anfangsposition (b) Po-
sition zu einem späteren Zeitpunkt

Lösung Wenn ω1 die Winkelgeschwindigkeit der Masse m1 und ω2 die der Masse m2
ist, gilt
2π 2π
ω1 = , ω2 = , (1.6)
T1 T2
und damit für die Winkel als Funktionen der Zeit

φ1 = ω1 t, φ2 = ω2 t. (1.7)

Daraus folgt, dass für eine Winkeldifferenz ∆φ = φ2 − φ1 die Zeit

φ2 − φ1
t= (1.8)
ω2 − ω1
benötigt wird (Abb. 1.6).

Kurzbeschreibung

Entwurf einer Datenstruktur durch Kombination von std::vector und std::array, die
eine Zahlentabelle mit vier Spalten und beliebig vielen Reihen darstellt
Berechnung der Winkel und Winkeldifferenzen für ein vorgegebenes Zeitintervall
Implementierung einer einfachen Suche innerhalb der Tabelle
Suche nach der gewünschten Winkeldifferenz und Ausgabe des Ergebnisses auf dem
Bildschirm

Quelltext und Daten Die Tabelle wird als ein std::vector von beliebig vielen
std::array-Instanzen definiert. Dort werden die Bewegungsdaten der beiden Massen
geschrieben. In Schritten von ∆t = 0.001 s wird die Tabelle mit Daten gefüllt.

/**
* @brief ex4 Gleichförmige Kreisbewegung von zwei Teilchen
*/
inline void ex4 () {
// Tabelle als Feld von Feldern mit vier Spalten
26 1 Eine Tour durch C++

using Table = std :: vector <std :: array <double , 4>>;


Table table ;

// Umlaufzeiten
const double T1 = 20;
const double T2 = 15;
// Winkelgeschwindigkeiten
const auto omega1 = 2 * Math :: PI / T1;
const auto omega2 = 2 * Math :: PI / T2;

// Erzeuge Daten
for ( double t = 0; t < T1; t += 0.001) {
const double phi1 = omega1 * t;
const double phi2 = omega2 * t;
// Compiler ermittelt die Länge des Arrays
// und den Datentypen der Elemente
std :: array vals = { t, phi1 , phi2 , phi2 - phi1 };
table. push_back (vals);
}

Listing 1.29 cppsrc/apps-x008/hpp/x008–04

Es folgt die Implementierung von (Gl. 1.8) und die Suche. Wenn ∆φD ein Eintrag in der
Tabelle und ∆φ die gesuchte Winkeldifferenz ist, betrachten wir die Suche als erfolgreich,
wenn |∆φD − ∆φ| < ϵ mit 10−6 ≤ ϵ ≤ 10−2 , da wir nicht erwarten können, dass der
gesuchte Wert mit einem Eintrag in der Tabelle exakt übereinstimmt.

// Formel für den exakten Wert (lambda - Funktion )


auto exactfn = [omega1 , omega2 ]( double deltaphi ) {
return deltaphi / ( omega2 - omega1 ); //
};

std :: cout << std :: fixed << std :: setprecision (3);

// starte Suche für Winkeldifferenzen


for (auto n : { 10, 30, 60, 90 }) {
// Umwandlung in rad
const auto angle = Math :: to_radians (n);
double epsilon = 1e -6;
Table :: iterator fidx;
do {
// Vergleiche ob der Eintrag mit Index 3 ( Winkeldifferenz )
// in der Tabellenreihe mit der gesuchten Winkeldifferenz
// übereinstimmt
auto pfn = [angle , & epsilon ]( const auto &a) { //
return std :: abs(a[3] - angle) <= epsilon ;
};

fidx = std :: find_if ( table . begin (), table.end (), pfn);


if (fidx != table .end ()) {
// Suche war erfolgreich !
break ;
}
} while (( epsilon *= 10) < 1e -2);

if (fidx != table .end ()) { // prüfe ob der Wert gültig ist


1.6 λ-Funktionen 27

// Index , Zeit (Suche), Winkeldifferenz , Zeit (exakt )


auto row = (* fidx);
std :: cout << std :: distance ( table.begin (), fidx) //
<< std :: setw (8) << row [0] //
<< std :: setw (8) << Math :: to_degrees (row [3]) //
<< std :: setw (8) << exactfn ( angle) << std :: endl;
} else {
// Wert wurde nicht gefunden
std :: cout << "time not found for angle:" //
<< Math :: to_degrees ( angle) << std :: endl;
}
}
}

Listing 1.30 cppsrc/apps-x008/hpp/x008–04

Nach Ausführung der Funktion erhalten wir folgende Ausgabe.

1665 1.665 9.990 1.667


5000 5.000 30.000 5.000
10000 10.000 60.000 10.000
15000 15.000 90.000 15.000

Listing 1.31

Die erste Spalte enthält den Reihenindex, die zweite die Zeiten, die dritte die Winkeldif-
ferenz und die vierte Spalte die exakt berechnete Zeit (Gl. 1.8).

1.6.2 Abschätzung von Zwischenwerten für einen vorgegebenen


Datensatz

Wir wollen in diesem Abschnitt eine einfache lineare Interpolation implementieren. Für
einen diskreten Datensatz (x, y), der mithilfe einer bekannten Funktion erstellt wurde,
sollen Geraden berechnet werden, welche immer zwei benachbarte Punkte miteinander
verbinden. Anschließend sollen mithilfe dieser Geraden Werte für nicht tabellierte Daten
berechnet und die Ergebnisse mit den exakt berechneten Werten verglichen werden.

y
(x1 , y1 )

y
(x2 , y2 )
(x0 , y0 )
x x
0
Abb. 1.7: Für jeweils zwei Punkte wird eine Gerade berechnet. Mit diesen Geraden
werden nicht tabellierte Punkte berechnet.
28 1 Eine Tour durch C++

Theorie Gesucht wird für xi < x < xi+1 eine Approximation für den Wert y = f (x).
Wir berechnen die Gerade, welche die Punkte (xi , yi ) und (xi+1 , yi+1 ) verbindet. Be-
trachten wir die Punkte (x0 , y0 ) und (x1 , y1 ) (Abb. 1.7). Für die gesuchte Gerade
y = a1 x + a0 muss
{
y0 = a 1 x 0 + a 0 (1.9a)
y1 = a 1 x 1 + a 0 (1.9b)
gelten, wobei durch dieses Gleichungssystem a0 und a1 ermittelt werden. Wir erhalten
als Lösung: 

 y1 − y0
 a1 = (1.10a)
x1 − x0

 y1 − y0
 a 0 = y1 − x1 (1.10b)
x1 − x0
Kurzbeschreibung Das Programm soll innerhalb einer Funktion umgesetzt werden, die
aus folgenden drei Teilen besteht:

Funktionen zur Berechnung der Geradengleichungen und der interpolierten Werte


Erstellung von diskreten Daten (xi , cos xi )
Berechnung für x mit xi < x < xi+1 von Näherungswerten und Vergleich mit den
exakt berechneten Werten

Quelltext und Daten Wir beginnen mit der Implementierung des Algorithmus zur li-
nearen Interpolation, wobei die Koeffizienten a0 und a1 mit einer λ-Funktion durch
Anwendung der Formeln (Gl. 1.10a) und (Gl. 1.10b) berechnet werden.

/**
* @brief ex1 Berechnung von Daten mittels linearer Interpolation .
*/
inline void ex1 () {
// ----------------------------------------------------------
// Berechnung der Geraden a1 x + a0 , die durch die Punkte
// (x0 ,y0) und (x1 ,y1) geht
// --------------------------------------------------------
auto line = []( double x0 , double y0 , double x1 , double y1) {
double a1 = (y1 - y0) / (x1 - x0);
double a0 = y1 - a1 * x1;
return std :: make_pair (a0 , a1);
};

// ----------------------------------------------------------
// für eine Gerade y(x) = a1 * x + a0 berechne y(x)
// ----------------------------------------------------------
auto lininterp = []( double a0 , double a1 , double x) { //
return a0 + a1 * x;
};

Listing 1.32 cppsrc/apps-x027/hpp/x027–01

Im zweiten Abschnitt erstellen wir einen Datensatz aus zwanzig Werten (x, cos(x)) mit
x = 0, 1, . . . 19 (Listing 1.33).
1.6 λ-Funktionen 29

// ---------------------------------------------
// Erstellung von Beispieldaten (x,y)
// -----------------------------------------------
// reserviere Speicher für 20 Elemente
std :: vector <double > xvalues (20) , yvalues (20);
// fülle x = 0.1 ,0.2 ,...
double xval = 0;
std :: generate ( xvalues . begin () , xvalues .end (), [& xval ]() { //
return xval += 0.1;
});
// fülle y = cos(x)
std :: transform ( xvalues . begin () ,
xvalues .end () , //
std :: begin ( yvalues ),
[]( double x) { return std :: cos(x); });

Listing 1.33 cppsrc/apps-x027/hpp/x027–02

Es folgt die Berechnung eines Näherungswertes für cos x mit x = 1.23. Innerhalb des
erstellten Datensatzes suchen wir nach xi , xi+1 , sodass xi < x < xi+1 ist. Wir berechnen
die Gerade und den interpolierten Wert.

// -------------------------------------
// Berechnung von y(x =1.23)
// ----------------------------------------
const double x = 1.23;
// suche 1. tes x_i >= x
auto itr = std :: upper_bound ( xvalues . begin (), xvalues .end (), x);
// wenn gefunden ...
if (itr != xvalues .end ()) {
// finde Index im Feld
auto idx = static_cast <size_t >( std :: distance ( xvalues .begin (),
itr));
std :: cout << " found index : " << idx //
<< " with x=" << xvalues [idx] //
<< " and y=" << yvalues [idx] << std :: endl;
const double x0 = xvalues [idx - 1];
const double x1 = xvalues [idx ];
const double y0 = yvalues [idx - 1];
const double y1 = yvalues [idx ];
auto [a0 , a1] = line(x0 , y0 , x1 , y1);
std :: cout << "(x0 ,y0)=(" << x0 << "," << y0 << ")" << std :: endl;
std :: cout << "(x1 ,y1)=(" << x1 << "," << y1 << ")" << std :: endl;
std :: cout << "(a0 ,a1)=(" << a0 << "," << a1 << ")" << std :: endl;
std :: cout << "x=" << x << std :: endl;
std :: cout << " interpolated value:" << lininterp (a0 , a1 , x) <<
std :: endl;
std :: cout << "true value :" << std :: cos(x) << std :: endl;
}
}

} // namespace nmx :: apps :: x027

Listing 1.34 cppsrc/apps-x027/hpp/x027–03


30 1 Eine Tour durch C++

found index: 12 with x=1.3 and y =0.267499


(x0 ,y0) =(1.2 ,0.362358)
(x1 ,y1) =(1.3 ,0.267499)
(a0 ,a1) =(1.50066 , -0.948589)
x =1.23
interpolated value :0.3339
true value :0.334238

Listing 1.35

1.7 Rechnen mit komplexen Zahlen


Komplexe Zahlen spielen in den Naturwissenschaften eine große Rolle. Wir denken z.B.
an die Untersuchung von harmonischen Schwingungen, was eines von vielen Beispielen
ist. Die C++-Standardbibliothek macht das Rechnen durch die Bereitstellung einer Klasse
für komplexe Zahlen einfach. Eine komplexe Zahl kann statt std::complex<double> z1 =
{5,4} auch so auto z1=5+4i initialisiert werden, wobei 5 der Real- und 4 der Imaginärteil
ist. Wir wollen mit zwei kleinen Beispielen das Rechnen mit komplexen Zahlen vorstellen.

1.7.1 Beispielanwendungen

Eigenschaften von komplexen Zahlen

Für eine gegebene komplexe Zahl z = 3+4i wird der Realteil Re {z} = 3, der Imaginärteil
( )
Im {z} = 4, der Betrag |z| = 5, die Phase φ = arctan 43 = 0.927, z 2 = 25 und das kom-
plex Konjugierte z̄ = 3 − 4i berechnet. Es folgt eine Umrechnung von Polarkoordinaten
in kartesische für z.

/**
* @brief ex1 Eigenschaften von komplexen Zahlen
*/
inline void ex1 () {
using namespace std :: literals :: complex_literals ;
std :: complex <double > z = 3. + 4.i;

std :: cout << "real part:" << std :: real(z) << std :: endl;
std :: cout << " imaginary part:" << std :: imag(z) << std :: endl;

std :: cout << " absolute value :" << std :: abs(z) << std :: endl;
std :: cout << " phase angle :" << std :: arg(z) << std :: endl;
std :: cout << "norm:" << std :: norm(z) << std :: endl;

std :: cout << " complex conjugate :" << std :: conj(z) << std :: endl;
std :: cout << " polar component :" << std :: polar (5. , 0.927295) <<
std :: endl;
}

Listing 1.36 cppsrc/apps-x005/hpp/x005–01


1.7 Rechnen mit komplexen Zahlen 31

real part :3
imaginary part :4
absolute value :5
phase angle :0.927295
norm :25
complex conjugate :(3 , -4)
polar component :(3 ,4)

Listing 1.37

Rechnen mit komplexen Zahlen

Wenn z1 , z2 komplexe Zahlen mit z1 = 3 + 4i und z2 = 2 − 5i darstellen, sollen die


Ausdrücke z1 + z2 , z1 − z2 , z1 + z¯1 , z2 − z¯2 , z1 /z2 und z13 z22 berechnet werden.

/**
* @brief ex2 Rechnen mit komplexen Zahlen
*/
inline void ex2 () {
using namespace std :: literals :: complex_literals ;
std :: complex <double > z1 = 3. + 4i, z2 = 2. - 5i;
std :: cout << "z1=" << z1 << ", z2=" << z2 << std :: endl;

std :: cout << "z1+z2=" << z1 + z2 << std :: endl;


std :: cout << "z1 -z2=" << z1 - z2 << std :: endl;

std :: cout << "z1+conj(z2)=" << z1 + std :: conj(z2) << std :: endl;
std :: cout << "z1 -conj(z2)=" << z1 - std :: conj(z2) << std :: endl;

std :: cout << "z1/z2=" << z1 / z2 << std :: endl;


std :: cout << "z1 ^3+ z2 ^2=" << std :: pow(z1 , 3) * std :: pow(z2 , 2) <<
std :: endl;
}

} // namespace nmx :: apps :: x005

Listing 1.38 cppsrc/apps-x005/hpp/x005–02

z1 =(3 ,4) , z2 =(2 , -5)


z1+z2 =(5 , -1)
z1 -z2 =(1 ,9)
z1+conj(z2)=(5 ,9)
z1 -conj(z2)=(1 , -1)
z1/z2 =( -0.482759 ,0.793103)
z1 ^3+ z2 ^2=(3337 ,1416)

Listing 1.39
32 1 Eine Tour durch C++

1.8 Benutzerdefinierte Literale


Eine Zeichenfolge, die einen konstanten Wert von einem bestimmten Datentyp darstellt,
wird als Literal bezeichnet. In C++ begegnen wir unter anderem Literalen für ganzzahlige
Zahlen (z.B. 23) oder Fließkomma-Zahlen (z.B. 45.32) und Buchstaben oder Zeichenket-
ten wie 'c' oder "Test". Wenn einem Literal ein Suffix nachgestellt wird, kann sein Typ
weiter spezifiziert werden. So ist 3.24f eine Fließkomma-Zahl vom Typ float. Neben den
in der Sprache eingebauten Suffixen können auch benutzerdefinierte Literale definiert
werden. Es handelt sich dabei um normale Funktionen, die eine besondere Syntax ha-
ben. Wir haben bereits im vorherigen Abschnitt das i zum Bezeichnen von imaginären
Zahlen kennengelernt.

1.8.1 Umrechnen in SI-Einheiten

Werden in Programmen physikalische Größen nicht in SI-Einheiten angegeben, bietet es


sich an, über benutzerdefinierte Literale die Umrechnung in Einheiten des internationalen
Systems durchzuführen. Dazu definieren wir Literale zur Umrechnung der Längeneinhei-
ten km, mm, cm in m, zur Umrechnung von Gradmaß ins Bogenmaß, von g in kg und von
Stunden und Minuten in Sekunden. Wird z.B. einer Zahl ein _g (bei benutzerdefinierten
Literalen muss das Suffix mit einem Unterstrich beginnen) nachgestellt, so bedeutet dies,
dass die Angabe von Gramm in Kilogramm umgerechnet wird. (Abb. 1.8).

Ausgabe in kg Suffix Eingabe in g

constexpr double operator""_g(long long double x){

3.5_g 0.035 kg
return static_cast<double>(x)*1e-3;
}

Umrechnung von g in kg
Abb. 1.8: Benutzerdefiniertes Literal zur Umrechnung von g in kg

/**
* Umrechnung von Einheiten Ausgabe : SI - Einheiten
*/
namespace nmx {

// Längen km -> m, cm -> m und mm -> m


constexpr double operator "" _km(long double x) {
return static_cast <double >(x) * 1e3;
}

constexpr double operator "" _cm(long double x) {


1.8 Benutzerdefinierte Literale 33

return static_cast <double >(x) * 1e -2;


}

constexpr double operator "" _mm(long double x) {


return static_cast <double >(x) * 1e -3;
}

Listing 1.40 cppsrc/nmx–xunits/hpp/xunits–01

// ** Winkel deg -> rad


constexpr double operator "" _deg(long double x) {
return static_cast <double >(x) * M_PI / 180.0;
}

Listing 1.41 cppsrc/nmx–xunits/hpp/xunits–02

// ** Massen g -> kg
constexpr double operator "" _g(long double x) {
return static_cast <double >(x) * 1e -3;
}

Listing 1.42 cppsrc/nmx–xunits/hpp/xunits–03

// ** Zeit Stunden -> s und Minuten -> s


constexpr double operator "" _h(long double x) {
// benutze gsl - Konstante zur Berechnung
return ( static_cast <double >(x) * GSL_CONST_MKSA_HOUR );
}

constexpr double operator "" _min(long double x) {


// benutze gsl - Konstante zur Berechnung
return ( static_cast <double >(x) * GSL_CONST_MKSA_MINUTE );
}

} // namespace nmx

Listing 1.43 cppsrc/nmx–xunits/hpp/xunits–04

Zur Umrechnung der Zeiteinheiten wurden die Konstanten GSL_CONST_MKSA_MINUTE (=6e1)


und GSL_CONST_MKSA_HOUR (=3.6e3) verwendet, welche von der gsl bereitgestellt werden.

1.8.2 Beispielanwendungen

In den folgenden Aufgaben sollen alle Größen in SI-Einheiten umgerechnet werden.

1. Es soll die Dichte eines Blocks der Masse = 50 g mit Länge 5 cm, Breite 3 cm und
Höhe 2 cm berechnet werden.
2. Ein Auto fährt mit einer konstanten Geschwindigkeit v = 36 km/h. Wie lautet die
Geschwindigkeit in m/s?
34 1 Eine Tour durch C++

3. Die Schwingungsdauer eines mathematischen Pendels mit der Länge l = 30 cm und


eine Masse 20 g.
4. Die Drehfrequenz einer Zentrifuge, die mit 9000 Umdrehungen pro Minute rotiert.

Quelltext und Daten Zur Umrechnung aller Einheiten, werden, die in Listing 1.40 bis
Listing 1.43 definierten Literale eingesetzt. Zusätzlich dazu benötigen wir die Formel für
die Dichte d = mV und die Frequenz f = N /∆t.

/**
* @brief ex3 Umrechnung von Einheiten mithilfe von benutzerdefinierten
* Literalen
*/
void ex3 () {
std :: cout << std :: setprecision (2) << std :: scientific ;
const auto g = 9.81;
// Aufgabe (1)
auto volume = 5. _cm * 3. _cm * 2. _cm;
auto mass = 50.0 _g;
auto density = mass / volume ;
std :: cout << "V=" << volume << std :: endl;
std :: cout << "d=" << density << std :: endl;
// Aufgabe (2)
std :: cout << "v=" << 36.0 _km / 1. _h << std :: endl;
// Aufgabe (3)
auto period = 2 * M_PI * sqrt (30.0 _cm / g);
std :: cout << " period =" << period << std :: endl;
// Aufgabe (4)
auto frequency = 9000 / 1.0 _min;
std :: cout << "f=" << frequency << std :: endl;
}

} // namespace nmx :: apps :: x003

Listing 1.44 cppsrc/apps-x003/hpp/x003–01

Die Ausgabe des Programms auf dem Bildschirm ist:

V =3.00e -05
d =1.67e+03
v =1.00e+01
period =1.10e+00
f =1.50e+02

Listing 1.45

1.9 Statische Variablen und Funktionen


Das Schlüsselwort static wird in C++ unterschiedlich verwendet. Eine Variable, die in-
nerhalb einer Funktion oder eines Blocks als static gekennzeichnet ist, erhält einen festen
Speicherplatz, der auch nach Beendigung der Funktion weiterhin existiert und gültig ist.
1.9 Statische Variablen und Funktionen 35

Die Variable erhält beim ersten Funktionsaufruf, falls nichts anderes angegeben wird, den
Wert 0. Mithilfe von statischen Variablen innerhalb von Funktionen können sehr einfach
Zahlenfolgen oder Filter zur Auswahl von Elementen eines Containers nach bestimmtem
Kriterien realisiert werden.

1.9.1 Beispielanwendungen

Speicherung und Ausgabe von n geraden Zahlen

Für ein vorgegebenes n sollen die ersten n geraden Zahlen in einem Feld gespeichert und
auf dem Bildschirm ausgeben werden.

Kurzbeschreibung Die Erstellung der Zahlenfolge wird eine Funktion, welche die Rol-
le eines Zahlengenerators spielen wird, übernehmen. Eine andere Funktion wird durch
Aufruf des Zahlengenerators ein Feld füllen. Die Zahlen werden schließlich auf dem Bild-
schirm ausgeben.

Quelltext und Daten Das Programmieren des Zahlengenerators als λ-Funktion ist ein-
fach. Eine statische Variable innerhalb der Funktion erhält den Anfangswert 0. Bei jedem
Aufruf wird der Wert um 2 erhöht. Zur Speicherung der Zahlen wird ein leeres Feld mit
n Elementen initialisiert. Einem speziellen stl-Algorithmus std::generate_n werden das
Feld und der Zahlengenerator übergeben und damit das Feld gefüllt.

/**
* @brief ex4 Speicherung und Ausgabe von n geraden Zahlen
*/
inline void ex4 () {
// lambda zur Generierung von Zahlen
auto fn = []() {
static double val = 0;
return val += 2; // erste Zahl ist die 2
};
//C-Feld erzeuge Speicher für 9 Elemente
const size_t n = 9;
int values [9];
std :: generate_n (values , n, fn);
// Ausgabe auf dem Bildschirm
for (auto x : values ) {
std :: cout << x << ",";
}
std :: cout << std :: endl;
}
} // namespace nmx :: apps :: x007

Listing 1.46 cppsrc/apps-x007/hpp/x007–04

2 ,4 ,6 ,8 ,10 ,12 ,14 ,16 ,18 ,

Listing 1.47
36 1 Eine Tour durch C++

Umlaufzeit eines Planeten innerhalb des Sonnensystems

In diesem Beispiel werden wir mithilfe von statischen Variablen innerhalb von Funktionen
Elemente einer Zahlenfolge herausfiltern.
Ein imaginärer Planet umkreist in einer elliptischen Bahn unsere Sonne mit dp = αde
und a > 0, wobei dp und de die Längen der großen Halbachsen des Planeten und der Erde
von der Sonne sind. Es soll ein Programm geschrieben werden, mit dem die Umlaufzeit des
Planeten in Abhängigkeit von α = 2, 3 . . . , 50 berechnet wird. Jeder zehnte berechnete
Wert soll auf dem Bildschirm ausgegeben werden.

Theorie Wir wenden das dritte Kepler’sche Gesetz für die Umlaufzeiten und die großen
Halbachsen der Umlaufbahnen der Erde und des imaginären Planeten an.

Tp2 dp3 3

2
= 3 = α 3 ⇒ Tp = α 2 Te (1.11)
Te de
Damit hätten wir die Umlaufzeit des Planeten in Abhängigkeit von der Umlaufzeit der
Erde berechnet.
3
Kurzbeschreibung Wertepaare der Form (α, α 2 ) werden mit einer Schleife erzeugt und
in einem Container gespeichert. Zur Ausgabe werden die Elemente des Containers mit
einer Schleife durchlaufen und mittels eines Filters jedes zehnte Element des Containers
auf dem Bildschirm geschrieben.

Quelltext und Daten Die berechneten Werte werden als Zahlenpaare (std::pair) in ei-
nem std::vector gespeichert. Der Container reserviert den nötigen Speicher automatisch,
was bedeutet, dass wir uns nicht vorher auf die Anzahl der Elemente festlegen müssen.
Für die Implementierung des Filters wählen wir eine λ-Funktion. Dort wird ein Zähler
mit dem Wert 1 als static initialisiert. Bei jedem Aufruf wird dieser Zähler um 1 erhöht.
Wenn der Wert 10 erreicht wird, gibt die Funktion ein true zurück und der Zähler wird
wieder auf eins gesetzt, andernfalls lautet der Rückgabewert false.

/**
* @brief ex3 Umlaufzeit eines Planeten innerhalb des Sonnensystems
*/
inline void ex3 () {
// Synonym für den Typ des Wertepaares
using Item = std :: pair <double , double >;
// Container zur Speicherung der Werte
std :: vector <Item > values ;
// der Container wird gefüllt
for ( double a = 2; a < 50; a += 1) {
values . push_back ({ a, std :: pow(a, 1.5) });
}
// Filter
auto filter = []() {
static size_t d = 1;
// wenn nicht 10 erhöhe Wert um 1
d = (d == 10) ? 1 : d + 1;
return d == 1;
};
1.9 Statische Variablen und Funktionen 37

// Schreibe jedes n-te Element heraus


for (const auto &item : values ) {
if ( filter ()) {
std :: cout << "(" << item. first << "," //
<< item. second //
<< ")" << std :: endl;
}
}
}

Listing 1.48 cppsrc/apps-x007/hpp/x007–03

(11 ,36.4829)
(21 ,96.2341)
(31 ,172.601)
(41 ,262.528)

Listing 1.49

1.9.2 Statische Variablen und Funktionen innerhalb von Strukturen

Statische Variablen oder Funktionen, die innerhalb einer Klasse oder Struktur deklariert
sind, werden über diese abgerufen. Sie sind aber nicht an Instanzen der Klasse oder
Struktur gebunden, sondern werden von allen Instanzen geteilt. Da nur eine Kopie dieser
Variablen existiert und diese über den Klassennamen aufgerufen werden, eignen sie sich
sehr gut, um Konfigurationsparameter zu speichern. Ähnliches gilt für statische Funk-
tionen. Es handelt sich um globale Funktionen. Dadurch, dass sie auch über eine Klasse
aufgerufen werden, können mit ihnen Hilfsfunktionen gruppiert werden.

1.9.3 Speicherung von Konfigurationsdaten

Programme benötigen oft Konfigurationsparameter, die bei ihrem Start geladen werden
und das Verhalten oder das Aussehen eines Programms festlegen. Die Beispielprogramme
in diesem Buch werden Daten hauptsächlich in Dateien speichern. Um den Überblick zu
behalten ist es sinnvoll, diese in einem bestimmten Verzeichnis bzw. in Unterverzeichnisse
eines Basisverzeichnisses zu speichern (Abb. 1.9). Da die Beispielprogramme auf unter-
schiedlichen Rechnern laufen sollen, ist es wichtig, dass der Name des Basisverzeichnisses
konfigurierbar ist.
38 1 Eine Tour durch C++

data_directory
home_directory
(Basisverzeichnis Daten)
data
x001 Dateien

current_directory plot.tex
x015
(aktuelles Verzeichnis) table.csv

x030
Abb. 1.9: Konfigurationsparameter zur Ausgabe von Dateien in Verzeichnissen

Wenn ein Programm in kompilierter Form vorliegt, kann dies nur über das Lesen
einer externen Textdatei geschehen, die beim Start eines Programms geladen wird. Für
die hier vorliegenden Beispielprogramme wird der Quelltext zur Verfügung gestellt. Wir
werden deshalb den Namen des Basisverzeichnisses als statische Variable innerhalb einer
Struktur speichern und diesen direkt in die Quelltextdatei schreiben.

/**
* @brief The Config struct Speicherung von Konfigurationsdaten
*/
struct Config {
private :
// in diesem Verzeichnis werden Dateien mit Daten geschrieben
inline static std :: string current_directory ;

public :
// Basisverzeichnis , alle anderen
// Verzeichnisse befinden sich in diesem Verzeichnis
inline static const std :: string home_directory =
"/home/ eliasp67 / xbook2 ";

// Daten werden in Unterverzeichnisse dieses


// Verzeichnisses kopiert
inline static const std :: string data_directory = home_directory +
"/data";

Listing 1.50 cppsrc/nmx–xconfig/hpp/xconfig–01

Die Struktur erhält zusätzlich eine private Variable mit dem Namen eines aktuellen
Verzeichnisses. Dieser kann jederzeit über die statische Funktion Config::set_current_dir
festgelegt werden.

/**
* @brief set_current_dir setze Namen des aktuellen
* Datenverzeichnisses
* @param dirname Name des Ordners
*/
inline static void set_current_dir ( const std :: string & dirname ) { //
current_directory = dirname ;
}

Listing 1.51 cppsrc/nmx–xconfig/hpp/xconfig–02


1.9 Statische Variablen und Funktionen 39

Wird get_current_dir aufgerufen, erhalten wir nicht nur den Namen, sondern auch den
kompletten Pfad zum Verzeichnis.

/**
* @brief get_current_dir gebe aktuelles Datenverzeichnis
* @param flag wenn false ohne Pfad ( optional )
* @return der Name des aktuelles Datenverzeichnisses
*/
inline static std :: string get_current_dir (bool flag = true) { //
return flag ? ( data_directory + "/" + current_directory ) :
current_directory ;
}

Listing 1.52 cppsrc/nmx–xconfig/hpp/xconfig–03

Um Dateinamen z.B. an Ausgabeströmen zu übergeben, sind die beiden folgenden Ele-


mentfunktionen nützlich. Mit der Angabe von einer bzw. zwei Zeichenketten wird der
vollständige Name einer Datei innerhalb des aktuellen Verzeichnisses ausgegeben.

/**
* @brief get_current_file gebe vollständigen Pfad zu einer Datei
* @param fname Name der Datei
* @return vollständigen Pfad zu einer Datei
*/
inline static std :: string get_current_file ( const std :: string
&fname) { //
return get_current_dir () + "/" + fname ;
}

Listing 1.53 cppsrc/nmx–xconfig/hpp/xconfig–04

/**
* @brief get_current_file gebe vollständigen Pfad zu einer Datei
* kann mit der Compiler - Variablen __func__ genutzt werden
* @param fname Name der Datei ohne Dateiendung
* @param ext Dateiendung
* @return vollständigen Pfad zu einer Datei
*/
inline static std :: string get_current_file ( const char *fname , const
char *ext) { //
std :: stringstream sstream ;
sstream << fname << "." << ext;
return get_current_file ( sstream .str ());
}
}; // Config

} // namespace nmx :: settings

Listing 1.54 cppsrc/nmx–xconfig/hpp/xconfig–05


40 1 Eine Tour durch C++

1.9.4 Mathematische Hilfsfunktionen

Wir möchten eine kleine Gruppe von nützlichen mathematischen Funktionen und Va-
riablen einführen, die im Rahmen dieses Buches immer wieder gebraucht werden. Dazu
gehören die Zahlen e und π sowie statische Funktionen zur Umrechnung vom Gradmaß
ins Bogenmaß und umgekehrt.

/**
* @brief The Math struct Mathematische Hilfsfunktionen
*/
struct Math {
// 3.14159265358979323846
inline static constexpr double PI = M_PI;
// 2.71828182845904523536
inline static constexpr double E = M_E;
// rad -> deg
inline static double to_radians ( double x) { return PI / 180 * x; }
// deg -> rad
inline static double to_degrees ( double x) { return 180 / PI * x; }

Listing 1.55 cppsrc/nmx–xmath/hpp/xmath–01

Es folgt eine Funktion, die testet, ob eine double-Zahl 0 ist. Eine weitere testet, ob zwei
double-Zahlen gleich sind und eine letzte liefert das Vorzeichen einer Zahl.

// ** ist eine Zahl 0?


inline static bool is_zero ( double x, double epsilon ) { //
return abs(x) < epsilon ;
}
// Vergleich von zwei Zahlen mit gewünschter Genauigkeit
inline static int cmp( double x, double y, double epsilon ) { //
return gsl_fcmp (x, y, epsilon );
}
// Vorzeichen einer Zahl
inline static int sign( double x) { return GSL_SIGN (x); }
}; // Math
} // namespace nmx

Listing 1.56 cppsrc/nmx–xmath/hpp/xmath–02

1.9.5 Physikalische Konstanten und Hilfsfunktionen

Die universelle Gravitationskonstante, der Radius und die Masse der Erde sowie die
Gravitationsbeschleunigung g in der Nähe der Erdoberfläche sind Konstanten, die wir
ebenfalls in Form von statischen Variablen speichern möchten. Zu diesem Zweck füh-
ren wir einen Namensraum gravitation ein. Für die Erddaten wird eine Struktur Earth
innerhalb dieses Namensraums eingeführt und für die Universalkonstante G eine mit
dem Namen Astronomy. Die Werte für G und g werden von der GNU Scientific Library
bereitgestellt.
1.9 Statische Variablen und Funktionen 41

/**
* physikalische Konstanten
*/
namespace gravitation {

// Astronomische Daten
struct Astronomy {
inline static double G{ GSL_CONST_MKSA_GRAVITATIONAL_CONSTANT };
};

// Erddaten
struct Earth {
inline static constexpr double g{ GSL_CONST_MKSA_GRAV_ACCEL };
inline static constexpr double mass{ 5.9722 e24 };
// zur Initialisierung des Erdradius wird das benutzerdefinierte
// Literal km benutzt
inline static constexpr double radius { 6378.137 _km };
};
} // namespace gravitation

Listing 1.57 cppsrc/nmx–xphysics/hpp/xphysics–01

Die kinetische Energie K = 12 mv 2 eines Teilchens und seine potenzielle Energie U =


mgh werden sehr häufig in Mechanikaufgaben gebraucht. Wir führen speziell dafür eine
Struktur Mechanics ein, die die beiden folgenden statischen Funktionen enthält:

/**
* @brief kinetic_energy Hilfsfunktion
* @param mass Masse
* @param velocity Geschwindigkeit
* @return kinetische Energie eines Teilchens
*/
inline static double kinetic_energy ( double mass , double velocity ) {
return 0.5 * mass * pow(velocity , 2);
}

Listing 1.58 cppsrc/nmx–xphysics/hpp/xphysics–03

/**
* @brief potential_energy Hilfsfunktion
* @param mass Masse
* @param height Höhe
* @return potenzielle Energie im konstanten Gravitationsfeld
*/
inline static double potential_energy ( double mass , double height ) {
return mass * gravitation :: Earth ::g * height ;
}
}; // Mechanics
} // namespace nmx

Listing 1.59 cppsrc/nmx–xphysics/hpp/xphysics–04


42 1 Eine Tour durch C++

1.9.6 Beispielanwendungen

Umrechnung von Bogenmaß in Gradmaß

Es sollen Sinus, Kosinus und Tangens für die Winkel φ = 0° . . . 90° in Schritten von
∆φ = 30° in die Standardausgabe geschrieben werden.

Quelltext und Daten Zur Ausgabe der Ergebnisse benötigen wir die Umrech-
nung vom Bogenmaß ins Gradmaß, da die trigonometrischen Funktionen der C++-
Standardbibliothek die Angabe der Winkel in Bogenmaß erwarten. Die Funktionswerte
werden innerhalb einer Schleife berechnet, nachdem die Winkel mittels der Hilfsfunktion
Listing 1.55 ins Bogenmaß umgerechnet werden. Es wird darauf geachtet, dass statt einer
sehr großen Zahl (1.6331e+16) für den Tangens von π2 , nan ausgedruckt wird.

/**
* @brief ex1 Umrechnung Bogen - und Gradmaß
*/
inline void ex1 () {
std :: cout << std :: scientific << std :: setprecision (3);

// Name der Spalten (erste Reihe)


std :: cout << "phi [deg]" //
<< std :: setw (15) << "phi [rad]" //
<< std :: setw (15) << "cos(phi)" //
<< std :: setw (15) << "sin(phi)" //
<< std :: setw (15) << "tan(phi)" << std :: endl;

for ( double phi = 0; phi <= 90; phi += 30) {


double phiRad = Math :: to_radians (phi);
std :: cout << phi //
<< std :: setw (15) << phiRad //
<< std :: setw (15) << cos( phiRad ) //
<< std :: setw (15) << sin( phiRad );
if (Math :: cmp(phiRad , 0.5 * Math ::PI , 1e -9) == 0) {
std :: cout << std :: setw (15) << "nan" << std :: endl;
} else {
std :: cout << std :: setw (15) << tan( phiRad ) << std :: endl;
}
}
}

Listing 1.60 cppsrc/apps-x007/hpp/x007–01

Wir führen das Programm aus und erhalten:

phi [deg] phi [rad] cos(phi) sin(phi) tan(phi)


0.000 e+00 0.000 e+00 1.000 e+00 0.000e+00 0.000e+00
3.000 e+01 5.236e -01 8.660e -01 5.000e -01 5.774e -01
6.000 e+01 1.047 e+00 5.000e -01 8.660e -01 1.732e+00
9.000 e+01 1.571 e+00 6.123e -17 1.000e+00 nan

Listing 1.61
1.9 Statische Variablen und Funktionen 43

Satellit auf kreisförmiger Bahn um die Erde

Ein Satellit mit der Masse m = 720 kg wird in eine kreisförmige Umlaufbahn um die
Erde in einer Höhe h = αR gebracht, wobei R der Erdradius und α > 1 ist.

1. Wie groß ist die Zentripetalkraft auf den Satelliten?


2. Wie ändert sich der Wert der Gravitationsbeschleunigung mit wachsendem Kreis-
bahnradius?
3. Schreiben Sie in die Standardausgabe für α = 1.55, 1.75, 1.81, 1.92 die Werte des
Kreisbahnradius, der Zentripetalkraft und der Gravitationsbeschleunigung.

Setzen Sie die Gravitationsbeschleunigung auf der Erdoberfläche sowie den Erdradius
und die Erdmasse als bekannt voraus.

Theorie Die Gravitation spielt die Rolle der Zentripetalkraft FZ :

Mm
FZ = G , (1.12)
r2
wobei G die universelle Gravitationskonstante, M die Erdmasse, m die Satellitenmasse
und r der Kreisbahnradius gemessen vom Erdmittelpunkt ist. Die Gravitationsbeschleu-
nigung im Abstand r vom Erdmittelpunkt beträgt

GM
g= . (1.13)
r2
Wenn g0 die Gravitationsbeschleunigung auf der Erdoberfläche und g1 die, entlang der
Satellitenbahn ist, gilt:



GM
( )2
 g0 =
R2 R g0
⇒ g1 = g0 = . (1.14)

 GM R + αR (1 + α)2
 g1 = 2
(R + h)

Kurzbeschreibung Wir werden den Radius der Kreisbahn, die Gravitationskraft, die
Gravitationsbeschleunigung und das Verhältnis gg10 in Abhängigkeit von α ausgeben. Es
werden dabei die vordefinierten Werte für die universelle Gravitationskonstante und die
Erdbeschleunigung (Listing 1.57) eingesetzt.

Quelltext und Daten Der Quelltext ist einfach zu verstehen. Zu beachten ist die
Programmierung der for-Schleife, in der die Werte für α direkt mit einer Liste
(std::initializer_list) angegeben werden.

/**
* @brief ex2 Satellit auf kreisförmiger Bahn um die Erde
*/
inline void ex2 () {
using namespace gravitation ;
const double m = 720;
std :: cout << std :: scientific << std :: setprecision (3);
// Namen der Spalten ( erste Reihe)
44 1 Eine Tour durch C++

std :: cout << " alpha " << std :: setw (12) << "r" << std :: setw (12) //
<< " force " << std :: setw (12) << "g1" << std :: setw (12) //
<< "g1/g0" << std :: endl;
// berechne Daten
for ( double alpha : { 1.55 , 1.75 , 1.81 , 1.92 }) {
const double c = 1 + alpha ;
const double r = alpha * Earth :: radius ;
const double force = Astronomy ::G * Earth :: mass * m / pow(r, 2);
const double g1 = Earth ::g / pow(c, 2);
std :: cout << alpha << std :: setw (12) << r << std :: setw (12) //
<< force << std :: setw (12) << g1 << std :: setw (12) //
<< g1 / Earth ::g << std :: endl;
}
}

Listing 1.62 cppsrc/apps-x007/hpp/x007–02

Nach Ausführung des Programms erhalten wir folgende Ausgabe:

alpha r force g1 g1/g0


1.550 e+00 9.886 e+06 2.936 e+03 1.508e+00 1.538e -01
1.750 e+00 1.116 e+07 2.303 e+03 1.297e+00 1.322e -01
1.810 e+00 1.154 e+07 2.153 e+03 1.242e+00 1.266e -01
1.920 e+00 1.225 e+07 1.913 e+03 1.150e+00 1.173e -01

Listing 1.63

1.10 Präprozessor-Anweisungen
Mit der Anweisung #define haben wir die Möglichkeit, Zeichenketten einzuführen, die
vor der Übersetzung des Programms durch den Compiler vom Präprozessor gegen eine
andere Zeichenkette ausgetauscht werden. Der Compiler sieht anschließend nur die expan-
dierten Makros. Neben einfachen Makros besteht die Möglichkeit auch Makrofunktionen
zu schreiben.

1.10.1 Ein Beispielprogramm mit Makros und Makrofunktionen

Im folgenden Programm sehen wir wie ein Programm vor der Bearbeitung durch den
Präprozessor aussieht.

/**
* Berechnung der Fläche eines Kreises
*/
# include <iostream >

# define MY_PI 3.14


# define MY_CIRCLE_AREA (R) MY_PI *R*R

int main(void){
1.10 Präprozessor-Anweisungen 45

std :: cout << "pi=" << MY_PI << std :: endl;


srd :: cout << " circle area with radius R" << 2 << ":" <<
MY_CIRCLE_AREA (2) << std :: endl;
return 0;
}

Listing 1.64 cppsrc/src–cppxbase/pp.cpp

Danach sieht das Programm so aus (Listing 1.65), wobei wir hier nur den Teil des er-
zeugten Codes dargestellt haben, der für uns interessant ist.

....
int main(void){

std :: cout << "pi=" << 3.14 << std :: endl;


srd :: cout << " circle area with radius R" << 2 << ":" << 3.14*2*2 <<
std :: endl;
return 0;
}

Listing 1.65 cppsrc/src–cppxbase/pp.out

1.10.2 Vordefinierte Makros

Die vordefinierten Makros __FILE__ und __LINE__ werden vom Präprozessor durch
den Dateinamen bzw. durch die Zeilennummer ersetzt (Listing 1.66). __func__ und
__PRETTY_FUNCTION__ sind keine Makros, sondern vordefinierte Variablen, die vom Com-
piler ersetzt werden. Beide geben den Namen der Funktion wieder, in der sie stehen.
__PRETTY_FUNCTION__ enthält den Funktionsnamen inklusive Klassenzugehörigkeit und
Namensraum. __PRETTY_FUNCTION__ gehört nicht zum Standard, wird aber von den meis-
ten Compilern unterstützt.
Wird einem Makroargument ein # vorangestellt, wandelt der Präprozessor das Argu-
ment in eine Zeichenkette um. Im folgenden Beispiel nutzen wir dies, um eine Bedingung
als Zeichenkette auszugeben und so die Arbeit des Programms zur Laufzeit zu verfolgen.

/**
* @brief ex1 Präprozessor - Anweisungen
*/
inline void ex1 () {
// wandle Argument in Zeichenkette um
# define MYSTRING (x) #x
int n = 10;
std :: cout << " check condition :" << MYSTRING (n > 10) << std :: endl;
std :: cout << "in file: " << __FILE__ << std :: endl;
std :: cout << " function name: " << __func__ << std :: endl;
std :: cout << " pretty function name: " << __PRETTY_FUNCTION__ <<
std :: endl;
std :: cout << "at line: " << __LINE__ << std :: endl;
if (n > 10) {
std :: cout << "is true" << std :: endl;
46 1 Eine Tour durch C++

} else {
std :: cout << "is false " << std :: endl;
}
}

Listing 1.66 cppsrc/apps-x024/hpp/x024–01

check condition :n > 10


in file: ../../ cppdevsrc / numexp /apps/x024.h
function name: ex1
pretty function name: void nmx :: apps :: x024 :: ex1 ()
at line: 21
is false

Listing 1.67

1.10.3 Beispielanwendungen

Wir wollen in einem Beispiel mittels eines Makros den Aufruf von stl-Algorithmen für
den Fall, dass alle Elemente eines Containers durchlaufen werden müssen, vereinfachen.
Um in diesen Fall nicht immer die Anweisungen std::begin und std::end schreiben zu
müssen, führen wir ein Makro mit dem Namen ALL ein.

Quelltext und Daten In der folgenden Funktion wird eine Liste mit den Zahlen 0 bis 9
generiert. Anschließend wird nach dem ersten Element gesucht, das größer als 5 ist. Die
Zahlen ab diesem Element sowie die ganze Liste werden auf dem Bildschirm geschrieben
(Listing 1.70). Zum Schluss wird das eingeführte Makro ALL durch die Anweisung #undef
gelöscht.

# include <algorithm >


# include <iterator >

int main(void)
{
// definiere Makro
# define ALL(C) std :: begin (C), std :: end(C)

std ::list <double > lst (10);

std :: generate (ALL(lst), []() {


static double x = 0;
return x++;
});

// finde Element , welches größer als 5 ist


auto fitr = std :: find_if (ALL(lst), []( double x) { //
return x > 5;
});

std :: copy(lst. begin () , fitr , //


1.11 Schreiben und Lesen von Daten in Dateien 47

std :: ostream_iterator <double >( std ::cout , ","));


std :: cout << std :: endl;

// gebe alle Elemente auf dem Bildschirm aus


std :: copy(ALL(lst), //
std :: ostream_iterator <double >( std ::cout , ","));

// lösche Makro
#undef ALL
return 0;
}

Listing 1.68 cppsrc/src–cppxbase/pp1.cpp

Nachdem der Präprozessor das Makro ALL expandiert hat, sieht der für uns relevante
Quelltextabschnitt so aus:

int main(void)
{
std ::list <double > lst (10);
std :: generate (std :: begin (lst),std :: end(lst), []() {
static double x = 0;
return x++;
});
auto fitr = std :: find_if (std :: begin (lst),std :: end(lst),
[]( double x) { return x > 5; });
std :: copy(lst. begin () , fitr ,
std :: ostream_iterator <double >( std ::cout , ","));
std :: cout << std :: endl;
std :: copy(std :: begin (lst),std :: end(lst),
std :: ostream_iterator <double >( std ::cout , ","));
return 0;
}

Listing 1.69 cppsrc/src–cppxbase/pp1.out

0,1,2,3,4,5,
0,1,2,3,4,5,6,7,8,9,

Listing 1.70

1.11 Schreiben und Lesen von Daten in Dateien


Gerade im naturwissenschaftlichen Bereich speichern Programme die berechneten Daten
in Dateien, damit diese in Tabellen oder Diagrammen dargestellt werden können. C++
verfügt mit fstream, ofstream und ifstream über Objekte, die das Lesen und Schrei-
ben von Daten in Dateien ermöglichen. Der Vorteil beim Arbeiten mit diesen Objekten
ist, dass die Dateien mit Ablauf der Lebenszeit dieser Objekte automatisch geschlossen
werden. Bei fstream handelt es sich um ein Objekt, mit dem Daten aus einer Datei gele-
48 1 Eine Tour durch C++

sen und geschrieben werden können. Mit einem ifstream werden Daten von einer Datei
gelesen und mit einem ofstream Daten in eine Datei geschrieben.

1.11.1 Beispielanwendungen

Schreiben von Funktionswerten in Dateien, Version 1.0

Wir beginnen mit einem Beispielprogramm, welches die Funktionen f1 (x) = x2 , f2 (x) =
x3 , f3 (x) = x4 und f4 (x) = x5 auswertet und die Ergebnisse in eine Datei schreibt.

Quelltext und Daten Die Anwendung wird innerhalb einer Funktion realisiert. Da nicht
anders angegeben, werden die Daten durch ein Komma separiert in eine Datei geschrie-
ben werden. Mithilfe der Konfigurationsparameter (Listing 1.50) wird der Dateiname
inklusive Verzeichnis festgelegt. Der stream-Status wird getestet, um herauszufinden, ob
die Öffnung der Datei erfolgreich war. Anschließend werden mit einer Schleife die Funk-
tionswerte generiert und in die Datei geschrieben. Wir profitieren von der Tatsache, dass
nach Ablauf der Lebenszeit des ofstream-Objekts die Datei automatisch geschlossen wird.

/**
* @brief ex1 Ausgabe von Funktionswerten in eine Datei
*/
inline void ex1 () {
// Konfigurationsdaten nutzen
using namespace nmx :: settings ;
// Pfad zu einem Ausgabeverzeichnis benennen
Config :: set_current_dir ("x002");
// Dateiname zu Ausgabe der Werte
std :: string fname = Config :: get_current_file (__func__ , "dat");

try {
std :: ofstream fout{ fname };
// wurde die Datei zum Schreiben geöffnet ?
if (! fout) {
throw std :: ios_base :: failure { fname };
}
// Formatierung
fout << std :: scientific << std :: setprecision (2);
// Ausgabe
for ( double x = -2; x <= 2; x += 1) {
fout << std :: setw (9) << x << "," << std :: pow(x, 2) //
<< "," << std :: setw (9) << std :: pow(x, 3) //
<< "," << std :: setw (9) << std :: pow(x, 4) //
<< "," << std :: setw (9) << std :: pow(x, 5) << std :: endl;
}

} catch (std :: ios_base :: failure ferror ) {


std :: cout << " could not open file:" //
<< ferror .what () << std :: endl;
}
}

Listing 1.71 cppsrc/apps-x002/hpp/x002–01


1.11 Schreiben und Lesen von Daten in Dateien 49

Es folgt der von der Funktion erzeugte Dateiinhalt.

-2.00e+00 ,4.00e+00 , -8.00e+00 , 1.60e+01 , -3.20e+01


-1.00e+00 ,1.00e+00 , -1.00e+00 , 1.00e+00 , -1.00e+00
0.00e+00 ,0.00e+00 , 0.00e+00 , 0.00e+00 , 0.00e+00
1.00e+00 ,1.00e+00 , 1.00e+00 , 1.00e+00 , 1.00e+00
2.00e+00 ,4.00e+00 , 8.00e+00 , 1.60e+01 , 3.20e+01

Listing 1.72 data/x002/ex1.dat

Schreiben von Funktionswerten in Dateien, Version 2.0

Das soeben geschriebene Programm kann als Grundlage dienen, um beliebige Funkti-
onswerte zu berechnen und zu speichern. Wir können durch Austausch der Funktionen
innerhalb der Schleife und durch Änderung des Dateinamens das Programm zur Generie-
rung von Daten unserer Wahl nutzen. Wenn schnell ein Programm erstellt werden soll,
ist das eine akzeptable Lösung. Ist aber das Ziel, mehr als ein kleines Testprogramm
zu schreiben, sollte ein modularer Ansatz in Betracht gezogen werden. Das Öffnen ei-
ner Datei und die Generierung der Daten können als eigenständige Teile implementiert
werden.

/**
* @brief open_file öffne Ausgabestrom für eine Datei
* @param fname Dateiname
*/
auto open_file ( const std :: string & fname ) {
std :: ofstream fout{ fname };
// wurde die Datei zum Schreiben geöffnet ?
if (! fout) {
throw std :: ios_base :: failure { fname };
}
return fout;
}

Listing 1.73 cppsrc/apps-x002/hpp/x002–02

In einem zweiten Schritt überlegen wir, wie Werte für eine beliebige Anzahl von Funk-
tionen gespeichert werden können. Wir setzen als erstes ein Synonym für den Typ der
Funktionen, die wir auswerten wollen. Wir sparen dadurch das Schreiben von langen
Bezeichnern.

/**
* Synonym für eine Funktion mit Eingabe double , Rückgabewert double
*/
using RealFN = std :: function < double ( double ) >;

Listing 1.74 cppsrc/apps-x002/hpp/x002–03


50 1 Eine Tour durch C++

Zur Generierung der Funktionsdaten benötigen wir neben der Liste mit den Funktio-
nen auch den Wertebereich, innerhalb dessen die Funktionen ausgewertet werden. Die
Daten sollen in eine Datei geschrieben werden, was die Übergabe eines Ausgabestroms
notwendig macht.

/**
* @brief fn_data Ausgabe von Funktionswerten in eine Datei
* @param fout Ausgabestrom
* @param fnlst Liste mit Funktionen f(x) = y
* @param params Feld mit Intervallgrenzen und Schrittweite
*/
inline void fn_data (std :: ofstream &fout ,
std :: initializer_list <RealFN > fnlst ,
std :: array <double , 3> params ) {
// structured bindings jedes Element des Feldes wird
// einer Variablen zugeordnet
auto [xmin , xmax , dx] = params ;
for ( double x = xmin; x <= xmax; x += dx) {
// für jedes x
fout << x;
for (auto &fn : fnlst ) {
// werden die dazugehörigen Funktionswerte ausgegeben
fout << "," << fn(x);
}
fout << std :: endl;
}
}

Listing 1.75 cppsrc/apps-x002/hpp/x002–04

Es folgt ein Beispiel, welches mit den soeben erstellten Funktionen das Programm Listing
1.71 neu formuliert.

/**
* @brief ex2 schreibe Funktionsdaten für x^2,x^3,x^4,x^5
*/
inline void ex2 () {
using namespace nmx :: settings ;
Config :: set_current_dir ("x002");
std :: string fname = Config :: get_current_file (std :: string ( __func__ )
+ ".dat");

auto fn2 = []( double x) { return std :: pow(x, 2); };


auto fn3 = []( double x) { return std :: pow(x, 3); };
auto fn4 = []( double x) { return std :: pow(x, 4); };
auto fn5 = []( double x) { return std :: pow(x, 5); };

try {
// öffne Ausgabestrom
auto fout = open_file ( fname );
fout << std :: scientific << std :: setprecision (2);

// Berechne und schreibe Funktionsdaten mit xmin = -2,


// xmax =2 und dx = 0.5
fn_data (fout , { fn2 , fn3 , fn4 , fn5 }, { -2, 2, 0.5 });
1.12 Aufzählungen 51

} catch (std :: ios_base :: failure ferror ) {


std :: cout << " could not open file:" //
<< ferror .what () << std :: endl;
}
}

Listing 1.76 cppsrc/apps-x002/hpp/x002–05

1.12 Aufzählungen
Eine bequeme Art, eine Liste von ganzzahligen Konstanten als separaten Datentyp zu
definieren, sind Aufzählungen. Die Elemente der Liste werden im Standardfall von 0
aufwärts nummeriert. Aufzählungen können einen Namen haben, der gleichzeitig auch
der Name des Datentyps ist. Die Werte dieses Datentyps werden automatisch in int
umgewandelt. Eine automatische Konvertierung findet für die Elemente der neuen Vari-
ante enum class nicht statt. Hier ist im Standardfall eine explizite Umwandlung mittels
static_cast<int> notwendig. Im folgenden Beispiel wird mithilfe von enum und enum class
auf die Elemente eines Feldes zugegriffen.

/**
* @brief ex1 Aufzählungstypen
*/
inline void ex1 () {
enum Idx { a, b, c, d };
enum class CIdx { a, b, c, d };

std :: array <double , 4> v{ 1, 2, 3, 4 };


// Zugriff auf Elemente über Index :
// mit automatischer Konvertierung in int
std :: cout << v[Idx ::b] << std :: endl;
// Zugriff auf Elemente über Index :
// muss in int konvertiert werden
std :: cout << v[ static_cast <size_t >( CIdx ::b)] << std :: endl;
}

Listing 1.77 cppsrc/apps-x026/hpp/x026–01

1.13 Zeichenketten in C++


Das Arbeiten mit Zeichenkletten ist eine der wichtigsten Aufgaben von Computerpro-
grammen. Obwohl in den Naturwissenschaften der Schwerpunkt ganz klar auf dem Rech-
nen mit Zahlen liegt, ist die Verarbeitung von Zeichenketten ein Aspekt, der nicht außer
Acht gelassen werden sollte. Mit std::string stellt C++ eine Klasse zur Bearbeitung von
Zeichenketten zur Verfügung. Ein Blick auf die Anzahl der Elementfunktionen zeigt, wie
52 1 Eine Tour durch C++

umfangreich diese Klasse ist. Wir wollen uns in diesem Abschnitt auf das Suchen und
Ersetzen von Teilen einer Zeichenkette sowie die Aufteilung von Zeichenketten konzen-
trieren.

1.13.1 Beispielanwendungen

Zeichenketten für ein gegebenes Trennzeichen aufteilen

Eine vorgegebene Zeichenkette soll in Teilen aufgespalten werden und speziell an Stellen,
an denen ein vorgegebenes Trennzeichen vorkommt.

Kurzbeschreibung Wir werden die Klasse std::stringstream einsetzen, die ähnlich wie
std::cout arbeitet. Statt aber die Daten auf dem Bildschirm zu schreiben, werden diese
auf Zeichenfelder umgeleitet. Die Daten in einem std::stringstream können jederzeit als
ein std::string ausgegeben werden.

Quelltext und Daten Wir übergeben eine Zeichenkette einem std::stringstream, das
kombiniert mit dem Aufruf der Funktion std::getline diese Zeichenkette immer dort
spaltet, wo ein Trennzeichen (hier ein Komma) gefunden wird. Genauer formuliert liest
std::getline den Inhalt des std::stringstream und kopiert diesen in einem std::string,
bis ein vorgegebenes Trennzeichen vorkommt. In diesem Fall gibt std::getline false
zurück. Die Teile der Zeichenkette werden in die Variable token geschrieben und auf dem
Bildschirm ausgegeben (Listing 1.79).

/**
* @brief ex4 Zeichenketten für ein gegebenes Trennzeichen aufteilen
*/
inline void ex4 () {
std :: string txt = "eins ,zwei ,drei ,vier ,fünf ,sechs ,"
"sieben ,acht ,neun und zehn";

// Zeichenkette wird einem stringstream übergeben


std :: stringstream sstream { txt };
std :: string token ;

// übetrage Inhalt der Zeichenkette bis Trennzeichen vorkommt


while (std :: getline (sstream , token , ',')) {
// schreibe den extrahierten Teil der Zeichenkette mit
// anderem Trennzeichen
std :: cout << token << "-";
}
std :: cout << std :: endl;
}

Listing 1.78 cppsrc/apps-x006/hpp/x006–08

eins -zwei -drei -vier -fünf -sechs -sieben -acht -neun und zehn -

Listing 1.79
1.13 Zeichenketten in C++ 53

Zeichenketten aufteilen mit mehreren Trennzeichen

Das soeben besprochene Beispiel ist einfach zu programmieren, hat aber den Nachteil,
dass es die Angabe nur eines Trennzeichens erlaubt. Falls für eine Zeichenkette mehr als
ein Trennzeichen angegeben werden soll, müssen wir nach einer alternativen Möglichkeit
suchen, dies zu programmieren.

Quelltext Eine mögliche Lösung führt über die Aufrufe der Elementfunktionen
find_first_not_of und find_first_of von std::string.

/**
* @brief ex3 Zeichenketten aufteilen mit mehreren Trennzeichen
*/
inline void ex3 () {
// Textvorgabe
std :: string txt = "eins ,zwei ,drei ,vier ,fünf ,sechs ,"
"sieben ,acht ,neun und zehn";
// Trennzeichen : Leerzeichen und Komma
std :: string delim = " ,";
// Anfangsposition
size_t beg , pos = 0;
// finde die erste Stelle nach "pos" in der KEIN Trennzeichen
// gefunden wurde
while (( beg = txt. find_first_not_of (delim , pos)) !=
std :: string :: npos) {
// finde die erste Stelle in der EIN Trennzeichen gefunden wurde
pos = txt. find_first_of (delim , beg + 1);
// schreibe die gefunden Teil - Zeichenkette
std :: cout << beg << "\t" //
<< (pos != std :: string :: npos ? pos : 1000) //
<< "\t" << txt. substr (beg , pos - beg) << "\n";
}
}

Listing 1.80 cppsrc/apps-x006/hpp/x006–09

Daten Es folgt die Ausgabe des Programms. Jede extrahierte Zeichenkette wird zusam-
men mit den Positionen in der ursprünglichen Zeichenkette angegeben. Zu beachten ist,
dass im Programm nicht mit den expliziten Längen der Zeichenketten gearbeitet wird.
Der Grund ist, dass z.B. die Zeichenkette «fünf» intern nicht die Länge 4 hat.

0 4 eins
5 9 zwei
10 14 drei
15 19 vier
20 25 fünf <---
26 31 sechs
32 38 sieben
39 43 acht
44 48 neun
49 52 und
53 1000 zehn

Listing 1.81
54 1 Eine Tour durch C++

Suchen und Ersetzen von Zeichenketten innerhalb eines Texts

Wir wollen mit einem Beispiel die Schritte aufzeigen, die notwendig sind, um einen Teil
eines Textes zu suchen und zu ersetzen. Dabei sollen wieder Methoden der std::string-
Klasse zum Einsatz kommen. Im Beispieltext «die Bewegung eines Objekts in einer Flüs-
sigkeit beschreiben ...» soll das Wort «Objekts» (wir nennen es w1 ) durch das Wort
«Teilchens» (w2 ) ersetzt werden.

Quelltext Wir beginnen mit der Initialisierung der Variablen für den Beispieltext sowie
für w1 und w2 .

/**
* @brief ex2 Suchen und Ersetzen von Zeichenketten innerhalb
* eines Texts
*/
inline void ex2 () {
// Textvorlage
std :: string txt = "die Bewegung eines Objekts in einer"
" Flüssigkeit beschreiben ...";
std :: cout << "Text:" << std :: endl;
std :: cout << txt << std :: endl;
// Wort im Text
const std :: string w1 = " Objekts ";
// wird ersetzt durch
const std :: string w2 = " Teilchens ";

Listing 1.82 cppsrc/apps-x006/hpp/x006–10

In einem ersten Versuch suchen wir die Position des ersten Zeichens von w1 und löschen
ab dieser Position n Zeichen, wobei n die Länge von w1 ist. Anschließend fügen wir w2
an der Position des ersten Zeichens von w1 ein.

//**------------------
// Methode 1
// --------------------
std :: cout << " Methode 1:" << std :: endl;
auto txt1 = txt;
size_t pos = txt1.find(w1);
// finde Position von w1 und schneide es heraus
if (pos != std :: string :: npos) {
txt1.erase (pos , w1. length ());
std :: cout << txt1 << std :: endl;
} else {
std :: cout << "not found :" << std :: endl;
}
// füge an der Position eine neue Zeichenkette ein
if (pos != std :: string :: npos) {
txt1. insert (pos , w2);
std :: cout << txt1 << std :: endl;
} else {
std :: cout << "not found :" << std :: endl;
}

Listing 1.83 cppsrc/apps-x006/hpp/x006–11


1.13 Zeichenketten in C++ 55

In einem zweiten Versuch ersetzen wir beide Schritte durch einen. Hier wird wieder die
Position des ersten Zeichens von w1 gesucht und anschließend mit einer Elementfunktion
von std::string, durch w2 ersetzt.

//**----------------------
// Methode 2
// ------------------------
std :: cout << " Methode 2:" << std :: endl;
auto txt2 = txt;
pos = txt2.find(w1);
// ausschneiden und ersetzen mit einem Befehl
if (pos != std :: string :: npos) {
txt2. replace (pos , w1. length () , w2);
std :: cout << txt2 << std :: endl;
} else {
std :: cout << "not found :" << std :: endl;
}
}

} // namespace nmx :: apps :: x006

Listing 1.84 cppsrc/apps-x006/hpp/x006–12

Wir sehen in Listing 1.85 die Ausgabe des Programms. An erster Stelle befindet sich
der ursprüngliche Text; es folgt das Ersetzen von w1 durch w2 in zwei Schritten und
schließlich das Ganze in einem Schritt.

Text:
die Bewegung eines Objekts in einer Flüssigkeit beschreiben ...
Methode 1:
die Bewegung eines in einer Flüssigkeit beschreiben ...
die Bewegung eines Teilchens in einer Flüssigkeit beschreiben ...
Methode 2:
die Bewegung eines Teilchens in einer Flüssigkeit beschreiben ...

Listing 1.85

Suchen und Ersetzen von Zeichenketten innerhalb eines Texts

Wir wollen in diesem Abschnitt eine Physikaufgabe, die in Form einer Textdatei vorliegt,
mit einem Programm bearbeiten. Der Inhalt der Textdatei soll keine vollständig durchge-
rechnete Aufgabe sein, sondern vielmehr eine Vorlage, in der die bekannten Größen und
Antworten nicht im Dokument stehen, sondern an deren Stelle nur Platzhalter zu finden
sind. Dem Programm sollen die bekannten Größen übergeben werden, welches dann die
Antworten berechnet und bekannte sowie berechnete Größen im Text ersetzt. Wir begin-
nen mit der Formulierung der Aufgabe, die wir ganz normal lösen werden, damit wir auf
dieser Basis mit der Erstellung des Programms und der Textvorlage fortfahren können.
56 1 Eine Tour durch C++

Mechanische Energie einer Masse in einer kreisförmigen Umlaufbahn um


die Erde

Eine Masse m wird in einer Höhe h von der Erdoberfläche in eine kreisförmige Umlauf-
bahn um die Erde gebracht. Wie groß ist die mechanische Energie der Masse?

Lösung Die potenzielle Energie des Systems Masse-Erde wird durch

Mm
U = −G (1.15)
R
gegeben, wobei M die Masse der Erde und R der Abstand der Masse vom Erdmittelpunkt
ist. Zur Berechnung der Geschwindigkeit der Masse nutzen wir die Tatsache, dass die
Gravitation die Rolle der Zentripetalkraft spielt.

Mm v2 M M
G 2 =m ⇒ v2 = G ⇒v= G , (1.16)
R R R R
wobei wir hier direkt die Beträge gleichgesetzt haben. Es folgt die gesamte mechanische
Energie:
Mm
E = K + V = −G (1.17)
2R
Erstellung einer Vorlage für die Aufgabe Wir speichern die Aufgabe in leicht abgeän-
derter Form als Textdatei ab, die wir als Vorlage nutzen wollen.

---------------------Aufgabe -----------------------------------------
Eine Masse m=$1 kg wird in einer Höhe h=$2 m von der
Erdoberfläche in eine kreisförmige Umlaufbahn um die Erde gebracht .
Wie groß ist die mechanische Energie der Masse ?
--------------------Antwort -----------------------------------------
Der Bahnradius beträgt R=$3 m
und die mechanische Energie ist E=$4 J

Listing 1.86 data/x006/qstn1.txt

Im Vergleich zum ursprünglichen Aufgabentext hat sich Folgendes geändert: Jeder be-
kannten sowie gesuchten Größe wird ein =-Zeichen sowie ein Platzhalter hintenan gestellt.
Die Platzhalter sind nummeriert, sodass sie vom Programm eindeutig identifiziert werden
können. Alle Größen werden in SI-Einheiten angegeben.

Kurzbeschreibung Wir benötigen zur Erstellung des Programms einen Container zur
Speicherung des Texts, der aus der Datei geladen wird. Zusätzlich wird ein assoziativer
Container, der jedem Platzhalter eine Zahl zuordnet, benötigt. Der Text soll im ersten
Schritt geladen werden und Zeile für Zeile nach Platzhaltern durchsucht werden. Jedes
Mal, wenn einer gefunden wird, ersetzt das Programm diesen durch den dazugehörigen
Wert.
1.13 Zeichenketten in C++ 57

Quelltext Wir wollen lange Bezeichner vermeiden und setzen Synonyme für die Con-
tainer zur Speicherung des Texts und der Platzhalter-Werte-Paare. Für den Text wählen
wir einen std::vector der jede gelesene Zeile des Textes als Element betrachtet und am
Ende einfügt. Der andere Container wird eine std::map sein, die jedem Platzhalter eine
Zahl zuordnen wird.

// Verzeichnisname und Synonyme


const std :: string dirname = settings :: Config :: data_directory + "/x006";

using Document = std :: vector <std :: string >;


using Vartable = std ::map <std :: string , double >;

Listing 1.87 cppsrc/apps-x006/hpp/x006–01

Es folgt eine Funktion zum Einlesen des Texts. Nach der Öffnung des Eingabestroms
wird jede Zeile eingelesen und im Vektor gespeichert.

/**
* @brief read_text Einlesen des Texts
*/
inline auto read_text () {
const std :: string fname = dirname + "/qstn1 .txt";
// öffne Datei
std :: ifstream ifs{ fname };
if (! ifs) {
throw std :: ios_base :: failure (" error open:" + fname);
}

std :: string line;


Document lines ;
// lese Datei zeilenweise und speichere in std :: vector ab
while ( getline (ifs , line)) {
lines. push_back (line);
}

return lines ;
}

Listing 1.88 cppsrc/apps-x006/hpp/x006–02

Die Berechnung der fehlenden Größen ist Aufgabe der nächsten Funktion. Ihr wird die
Adresse des Containers mit den bekannten Größen übergeben, die unbekannten werden
berechnet und dem Container hinzugefügt.

/**
* @brief calc Berechnung der fehlenden Größen
* @param values (std :: map) enthält die vorgegebenen und
* die zu berechnenenden Werte in der Form (Name ,Wert)
*/
inline void calc( Vartable & values ) {
using namespace gravitation ;
double mass = values .at("$1");
double height = values .at("$2");
double radius = Earth :: radius + height ;
58 1 Eine Tour durch C++

double energy = -0.5 * Astronomy ::G * Earth :: mass * mass / radius ;


// setze berechnete Werte
values ["$3"] = radius ;
values ["$4"] = energy ;
}

Listing 1.89 cppsrc/apps-x006/hpp/x006–04

Das Suchen und Ersetzen im Text findet in einer separaten Funktion statt. Es wird über
die Zeilen des Dokuments iteriert. In jeder Zeile wird überprüft, ob sie einen Platzhalter
enthält, der dann ersetzt wird. Dafür werden die Elementfunktionen von std::string aus
der Standardbibliothek genutzt.

/**
* @brief calc_replace
* @param txt das Dokument als ein Feld von Zeichenketten
* @param values Tabelle mit ( Platzhalter , berechnete Werte )
*/
inline auto replace ( Document &txt , const Vartable & values ) {
// Iteration über alle Zeilen des Dokuments
for (auto &line : txt) {
// Iteration über alle Platzhalter
for (const auto &item : values ) {
// Platzhalter
const auto key = item. first ;
// Wert als Zeichenkette
const auto val = convert (item. second );
size_t pos;
// suche
while (( pos = line.find(key)) != std :: string :: npos) {
// ersetze
line. replace (pos , key. length (), val);
}
}
}
}

Listing 1.90 cppsrc/apps-x006/hpp/x006–05

Zur Konvertierung einer Zahl in eine Zeichenkette wird folgende Funktion eingesetzt,
welche zusätzlich eine Formatierung durchführt.

/**
* @brief convert Konvertierung der Zahl in eine Zeichenkette
* @param x Zahl
* @return Zahl formatiert als Zeichenkette
*/
inline auto convert ( double x) {
std :: stringstream sstream ;
sstream << std :: scientific << std :: setprecision (2) << x;
return sstream .str ();
}

Listing 1.91 cppsrc/apps-x006/hpp/x006–03


1.13 Zeichenketten in C++ 59

Das Dokument wird mithilfe der nächsten Funktion abgespeichert. Nach der Überprü-
fung, ob die Ausgabedatei erfolgreich geöffnet wurde, wird über die Elemente des Con-
tainers iteriert und in die Datei geschrieben.

/**
* @brief save Dokument wird gespeichert
* @param doc das Dokument
*/
inline void save_answer ( const Document &doc) {
const std :: string fname = dirname + "/ answr1 .txt";
std :: ofstream ofs{ fname };

if (! ofs) {
throw std :: ios_base :: failure (" error open:" + fname);
}

for (const auto &line : doc) {


ofs << line << std :: endl;
}
}

Listing 1.92 cppsrc/apps-x006/hpp/x006–06

Folgendes Beispielprogramm zeigt, wie diese Funktionen zusammenarbeiten.

/**
* @brief ex1 Bearbeitung eines Texts und Berechnung der
* unbekannten physikalischen Größen
*/
inline void ex1 () {
auto doc = read_text ();
Vartable vtable { { "$1", 10 }, { "$2", 300. _km } };
calc( vtable );
replace (doc , vtable );
save_answer (doc);
}

Listing 1.93 cppsrc/apps-x006/hpp/x006–07

Es folgt das Ergebnis der Bearbeitung.

---------------------Aufgabe -----------------------------------------
Eine Masse m=1.00 e+01 kg wird in einer Höhe h=3.00e+05 m von der
Erdoberfläche in eine kreisförmige Umlaufbahn um die Erde gebracht .
Wie groß ist die mechanische Energie der Masse ?
--------------------Antwort -----------------------------------------
Der Bahnradius beträgt R =6.68 e+06 m
und die mechanische Energie ist E= -2.98e+08 J

Listing 1.94 data/x006/answr1.txt


60 1 Eine Tour durch C++

1.14 Übungsaufgaben
1. Speichern Sie die Zahlen 1, 2, ..., 100 in einem std::vector und berechnen Sie anschlie-
ßend mit dem stl-Algorithmus std::accumulate die Summe dieser Zahlen.
2. Berechnen Sie den Ausdruck eiπ + 1.
3. Das Paket pgfplots basiert auf TikZ und wird zur Erzeugung von Diagrammen direkt
in den LATEX-Quelltext integriert. Folgender Quelltext

\begin{ tikzpicture }
\begin{axis }[ xlabel = $x$, ylabel = {$f(x)$},]
\ addplot [ domain = -2:1 , samples =100 , color=black ,]{2*x^2 + x - 1};
\end{axis}
\end{ tikzpicture }

Listing 1.95

erzeugt das Diagramm für die Funktion f (x) = 2x2 + x − 1 (Abb. 1.10), wobei mit
domain = -2:1 die Grenzen entlang der x-Achse, mit color=black die Farbe der Kurve
und mit 2*x^2 + x - 1 die zu zeichnende Funktion angegeben wird. Führen Sie Platz-
halter der Form @1,@2,... für die Angabe der Grenzen, der Farbe und der Funktion
ein und speichern Sie den Quelltext (Listing 1.95) als Vorlage ab. Erzeugen Sie mit
einem Programm durch Bearbeitung der abgespeicherten Vorlage, ein Diagramm für
f (x) = x3 und x ∈ [−1, 1]. Wählen Sie für die Farbe der Kurve color=red.
4. Ein Fahrzeug startet aus der Ruhelage mit einer Beschleunigung aA = 1 m/s2 und
befindet sich 300 m hinter einem zweiten Fahrzeug B, welches sich mit konstanter
Geschwindigkeit vB = 10 m/s bewegt. Schreiben Sie ein Programm, welches die Be-
wegungsdaten t, xA (Ort des Fahrzeugs A), xB (Ort des Fahrzeugs B), vA , vB für
die zwei Fahrzeuge aufzeichnet und diese in die Standradausgabe schreibt.

4
f (x)

−2 −1.5 −1 −0.5 0 0.5 1


x

Abb. 1.10: Die Funktion f (x) = 2x2 + x − 1


2 Klassen in C++

Übersicht
2.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.2 Einfache Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.3 Klassentemplates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
2.4 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
2.5 Programmieren mit Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
2.6 Schnittstellen für Klassen mithilfe von Templates definieren . . . . . . . . . . . . . . . 119
2.7 Eigene Werkzeuge bauen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
2.8 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

2.1 Einleitung
C++ ist keine rein objektorientierte Sprache. Sie unterstützt aber neben anderen auch
diesen Programmierstil, in dem Klassen das zentrale Element sind. Es handelt sich dabei
um benutzerdefinierte Vorlagen zur Konstruktion von Objekten, die über eigene Daten
verfügen und mittels eigener Funktionen, den Elementfunktionen, diese Daten manipu-
lieren. Instanzen einer Klasse sind Objekte, die mithilfe dieser Vorlagen erzeugt werden
und über ihre Elementfunktionen mit anderen Objekten interagieren, die nicht von dem-
selben Typ sein müssen. Es entsteht durch die Verwendung des Begriffs «Objekt» der
Eindruck, dass mit Klassen nur Gegenstände des Alltags abgebildet werden können. Dies
ist jedoch nicht richtig. Mit Klassen können genauso gut Prozesse beschrieben werden,
die durch die Interaktion von Objekten entstehen, die wiederum Instanzen von Klassen
sind. Als Beispiel könnte hier die Beschreibung der Bewegung einer Rakete dienen, in
der mit einer Klasse die Rakete selbst und mit einer anderen Klasse die Bewegungsdaten
ihrer Flugbahn verwaltet werden. Allgemein kann jedes System, welches einen inneren
Zustand besitzt und diesen durch Interaktion mit anderen Systemen verändern kann,
als Klasse dargestellt werden. Zu den wichtigsten Eigenschaften einer Klasse (neben der,
eigene Daten und Elementfunktionen zu besitzen) gehört die Möglichkeit, Eigenschaften
von anderen Klassen zu erben und die eigenen Eigenschaften an andere zu vererben.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_2
62 2 Klassen in C++

2.2 Einfache Klassen


Der Weg von der Beobachtung eines Systems zu dessen Abbildung durch eine Klasse er-
fordert die Identifizierung seiner Eigenschaften und ihre Beschreibung im Quelltext durch
Attribute. So könnte z.B. ein Dokument als eine Klasse abgebildet werden, die intern den
Text in Form von Zeichenketten verwaltet und mittels Elementfunktionen in diesem Text
suchen oder gar den Text manipulieren kann. Eine Zahlentabelle wäre ein anderes Bei-
spiel, in dem Zahlen in einem Feld gespeichert werden und mittels Elementfunktionen
mathematische Operationen ausgeführt werden könnten.

2.2.1 Zugriff auf Elemente der Klassen

Sowohl Daten als auch Elementfunktionen einer Klasse können Zugriffsrechte zugewiesen
werden. Mit diesen wird festgelegt, wer auf die Elemente der Klasse zugreifen darf. Durch
die Schlüsselwörter public, protected und private wird eine Klasse in Bereiche aufgeteilt.
Daten und Elementfunktionen, die sich im public-Bereich befinden, unterliegen keiner
Zugriffsbeschränkung und sind auch außerhalb der Klasse sichtbar und, wenn nicht als
konstant deklariert, auch manipulierbar. Die Summe aller public-Elementfunktionen bil-
det die Schnittstelle eines Objekts zur Außenwelt. Sind Daten und Elementfunktionen im
protected-Bereich deklariert, können diese nur von public abgeleiteten Klassen genutzt
werden. Ausschließlich innerhalb der Klasse sichtbar sind alle ihre Elemente, welche im
private-Bereich deklariert wurden (Listing 2.1).

/**
* @brief The Example class Beispielklasse
*/
class Example
{
private :
// Daten der Klasse
// Zugriff nur innerhalb der Klasse
double _a , _b;
// ......
protected :
// nur innerhalb der Klasse und
// von abgeleiteten Klassen sichtbar
void set_a( double x) { _a = x; }

public :
// ...
// Elementfunktion greift auf
// private Daten zu. Ist selbst
// außerhalb der Klasse sichtbar
double sum () const { return _a + _b; }
// ...
};

Listing 2.1 cppsrc/src–cppxbase/cppxbase–01


2.2 Einfache Klassen 63

2.2.2 Eine Klasse für das mathematische Pendel

Als erstes Beispiel zur Konstruktion einer Klasse wählen wir das mathematische Pen-
del. Unter diesem Begriff finden wir in Physikbüchern nicht nur die Beschreibung des
Gegenstands, sondern auch die seiner Bewegung durch die Aufstellung und Lösung der
Bewegungsgleichungen. Wir werden uns hier auf die Abbildung der Eigenschaften des
Systems mittels einer Klasse konzentrieren und die Bewegung außer Acht lassen. Damit
hätten wir ein sehr einfaches System, dass aus einer dünnen, masselosen Stange besteht,
die am oberen Ende so befestigt ist, dass sie sich ohne Reibung drehen kann, und einer
Masse, z.B. einer Kugel, die am anderen Ende befestigt ist und hin und her schwingen
kann (Abb. 2.1). Zu den Eigenschaften des Pendels gehören die Länge der Stange l und
die Masse der Kugel
√ m. Nicht dazu gehören z.B. die Auslenkung φ und die Schwingungs-
dauer T0 = 2π gl . Während der Winkel φ klar zur Beschreibung der Bewegung des
Systems gehört, ist nicht sofort ersichtlich, warum die Schwingungsdauer keine Eigen-
schaft des Systems ist, hängt sie doch nur von l ab. Ein Argument ist, dass diese Formel
nur für kleine Ausschläge gilt und für größere der Maximalausschlag mit in die Formel
aufgenommen wird.

l l
φ m
m

(a) (b)
Abb. 2.1: Mathematisches Pendel. (a) Ruhelage (b) Ausgelenkt um einen Winkel φ

Entwurf Aus der Beschreibung des Systems folgt, dass die Klasse für das Pendel die
Länge l und die Masse m intern speichern wird. Auf diese zwei Attribute wird nur ein
lesender Zugriff erlaubt sein, da ihre Änderung die Beschreibung eines anderen Pendels
zur Folge hätte.

Quelltext Wir beginnen mit der Programmierung der Klasse und speichern die Masse
und die Länge des Pendels im private-Bereich ab.

/**
* @brief The Pendulum class Eigenschaften eines mathematischen Pendels
*/
class Pendulum
{
private :
double _length , _mass ;

public :

Listing 2.2 cppsrc/apps-x009/hpp/x009–01


64 2 Klassen in C++

Private Daten können außerhalb der Klasse nur mithilfe von Elementfunktionen geän-
dert werden. Da wir dies aber nicht zulassen wollen, kann ihre Initialisierung nur über
den Konstruktor erfolgen. Innerhalb des Konstruktors wird geprüft, ob gültige Werte
übergeben wurden.

/**
* @brief Pendulum Konstruktor
* @param l Länge
* @param m Masse
*/
Pendulum ( double l, double m)
: _length { l }
, _mass { m } {
// Länge und Masse sind positive Größen
if (l <= 0 || m <= 0) {
throw std :: domain_error ( __func__ );
}
}

Listing 2.3 cppsrc/apps-x009/hpp/x009–02

Die Beschreibung der Klasse ist mit den beiden folgenden Funktionen zum Lesen der
Länge und der Masse abgeschlossen.

/**
* @brief length Zugriff auf interne Variable
* @return Länge des Pendels
*/
double length () const { return _length ; }

Listing 2.4 cppsrc/apps-x009/hpp/x009–03

/**
* @brief mass Zugriff auf interne Variable
* @return Masse des Pendels
*/
double mass () const { return _mass ; }
}; // Pendulum

Listing 2.5 cppsrc/apps-x009/hpp/x009–04

Beispiel

Es folgt ein kleines Beispielprogramm (Listing 2.6), welches eine Instanz eines mathema-
tischen Pendels erstellt und seine Eigenschaften sowie die Schwingungsdauer für kleine
Ausschläge auf den Bildschirm ausgibt. Die Schwingungsdauer berechnen wir mit Ele-
mentfunktionen der soeben erstellten Klasse.
2.2 Einfache Klassen 65

/**
* @brief ex1 Beispiel : Eigenschaften eines mathematischen Pendels
*/
inline void ex1 () {
using namespace gravitation ;
Pendulum p{ 1, 2 };
// Schwingungsdauer für kleine Auslenkungen
const double T0 = 2 * Math :: PI * std :: sqrt(p. length () / Earth ::g);
std :: cout << p. length () << std :: endl;
std :: cout << p.mass () << std :: endl;
std :: cout << T0 << std :: endl;
}

} // namespace nmx :: apps :: x009

Listing 2.6 cppsrc/apps-x009/hpp/x009–05

2.2.3 Die Beschreibung einer Hook’schen Feder

Wir werden mit einer Klasse die physikalischen Eigenschaften einer idealen Feder be-
schreiben, welche entlang ihrer Achse gedehnt oder gestaucht wird. Zur Konstruktion
der Klasse werden Informationen zu den Eigenschaften von Federn benötigt. Als Infor-
mationsquelle könnten die Daten eines Experiments oder eine Aufgabe aus einem Phy-
sikbuch genutzt werden. In typischen Physikaufgaben wird die Bewegung einer Masse,
welche am Ende einer Feder befestigt ist, untersucht. Im Idealfall besitzt die Feder selbst
keine Masse oder diese ist im Vergleich zur befestigten Masse vernachlässigbar klein.
Die Feder übt auf die Masse eine Kraft F = −kx aus, welche proportional zur Auslen-
kung ist. Die Konstante k heißt Federkonstante und ist eine Eigenschaft der Feder. Über
die Federkonstante lässt sich die in der Feder gespeicherte elastische potenzielle Energie
U = 12 kx2 berechnen. Damit hätten wir alle Informationen, um zum Entwurf der Klasse
überzugehen.

x=0 x=0
F = −kx F = −kx
k m k

x x
(a) (b)
Abb. 2.2: (a) Die gedehnte Feder übt eine der Auslenkung proportionale Kraft auf eine
Masse m aus. (b) Die Feder wird isoliert betrachtet und dient als Vorlage für die zu
implementierende Klasse.

Entwurf Da die Feder masselos ist, bleibt die Federkonstante k als einzige konstante
charakteristische Eigenschaft übrig. Diese wird einmal bei der Erzeugung einer Instanz
gesetzt und darf über eine Methode der Klasse nur gelesen werden. Die Auslenkung ist
66 2 Klassen in C++

ebenfalls ein Zustand der Feder, die aber durch Interaktion mit einem anderen Objekt
verändert werden kann. Wir werden die momentane Auslenkung der Feder in einer Va-
riablen speichern. Ihr Wert soll aber mittels einer Elementfunktion zu verändern sein.
Die Kraft und die potenzielle Energie hängen unmittelbar mit der Auslenkung zusammen
und sind auch Eigenschaften der Feder. Wir werden die Klasse so programmieren, dass
die beiden Größen als Variablen innerhalb der Klasse angelegt und dass bei Änderung
der Auslenkung diese automatisch neu berechnet werden.
Zwei oder mehrere Federn können miteinander zu einem System kombiniert werden,
das wieder die Eigenschaften einer Feder hat. Die resultierende Federkonstante wird aus
den beiden Federkonstanten des Systems berechnet. Wir möchten die Berechnung einer
äquivalenten Feder für zwei oder mehrerer Federn im Entwurf der Klasse mit berücksich-
tigen.

Quelltext Wir speichern im private-Bereich mit vier Variablen, die im Entwurf aufge-
listeten Attribute der Klasse. Außer der Federkonstanten haben alle anderen Attribute
bei Erzeugung einer Instanz den Wert 0. Dies entspricht der Situation einer entspannten
Feder.

/**
* @brief The Spring class Klasse
* Eigenschaften einer idealen Feder
*/
class Spring
{
private :
double _k; // Federkonstante
double _elongation = 0; // Auslenkung
// Kraft , elastische potenzielle Energie
double _force = 0, _ePotential = 0;

public :

Listing 2.7 cppsrc/apps-x010/hpp/x010–01

Es soll nicht möglich sein, eine Instanz ohne die Angabe einer Federkonstante zu erzeu-
gen. Deswegen erhält die Klasse einen Konstruktor mit einem Eingabeargument für die
Federkonstante. Dieses ist immer eine positive Zahl. Innerhalb des Konstruktors wird
überprüft, ob k diese Bedingung erfüllt.

/**
* @brief Spring Konstruktor
* @param k Federkonstante
*/
Spring ( double k)
: _k{ k } {
if (_k <= 0) {
throw std :: domain_error ( __func__ );
}
}

Listing 2.8 cppsrc/apps-x010/hpp/x010–02


2.2 Einfache Klassen 67

Wird die Feder gedehnt oder gestaucht, wird nicht nur der Wert der Auslenkung an-
gepasst, sondern auch die Kraft und die potenzielle Energie werden neu berechnet. Die
Richtung der Auslenkung wird über das Vorzeichen bestimmt.

/**
* @brief set_elongation setze Wert für die Auslenkung
* @param x Auslenkung
*/
void set_elongation ( double x) {
_elongation = x; // neue Auslenkung
_force = -_k * x; // aktualisiere Kraft
// aktualisiere potenzielle Energie
_ePotential = 0.5 * _k * pow( _elongation , 2);
}

Listing 2.9 cppsrc/apps-x010/hpp/x010–03

Die Werte von k, x, F und U können über folgende Elementfunktionen einzeln abgefragt
werden.

/**
* @brief k lese ...
* @return Federkonstante
*/
double k() const { return _k; }

Listing 2.10 cppsrc/apps-x010/hpp/x010–04

/**
* @brief elongation lese ...
* @return die Auslenkung der Feder
*/
double elongation () const { return _elongation ; }

Listing 2.11 cppsrc/apps-x010/hpp/x010–05

/**
* @brief force lese ...
* @return Federkraft ( momentaner Zustand der Feder)
*/
double force () const { return _force ; }

Listing 2.12 cppsrc/apps-x010/hpp/x010–06

/**
* @brief e_potential lese ...
* @return elastische potenzielle Energie
* ( momentaner Zustand der Feder)
*/
double e_potential () const { return _ePotential ; }

Listing 2.13 cppsrc/apps-x010/hpp/x010–07


68 2 Klassen in C++

Es besteht mit einer zusätzlichen Elementfunktion die Möglichkeit, Auslenkung, Kraft


und potenzielle Energie gleichzeitig zu lesen.

/**
* @brief values
* @return Feld mit Auslenkung ,Kraft , potenzielle Energie
*/
auto values () const { //
return std :: array { _elongation , _force , _ePotential };
}

Listing 2.14 cppsrc/apps-x010/hpp/x010–08

Die Implementierung der Klasse wäre an dieser Stelle abgeschlossen, wenn wir uns nicht
vorgenommen hätten, aus einzelnen Federn zusammengesetzte Systeme zu untersuchen.
Wir beschränken uns hier der Einfachheit halber auf eindimensionale Systeme und ent-
nehmen die nötigen Informationen aus Abb. 2.3. Es gibt zwei Möglichkeiten zwei Federn
miteinander zu kombinieren. Die erste ist, die Federn hintereinander (in Reihe, Abb.
2.3b) und die zweite ist, sie parallel zueinander zu schalten (Abb. 2.3c).

k1 k1 k1 k2 k1 k2

k2 k3

(a) (b) (c) (d)


Abb. 2.3: (a) Eine Feder (b) Reihenschaltung von zwei Federn (c) Parallelschaltung von
zwei Federn (d) Kombination aus drei Federn

Bevor wir mit der Implementierung der Funktionen beginnen, geben wir die Formeln
zur Berechnung von äquivalenten Federn ohne Herleitung an:

1 1 1
= + (Reihenschaltung) (2.1a)
k k1 k2
k = k 1 + k2 (Parallelschaltung) (2.1b)

Für jede der oben aufgelisteten Formeln wird eine Funktion implementiert. Dabei kann
es sich nicht um Elementfunktionen der Klasse handeln, da sie keine Eigenschaften ei-
ner Instanz verwalten oder manipulieren, sondern vielmehr neue Instanzen generieren.
Der Bezug zur Klasse ist jedoch gegeben; deswegen führen wir die folgenden statischen
Funktionen ein.
2.2 Einfache Klassen 69

/**
* @brief add_paralell Parallel - Schaltung von zwei Federn
* @param s1 erste Feder
* @param s2 zweite Feder
* @return äquivalente Feder
*/
inline static Spring add_paralell ( const Spring &s1 , const Spring
&s2) {
double k = s1.k() + s2.k();
return Spring { k }; // Kopierkonstruktor automatisch generiert
}

Listing 2.15 cppsrc/apps-x010/hpp/x010–09

/**
* @brief add_series Reihen - Schaltung von zwei Federn
* @param s1 erste Feder
* @param s2 zweite Feder
* @return äquivalente Feder
*/
inline static Spring add_series ( const Spring &s1 , const Spring s2) {
double tmp = 1. / s1.k() + 1. / s2.k();
// Kopierkonstruktor automatisch generiert
return Spring { 1 / tmp };
}
}; // Spring

Listing 2.16 cppsrc/apps-x010/hpp/x010–10

Obwohl wir keinen Kopierkonstruktor für die Klasse Spring implementiert haben, wird
einer innerhalb der Funktionen genutzt. Der Compiler hat in diesen Fällen einen Kopier-
konstruktor automatisch generiert.

Beispiel

Eine äquivalente Feder wird für die Kombination aus Abb. 2.3d berechnet. Es werden drei
Instanzen der Klasse Spring erzeugt. Mithilfe der eingeführten statischen Funktionen (Lis-
ting 2.15 und Listing 2.16) wird die äquivalente Feder berechnet. Für die Federkonstanten
werden die Beispielwerte k1 = 200 N/m, k2 = 100 N/m und k3 = 100 N/m eingesetzt.
Die äquivalente Feder wird um x = 0.1 m gedehnt. Berechnet wird die Kraft, welche
von der Feder auf eine befestigte Masse ausgeübt wird sowie die in der Feder gespei-
cherte elastische potenzielle Energie. Die Daten werden anschließend auf dem Bildschirm
angezeigt (Listing 2.18).

/**
* @brief ex1 Berechnung von äquivalenten Federn
*/
inline void ex1 () {
const Spring s1{ 200 }, s2{ 100 }, s3{ 100 };
Spring s = Spring :: add_paralell (s1 , s2);
70 2 Klassen in C++

s = Spring :: add_series (s3 , s);


// Auslenkung
const double x = 0.10;
std :: cout << "k1=" << s1.k() << " N/m,";
std :: cout << "k2=" << s2.k() << " N/m,";
std :: cout << "k3=" << s3.k() << " N/m" << std :: endl;
std :: cout << "k=" << s.k() << " N/m" << std :: endl;
// Feder wird gedehnt
s. set_elongation (x);
std :: cout << "x=" << x << " m," //
<< "F=" << s. force () << " N," //
<< "U=" << s. e_potential () << " J" << std :: endl;
}
} // namespace nmx :: apps :: x010

Listing 2.17 cppsrc/apps-x010/hpp/x010–11

k1 =200 N/m,k2 =100 N/m,k3 =100 N/m


k=75 N/m
x=0.1 m,F= -7.5 N,U =0.375 J

Listing 2.18

2.2.4 Eine Klasse für rationale Zahlen

Wenn eine reelle Zahl als das Verhältnis zweier ganzer Zahlen dargestellt werden kann,
so haben wir eine rationale Zahl, die durch einen Bruch m n
mit n, m ∈ Z und m ̸=
0 dargestellt werden kann. Diese Zahlen können unter anderem addiert, subtrahiert,
multipliziert und dividiert werden.

Entwurf Die Attribute der Klasse sind schnell gefunden. Es sind der Nenner und Zähler
einer rationalen Zahl.

Quelltext Beide Attribute sollen außerhalb der Klasse nicht verändert werden können.
Andernfalls hätte dies zur Folge, dass ein Objekt seine Identität verlieren würde. Deshalb
werden Nenner und Zähler im private-Bereich gespeichert.

/**
* @brief Rational Klasse für rationale Zahlen
*/
class Rational
{
private :
long long _num , _den;

public :

Listing 2.19 cppsrc/apps-x011/hpp/x011–01


2.2 Einfache Klassen 71

Der einzige Konstruktor der Klasse erwartet einen Wert für Zähler und Nenner. Werden
keine Werte angegeben, ist der Zähler 0 und der Nenner 1. Innerhalb des Konstruktors
wird geprüft, ob der Nenner einen gültigen Wert besitzt, d.h. nicht 0 ist. Im Fall eines
ungültigen Wertes wird eine Ausnahme ausgeworfen. Zusätzlich werden, falls der Nenner
negativ ist, Zähler und Nenner mit −1 multipliziert.

/**
* @brief Rational Konstruktor
* @param n Nenner
* @param d Zähler
*/
inline Rational (long long n = 0, long long d = 1)
: _num{ n }
, _den{ d } {
if (d == 0) {
throw std :: domain_error ( __func__ );
}
if (d < 0) {
_num *= -1;
_den *= -1;
}
}

Listing 2.20 cppsrc/apps-x011/hpp/x011–02

Nenner und Zähler können mittels der beiden folgenden Elementfunktionen gelesen wer-
den.

/**
* @brief num lese ...
* @return Zähler
*/
inline auto num () const { return _num; }

Listing 2.21 cppsrc/apps-x011/hpp/x011–03

/**
* @brief den lese ...
* @return Nenner
*/
inline auto den () const { return _den; }

Listing 2.22 cppsrc/apps-x011/hpp/x011–04

Die Funktion der Standardbibliothek std::gcd berechnet den größten gemeinsamen Teiler
zweier Zahlen. Wir nutzen diese Funktion, um den Bruch zu vereinfachen.

/**
* @brief simplify vereinfache Bruch
*/
inline void simplify () {
long gcd = std :: gcd(_num , _den);
72 2 Klassen in C++

_num /= gcd;
_den /= gcd;
}

Listing 2.23 cppsrc/apps-x011/hpp/x011–05

Die Klasse enthält einen Typ-Umwandlungsoperator, mit dem Instanzen in double kon-
vertiert werden. Dies ist nützlich, wenn mit gemischten Termen aus rationalen und reellen
Zahlen gerechnet wird. Durch das Schlüsselwort explicit lassen wir es nicht zu, dass eine
Konvertierung vom Compiler automatisch durchgeführt wird.

/**
* @brief operator double Darstellung als reelle Zahl
*/
explicit operator double () const { //
return static_cast <double >( _num) / static_cast <double >( _den);
}

Listing 2.24 cppsrc/apps-x011/hpp/x011–06

Mit folgendem Ausdruck wird zur Übersetzungszeit getestet, ob ein Datentyp eine ganze
Zahl repräsentiert. Mehr Information zu diesen Ausdrücken sind unter dem Stichwort
type_traits zu finden.

/**
* Test: ist T eine ganze Zahl?
* std :: is_arithmetic_v true wenn z.B. int , bool , float ,...
* std :: is_integral_v true wenn z.B. int , bool , char ,...
*/
template <class T>
static constexpr bool is_int_like_v = //
(std :: is_arithmetic_v <T> && std :: is_integral_v <T> //
&& !std :: is_same_v <T, bool > && !std :: is_same_v <T, char >);

Listing 2.25 cppsrc/apps-x011/hpp/x011–07

Ob ein Datentyp eine ganze oder eine rationale Zahl repräsentiert, kann zur Überset-
zungszeit mit folgendem Ausdruck ermittelt werden.

// Test : ist T eine ganze oder eine rationale Zahl


template <class T>
static constexpr bool is_rational_like_v = //
is_int_like_v <T> || std :: is_same_v <T, Rational >;

Listing 2.26 cppsrc/apps-x011/hpp/x011–07

Wenn eine rationale Zahl zu einer ganzen oder rationalen Zahl addiert wird, dann soll
das Ergebnis eine rationale Zahl sein. Wird hingegen eine rationale Zahl mit einer Fließ-
kommazahl addiert, so soll das Ergebnis eine Fließkommazahl sein. Für zwei rationale
Zahlen r1 und r2 ist der Ausdruck r1 += r2 äquivalent zu r1 = r1 + r2 und der Rück-
gabewert ist eine Referenz auf r1 und damit eine Referenz auf eine rationale Zahl. Es ist
2.2 Einfache Klassen 73

deswegen nicht möglich, die Addition mit einer Fließkommazahl in die Implementierung
des Operators += mit aufzunehmen. Daraus folgt, dass der Operator nur dann aufgerufen
werden darf, wenn das Eingabeargument eine ganze oder eine rationale Zahl ist.
Durch den Einsatz von enable_if können wir dem Compiler Bedingungen angeben, für
die eine Funktion bzw. ein Operator aufgerufen werden darf. Wir werfen als erstes einen
Blick auf die Definition von enable_if, das seit C++11 zum Standard gehört und in der
Header-Datei <type_traits> zu finden ist.

template <bool B, class T = void >


struct enable_if {};

template <class T>


struct enable_if <true , T> { typedef T type; };

template < bool B, class T = void >


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

Listing 2.27

Wir sehen, dass, falls die Bedingung B wahr ist, diese Struktur ein Synonym type vom
Typ T definiert, sonst aber leer ist. Betrachten wir als nächstes die Implementierung von
+=.

/**
* @brief operator +=
* @param r ganze oder rationale Zahl
* @return Referenz auf aufrufendes Objekt
*/
template <class T, std :: enable_if_t < is_rational_like_v <T>> * =
nullptr >
inline Rational & operator +=( const T &r) {
if constexpr ( is_int_like_v <T >) {
_num = _num + _den;
} else {
_num = _num * r._den + r._num * _den;
_den = _den * r._den;
}
simplify ();
return *this;
}

Listing 2.28 cppsrc/apps-x011/hpp/x011–08

Der Compiler versucht während der Kompilierung, als zweiten Template-Parameter


einen Zeiger von Typ void zu deklarieren. Dies gelingt aber nur dann, wenn
is_rational_like_v<T> wahr ist. Wenn nicht, z.B. wenn für T double eingesetzt wird,
folgt kein Übersetzungsfehler (Stichwort SFINAE). Es wird nur diese Variante des +=-
Operators verworfen. Ebenfalls zur Übersetzungszeit wird im Fall einer ganzen Zahl der
entsprechende Code durch die Anweisung if constexpr... generiert. Der Bruch wird
immer, bevor die Referenz auf das aufrufende Objekt zurückgegeben wird, vereinfacht.
Es folgen die Implementierungen für die Operatoren -=, *= und /=.
74 2 Klassen in C++

/**
* @brief operator -=
* @param r ganze oder rationale Zahl
* @return Referenz auf aufrufendes Objekt
*/
template <class T, std :: enable_if_t < is_rational_like_v <T>> * =
nullptr >
inline Rational &operator -=( const T &r) {
if constexpr ( is_int_like_v <T >) {
_num = _num - _den;
} else {
_num = _num * r._den - r._num * _den;
_den = _den * r._den;
}
simplify ();
return *this;
}

Listing 2.29 cppsrc/apps-x011/hpp/x011–09

/**
* @brief operator *=
* @param r ganze oder rationale Zahl
* @return Referenz auf aufrufendes Objekt
*/
template <class T, std :: enable_if_t < is_rational_like_v <T>> * =
nullptr >
inline Rational & operator *=( const T &r) {
if constexpr ( is_int_like_v <T >) {
_num = _num * r;
} else {
_num *= r._num;
_den *= r._den;
}
simplify ();
return *this;
}

Listing 2.30 cppsrc/apps-x011/hpp/x011–10

/**
* @brief operator /=
* @param r ganze oder rationale Zahl
* @return Referenz auf aufrufendes Objekt
*/
template <class T, std :: enable_if_t < is_rational_like_v <T>> * =
nullptr >
inline Rational & operator /=( const T &r) {
if (r.num () == 0) {
throw std :: overflow_error (" operator /=");
}
if constexpr ( is_int_like_v <T >) {
_den *= r;
} else {
_num *= r._den;
2.2 Einfache Klassen 75

_den *= r._num;
}
return *this;
}

Listing 2.31 cppsrc/apps-x011/hpp/x011–11

Damit ist die Implementierung der Klasse vorerst abgeschlossen. Es bleibt aber noch
Arbeit zu tun, da die binären Operatoren +,-,*,/ programmiert werden müssen. Wir
holen dies weiter unten in diesem Kapitel nach.

2.2.5 Eine Klasse zur Beschreibung von rechtwinkligen Dreiecken

In den bisher vorgestellten Beispielen wurden Klassen mit wenigen Attributen implemen-
tiert. Bei wachsender Anzahl der Eigenschaften eines Objekts muss überlegt werden, wie
diese innerhalb der Klasse so gruppiert werden können, dass der Quelltext übersichtlich
bleibt. Wir werden mit einer Klasse zur Beschreibung von rechtwinkligen Dreiecken eine
Möglichkeit zur Gruppierung von Variablen vorstellen. Wie immer beginnen wir mit der
Identifizierung der charakteristischen Eigenschaften des Objekts. Ein Dreieck (Abb. 2.4)
wird durch die Angabe seiner drei Seiten und Winkel vollständig beschrieben.

c
b

C a B
Abb. 2.4: Rechtwinkliges Dreieck

Kurzbeschreibung Folgende Formeln werden zur Berechnung der Eigenschaften eines


rechtwinkligen Dreiecks benutzt:
√ a b 1
c= a2 + b2 , sin A = , sin B = , U = a + b + c, A = ab, (2.2)
c c 2
wobei U der Umfang und A die Fläche des Dreiecks sind. Wir werden uns mit der
Bezeichnung der Seiten und Winkel an Abb. 2.4 halten. Die Angabe von Kathete und
Gegenkathete reicht aus, um alle restlichen Seiten und Winkel sowie den Umfang und
die Fläche des Dreiecks zu berechnen.

Quelltext Wir beabsichtigen auch hier die Änderung der Eigenschaften des Objekts
außerhalb der Klasse nicht zu erlauben und speichern diese im private-Bereich ab. Zur
Speicherung der Winkel und Seiten verwenden wir jeweils ein dreidimensionales Feld
und für die Fläche und den Umfang eine Variable. Damit sind die Attribute der Klasse
in logische Einheiten gruppiert und die Klasse selbst bleibt übersichtlich. Wir achten
76 2 Klassen in C++

darauf, dass der Winkel A und die Seite a usw. dieselbe Position innerhalb ihrer Felder
innehaben. Der Grund wird weiter unten klar.

/**
* @brief The RightTriangle Klasse für ein rechtwinkliges Dreieck
*/
class RightTriangle
{
private :
// speichere Seiten
std :: array <double , 3> _sides ;
// speichere Winkel
std :: array <double , 3> _angles ;
// Fläche , Umfang
double _area , _perimeter ;

public :

Listing 2.32 cppsrc/apps-x012/hpp/x012–01

Dem Konstruktor werden die zwei senkrechten Seiten übergeben. Mit einer Funktion
der Standardbibliothek (std::hypot) wird die Hypotenuse berechnet. Alle Winkel des
Dreiecks werden mit Formeln aus der Trigonometrie (Gl. 2.2) berechnet und gespeichert.
Dem Konstruktor dürfen nur positive Zahlen übergeben werden. Eine entsprechende
Überprüfung findet innerhalb des Konstruktors statt.

/**
* @brief RightTriangle Konstruktor
* @param a Kathete
* @param b Gegenkathete
*/
inline RightTriangle ( double a, double b) {
if (a <= 0.0 || b <= 0.0) {
throw std :: domain_error ( __func__ );
}
const double c = std :: hypot (a, b);
_sides = { a, b, c };
_angles = { asin(a / c), asin(b / c), 0.5 * Math ::PI };
_area = 0.5 * a * b;
_perimeter = a + b + c;
}

Listing 2.33 cppsrc/apps-x012/hpp/x012–02

Da auf die Seiten und Winkel außerhalb der Klasse nicht zugegriffen werden kann, kön-
nen diese Werte nur gelesen werden, wenn Schnittstellen in Form von Elementfunktio-
nen existieren. Wir könnten für jede interne Variable eine zugehörige Elementfunktion
programmieren. Dies würde aber zu einer Flut von Elementfunktionen führen. Um den
Programmieraufwand so gut es geht zu reduzieren, greifen wir per Index, repräsentiert
durch einen Aufzählungstypen auf die Winkel und Seiten des Dreiecks zu. Durch die
Positionierung der Winkel und Seiten in ihren jeweiligen Feldern kommen wir mit einer
Aufzählung aus.
2.2 Einfache Klassen 77

/**
* @brief The Idx enum Zugriff auf die Seiten und Winkel des
* Dreiecks per Index ( Aufzählungstyp )
*/
enum Idx { a, b, c };

Listing 2.34 cppsrc/apps-x012/hpp/x012–03

Die zwei nächsten Funktionen geben Referenzen auf die internen Felder zurück. Es
ist somit z.B. möglich, mit angles()[Idx::a] auf den Winkel A und entsprechend mit
sides()[Idx::a] auf die Seite a lesend zuzugreifen.

/**
* @brief sides
* @return Feld mit Längen der Seiten
*/
inline const auto &sides () const { return _sides ; }

Listing 2.35 cppsrc/apps-x012/hpp/x012–04

/**
* @brief angles
* @return Feld mit allen Winkeln
*/
inline const auto & angles () const { return _angles ; }

Listing 2.36 cppsrc/apps-x012/hpp/x012–06

Einfacher erfolgt der Zugriff über folgende Elementfunktionen.

/**
* @brief side Länge einer Seite
* @param idx Index
*/
inline auto side(Idx idx) const { return _sides .at(idx); }

Listing 2.37 cppsrc/apps-x012/hpp/x012–05

/**
* @brief angle lese einen Winkel
* @param idx Index
*/
inline auto angle (Idx idx) const { return _angles .at(idx); }

Listing 2.38 cppsrc/apps-x012/hpp/x012–07

Die Abfrage der Fläche und des Umfangs des Dreiecks erfolgt mithilfe der nächsten beiden
Elementfunktionen (Listing 2.39, Listing 2.40).
78 2 Klassen in C++

/**
* @brief area
* @return Fläche des Dreiecks
*/
inline double area () const { return _area; }

Listing 2.39 cppsrc/apps-x012/hpp/x012–08

/**
* @brief perimeter
* @return Umfang des Dreiecks
*/
inline double perimeter () const { return _perimeter ; }

Listing 2.40 cppsrc/apps-x012/hpp/x012–09

Zur Ausgabe der Daten des Dreiecks wird der <<-Operator überladen. Das Lesen der
Winkel und Seiten erfolgt mittels der oben diskutierten Elementfunktionen Listing 2.37
und Listing 2.38.

/**
* @brief operator << Ausgabe der Daten eines Dreiecks
* @param os Ausgabestrom
* @param t Dreieck
* @return Referenz auf Ausgabestrom
*/
friend std :: ostream &operator <<( std :: ostream &os , const
RightTriangle &t) {
using Idx = RightTriangle :: Idx;
os << "{" << t.side(Idx ::a) << ",";
os << t.side(Idx ::b) << ",";
os << t.side(Idx ::c) << "}" << std :: endl;
os << "{" << t. angle (Idx ::a) << ",";
os << t. angle (Idx ::b) << ",";
os << t. angle (Idx ::c) << "}" << std :: endl;
os << "{" << t.area () << "," //
<< t. perimeter () << "}" << std :: endl;
return os;
}
}; // RightTriangle

Listing 2.41 cppsrc/apps-x012/hpp/x012–10

Beispiel

Wir instanziieren durch die Angabe der beiden senkrecht aufeinanderstehenden Seiten
ein Dreieck und geben seine Daten aus. Im nächsten Schritt wird eine Kopie des Drei-
ecks über den Kopierkonstruktor angelegt. Obwohl für die Klasse kein Kopierkonstruktor
implementiert wurde, erzeugt der Compiler automatisch einen, mit dem die internen Va-
riablen in der Reihenfolge, in der sie angelegt wurden, kopiert werden.
2.2 Einfache Klassen 79

/**
* @brief ex1 Beispiel Ausgabe der Daten für ein rechtwinkliges
* Dreieck
*/
inline void ex1 () {
using Triangle = RightTriangle ;
Triangle tr0{ 1, 2 };
std :: cout << tr0;
// kopiere Dreieck
Triangle tr1 = tr0;
std :: cout << " -------------------------------\n";
std :: cout << tr1;
}

} // namespace nmx :: apps :: x012

Listing 2.42 cppsrc/apps-x012/hpp/x012–11

{1 ,2 ,2.23607}
{0.463648 ,1.10715 ,1.5708}
{1 ,5.23607}
-------------------------------
{1 ,2 ,2.23607}
{0.463648 ,1.10715 ,1.5708}
{1 ,5.23607}

Listing 2.43

2.2.6 Abbildung von Prozessen mit Klassen

Klassen eignen sich nicht nur zur Abbildung von materiellen Gegenständen, sondern
auch zur Beschreibung von Prozessen. Folgende Physikaufgabe soll als Beispiel für einen
Prozess dienen, den wir mit einer Klasse abbilden werden.

Geradlinige Bewegung mit konstanter Beschleunigung

Ein Massenpunkt der Masse m bewegt sich auf gerader Strecke und beschleunigt mit a
gleichförmig. Zum Zeitpunkt t0 = 0 befindet er sich am Ort x0 und hat eine Geschwin-
digkeit v0 . Mit einem Computerprogramm sollen Bewegungsdaten für die Bewegung des
Massenpunktes berechnet werden.

Theorie Zur Erstellung des Programms werden die Formeln für die zu berechnenden
Größen benötigt. Dazu müssen folgende Fragen beantworten werden:

Welche Geschwindigkeit hat der Massenpunkt und an welchem Ort befindet er sich
zu einem späteren Zeitpunkt t1 ?
Um welche Strecke hat sich der Massenpunkt in der Zeit t1 weiterbewegt? Wie groß
war seine Durchschnittsgeschwindigkeit?
80 2 Klassen in C++

Wir legen den Einheitsvektor ⃗ex entlang der Bewegungsrichtung. Der Ort und die Ge-
schwindigkeit als Funktionen der Zeit lauten:

 x(t) = x0 + v0 t + 1 at2 (2.3a)
2

v(t) = v0 + at. (2.3b)

Die Durchschnittsgeschwindigkeit berechnen wir mit

s
v̄ = , (2.4)
t
wobei s, die von 0 bis t zurückgelegte Strecke ist.

Kurzbeschreibung Wir suchen nach charakteristischen Eigenschaften der Bewegung des


Massenpunktes. Die nötigen Informationen bekommen wir aus den hergeleiteten Glei-
chungen. Wir unterscheiden zwischen solchen Eigenschaften, die ihren Wert während des
Ablaufs des Prozesses nicht ändern, und solchen, die abhängig von der Zeit sind. Zur
ersten Gruppe gehören die Masse m, die Beschleunigung a und die Anfangsbedingungen
x0 und v0 . Zur zweiten Gruppe gehören der Ort x, die Geschwindigkeit v, die zurückge-
legte Strecke s und die mittlere Geschwindigkeit v̄. Die physikalischen Größen der ersten
Gruppe werden Attribute und die der zweiten Elementfunktionen der Klasse sein, die
wir mit Gl. 2.3a, Gl. 2.3b, Gl. 2.4 implementieren werden.

Quelltext Die Masse und die Beschleunigung sind laut Angaben der Aufgabe für eine
gegebene Situation feste Größen und werden zusammen mit den Anfangsbedingungen,
die ebenfalls fest sind, im privaten Bereich der Klasse gespeichert. Bei den Anfangsbe-
dingungen handelt es sich um zusammenhängende Größen, die wir als Elemente eines
Feldes speichern.

/**
* @brief The Motion class
* Geradlinige Bewegung mit konstanter Beschleunigung
*/
class Motion
{
private :
// Masse , Beschleunigung
double _mass , _acceleration ;
// Anfangsbedingungen
std :: array <double , 2> _ivalues ;

public :

Listing 2.44 cppsrc/apps-x022/hpp/x022–01

Da nur ein lesender Zugriff auf die privaten Variablen erlaubt sein soll, können diese nur
über den Konstruktor initialisiert werden. Durch die Festlegung des dritten Eingabear-
guments als Feld mit zwei Elementen stellen wir sicher, dass die korrekte Anzahl von
Anfangsbedingungen dem Konstruktor übergeben wird.
2.2 Einfache Klassen 81

/**
* @brief Motion Konstruktor
* @param m Masse
* @param a Beschleunigung
* @param init Anfangsbedingungen
*/
Motion ( double m, double a, std :: array <double , 2> init)
: _mass { m }
, _acceleration { a } {
_ivalues = init;
}

Listing 2.45 cppsrc/apps-x022/hpp/x022–02

Es folgen die Implementierungen von Gl. 2.3a und Gl. 2.3b für Ort und Geschwindigkeit
als Funktion der Zeit.

/**
* @brief x Ortsfunktion
* @param t Zeit
* @return Ort zum Zeitpunkt t
*/
double x( double t) const {
return _ivalues [0] + _ivalues [1] * t //
+ 0.5 * + _acceleration * std :: pow(t, 2);
}

Listing 2.46 cppsrc/apps-x022/hpp/x022–03

/**
* @brief v Geschwindigkeit
* @param t Zeit
* @return Geschwindigkeit zum Zeitpunkt t
*/
double v( double t) const { return _ivalues [1] + _acceleration * t; }

Listing 2.47 cppsrc/apps-x022/hpp/x022–04

Die zurückgelegte Strecke als Funktion der Zeit wird von dieser Funktion berechnet.

/**
* @brief distance Berechnung der zurückgelegten Strecke
* @param t Zeit
* @return Strecke
*/
double distance ( double t) const { return x(t) - _ivalues [0]; }

Listing 2.48 cppsrc/apps-x022/hpp/x022–07

Die zurückgelegte Distanz benutzen wir in der nächsten Funktion, um die mittlere Ge-
schwindigkeit zu berechnen.
82 2 Klassen in C++

/**
* @brief vmean Berechnung der mittleren Geschwindigkeit
* @param t Zeit
* @return mittlere Geschwindigkeit
*/
double vmean ( double t) const { return distance (t) / t; }

Listing 2.49 cppsrc/apps-x022/hpp/x022–08

Es folgen die Methoden zum Lesen von Masse, Beschleunigung und Anfangsbedingungen.

/**
* @brief mass lese ...
* @return Masse
*/
double mass () const { return _mass ; }

Listing 2.50 cppsrc/apps-x022/hpp/x022–05

/**
* @brief acceleration lese ...
* @return Beschleunigung
*/
double acceleration () const { return _acceleration ; }

Listing 2.51 cppsrc/apps-x022/hpp/x022–06

/**
* @brief init_values
* @return Referenz auf Feld mit Anfangsbedingungen
*/
const auto & init_values () const { return _ivalues ; }
}; // Motion

} // namespace nmx :: apps :: x022

Listing 2.52 cppsrc/apps-x022/hpp/x022–09

2.2.7 Klasse als interaktive Benutzerschnittstelle

Wir nehmen die soeben bearbeitete Physikaufgabe als Ausgangsszenario für ein Pro-
gramm, welches Benutzereingaben über die Tatstatur einliest, Ergebnisse berechnet und
diese auf dem Bildschirm ausgibt. Wir haben bereits ähnliches im ersten Kapitel pro-
grammiert.
2.2 Einfache Klassen 83

Geradlinige Bewegung mit konstanter Beschleunigung

Ein Massenpunkt der Masse m bewegt sich auf gerader Strecke und beschleunigt mit a
gleichförmig. Zum Zeitpunkt t0 = 0 befindet er sich am Ort x0 und hat eine Geschwin-
digkeit v0 . Es soll ein interaktives Computerprogramm erstellt werden, das a, x0 , v0 und
t1 von der Tastatur einließt und x(t1 ), v(t1 ), die Strecke s(t1 ) und die Durchschnittsge-
schwindigkeit v̄(t1 ) ausgibt.

Theorie Für diese Art von Bewegung wurde im vorherigen Beispiel ein Modell in Form
einer Klasse erstellt. Dieses werden wir zur Realisierung des Programms einsetzen.

Kurzbeschreibung Wir benötigen eine Benutzerschnittstelle, über die der Benutzer mit
der Modellklasse kommuniziert. Sie muss folgende Eigenschaften haben:

Variablen zur Speicherung von Eingabe- und Ausgabewerten


Berechnung der Ergebnisse mithilfe des Modells aus Abschnitt 2.2.6
Ausgabe der Ergebnisse auf dem Bildschirm

Zusätzlich soll das Programm ähnlich wie im Abschnitt 1.5.1 nicht nach einer Berechnung
von Bewegungsdaten beendet werden, sondern neue Daten berechnen, bis der Benutzer
es mit einem Befehl beendet.

Quelltext Wir beginnen mit der Programmierung der Benutzerschnittstelle und stellen
als erstes fest, dass es sich sowohl bei den Eingabe- als auch bei den Ausgabewerten
um reelle Zahlen handelt. Dies bedeutet, dass wir zu ihrer Speicherung jeweils ein Feld
anlegen können. Wir wissen auch, dass bei einer Interaktion mit einem Benutzer alle
Werte einen Namen haben müssen, damit die physikalischen Größen bei jeder Ein- und
Ausgabe identifiziert werden können. Wir speichern deswegen in jedem Feld statt einer
reinen Zahl ein Paar, bestehend aus Namen und Wert der physikalischen Größe.

/**
* @brief The UserInput class ( Benutzerschnittstelle )
* Geradlinige Bewegung mit konstanter Beschleunigung
*/
class UserInput
{
using Value = std :: pair <std :: string , double >;

// Zugriff auf die Eingabewerte


enum Idx { x0 , v0 , a, time };

private :
// Eingabe (Name ,Wert) ,...
std :: vector <Value > _in;
// Ausgabe (Name ,Wert) ,...
std :: vector <Value > _out;

protected :

Listing 2.53 cppsrc/apps-x023/hpp/x023–01


84 2 Klassen in C++

Mit einer Hilfsfunktion wird jede Eingabe des Benutzers gelesen. Sie automatisiert den
Eingabeprozess, indem mit einem Funktionsaufruf der Name einer physikalischen Größe
auf dem Bildschirm geschrieben und dann ihr Wert gelesen und auf Gültigkeit überprüft
wird.

/**
* @brief UserInput :: read
* @param label Eingebeaufforderung
* @return Benutzereingabe als Zahl
*/
double read(std :: string & label ) {
double val;
std :: cout << label << "=?\t";
std :: cin >> val;
// Fehler wenn val nicht vom Typ double ist
if (std :: cin.fail ()) {
throw std :: invalid_argument (label );
}
return val;
}

public :

Listing 2.54 cppsrc/apps-x023/hpp/x023–02

Es folgt der Konstruktor, der die Namen aller physikalischen Größen speichert und gleich-
zeitig ihren Wert auf 0 setzt.

/**
* @brief UserInput :: UserInput Standardkonstruktor
*/
inline UserInput () {
// Namen für Eingabevariablen werden initialisiert
for (const auto label : { "x0", "v0", "a", "t1" }) {
_in. push_back ( Value { label , 0 });
}
// Namen für Ausgabevariablen werden initialisiert
for (const auto & label : { "x", "v", "s", "vbar" }) {
_out. push_back ( Value { label , 0 });
}
}

Listing 2.55 cppsrc/apps-x023/hpp/x023–03

Die nächste Elementfunktion ist für die Berechnung der Ergebnisse zuständig. Sie tut
dies mithilfe einer Instanz der Prozessklasse.

/**
* @brief calculate Ergebnisse werden berechnet
*/
inline void calculate () {
// Komponente zur Berechnung der Ergebnisse
x022 :: Motion motion { 1, _in[Idx ::a]. second , {
_in[Idx ::x0 ]. second , _in[Idx :: v0 ]. second } };
// Wertezuweisung
2.2 Einfache Klassen 85

double t1 = _in[Idx :: time ]. second ;


_out [0]. second = motion .x(t1);
_out [1]. second = motion .v(t1);
_out [2]. second = motion . distance (t1);
_out [3]. second = motion . vmean (t1);
}

Listing 2.56 cppsrc/apps-x023/hpp/x023–04

Die Aufforderung zur Eingabe der Werte geschieht über eine weitere Elementfunktion
(Listing 2.57). Wir sehen, wie dies über eine Schleife abgewickelt wird.

/**
* @brief UserInput :: user_input Eingabeaufforderung
*/
inline void input () {
for (auto &vals : _in) {
vals. second = read(vals. first );
}
// letzte Eingabe : Zeit
if (_in.back (). second <= 0) {
throw std :: domain_error ("time must be >0");
}
}

Listing 2.57 cppsrc/apps-x023/hpp/x023–05

Ähnlich einfach ist auch die Ausgabe zu programmieren.

/**
* @brief output Ausgabe der Ergebnisse
*/
inline void output () {
std :: cout << std :: fixed << std :: setprecision (2);
std :: cout << " ------------output -------" << std :: endl;
// iteriere über Ausgabewerte (Name ,Wert) ,...
for (const auto &[ label , val] : _out) {
std :: cout << label //
<< "=" << val << std :: setw (3) << "\t";
}
std :: cout << std :: endl;
}

Listing 2.58 cppsrc/apps-x023/hpp/x023–06

Die Benutzereingaben werden mithilfe dieser Elementfunktion gelesen, ausgewertet und


auf dem Bildschirm ausgegeben.

/**
* @brief ui Benutzerschnittstelle (ein Rechengang )
*/
inline void ui () {
constexpr auto maxstreamsize =
std :: numeric_limits <std :: streamsize >:: max ();
try {
86 2 Klassen in C++

// Eingabe -> Auswertung ->Ausgabe


input ();
calculate ();
output ();
} catch (std :: domain_error ex1) {
// wenn negative Zeiten eingegeben werden
// Fehlerbits werden zurückgesetzt ,
// sonst kann cin nicht mehr genutzt werden
std :: cin. clear ();
// verwerfe alle Zeichen bis \n
std :: cin. ignore ( maxstreamsize , '\n');
std :: cerr << " input error :" << ex1.what () << std :: endl;
} catch (std :: invalid_argument ex2) {
// wenn statt Zahlen Buchstaben eingegeben werden
std :: cin. clear ();
std :: cin. ignore ( maxstreamsize , '\n');
std :: cerr << " input error for:" << ex2.what () << std :: endl;
}
}

Listing 2.59 cppsrc/apps-x023/hpp/x023–07

Innerhalb einer Schleife wird ein kompletter Rechengang, solange wiederholt, bis der
Benutzer diesen per Befehl beendet. Dies geschieht durch die Eingabe von y oder n.

/**
* @brief run Eingabe ,Berechnung , Ausgabe
*/
inline void run () {
char cmd = 'y'; // die Schleife soll wiederholt werden
std :: cout << " ------------input -------------" << std :: endl;
while (cmd == 'y') {
ui(); // starte Eingabe ,Berechnung , Ausgabe
do {
std :: cout << "new calculation ? y[yes] / n[no]" <<
std :: endl;
std :: cin >> cmd;
} while (cmd != 'y' && cmd != 'n');
}
}

Listing 2.60 cppsrc/apps-x023/hpp/x023–08

Ein Beispielprogramm zu schreiben ist einfach, denn die ganze Arbeit wird von den
Elementfunktionen der Klasse übernommen.

/**
* @brief ex1 Beispielprogramm Geradlinige Bewegung mit konstanter
* Beschleunigung
*/
inline void ex1 () {
UserInput ui;
ui.run ();
}

Listing 2.61 cppsrc/apps-x023/hpp/x023–09


2.3 Klassentemplates 87

Wie das Programm arbeitet, zeigt folgende Ausgabe auf dem Bildschirm:

------------input -------------
x0=? 10
v0=? 2
a=? 2
t1=? 10
------------output -------
x =130.00 v =22.00 s =120.00 vbar =12.00
new claculation ? y[yes] / n[no]
y
x0=? 2
v0=? 3
a=? 1
t1=? 1
------------output -------
x =5.50 v=4.00 s =3.50 vbar =3.50
new claculation ? y[yes] / n[no]
n

Listing 2.62

2.3 Klassentemplates
Klassen sind Vorlagen zur Erstellung von Objekten. Klassentemplates sind Vorlagen zur
Erstellung von Klassen. Beim Entwurf einer Klasse müssen die Datentypen aller Attribute
bekannt sein. Bei Klassentemplates wird einem oder mehreren Attributen kein Datentyp
zugewiesen. Stattdessen wird ein Platzhalter eingesetzt. Wird dieser festgelegt, entsteht
eine neue Klasse. Wir wollen dieses Konzept anhand eines Beispiels demonstrieren.

2.3.1 Eine Klasse zur Darstellung von Polynomen

Die Umsetzung unseres Vorhabens setzt als erstes die Identifizierung der Eigenschaften
eines Polynoms voraus. Dies geschieht am besten über die Untersuchung der mathema-
tischen Formel. Bei Polynomen handelt es sich um Ausdrücke der Form

pn (x) = cn xn + cn−1 xn−1 + · · · + c1 x + c0 , (2.5)


wobei die Koeffizienten cn sowohl reell als auch komplex sein können. Dasselbe gilt für
die Variable x, die ebenfalls reell oder komplex sein kann.

Kurzbeschreibung Das Polynom wird durch seine Koeffizienten eindeutig festgelegt.


Die Klasse wird demzufolge die Koeffizienten des Polynoms speichern müssen. Sie muss
zusätzlich in der Lage sein, für einen Eingabewert x einen Wert pn (x) zu berechnen.

Quelltext Ein erster Gedanke wäre, eine Klasse für die Variante mit komplexen und
eine für die mit reellen Koeffizienten zu entwerfen. Ein Blick auf die Formel zeigt aber,
88 2 Klassen in C++

dass dies überflüssig ist. Verwenden wir statt Komplex (complex) und Reell (double) einen
Platzhalter für den Datentyp der Koeffizienten, hätten wir mit einem Klassentemplate
beide Varianten. Wir bleiben aber nicht nur bei diesem Template-Parameter, sondern
führen noch einen weiteren ein, der den Grad des Polynoms festlegt. Dieser zweite Pa-
rameter erlaubt es bereits während der Kompilierung, ein Feld passender Größe für die
Speicherung der Koeffizienten anzulegen. Dabei wird das Element mit dem Index 0 des
Feldes dem Koeffizienten c0 zugeordnet usw.

/**
* @brief The Polynom class Darstellung von Polynomen
* @param T Datentyp der Koeffizienten
* @param N Grad des Polynoms . N+1 : Anzahl der Koeffizienten
*/
template <class T, size_t N>
class Polynom
{
protected :
// speichere Koeffizienten , Anzahl Grad des Polynoms +1
std :: array <T, N + 1> _c;

public :

Listing 2.63 cppsrc/apps-x013/hpp/x013–02

Der Konstruktor erwartet die Liste der Koeffizienten des Polynoms, beginnend mit dem
Element c0 . Durch die Verwendung eines std::array wird sichergestellt, dass die richtige
Anzahl der Koeffizienten übergeben wird.

/**
* @brief Polynom Konstruktor
* @param carray Feld mit Koeffizienten
*/
Polynom (std :: array <T, N + 1> clst) {
std :: copy(std :: begin (clst), std :: end(clst), std :: begin(_c));
}

Listing 2.64 cppsrc/apps-x013/hpp/x013–03

Ein Zugriff auf die Koeffizienten des Polynoms erfolgt mittels der Elementfunktion (Lis-
ting 2.65). Eine Überprüfung auf Gültigkeit des Index wird ebenfalls durchgeführt.

/**
* @brief c lese Koeffizienten
* @param idx Index Position im Feld
* @return Koeffizient
*/
T c( size_t idx) const {
// Zugriff mit Index - Überprüfung
return _c.at(idx);
}

Listing 2.65 cppsrc/apps-x013/hpp/x013–04


2.3 Klassentemplates 89

Alle Elementfunktionen zur Auswertung der Polynome sollen mithilfe von gsl -Funktionen
implementiert werden. Je nach Datentyp der Koeffizienten und des Eingabearguments
müssen folgende Fälle berücksichtigt werden:

Polynom mit reellen Koeffizienten


– Auswertung für reelles x
∗ Rückgabewert reell, gsl-Funktion: gsl_poly_eval
– Auswertung für komplexes x
∗ Rückgabewert komplex, gsl-Funktion: gsl_poly_complex_eval
Polynom mit komplexen Koeffizienten
– Auswertung für reelles oder komplexes x
∗ Rückgabewert komplex, gsl-Funktion: gsl_complex_poly_complex_eval

Es ist möglich, alle Fälle mittels einer Elementfunktion vom Compiler generieren zu las-
sen. Es ist aber übersichtlicher, wenn die Implementierung in zwei Elementfunktionen
aufgeteilt wird. Wir beginnen mit der Auswertung des Polynoms, wenn das Eingabear-
gument komplex ist. Es werden zwei Versionen je nach Datentyp der Koeffizienten vom
Compiler generiert.

/**
* @brief eval Auswertung des Polynoms
* @param z komplexe Variable
* @return Wert des Polynoms an der Stelle z
*/
auto eval( gsl_complex z) const {
// komplexe Koeffizienten , komplexe Variable
if constexpr (std :: is_same_v <T, gsl_complex >) {
return gsl_complex_poly_complex_eval ( //
_c.data () ,
N + 1,
z);
} else {
// reelle Koeffizienten , komplexe Variable
return gsl_poly_complex_eval (_c.data (), N, z);
}
}

Listing 2.66 cppsrc/apps-x013/hpp/x013–05

Wenn das Eingabeargument des Polynoms reell ist, generiert der Compiler ebenfalls zwei
Versionen, denn wenn die Koeffizienten des Polynoms komplex sind, muss das Eingabe-
argument in eine komplexe Zahl umgewandelt werden.

/**
* @brief eval Auswertung des Polynoms
* @param x reelle Variable
* @return Wert des Polynoms an der Stelle x
*/
auto eval( double x) const {
90 2 Klassen in C++

// komplexe Koeffizienten , reelle Variable


if constexpr (std :: is_same_v <T, gsl_complex >) {
return gsl_complex_poly_complex_eval ( //
_c.data () ,
N + 1,
gsl_complex { x, 0 });
} else {
// reelle Koeffizienten , reelle Variable
return gsl_poly_eval (_c.data () , N + 1, x);
}
}

Listing 2.67 cppsrc/apps-x013/hpp/x013–06

Polynome können auch mit der überladenen Version des Klammeroperators ausgewertet
werden. Als Template implementiert, wird intern immer die richtige Variante von eval
(Listing 2.66 und Listing 2.67) aufgerufen.

/**
* @brief operator () Auswertung des Polynoms
* @param x Variable ( komplex oder reell)
* @return Wert des Polynoms an der Stelle x
*/
template <class R>
auto operator ()(R x) const {
return eval(x);
}

Listing 2.68 cppsrc/apps-x013/hpp/x013–07

Wir kümmern uns als Nächstes um die Ausgabe von Polynomen auf dem Bildschirm oder
in Dateien. Hierzu überladen wir den Operator <<, der die Daten eines Polynoms an Ob-
jekte der ostream-Klasse weiterleitet. Die Koeffizienten werden innerhalb von Klammern
und durch Komma getrennt ausgegeben.

/**
* @brief operator << Ausgabe der Koeffizienten des Polynoms
* {c0 ,c1 ,c2 ,... , cn}
* @param os Ausgabestrom
* @param p Polynom
* @return Referenz auf Ausgabestrom
*/
friend inline std :: ostream &operator <<( std :: ostream &os , const
Polynom &p) {
os << "{" << p.c(0);
for ( size_t idx = 1; idx < p._c.size (); idx ++) {
os << "," << p.c(idx);
}
return os << "}";
}

Listing 2.69 cppsrc/apps-x013/hpp/x013–08


2.3 Klassentemplates 91

Ein Problem muss noch gelöst werden. Wenn die Koeffizienten vom Typ gsl_complex sind,
meldet der Compiler bei der Verwendung des <<-Operators einen Fehler. Wir schließen
diese Lücke und überladen den <<-Operator für die Ausgabe von Elementen vom Typ
gsl_complex.

/**
* @brief operator <<
* @param os Ausgabestrom
* @param c gsl - Darstellung von komplexen Zahlen
* @return Referenz auf Ausgabestrom
*/
inline std :: ostream &operator <<( std :: ostream &os , const gsl_complex &c)
{
return os << "(" << GSL_REAL (c) << "," << GSL_IMAG (c) << ")";
}

Listing 2.70 cppsrc/apps-x013/hpp/x013–01

Wir führen eine letzte Elementfunktion ein, mit welcher der Grad des Polynoms abgefragt
werden kann.

/**
* @brief degree
* @return Grad des Polynoms
*/
inline constexpr size_t degree () const { return N; }
}; // Polynom

Listing 2.71 cppsrc/apps-x013/hpp/x013–09

Die Klasse ist hiermit vollständig implementiert und kann mit folgendem Programm
getestet werden.

Beispiel Das Polynom p1 (x) = 3x2 +2x+1 wird auf dem Bildschirm ausgegeben und für
x = −0.5 ausgewertet. Dasselbe gilt auch für p2 (x) = 4x3 + 3x2 − 2x + 1. Das komplexe
Polynom p3 (x) = (2 + 2i)x + 1 + i wird ebenfalls auf dem Bildschirm ausgegeben und
für x = 1 und x = 1 + 2i ausgewertet.

/**
* @brief cppx4a Beispiel Auswertung von Polynomen
*/
inline void ex1 () {
// Polynom 2 Grades mit reellen Koeffizienten
Polynom <double , 2> p1{ { 1., 2., 3. } };
std :: cout << "p1=" << p1 << std :: endl;
std :: cout << "p1 ( -0.5)=" << p1 ( -0.5) << std :: endl;
std :: cout << " ----------------" << std :: endl;
// Polynom 3 Grades mit reellen Koeffizienten
Polynom <double , 3> p2{ { 1., -2., 3., 4. } };
std :: cout << "p2=" << p2 << std :: endl;
std :: cout << "p2 ( -0.5)=" << p2 ( -0.5) << std :: endl;
std :: cout << " ----------------" << std :: endl;
// Polynom 1 Grades mit komplexen Koeffizienten
92 2 Klassen in C++

Polynom < gsl_complex , 1> p3{ { gsl_complex { 1., 1. }, //


gsl_complex { 2., 2. } } };

// Ausgabe
std :: cout << "p3=" << p3 << std :: endl;
std :: cout << "p3 (1)=" << p3 (1) << std :: endl;
std :: cout << "p3 ({1 ,2})=" << p3( gsl_complex { 1., 2. }) << std :: endl;
}

} // namespace nmx :: apps :: x013

Listing 2.72 cppsrc/apps-x013/hpp/x013–10

p1 ={1 ,2 ,3}
p1 ( -0.5) =0.75
----------------
p2 ={1 , -2 ,3 ,4}
p2 ( -0.5) =2.25
----------------
p3 ={(1 ,1) ,(2,2)}
p3 (1) =(3 ,3)
p3 ({1 ,2}) =( -1 ,7)

Listing 2.73

2.3.2 Erzeugen von Zwischenspeicher für Funktionswerte mit


Klassentemplates

Wir stellen uns eine Situation vor, in der ein Programm intensiv Gebrauch von be-
stimmten Funktionswerten machen muss. Dies könnte z.B. bei Benutzung von n! zur
Berechnung von Taylorpolynomen oder der Werte von sin und cos für eine Grafikausga-
be sein. In diesem Fall könnte, statt die Funktionswerte immer wieder neu zu berechnen,
eine Tabelle erstellt werden, die häufig benutzte Werte zwischenspeichert.

Entwurf Wir werden den Zwischenspeicher mithilfe eines Klassentemplates, dessen


Template-Parameter die Datentypen des Eingabearguments und des Rückgabewerts der
Funktion sind, realisieren. Zur Speicherung der Funktionswerte benötigt die Klasse eine
Datenstruktur, die zu jeden Wert für x den entsprechenden y-Wert liefert, mit ande-
ren Worten eine Datenstruktur, welche Schlüssel-Wertepaare speichert. Die Klasse muss
natürlich auch die Funktion kennen, für welche die Daten zwischengespeichert werden
sollen.

Quelltext Wir wählen zur Speicherung der (x, y)-Werte ein std::map. Das Klassentem-
plate std::function<Y(X)> steht stellvertretend für jede Funktion bzw. für jedes Funkti-
onsobjekt, dass ein Eingabeargument vom Typ X erwartet und einen Wert vom Typ Y
zurückgibt. Der dritte Template-Parameter wird standardmäßig auf false gesetzt. Seine
Bedeutung wird weiter unten erklärt.
2.3 Klassentemplates 93

/**
* @brief The Cache class Erzeugen von Zwischenspeicher für
* Funktionswerte
*/
template <class X, class Y, bool DEBUG = false >
class Cache
{
private :
// Speicher für (x,f(x))
std ::map <X, Y> _table ;
// Zeiger auf Funktionsobjekt f(x)
std :: function <Y(X)> _fn;

public :

Listing 2.74 cppsrc/apps-x014/hpp/x014–01

Der Konstruktor erwartet als Eingabe eine Funktion mit einem Eingabeparameter vom
Typ X und eine Ausgabe vom Typ Y.

/**
* @brief Cache Konstruktor
* @param fn Zeiger auf Funktion
*/
Cache(Y (*fn)(X))
: _fn{ fn } {}

Listing 2.75 cppsrc/apps-x014/hpp/x014–02

Für ein gegebenes x wird ein Wert y zurückgegeben. Ist x in der internen Tabelle re-
gistriert, wird aus dieser y ermittelt. Andernfalls wird y berechnet, (x, y) in die Tabelle
geschrieben und gleichzeitig y zurückgegeben. Der Template-Parameter DEBUG dient zur
Ausgabe einer Meldung, wenn der ermittelte Wert aus der Tabelle stammt.

/**
* @brief get berechne Funktionenwert
* @param x Variable
* @return Funktionswert
*/
Y get(X x) {
const auto fitr = _table .find(x);
if (fitr != _table .end ()) {
// Wert wurde im Speicher gefunden
if constexpr ( DEBUG ) {
std :: cout << "from cache :";
}
return fitr -> second ;
}
Y val = _fn(x); // Wert wird berechnet
_table [x] = val; // ... und gespeichert
return val;
}

Listing 2.76 cppsrc/apps-x014/hpp/x014–03


94 2 Klassen in C++

Die Anzahl der gespeicherten Werte erhalten wir mit folgender Funktion

/**
* @brief size
* @return Anzahl der gespeicherten Werte
*/
auto size () const { return _table .size (); }
}; // Cache

Listing 2.77 cppsrc/apps-x014/hpp/x014–04

Beispielanwendungen

Zur Berechnung von n! programmieren wir eine Funktion, die eine Kombination aus
√ ( )n
rekursiver Berechnung der Fakultät und der Stirlingformel n! ≈ 2πn ne ist. Letztere
berechnet für große n einen Näherungswert für n!.

/**
* @brief fac Berechnung von n! exakt oder mit einer Näherungsformel
* @param n ganze Zahl
* @return n!
*/
double fac( size_t n) {
if (n < 1000) {
return n == 0 ? 1 : n * fac(n - 1);
}
// Stirling - Formel
return std :: sqrt (2 * Math :: PI * n) * std :: pow(n / Math ::E, n);
}

Listing 2.78 cppsrc/apps-x014/hpp/x014–05

Folgendes Beispiel zeigt die Klasse im Einsatz. Es wird eine Instanz ohne und eine mit
Ausgabe einer Meldung erzeugt.

/**
* @brief cppx6 Beispiel
*/
inline void ex1 () {
Cache <size_t , double > c1{ fac }; // ohne Meldung
for (auto n : { 2, 9, 50, 100 , 2, 9 }) {
std :: cout << n << "! =" << c1.get( static_cast <size_t >(n)) <<
std :: endl;
}
std :: cout << " ---------------------------" << std :: endl;
Cache <size_t , double , true > c2{ fac }; // mit Meldung
for (auto n : { 2, 9, 50, 100 , 2, 9 }) {
std :: cout << n << "! =" << c2.get( static_cast <size_t >(n)) <<
std :: endl;
}
}

Listing 2.79 cppsrc/apps-x014/hpp/x014–06


2.3 Klassentemplates 95

Nach Ausführung des Programms erhalten wir folgende Ausgabe.

2! =2
9! =362880
50! =3.03634 e+64
100! =9.32485 e+157
2! =2
9! =362880
---------------------------
2! =2
9! =362880
50! =3.03634 e+64
100! =9.32485 e+157
2! =from cache :2
9! =from cache :362880

Listing 2.80

n=0 n! ≈ 2.71828 berechnen. Zur
In einem weiteren Beispiel werden wir die Summe 10000 1

Berechnung von n! setzen wir, die in diesem Abschnitt programmierte Klasse ein. Die
Summe soll zwei Mal berechnet und in beiden Fällen die Zeit gemessen werden. Wir
erwarten, dass beim ersten Mal die gemessene Zeit größer sein wird als beim zweiten, da
im ersten Durchlauf alle Werte berechnet werden müssen. Im zweiten Durchlauf werden
sie vom Speicher abgerufen. Für die Zeitmessung setzen wir, die in der Header-Datei
<chrono> definierte Uhr steady_clock ein, welche die Uhrzeit unabhängig von der Sys-
temuhr liefert. Mit steady_clock::now wird der Zeitpunkt wiedergegeben, zu dem diese
Funktion aufgerufen wurde. Mit duration<double, std::milli> berechnen wir die Diffe-
renz zwischen zwei Zeitpunkten in Millisekunden.

/**
* @brief ex2 Berechnung eines Näherungswerts für e mit 1+1/2!+1/3!+...
*/
inline void ex2 () {
// vermeide lange Bezeichner
using namespace std :: chrono ;

Cache <size_t , double > c1{ fac };


// Anzahl der Summenterme
const size_t N = 1e5;
// Berechnung der Summe mit Zeitmessung
double sum1 = 0;
auto start1 = steady_clock :: now ();
for ( size_t n = 0; n < N; n++) {
sum1 += 1 / c1.get(n);
}
auto end1 = steady_clock :: now ();
auto dt1 = duration <double , std :: milli >( end1 - start1 ).count ();

// Wiederholung der Berechnung


double sum2 = 0;
auto start2 = steady_clock :: now ();
for ( size_t n = 0; n < N; n++) {
sum2 += 1 / c1.get(n);
}
96 2 Klassen in C++

auto end2 = steady_clock :: now ();


auto dt2 = duration <double , std :: milli >( end2 - start2 ).count ();

// Ausgabe der Ergebnisse auf dem Bildschirm


std :: cout << "dt1=" << dt1 << " ms" << std :: endl;
std :: cout << " value =" << sum1 << std :: endl;
std :: cout << " ---------------------------" << std :: endl;
std :: cout << "dt2=" << dt2 << " ms" << std :: endl;
std :: cout << " value =" << sum2 << std :: endl;
std :: cout << " ---------------------------" << std :: endl;
std :: cout << "dt2/dt1=" << dt2 / dt1 << std :: endl;
std :: cout << " cache size=" << c1.size () << std :: endl;
}

Listing 2.81 cppsrc/apps-x014/hpp/x014–07

Wir sehen wie sich die Rechenzeit, durch die Speicherung der Werte für n! deutlich
reduziert. Die gemessenen Zeiten hängen von der Rechnerkonfiguration ab.

dt1 =43.1981 ms
value =2.71828
---------------------------
dt2 =8.47219 ms
value =2.71828
---------------------------
dt2/dt1 =0.196124
cache size =100000

Listing 2.82

2.3.3 Rechnen mit Rationalen Zahlen

Wir werden in diesem Abschnitt die binären Operatoren +,-,*,/ für rationale Zahlen
implementieren. Es ist abzusehen, dass die Programmierung dieser Operatoren immer
demselben Schema folgen wird, weswegen wir folgende Elementfunktion einführen:

/**
* @brief helpfn Hilfsfunktion zur Implementierung von +,-,*,/
* @param arg1 reelle , ganze oder rationale Zahl
* @param arg2 reelle , ganze oder rationale Zahl
* @param fn lambda Funktion für +,-,*,/
* z.B. []( double &x, double y){ return x+=y; }
*/
template <class X, class Y, class FN >
inline friend auto helpfn ( const X &arg1 , const Y &arg2 , FN fn) {
// wenn eine der beiden Zahlen eine Fließkommazahl ist
if constexpr (std :: is_floating_point_v <X> || //
std :: is_floating_point_v <Y>) {
// ... ist das Ergebnis auch eine Fließkommazahl
double rout = static_cast <double >( arg1);
fn(rout , static_cast <double >( arg2));
return rout;
} else {
2.3 Klassentemplates 97

Rational rout{ arg1 };


fn(rout , Rational { arg2 });
rout. simplify ();
return rout;
}
}

Listing 2.83 cppsrc/apps-x011/hpp/x011–12

Es handelt sich um ein Funktionstemplate mit drei Template-Parametern. Es erwar-


tet Objekte vom Typ X und Y sowie ein Funktionsobjekt FN als Eingabeargumente. Der
Compiler generiert eine Version dieser Funktion, wenn eines der beiden Eingabeargumen-
te eine Fließkommazahl ist. In diesem Fall wird unabhängig vom Datentyp des anderen
Eingabearguments, dieses auch in eine Fließkommazahl umgewandelt. Anschließend wer-
den beide Eingabeargumente dem Funktionsobjekt FN übergeben. In allen anderen Fällen
wird davon ausgegangen, dass mindestens eines der beiden Eingabeargumente eine ra-
tionale Zahl ist und das andere, wenn nicht eine rationale Zahl, dann eine ganze Zahl ist,
sodass es in eine rationale Zahl umgewandelt werden kann. Durch das Schlüsselwort auto
wird der Compiler angewiesen, den Typ des Rückgabewertes selbst zu ermitteln. Mithilfe
dieser Hilfsfunktion implementieren wir die Addition, Subtraktion, Multiplikation und
Division wie folgt:

/**
* Implementierung von +,-,*,/ Funktionen werden nur dann
* vom Compiler erzeugt , wenn Z oder Y eine rationale Zahl ist.
*/
template <class X, class Y, is_candidate_t <X, Y> * = nullptr >
inline friend auto operator +( const X &x, const Y &y) {
return helpfn (x, y, []( auto &a, const auto &b) { a += b; });
}

Listing 2.84 cppsrc/apps-x011/hpp/x011–14

Die Hilfsfunktion (Listing 2.83) ruft zur Implementierung der Operatoren eine λ-
Funktion auf. Für die Addition wird die Operation += für rationale Zahlen (Listing 2.28)
ausgeführt. Entsprechendes gilt für die restlichen Operatoren.

template <class X, class Y, is_candidate_t <X, Y> * = nullptr >


inline friend auto operator -( const X &x, const Y &y) {
return helpfn (x, y, []( auto &a, const auto &b) { a -= b; });
}

Listing 2.85 cppsrc/apps-x011/hpp/x011–14

template <class X, class Y, is_candidate_t <X, Y> * = nullptr >


inline friend auto operator *( const X &x, const Y &y) {
return helpfn (x, y, []( auto &a, const auto &b) { a *= b; });
}

Listing 2.86 cppsrc/apps-x011/hpp/x011–14


98 2 Klassen in C++

template <class X, class Y, is_candidate_t <X, Y> * = nullptr >


inline friend auto operator /( const X &x, const Y &y) {
return helpfn (x, y, []( auto &a, const auto &b) { a /= b; });
}

Listing 2.87 cppsrc/apps-x011/hpp/x011–14

Ohne jegliche Restriktion würde der Compiler diese Operatoren auf jede Addition, Mul-
tiplikation usw. versuchen anzuwenden, d.h. auch dort, wo keine rationalen Zahlen vor-
kommen. Dies verhindern wir durch den dritten Templateparameter.

/**
* zur Implementierung von +,-,*,/ sind nur rationale Zahlen
* zugelassen . X oder Y muss eine rationale Zahl sein
*/
template <class X, class Y>
using is_candidate_t = //
std :: enable_if_t <std :: is_same_v <X, Rational > ||
std :: is_same_v <Y, Rational >>;

Listing 2.88 cppsrc/apps-x011/hpp/x011–13

Der Compiler versucht, einen Zeiger vom Typ void als dritten Template-Parameter zu
deklarieren (Listing 2.84 bis Listing 2.87). Dies gelingt aber nur dann, wenn einer der
beiden Datentypen X oder Y eine rationale Zahl ist. Wir fahren mit der Programmierung
der Vergleichsoperatoren fort, die nur dann angewandt werden, wenn eines der beiden
Eingabeargumente eine rationale Zahl ist.

/**
* @brief operator == Vergleichsoperator
* @param r1 rationale Zahl
* @param r2 rationale Zahl
* @return true wenn die rationalen Zahlen gleich sind
*/
template < typename X, typename Y, is_candidate_t <X, Y> * = nullptr >
inline friend bool operator ==( const X &x, const Y &y) { //
if constexpr (std :: is_same_v <X, Y>) {
// beide Eingabeargumente sind rationale Zahlen
return x.num () * y.den () == x.den () * y.num ();
} else if constexpr (std :: is_same_v <Y, Rational >) {
return x * y.den () == y.num ();
} else {
return x.num () == x.den () * y;
}
}

Listing 2.89 cppsrc/apps-x011/hpp/x011–15

/**
* @brief operator != Vergleichsoperator
* @param r1 rationale Zahl
* @param r2 rationale Zahl
2.3 Klassentemplates 99

* @return true wenn die rationalen Zahlen nicht gleich sind


*/
template <class X, class Y, is_candidate_t <X, Y> * = nullptr >
inline friend bool operator !=( const X &r1 , const Y &r2) { //
return !( r1 == r2);
}

Listing 2.90 cppsrc/apps-x011/hpp/x011–16

Die Potenz einer rationalen Zahl gibt dann eine rationale Zahl zurück, wenn der Exponent
eine ganze Zahl ist; sonst ist das Ergebnis eine Fließkommazahl.

/**
* @brief pow potenziere rationale Zahl
* @param r rationale Zahl
* @param n Potenz
* @return rationale Zahl wenn x ganzzahlig ist
*/
template <class T>
inline friend auto pow( const Rational &r, T n) { //
if constexpr (std :: is_same_v <T, int >) {
Rational r1 = n > 0 ? r : inverse (r);
long long num = //
static_cast <long long >( std :: pow(r1.num (), n));
long long den = //
static_cast <long long >( std :: pow(r1.den (), n));
return Rational { num , den };
} else {
return std :: pow( static_cast <double >(r),
static_cast <double >(n));
}
}

Listing 2.91 cppsrc/apps-x011/hpp/x011–17

Die Inverse einer rationalen Zahl wird berechnet.

/**
* @brief invert inverse rationale Zahl
* @param r rationale Zahl
* @return 1/r
*/
inline friend Rational inverse ( const Rational &r) { //
return Rational (r.den () , r.num ());
}

Listing 2.92 cppsrc/apps-x011/hpp/x011–18

Eine rationale Zahl wird negiert.

/**
* @brief negate eine rationale Zahl wird negiert
* @param r rationale Zahl
* @return -r
*/
inline friend Rational operator -( const Rational &r) { //
100 2 Klassen in C++

return Rational (-r.num () , r.den ());


}

Listing 2.93 cppsrc/apps-x011/hpp/x011–19

Der <<-Operator wird für rationale Zahlen überladen.

/**
* @brief operator << Ausgabe einer rationalen Zahl
* @param os Ausgabestrom
* @param r rationale Zahl
* @return Referenz auf Ausgabestrom
*/
inline friend std :: ostream &operator <<( std :: ostream &os , const
Rational &r) {
return os << r.num () << "/" << r.den ();
}
}; // Rational

Listing 2.94 cppsrc/apps-x011/hpp/x011–20

2.3.4 Beispiele

Es folgt eine Reihe von einfachen Beispielen. Die dazugehörigen Outputs werden nicht
angezeigt. Im ersten Beispiel wird eine rationale Zahl initialisiert, danach vereinfacht und
dann in eine Fließkommazahl umgewandelt.

/**
* @brief ex2 Initialisierung , Vereinfachung , Umwandlung in eine reelle
* Zahl
*/
inline void ex2 () {
Rational r3{ 5, 100 };
std :: cout << r3 << std :: endl;
r3. simplify ();
std :: cout << " simplified :" << r3 << std :: endl;
std :: cout << "as double :" << static_cast <double >(r3) << std :: endl;
}

Listing 2.95 cppsrc/apps-x011/hpp/x011–22

Die Vergleichsoperatoren werden angewandt.

/**
* @brief ex3 Anwendung der Vergleichsoperatoren
*/
inline void ex3 () {
Rational r4{ 2, 4 };

// Ausgabe der bool 'schen Werte


std :: cout << std :: boolalpha << (r4 == Rational { 1, 2 }) <<
std :: endl;
2.3 Klassentemplates 101

std :: cout << std :: boolalpha << (r4 != Rational { 3, 4 }) <<


std :: endl;
std :: cout << std :: boolalpha << (r4 == 0.5) << std :: endl;
}

Listing 2.96 cppsrc/apps-x011/hpp/x011–23

Eine Zahl wird potenziert. Ist die Potenz eine Fließkommazahl, wird das Ergebnis in eine
Fließkommazahl umgewandelt.

/**
* @brief ex4 Potenzierung mit ganzer und mit einer reellen Zahl
*/
inline void ex4 () {
Rational r4{ 2, 4 };
std :: cout << pow(r4 , 3) << std :: endl;
std :: cout << pow(r4 , 3.1) << std :: endl;
}

Listing 2.97 cppsrc/apps-x011/hpp/x011–24


∑8 1 761
Die Summe n=1 n = 280 = 2.71786 wird berechnet. Das Ergebnis wird auch als
Fließkommazahl ausgegeben.

/**
* @brief ex5 Summe aus rationalen Zahlen
*/
inline void ex5 () {
Rational sum{ 0, 1 };

for (long idx = 1; idx <= 8; idx ++) {


sum += Rational { 1, idx };
}

std :: cout << sum << std :: endl;


std :: cout << static_cast <double >( sum) << std :: endl;
}

Listing 2.98 cppsrc/apps-x011/hpp/x011–25

Die Summe einer rationalen Zahl und einer Fließkommazahl ergibt eine Fließkommazahl.
Durch die Implementierung des Plus-Operators als Template wird die Addition einer
Fließkommazahl mit einer rationalen Zahl und umgekehrt berücksichtigt.

/**
* @brief ex6 Summe rationale Zahl mit reeller Zahl Summe ganze Zahl
* mit rationaler Zahl
*/
inline void ex6 () {
Rational r1{ 5, 100 };

auto c = r1 + 0.8;
auto c1 = 1.1 + Rational { 2, 2 };
102 2 Klassen in C++

std :: cout << c << std :: endl;


std :: cout << c1 << std :: endl;
}

Listing 2.99 cppsrc/apps-x011/hpp/x011–26

Zwei rationale Zahlen werden initialisiert. Die eingeführten binären Operatoren +,-,*,/
sowie += werden zum Rechnen mit diesen Zahlen eingesetzt.

/**
* @brief ex1 algebraische Operationen mit rationalen Zahlen
*/
inline void ex1 () {
Rational r1{ 1, 2 }, r2{ 1, 3 };
auto rout = r1 + r2;
std :: cout << r1 << "+" << r2 << "=" << rout << std :: endl;
rout = r1 - r2;
std :: cout << r1 << "-" << r2 << "=" << rout << std :: endl;
rout = r1 * r2;
std :: cout << r1 << "*" << r2 << "=" << rout << std :: endl;
rout = r1 / r2;
std :: cout << r1 << "/" << r2 << "=" << rout << std :: endl;
Rational r5 = r1;
r5 += 1;
std :: cout << r5 << std :: endl;
}

Listing 2.100 cppsrc/apps-x011/hpp/x011–21

2.4 Vererbung
Das Konzept der Vererbung ist ein wichtiges Hilfsmittel zur Wiederverwendung von be-
reits geschriebenem Quelltext. Eine neue Klasse kann von einer Basisklasse abgeleitet
werden und damit einen Teil oder alle Eigenschaften der Basisklasse erben. Dies bedeu-
tet, dass sowohl Daten als auch Elementfunktionen einer Basisklasse der abgeleiteten
Klasse so zur Verfügung gestellt werden können, als ob diese direkt für die abgeleitete
Klasse programmiert wurden. Durch die Schlüsselwörter public, protected und private
wird geregelt, welche Eigenschaften der Basisklasse die abgeleitete Klasse erbt. Sie ist
damit ein Spezialfall der Basisklasse. Wir wollen anhand eines Beispiels demonstrieren,
wie die Vererbung dazu beiträgt, das Schreiben von Quelltext zu reduzieren.

2.4.1 Berechnung von Potenzreihen bekannter Funktionen

Es sollen mit einem Programm mithilfe von Potenzreihen Näherungswerte für die Funk-
tionen ex , sin x und cos x berechnet werden.
2.4 Vererbung 103

Theorie Wir schreiben die Taylorpolynome für die folgenden drei Funktionen an der
Entwicklungsstelle x0 = 0:

x x2 x3 xn
ex = 1 + + + + ... + (2.6)
1! 2! 3! n!
x3 x5 x2n+1
sin x = x − + − . . . + (−1)n (2.7)
3! 5! (2n + 1)!
x2 x4 x6 x2n
cos x = 1 − + − + . . . + (−1)n (2.8)
2! 4! 6! (2n)!
xn 2n+1
Entwurf Eine schnelle Lösung wäre, die allgemeinen Terme n! , (−1)n (2n+1)!
x
und
2n
(−1)n (2n)!
x
zu implementieren und dann mithilfe von Schleifen die Summen zu berechnen.
Wir suchen jedoch nach Möglichkeiten, sich wiederholende Arbeitsschritte nur einmal zu
programmieren und diese immer wieder zu verwenden. Eine Möglichkeit wäre, dies durch
Kombination von normalen Funktionen umzusetzen. Mit der Vererbung steht uns jedoch
ein mächtigeres Werkzeug zur Verfügung, welches wir hier einsetzen werden. Wir suchen
durch die Betrachtung der Formeln nach gemeinsamen Elementen, die in die Basisklasse
geschrieben werden können. Ein Kandidat ist die Routine zur Berechnung der Summen.
Dort soll über Terme summiert werden, die erst in den abgeleiteten Klassen spezifiziert
werden. Die Berechnung von Fakultäten soll ebenfalls innerhalb der Basisklasse imple-
mentiert werden.

Quelltext Wir beginnen mit der Basisklasse. Neben dem Konstruktor finden wir im
protected-Bereich eine Variable zur Speicherung einer λ-Funktion. Diese soll in den ab-
geleiteten Klassen initialisiert werden und den allgemeinen Term zur Berechnung der
Summe implementieren. Die Klasse enthält eine Elementfunktion zur Berechnung und
Speicherung von n! und schließlich eine Methode, die in Abhängigkeit von n ein Vor-
zeichen zurückgibt. Als einziger Operator im public-Bereich steht der überladene Klam-
meroperator.

/**
* @brief The PowerSeries class Basisklasse Berechnung
* von Taylorpolynomen von Funktionen
*/
class PowerSeries
{
protected :
// Anzahl der Terme des Polynoms
size_t _n;

// Funktion : berechnet einzelne Terme


std :: function < double (size_t , double )> _fn;

// Zwischenspeicher n!
inline static std :: vector <double > cache ;

Listing 2.101 cppsrc/apps-x015/hpp/x015–01

Der Konstruktor legt die Anzahl der Terme für die Potenzreihe fest (Listing 2.102).
104 2 Klassen in C++

/**
* @brief PowerSeries Konstruktor
* @param n Anzahl der Terme
*/
inline PowerSeries ( size_t n)
: _n{ n } {}

Listing 2.102 cppsrc/apps-x015/hpp/x015–02

Die Summe wird mit Termen berechnet, die durch das intern gespeicherte Funktionsob-
jekt festgelegt werden.

/**
* @brief sum Summe aller Terme
* @param x Variable
* @return Näherungswert der Funktion
*/
inline double sum( double x) const {
double val = 0;
for ( size_t idx = 0; idx <= _n + 1; idx ++) {
// wende das intern gespeicherte Funktionsobjekt an
val += _fn(idx , x);
}
return val;
}

Listing 2.103 cppsrc/apps-x015/hpp/x015–03

Werte zur Berechnung von n! werden beim ersten Aufruf gespeichert. Ab einem bestimm-
ten n wird die Stirling-Formel eingesetzt.

/**
* @brief factorial Fakultät
* @param n (ganze Zahl )
* @return Werte aus Speicher oder aus Stirling - Formel
*/
static inline double factorial ( size_t n) {
// maximale Anzahl der gespeicherten Werte für n!
const size_t cachesize = 11;
if (cache . empty ()) {
//n! wird berechnet und gespeichert
double val = 1;
cache . push_back (val);
for ( size_t idx = 1; idx < cachesize ; idx ++) {
val *= idx;
cache . push_back (val);
}
}
if (n < cache .size ()) {
return cache [n]; // lese gespeicherten Wert
}
// Stirling - Formel
return sqrt (2 * Math :: PI * n) * std :: pow(n / Math ::E, n);
}

Listing 2.104 cppsrc/apps-x015/hpp/x015–04


2.4 Vererbung 105

Das Resultat von (−1)n wird berechnet.

/**
* @brief sign
* @param n Exponent
* @return Wert ( -1)^n
*/
static inline int sign( size_t n) { return n % 2 == 0 ? 1 : -1; }

public :

Listing 2.105 cppsrc/apps-x015/hpp/x015–05

Der Klammeroperator wird überladen (Listing 2.106), um das Programmieren mit dieser
Klasse zu vereinfachen.

/**
* @brief operator ()
* @param x Variable
* @return Näherungswert der Funktion
*/
inline double operator ()( double x) const { return sum(x); }
}; // PowerSeries

Listing 2.106 cppsrc/apps-x015/hpp/x015–06

Wir leiten von der Basisklasse eine neue Klasse zur Berechnung des Taylorpolynoms für
ex ab. Diese erbt alle Eigenschaften der Basisklasse, z.B. die Berechnung der Summen
und auch alle internen Variablen. Die neue Klasse ist mit wenigen Zeilen Quelltext pro-
grammiert. Hierdurch wird es weniger wahrscheinlich fehlerhafte Software zu schreiben.

/**
* @brief The Exp class Taylorpolynom für e^x
*/
class Exp : public PowerSeries
{
private :
// speichere den zuletzt berechneten Term: x^n
double _powterm ;

public :

Listing 2.107 cppsrc/apps-x015/hpp/x015–07

Der Konstruktor legt die Anzahl der Summenterme fest und spezifiziert die Funktion zur
Berechnung dieser Terme. Dies geschieht mithilfe der Elementfunktion Listing 2.109 der
Klasse und einer anonymen λ-Funktion.

/**
* @brief Exp Konstruktor
* @param n Anzahl der Terme
*/
Exp( size_t n)
106 2 Klassen in C++

: PowerSeries (n) {
// lambda - Funktion ruft Elementfunktion der Klasse auf
_fn = [this ]( size_t np , double x) { return term(np , x); };
}

Listing 2.108 cppsrc/apps-x015/hpp/x015–08


n
Der allgemeine Term xn! wird implementiert. Die Funktion greift auf die gespeicherten
Werte von n! zu und zur Berechnung von xn+1 wird der zuletzt berechnete und gespei-
cherte Wert xn genutzt.

/**
* @brief term Term des Taylor - Polynoms
* @param n n-ter Term
* @param x Variable
* @return Wert für Term n der Potenzreihe
*/
double term( size_t n, double x) {
_powterm = n == 0 ? 1 : _powterm *= x;
return _powterm / factorial (n);
}
}; // Exp

Listing 2.109 cppsrc/apps-x015/hpp/x015–09

Es folgen die entsprechenden Implementierungen für sin und cos. Analog zu ex wird ein
Konstruktor und eine Elementfunktion zur Berechnung der einzelnen Terme der Summe
implementiert.

/**
* @brief The Sin class Taylorpolynom für sin(x)
*/
class Sin : public PowerSeries
{
private :
double _powterm ;

public :

Listing 2.110 cppsrc/apps-x015/hpp/x015–10

/**
* @brief Sin Konstruktor
* @param n Anzahl der Terme
*/
Sin( size_t n)
: PowerSeries (n) {
_fn = [this ]( size_t np , double x) { return term(np , x); };
}

Listing 2.111 cppsrc/apps-x015/hpp/x015–11


2.4 Vererbung 107

/**
* @brief term Term des Taylor - Polynoms
* @param n n-ter Term
* @param x Variable
* @return Wert des n-ten Terms an der Stelle x
*/
double term( size_t n, double x) {
size_t npow = 2 * n + 1;
_powterm = n == 0 ? x : ( _powterm *= x * x);
return sign(n) / factorial (npow) * _powterm ;
}
}; // Sin

Listing 2.112 cppsrc/apps-x015/hpp/x015–12

/**
* @brief The Cos class Taylorpolynom für cos(x)
*/
class Cos : public PowerSeries
{
private :
double _powterm ;

public :

Listing 2.113 cppsrc/apps-x015/hpp/x015–13

/**
* @brief Cos Konstruktor
* @param n Anzahl der Terme
*/
Cos( size_t n)
: PowerSeries (n) {
_fn = [this ]( size_t np , double x) { return term(np , x); };
}

Listing 2.114 cppsrc/apps-x015/hpp/x015–14

/**
* @brief term Term des Taylor - Polynoms
* @param n n-ter term
* @param x Variable
* @return Wert des n-ten Terms an der Stelle x
*/
double term( size_t n, double x) {
size_t npow = 2 * n;
_powterm = n == 0 ? 1 : ( _powterm *= x * x);
return sign(n) / factorial (npow) * _powterm ;
}
}; // Cos

Listing 2.115 cppsrc/apps-x015/hpp/x015–15


108 2 Klassen in C++

Beispiel

Es werden zehn Terme der Taylorpolynome für die Funktionen ex , cos x und sin x an der
Stelle x = 0.25 berechnet und die approximierten Werte zusammen mit den von std::sin,
std::cos, std::exp berechneten Werten auf dem Bildschirm geschrieben (Listing 2.117).

/**
* @brief run_power_series Berechnung von Näherungswerten
* für e^x, sin(x), cos(x) 10 Terme
*/
inline void ex1 () {
const size_t nmax = 10; // Anzahl der Terme

const Exp myexp (nmax);


const Sin mysin (nmax);
const Cos mycos (nmax);

double x0 = 0.25;

// Berechnung und Ausgabe


std :: cout << std :: setprecision (7) << std :: scientific ;
std :: cout << "N=" << nmax << std :: endl;
std :: cout << "x0=" << x0 << std :: endl;
std :: cout << myexp (x0) << "\t" << std :: exp(x0) << std :: endl;
std :: cout << mysin (x0) << "\t" << std :: sin(x0) << std :: endl;
std :: cout << mycos (x0) << "\t" << std :: cos(x0) << std :: endl;

Listing 2.116 cppsrc/apps-x015/hpp/x015–16

N=10
x0 =2.5000000e -01
1.2840254 e+00 1.2840254 e+00
2.4740396e -01 2.4740396e -01
9.6891242e -01 9.6891242e -01

Listing 2.117

2.4.2 Nullstellen eines quadratischen Polynoms mit reellen


Koeffizienten

Von der allgemein formulierten Polynomklasse (Abschnitt 2.3.1) werden wir einen neuen
Datentyp generieren, der ein quadratisches Polynom mit reellen Koeffizienten abbildet.
Insbesondere interessiert uns hier die Berechnung seiner Nullstellen. Wir leiten daher
diese Klasse von der allgemeinen ab (Listing 2.63) und fügen die Elementfunktionen zur
Berechnung der Nullstellen hinzu. Die Implementierung gestaltet sich sehr einfach, da
der Konstruktor mittels einer einfachen Anweisung von der Basisklasse geerbt wird.

/**
* @brief The QPolynom class Nullstellen eines quadratischen Polynoms
2.4 Vererbung 109

* mit reellen Koeffizienten


*/
class QPolynom : public Polynom <double , 2>
{
using BaseT = Polynom <double , 2>;

public :
using BaseT :: BaseT ; // erbe Konstruktoren

Listing 2.118 cppsrc/apps-x016/hpp/x016–01

Zur Berechnung der Nullstellen setzen wir Funktionen der GNU Scientific Library ein. Es
wird unterschieden, ob wir nach reellen (gsl_poly_solve_quadratic) oder nach komplexen
(gsl_poly_complex_solve_quadratic) Nullstellen suchen. Die auf die reellen Nullstellen
spezialisierte Funktion gibt neben den Nullstellen auch ihre Anzahl zurück. Wir geben
deswegen als Rückgabewert immer einen Vektor, dessen Länge der Anzahl der Nullstellen
ist, zurück.

/**
* @brief real_roots berechne reelle Nullstellen
* @return Feld mit Nullstellen , die Länge des Feldes ist
* gleich der Anzahl der Nullstellen
*/
auto real_roots () const {
double x0 , x1; // die Nullstellen
int countroots = gsl_poly_solve_quadratic (c(2) , c(1) , c(0) ,
&x0 , &x1);

if ( countroots == 0) { // keine reelle Nullstellen , leerer Vektor


return std :: vector <double >{};
} else if ( countroots == 1) { // eine reelle Nullstelle
return std :: vector { x0 };
} else { // zwei reelle Nullstelle
return std :: vector { x0 , x1 };
}
}

Listing 2.119 cppsrc/apps-x016/hpp/x016–02

Es folgt die Berechnung der komplexen Nullstellen. Es wird immer ein Container mit
zwei Nullstellen zurückgegeben.

/**
* @brief complex_roots berechne komplexe Nullstellen
* @return Feld mit berechneten Nullstellen (immer zwei)
*/
auto complex_roots () const {
using CVector = std :: vector <std :: complex <double >>;
gsl_complex z0 , z1;
gsl_poly_complex_solve_quadratic (c(2) , c(1) , c(0) , &z0 , &z1);
return CVector { { GSL_REAL (z0), GSL_IMAG (z0) }, //
{ GSL_REAL (z1), GSL_IMAG (z1) } };
}

Listing 2.120 cppsrc/apps-x016/hpp/x016–03


110 2 Klassen in C++

Beispiel

In folgenden Beispielprogramm werden die Nullstellen von p1 (x) = 3x2 + 2x + 1 und


p2 (x) = x2 −3x+1 berechnet. Zur Ausgabe der Ergebnisse führen wir eine λ-Funktion ein.

/**
* @brief cppx4 Beispiel Berechnung von Nullstellen eines
* quadratischen Polynoms
*/
inline void ex1 () {
// lambda Funktion zur Ausgabe eines Polynoms und seiner Nullstellen
auto showResults = []( const QPolynom &p) {
std :: cout << p << std :: endl;
// suche reelle Nullstellen
auto roots = p. real_roots ();
if (roots . empty ()) { // keine reelle Nullstellen
std :: cout << "no real roots " << std :: endl;
auto croots = p. complex_roots ();
for (const auto &z : croots ) {
std :: cout << z << std :: endl;
}
} else {
std :: cout << "real roots " << std :: endl;
for (const auto &x : roots ) {
std :: cout << x << std :: endl;
}
}
};

// 3*x^2+2* x+1
QPolynom p1{ { 1, 2, 3 } };
auto roots = p1. real_roots ();
showResults (p1);
std :: cout << " -------------------" << std :: endl;
//x^2 -3*x+1
QPolynom p2{ { 1, -3, 1 } };
showResults (p2);
}

} // namespace nmx :: apps :: x016

Listing 2.121 cppsrc/apps-x016/hpp/x016–04

Es folgt die Ausgabe des Programms mit den Koeffizienten des Polynoms p1 und seiner
komplexen Nullstellen sowie den Koeffizienten von p2 und seiner zwei reellen Nullstellen.

{1 ,2 ,3}
no real roots
( -0.333333 , -0.471405)
( -0.333333 ,0.471405)
-------------------
{1,-3 ,1}
real roots
0.381966
2.61803

Listing 2.122
2.5 Programmieren mit Komponenten 111

2.5 Programmieren mit Komponenten


Software wird immer wieder Änderungen unterzogen und an neue Anforderungen an-
gepasst. Zugleich werden auftretende Fehler korrigiert. Ziel dabei ist, nicht den ganzen
Quelltext neu zu programmieren, sondern alte Teile gegen neue auszutauschen, ohne
dass dabei Nebeneffekte auftreten. Wie kann aber dieses Ziel erreicht werden? Kleine
Komponenten, die eine spezifische Aufgabe lösen und über Schnittstellen mit anderen
Komponenten kommunizieren, wären in diesem Fall die richtige Wahl. Intern können
dann Änderungen vorgenommen und die unerwünschten Nebeneffekte somit minimiert
werden. Klassen sind ein ideales Konzept, um Komponenten zu entwickeln. Wir werden
mit einem Beispiel die Programmierung von und mit Komponenten demonstrieren und
wählen als Projekt einen interaktiven Test zum Rechnen mit rationalen Zahlen.

2.5.1 Ein Test mit rationalen Zahlen

Der zu entwickelnde interaktive Test soll Aufgaben in Form von Fragen, wie z.B. 13 +
2
5 =? generieren und nach jeder Antwort dem Benutzer mitteilen, ob seine Antwort
richtig oder falsch ist. Bei einer falschen Antwort soll das richtige Ergebnis und nach
Abschluss des Tests die Anzahl der richtigen Antworten angezeigt werden. Die Tests
dürfen nicht identisch sein, d.h. beim Starten eines neuen Tests müssen andere Fragen
generiert werden.

Entwurf Ein interaktiver Test setzt voraus, dass eine Schnittstelle zur Kommuni-
kation mit dem Benutzer existiert. Wir haben die Möglichkeit, zwischen einer grafi-
schen Benutzeroberfläche und einer textbasierten Anwendung zu wählen. In der C++-
Standardbibliothek gibt es keine Funktionen zur Programmierung von grafischen Benut-
zeroberflächen, was bedeutet, dass in diesem Fall auf externe Bibliotheken zurückgegriffen
werden müsste. Wir entscheiden uns, um das Programm nicht übermäßig kompliziert zu
machen, für eine textbasierte Lösung. Die Benutzerschnittstelle wollen wir vom eigentli-
chen Test trennen und dafür eine eigene Komponente erstellen. Die beiden Komponenten
werden mittels ihrer Schnittstellen miteinander kommunizieren.
Der Wertebereich der rationalen Zahlen soll über Parameter gesteuert und damit der
Schwierigkeitsgrad des Tests bestimmt werden. Zugelassen sollen Addition, Subtraktion,
Multiplikation und Division sein.

Quelltext Wir beginnen mit der Programmierung der Komponente zur Generierung der
Fragen, die als Klassentemplate realisiert wird. Es besteht somit die Möglichkeit, diese
Komponente in anderen Programmen mit anderen Zahlentypen einzusetzen. Zur Identi-
fizierung der Operatoren +,-,*,/ wird ein Aufzählungstyp eingeführt, dessen Elemente
in einem Feld gespeichert werden. Eine vorgegebene Anzahl von rationalen Zahlen wird
in einem std::vector gespeichert. Aus diesem Vorrat an Zahlen und Operationen sollen
die Fragen generiert werden. Die Fragen selbst bilden wir mit einem std::tuple ab, ei-
nen Container der Daten von unterschiedlichen Datentypen speichern kann. In unserem
112 2 Klassen in C++

{ 12 , 1
3,
1
4,
1
5,
1
6,
1
7,
1
8, 9}
1
{+, −, ∗, /}

{ 14 , 1 1 1 1 1 1 1
} {−, /, +, ∗ }
6, 5, 7, 9, 8, 2, 3

{ 14 , 1
6, −}, { 15 , 1
7, /}

Abb. 2.5: Ein Test mit rationalen Zahlen: Ein Container mit Zahlen und ein Container
mit algebraischen Operationen werden gemischt und zwei Fragen für den Test generiert.

Fall wird jede Frage aus zwei rationalen Zahlen und einer Operation bestehen. Wir spei-
chern innerhalb der Klasse noch zusätzlich die Anzahl der Fragen, die ein Test beinhalten
soll, und zwei Indizes, mit denen über die gespeicherten rationalen Zahlen und über die
gespeicherten Operationen iteriert werden soll.

/**
* @brief The TestGenerator class Erzeugt Fragen ( Aufgaben ) zum Rechnen
* mit Zahlen . T kann eine ganze eine rationale oder auch eine
* Fließkommazahl sein.
*/
template <class T>
class TestGenerator
{
public :
enum class Ops { plus , minus , mul , div };
// Synonym : Speicher für Zahlen
using DataC = std :: vector <T >;
// Synonym : Speicher für mathematische Operationen
using OpsC = std :: array <Ops , 4>;
// Synonym : Testfrage
using Question = std :: tuple <T, T, Ops >;

private :
// rationale Zahlen
DataC _data ;
// mathematische Operationen
OpsC _types ;
// Anzahl der Fragen in einem Test
size_t _maxQuestions ;
size_t _eIdx , _tIdx ;

Listing 2.123 cppsrc/apps-x017/hpp/x017–01

Für einen mit Zahlen gefüllten Container soll die Wahrscheinlichkeit minimiert werden,
identische Tests zu generieren. Wir erreichen dies durch eine zufällige Änderung der Rei-
henfolge der Elemente bei einem Neustart des Tests oder wenn im Laufe eines Tests
bereits alle Zahlen eingesetzt wurden und wiederverwendet werden müssen. Warum der
Index einen ungültigen Wert zugewiesen bekommt, wird mit der Elementfunktion Lis-
2.5 Programmieren mit Komponenten 113

ting 2.125 klar. Die zufällige Anordnung der Elemente wird mithilfe der stl-Funktion
std::shuffle und eines Pseudo-Zufallsgenerators erreicht.

/**
* @brief shuffle_data mische die gespeicherten Elemente
*/
void shuffle_data () {
// erzeuge Startwert für den Zufallsgenerator
std :: random_device rd;

// Pseudo - Zufallsgenerator
std :: default_random_engine g(rd ());
std :: shuffle ( _data . begin () , _data.end () , g);

// Index hat ungültigen Wert!


_eIdx = _data .size ();
}

Listing 2.124 cppsrc/apps-x017/hpp/x017–02

Mit dem intern gespeicherten Index _eIdx wird über die im Container platzierten Zahlen
iteriert. Hat der Index einen ungültigen Wert, wird er gleich 0 gesetzt.

/**
* @brief next_rational gebe die nächste gespeicherte rationale Zahl
* @return rationale Zahl
*/
auto next_item () {
// wenn der Index einen ungültigen Wert hat erhält er den Wert 0
// sonst rückt er eine Position weiter
_eIdx = ( _eIdx == _data .size ()) ? 0 : _eIdx + 1;
return _data [ _eIdx ];
}

Listing 2.125 cppsrc/apps-x017/hpp/x017–03

Ähnlich verfahren wir mit den algebraischen Operatoren. Dadurch, dass sie in einem
Container gespeichert sind, können sie gemischt und somit zufällige Fragen generiert
werden (Listing 2.126, Listing 2.127).

/**
* @brief shuffle_ops mische algebraische Operationen
*/
void shuffle_ops () {
// erzeuge Startwert für den Zufallsgenerator
std :: random_device rd;

// Pseudo - Zufallsgenerator
std :: default_random_engine g(rd ());
std :: shuffle ( _types . begin () , _types .end (), g);
_tIdx = _types .size ();
}

Listing 2.126 cppsrc/apps-x017/hpp/x017–04


114 2 Klassen in C++

/**
* @brief next_op gebe nächste algebraische Operation
* @return Aufzählung
*/
auto next_op () {
// bei ungültigem Wert setze den Wert auf 0
_tIdx = ( _tIdx == _types .size ()) ? 0 : _tIdx + 1;
return _types [ _tIdx ];
}

Listing 2.127 cppsrc/apps-x017/hpp/x017–05

Ein Testgenerator wird durch die Angabe der Anzahl der Fragen und einen vorgegebenem
Vorrat an Zahlen erzeugt.

/**
* @brief TestGenerator Konstruktor
* @param maxq Anzahl der Fragen
* @param data Feld mit Zahlen zur Generierung von Fragen z.B.
* {1/2 ,1/3 ,1/4 ,...}
*/
TestGenerator ( size_t maxq , const DataC &data) {
_data = data; // kopiere Zahlen
shuffle_data (); // mische Container mit Zahlen

// Anzahl der Fragen im Test ( Startwert ).


// Wird nach einer neu gestellten Frage um 1 reduziert
_maxQuestions = maxq;

// initialisiere Container mit Operatoren


_types = { Ops :: plus , Ops :: minus , Ops ::mul , Ops :: div };

// mische Container mit Operatoren


shuffle_ops ();
}

Listing 2.128 cppsrc/apps-x017/hpp/x017–06

Eine Frage wird mit zwei Zahlen und einem Operator generiert und dann der interne
Zähler für die Anzahl der Fragen aktualisiert. Die zwei ersten Elemente eines std::tuple
sind Zahlen und das dritte und letzte Element der Operator (Abb. 2.5). Der einfachste
Weg ein std::tuple zu initialisieren ist über die Funktion std::make_tuple.

/**
* @brief next_question generiere eine Frage
* @return Frage (std :: tuple )
*/
auto next_question () {
// Anzahl der noch zu stellenden Fragen
_maxQuestions --;
// generiere Frage
return std :: make_tuple ( next_item (), next_item (), next_op ());
}

Listing 2.129 cppsrc/apps-x017/hpp/x017–07


2.5 Programmieren mit Komponenten 115

Es kann geprüft werden, ob noch Fragen generiert werden müssen.

/**
* @brief has_questions
* @return true wenn noch Fragen vorhanden sind
*/
bool has_questions () { return _maxQuestions != 0; }

Listing 2.130 cppsrc/apps-x017/hpp/x017–08

Die korrekte Antwort für eine Frage wird berechnet. Die Elemente des std::tuple werden
mit der Funktion std::get gelesen.

/**
* @brief get_result berechne korrekte Antwort
* @param q Frage
* @return Antwort rationale Zahl
*/
static T calc_result ( const Question &q) {
// lese erste Zahl
auto r1 = std ::get <0 >(q);
// lese zweite Zahl
auto r2 = std ::get <1 >(q);
// das Ergebnis
T r;
// welcher Operator ?
switch (std ::get <2 >(q)) {
case Ops :: plus: {
r = r1 + r2;
} break;
case Ops :: minus : {
r = r1 - r2;
} break;
case Ops :: mul: {
r = r1 * r2;
} break;
case Ops :: div: {
r = r1 / r2;
} break;
}
return r;
}
}; // TestGenerator

Listing 2.131 cppsrc/apps-x017/hpp/x017–09

Die dritte Komponente neben der Klasse für rationalen Zahlen und dem Testgenerator ist
die Benutzerschnittstelle. Wir benötigen hierfür Funktionen, welche die Fragen in Text
und umgekehrt die Benutzerantworten vom Textformat ins interne Format übersetzen.

/**
* @brief The TestUI class Test mit rationalen Zahlen
* Benutzerschnittstelle
*/
class TestUI
116 2 Klassen in C++

{
public :
using Rational = nmx :: apps :: x017 :: Rational ;
using Test = nmx :: apps :: x017 :: TestGenerator <Rational >;

// Testgenerator
Test test;
// algebraische Operationen
static const std :: array <std :: string , 4> ops;
// Anzahl der richtigen Anttworten
size_t _score = 0;
// maximale Punktzahl
const size_t _maxScore ;

public :

Listing 2.132 cppsrc/apps-x017/hpp/x017–10

Die Benutzerschnittstelle speichert intern eine Instanz eines Testgenerators sowie Varia-
blen, welche die erreichte und die maximale Punktzahl speichern. Zusätzlich wird ein
statisches Feld angelegt, in dem die Operatoren als Zeichenketten aufgelistet sind, z.B.
für Ops::plus das Zeichen + usw.

/**
* Umwandlung der Operatoren +,-,*,/ in Zeichenketten
*/
inline const std :: array <std :: string , 4> TestUI :: ops{ "+", "-", "*", "/"
};

Listing 2.133 cppsrc/apps-x017/hpp/x017–15

Eine Instanz der Benutzerschnittstelle wird durch die Angabe der Anzahl der Fragen und
eine Liste von Zahlen erzeugt, die an den Testgenerator weitergereicht werden.

/**
* @brief TestUI Konstruktor
* @param maxq Anzahl der Fragen
* @param data Container mit rationalen Zahlen
*/
TestUI ( size_t maxq , const Test :: DataC &data)
: test{ maxq , data }
, _maxScore { maxq } {}

Listing 2.134 cppsrc/apps-x017/hpp/x017–11

Die Übersetzung einer Frage in eine Zeichenkette erfolgt mithilfe eines std::stringstream.
Die Implementierung ist einfach, denn es existiert eine Version des Operators << für
rationale Zahlen (Listing 2.94).

/**
* @brief to_string Umwandlung Frage in Zeichenkette
* @param q Frage
* @return Frage als Zeichenkette
*/
2.5 Programmieren mit Komponenten 117

inline static std :: string to_string ( const Test :: Question &q) {


std :: stringstream sstream ;
// Frage hat die Form { rationale Zahl , rationale Zahl , Operator }
sstream << std ::get <0 >(q) // lese erste rationale Zahle
<< ops[ static_cast <size_t >( std ::get <2>(q))] // operator
<< std ::get <1 >(q); // lese zweite rationale Zahl
return sstream .str ();
}

Listing 2.135 cppsrc/apps-x017/hpp/x017–13

Die Benutzerantwort wird gelesen. Es wird immer eine Eingabe der Form a/b erwartet.
Ist das nicht der Fall, wird eine Ausnahme ausgeworfen. Zum Einsatz kommt die Funktion
std::stoll, welche eine long long Zahl aus einer Zeichenkette extrahiert.

/**
* @brief read Lese Antwort des Benutzers
* @param answer Antwort des Benutzers ( Zeichenkette )
* @return Antwort des Benutzers als rationale Zahl
*/
inline Rational read( const std :: string & answer ) {
size_t l;
// extrahiere Zähler z.B. 2 von 2/3
long long num = std :: stoll (answer , &l);
//l zeigt auf /
if (l == answer .size () || answer .at(l) != '/') {
throw std :: runtime_error (" expected /");
}
// Rest der Zeichenkette z.B. /3
auto part2 = answer . substr (l + 1);
// extrahiere Nenner
long long den = std :: stoll (part2 , &l, 10);
if (l != part2 .size ()) {
throw std :: runtime_error (" syntax error");
}
return { num , den };
}
}; // TestUI

Listing 2.136 cppsrc/apps-x017/hpp/x017–14

Der Test wird gestartet. Der Testgenerator erzeugt neue Fragen, die als Zeichenketten
auf dem Bildschirm geschrieben werden. Die Benutzereingabe wird gelesen und mit der
richtigen Antwort verglichen. Eine Meldung wird ausgegeben.

/**
* @brief run Interaktion mit dem Benutzer
*/
void run () {
while (test. has_questions ()) {
try {
std :: string answer ;
// generiere Frage
const auto q = test. next_question ();
// zeige Frage auf dem Bildschirm
std :: cout << "q: ";
118 2 Klassen in C++

std :: cout << to_string (q) << "=?" << std :: endl;
// lese Antwort als Zeichenkette
std :: cout << "a:";
getline (std ::cin , answer );
// berechne korrekte Antwort
auto cresult = Test :: calc_result (q);
auto uresult = read( answer );
if ( cresult == uresult ) {
std :: cout << "OK" << std :: endl;
_score ++;
} else {
std :: cout << " correct answer is:" //
<< cresult << std :: endl;
}
} catch (const std :: out_of_range &err) {
std :: cout << " wrong input format " << std :: endl;
std :: cout << " input must be like a/b" << std :: endl;
std :: cout << err.what () << std :: endl;
} catch (const std :: invalid_argument &err) {
std :: cout << " wrong input format " << std :: endl;
std :: cout << " input must be like a/b" << std :: endl;
std :: cout << err.what () << std :: endl;
} catch (const std :: runtime_error &err) {
std :: cout << " wrong input format " << std :: endl;
std :: cout << " input must be like a/b" << std :: endl;
std :: cout << err.what () << std :: endl;
}
}

const auto score = static_cast <double >( _score ) /


static_cast <double >( _maxScore ) * 100;
std :: cout << " score :" << score << "%" << std :: endl;
}

Listing 2.137 cppsrc/apps-x017/hpp/x017–12

Beispiel

Ein Vektor mit rationalen Zahlen wird über einer λ-Funktion mit Daten gefüllt. Es sollen
nur solche Zahlen gespeichert werden, deren Nenner größer als der Zähler ist. Dies wird
mit zwei Schleifen und die Vorgabe des maximalen Werts für den Nenner realisiert. Die
äußere Schleife durchläuft alle möglichen Werte für den Zähler, beginnend mit der 1. Die
innere Schleife beginnt mit dem gerade aktuellen Wert des Zählers, erhöht um 1. Der
Test wird mit diesem Vorrat an rationalen Zahlen gestartet.

/**
* @brief cppx3 Beispiel Test mit rationalen Zahlen
*/
inline void ex1 () {
// Erzeuge einen Container mit rationalen Zahlen
// lege den größten Nenner fest.
auto fn = []( long long lmaxden ) {
std :: vector < TestUI :: Rational > data;
for (long long idx = 1; idx < lmaxden ; idx ++) {
2.6 Schnittstellen für Klassen mithilfe von Templates definieren 119

for (long long jdx = idx + 1; jdx < lmaxden ; jdx ++) {
data. push_back ({ idx , jdx });
}
}
return data;
};
// Instanz eines Tests
TestUI quiz{ 5, fn (10) };
// starte Test
quiz.run ();
}

Listing 2.138 cppsrc/apps-x017/hpp/x017–16

Es folgt der Ablauf eines Tests mit fünf Fragen. Die ersten vier wurden richtig beantwor-
tet. Die korrekte Antwort auf die falsch beantwortete letzte Frage wird angezeigt. Am
Ende erscheint die erreichte Punktzahl in Prozent.

q: 4/9+4/5=?
a :56/45
OK
q: 3/6 -3/4=?
a: -1/4
OK
q: 1/2/6/9=?
a:3/4
OK
q: 2/3*6/7=?
a :11/21
correct answer is :4/7
q: 1/3+2/8=?
a :7/12
OK
score :80%

Listing 2.139

2.6 Schnittstellen für Klassen mithilfe von


Templates definieren
Klassentemplates sowie Funktionentemplates bieten die Möglichkeit, Teile eines Quell-
textes allgemein zu formulieren und somit unnötige Duplizierung von Programmcode zu
vermeiden. Wir wollen diesen Gedanken weiterverfolgen und eine Möglichkeit vorstellen,
Klassen aus Bausteinen zusammenzusetzen. Das Akronym CRTP steht für Curiously Re-
curring Template Pattern und bezeichnet eine Technik, in der eine Klasse von einem Klas-
sentemplate abgeleitet wird und die abgeleitete Klasse selbst der Template-Parameter der
Basisklasse ist. Wir können mit dieser Technik statische (zur Übersetzungszeit erzeugte)
Schnittstellen implementieren und damit eine Klasse mit Funktionalität ausstatten, was
120 2 Klassen in C++

eine Alternative zu virtuellen Funktionen ist. Diese Art zur Erstellung von Schnittstel-
len basiert auf der Tatsache, dass eine Methode eines Klassentemplates nur bei Bedarf
instanziiert wird.
Betrachten wir ein Beispiel, in dem für ein Klassentemplate die zwei Vergleichsopera-
toren =,!= implementiert werden sollen und Objekte von einem noch nicht festgelegten
Typ T miteinander verglichen werden. Da das Klassentemplate nicht wissen kann, wie
zwei Objekte von einem beliebigen Datentyp miteinander verglichen werden, ruft es eine
für den Datentyp T spezielle Elementfunktion is_equal_to auf.

# include <iostream >


#include <algorithm >

template <class T>


class A
{
protected :
A() {}

public :
friend bool operator ==( const T &x, const T &y)
{
return x. is_equal_to (y);
}

friend bool operator !=( const T &x, const T &y)


{
return !x. is_equal_to (y);
}
};

Listing 2.140 cppsrc/src–cppxbase/crtp1.cpp

Nehmen wir nun eine Klasse B welche intern eine ganze Zahl speichert. Sie wird von der
Klasse A abgeleitet und implementiert die Methode is_equal_to. Automatisch stehen der
Klasse B die zwei Vergleichsoperatoren zur Verfügung.

class B : public A<B>


{
private :
int _x;

public :
B(int x) : _x{x} { }

bool is_equal_to ( const B &b) const


{
return _x == b._x;
}
};

Listing 2.141 cppsrc/src–cppxbase/crtp1.cpp


2.6 Schnittstellen für Klassen mithilfe von Templates definieren 121

Betrachten wir eine weitere Klasse C, die Daten in einem Feld verwaltet. Auch diese imple-
mentiert eine für ihre Zwecke geeignete is_equal_to-Methode. Dadurch, dass C ebenfalls
von A abgeleitet wird, stehen auch dieser Klasse die Vergleichsoperatoren automatisch
zur Verfügung.

class C : public A<C>


{
private :
int _data [3];

public :
C(int x1 , int x2 , int x3) : _data {x1 , x2 , x3} { }

bool is_equal_to ( const C &c) const


{
return std :: equal (std :: begin ( _data),std :: end(_data),//
std :: begin (c. _data ),std :: end(c. _data ));
}
};

Listing 2.142 cppsrc/src–cppxbase/crtp1.cpp

Damit könnten wir ein Programm wie dieses schreiben.

int main(void)
{

B b1{3}, b2{4} , b3 {3};

std :: cout << std :: boolalpha << (b1 == b2) << " ,\t";
std :: cout << std :: boolalpha << (b1 == b3) << " ,\t";

C c1{1,2,3} , c2 {0 ,1 ,2} , c3 {1 ,2 ,3};

std :: cout << std :: boolalpha << (c1 == c2) << " ,\t";
std :: cout << std :: boolalpha << (c1 == c3) << std :: endl;

return 0;
}

Listing 2.143 cppsrc/src–cppxbase/crtp1.cpp

false , true , false , true

Listing 2.144

2.6.1 Beispielanwendung

Ausgehend von Gl. 2.6, Gl. 2.7 und Gl. 2.8 sollen die Taylorpolynome für die Funktionen
ex , sin(x) und cos x mithilfe von statischen Schnittstellen implementiert werden. Die
122 2 Klassen in C++

Werte für die Berechnung der Fakultät sollen zwischengespeichert und wiederverwendet
werden.

Kurzbeschreibung Wir werden insgesamt vier Klassen einführen. Eine wird für die
Speicherung der Werte der Fakultät zuständig sein und über einen Template-Parameter
die Größe des Speichers festlegen. Eine zweite Klasse wird die Basisfunktionalität zur
Berechnung der Taylorpolynom-Terme implementieren. Diese Klasse soll in Form einer
Schnittstelle implementiert werden. Ausgehend von dieser Basisklasse werden schließlich
die Klassen für die Berechnung der Taylorpolynome für die drei Funktionen folgen.

Quelltext Die Klasse zur Berechnung und Speicherung der Fakultät ist ein Klassentem-
plate, welches die Werte intern in einem std::array speichert. Die Länge des Feldes wird
über einen Template-Parameter N gesteuert, dessen Standardwert 10 sein wird. Alle Funk-
tionen und Elemente sind statisch. Eine Hilfsfunktion zur Berechnung der Terme wird
bereitgestellt. Sie berechnet und speichert die vorgesehenen Werte beim ersten Aufruf.

/**
* @brief The Factorial class Berechnung von Fakultäten
* @param Anzahl der vorberechneten Werte
*/
template < size_t N = 10>
class Factorial
{
private :
// reserviere Speicher für n!
inline static std :: array <double , N> _data;
// Daten berechnet ?
inline static bool _init = false;

public :
// berechne und speichere n! ( Hilfsfunktion )
inline static void init () {
if (! _init ) {
_data [0] = 1;
for ( size_t idx = 1; idx < N; idx ++) {
_data [idx] = idx * _data[idx - 1];
}
_init = true;
}
}

// Es dürfen keine Instanzen dieser Klasse erzeugt werden


Factorial () = delete ;
Factorial ( const Factorial &) = delete ;
Factorial & operator =( const Factorial &) = delete ;

// berechne n!
inline static double get( size_t n) { //
return n < N ? _data [n] : (n * get(n - 1));
}
};

Listing 2.145 cppsrc/apps-x018/hpp/x018–01


2.6 Schnittstellen für Klassen mithilfe von Templates definieren 123

Es folgt die Komponente zur Berechnung der Taylorpolynome. Diese ist ebenfalls ein
Klassentemplate. Der erste der beiden Template-Parameter gibt den Datentyp der ab-
geleiteten Klasse an und der zweite bestimmt die Anzahl der zu speichernden Fakultäts-
werte.

/**
* @brief The PowerSeries class Schnittstelle zur Entwicklung
* von Funktionen in Potenzreihen Parameter
* @param T die zu entwickelnde Funktion ( Klasse )
*/
template <class T = double , size_t N = 15>
class PowerSeries
{
public :
using Factorial = Factorial <N >;

protected :
size_t _n;
// Variable speichert die Potenzen von x
mutable double xval = 1;

protected :

Listing 2.146 cppsrc/apps-x018/hpp/x018–02

Über den Konstruktor wird die Anzahl der zu berechnenden Terme festgelegt.

/**
* @brief PowerSeries
* @param n Anzahl der Terme der Reihe
*/
inline PowerSeries ( size_t n)
: _n{ n } {
Factorial :: init ();
}

Listing 2.147 cppsrc/apps-x018/hpp/x018–03

Die nächste Elementfunktion leitet den Aufruf an die entsprechende Methode der ab-
geleiteten Klasse weiter. Dazu ist es notwendig, über ein static_cast dem Compiler
mitzuteilen, die Methode welcher Klasse aufgerufen werden soll. In diesem Fall ist es die
Elementfunktion der abgeleiteten Klasse.

/**
* @brief term allgemeiner Term ( Funktionsspezifisch )
* @param x Stelle an der die Potenzreihe ausgewertet werden soll
* @param n der Term der ausgewertet werden soll
* @return der berechnete Wert
*/
inline double term( double x, size_t n) const { //
return static_cast <const T *>( this)->term(x, n);
}

Listing 2.148 cppsrc/apps-x018/hpp/x018–04


124 2 Klassen in C++

Die Terme des Taylorpolynoms werden aufsummiert. Zur Berechnung der Terme wird
die Elementfunktion Listing 2.148 aufgerufen.

/**
* @brief sum Partialsumme ...
* @param x .. an der Stelle
* @return der berechnete Wert der Partialsumme
*/
inline double sum( double x) const {
xval = 1;
double val = 0;
for ( size_t idx = 0; idx < _n + 1; idx ++) {
val += term(x, idx);
}
return val;
}

Listing 2.149 cppsrc/apps-x018/hpp/x018–05

(−1)n wird berechnet.

/**
* @brief sign Berechne (-1)^n
* @param n ganze Zahl
* @return +1 oder -1
*/
inline int sign( size_t n) const { return n % 2 == 0 ? 1 : -1; }

public :

Listing 2.150 cppsrc/apps-x018/hpp/x018–06

Alternativ kann der Näherungswert einer Funktion mithilfe des überladenen Klammer-
operators berechnet werden.

/**
* @brief operator () berechne Funktionswert
* @param x Variable
* @return Funktionswert
*/
inline double operator ()( double x) const { return sum(x); }
}; // PowerSeries

Listing 2.151 cppsrc/apps-x018/hpp/x018–07

Es folgt die Implementierung des Taylorpolynoms für ex . Die Umsetzung ist einfach,
denn das Einzige, was neben dem Konstruktor noch implementiert werden muss, ist die
Formel für die einzelnen Terme der Reihe.

/**
* @brief The Exp class Die Funktion e^x als Taylor - Polynom
*/
class Exp : public PowerSeries <Exp >
{
2.6 Schnittstellen für Klassen mithilfe von Templates definieren 125

friend PowerSeries ; // Zugriff auf private Elemente

protected :

Listing 2.152 cppsrc/apps-x018/hpp/x018–08

/**
* @brief term Term des Taylor - Polynoms
* @param x Variable
* @param n Anzahl der Terme
* @return n-ter Term des Taylor - Polynoms
*/
inline double term( double x, size_t n) const { //
xval *= n == 0 ? 1 : x;
return xval / Factorial :: get(n);
}

public :

Listing 2.153 cppsrc/apps-x018/hpp/x018–09

/**
* @brief Exp Konstruktor
* @param n Anzahl der Terme
*/
Exp( size_t n)
: PowerSeries (n) {}
}; // Exp

Listing 2.154 cppsrc/apps-x018/hpp/x018–10

Es folgen die Klassen für den Sinus und Kosinus.

/**
* @brief The Sin class Die Funktion sin(x) als Taylor - Polynom
*/
class Sin : public PowerSeries <Sin >
{
friend PowerSeries ; // Zugriff auf private Elemente

protected :

Listing 2.155 cppsrc/apps-x018/hpp/x018–11

/**
* @brief term Term des Taylor - Polynoms
* @param x Variable
* @param n Anzahl der Terme
* @return n-ter Term des Taylor - Polynoms
*/
inline double term( double x, size_t n) const { //
const size_t npw = 2 * n + 1;
xval *= n == 0 ? x : (x * x);
126 2 Klassen in C++

return sign(n) * xval / Factorial :: get(npw);


}

public :

Listing 2.156 cppsrc/apps-x018/hpp/x018–12

/**
* @brief Sin Konstruktor
* @param n Anzahl der Terme
*/
Sin( size_t n)
: PowerSeries (n) {}
}; // Sin

Listing 2.157 cppsrc/apps-x018/hpp/x018–13

/**
* @brief The Cos class Die Funktion cos(x) als Taylor - Polynom
*/
class Cos : public PowerSeries <Cos >
{
friend PowerSeries ; // Zugriff auf private Elemente

protected :

Listing 2.158 cppsrc/apps-x018/hpp/x018–14

/**
* @brief term Term des Taylor - Polynoms
* @param x Variable
* @param n Anzahl der Terme
* @return n-ter Term des Taylor - Polynoms
*/
inline double term( double x, size_t n) const { //
const size_t npw = 2 * n;
xval *= n == 0 ? 1 : (x * x);
return sign(n) * xval / Factorial :: get(npw);
}

public :

Listing 2.159 cppsrc/apps-x018/hpp/x018–15

/**
* @brief Cos Konstruktor
* @param n Anzahl der Terme
*/
Cos( size_t n)
: PowerSeries (n) {}
}; // Cos

Listing 2.160 cppsrc/apps-x018/hpp/x018–16


2.6 Schnittstellen für Klassen mithilfe von Templates definieren 127

Ein Vergleich mit der Version aus Abschnitt 2.4.1 zeigt, dass hier keine Variable für ein
Funktionsobjekt gespeichert wird, mit dem die Terme des Polynoms berechnet werden.

Beispiel

Wir berechnen die Funktionswerte für ex , cos x und sin x an der Stelle x0 = 0.2 unter
Berücksichtigung der ersten zehn Terme. Zu den approximierten Werten werden auch die
von std::sin, std::cos und std::exp berechneten Werte ausgegeben (Listing 2.162).

/**
* @brief run_power_series Beispielanwendung
*/
inline void ex1 () {
const Exp myexp (5);
const Sin mysin (5);
const Cos mycos (5);
// berechne Funktionswert an dieser Stelle
const double x0 = 0.2;
std :: cout << myexp (x0) << std :: setw (10) << std :: exp(x0) <<
std :: endl;
std :: cout << mysin (x0) << std :: setw (10) << std :: sin(x0) <<
std :: endl;
std :: cout << mycos (x0) << std :: setw (10) << std :: cos(x0) <<
std :: endl;
}

Listing 2.161 cppsrc/apps-x018/hpp/x018–17

1.2214 1.2214
0.198669 0.198669
0.980067 0.980067

Listing 2.162

Es folgt ein weiteres Beispiel mit dem sin x, cos x und ex an der Stelle x = 0.2 berechnet
werden. Die Berechnung erfolgt mehrmals innerhalb einer Schleife, einmal mit den von
uns programmierten Klassen und dann mit std::sin, std::cos und std::exp. In beiden
Fällen wird die benötigte Zeit gemessen.

/**
* @brief ex2
*/
inline void ex2 () {
using namespace std :: chrono ;
constexpr size_t N = 3;
const Exp myexp (N);
const Sin mysin (N);
const Cos mycos (N);
// berechne Funktionswert an dieser Stelle
const double x0 = 0.2;

double y1 = 0.0 , y2 = 0.0 , y3 = 0.0 , y4 = 0.0, y5 = 0.0, y6 = 0.0;


128 2 Klassen in C++

auto start1 = steady_clock :: now ();


for ( double idx = 0; idx < 100000; idx ++) {
y1 = std :: exp(x0);
y2 = std :: sin(x0);
y3 = std :: cos(x0);
}
auto end1 = steady_clock :: now ();
auto diff1 = duration <double , std :: milli >( end1 - start1 ).count ();

auto start2 = steady_clock :: now ();


for ( double idx = 0; idx < 100000; idx ++) {
y4 = myexp (x0);
y5 = mysin (x0);
y6 = mycos (x0);
}
auto end2 = steady_clock :: now ();
auto diff2 = duration <double , std :: milli >( end2 - start2 ).count ();

std :: cout << std :: setprecision (4) << std :: scientific ;

std :: cout << "dt1=" << diff1 << " ms" << std :: endl;
std :: cout << "dt2=" << diff2 << " ms" << std :: endl;
std :: cout << "dt1/dt2=" << diff1 / diff2 << std :: endl;
std :: cout << " ---------------------------------" << std :: endl;
const size_t w = 15;
std :: cout << "exp(x)" << std :: setw(w) << "sin(x)" << std :: setw(w)
<< "cos(x)" << std :: endl;
std :: cout << y1 << std :: setw(w) << y2 << std :: setw(w) << y3 <<
std :: endl;
std :: cout << y4 << std :: setw(w) << y5 << std :: setw(w) << y6 <<
std :: endl;
}
} // namespace nmx :: apps :: x018

Listing 2.163 cppsrc/apps-x018/hpp/x018–18

dt1 =1.9100e -04 ms


dt2 =3.5770 e+00 ms
dt1/dt2 =5.3396e -05
---------------------------------
exp(x) sin(x) cos(x)
1.2214 e+00 1.9867e -01 9.8007e -01
1.2213 e+00 1.9867e -01 9.8007e -01

Listing 2.164

2.7 Eigene Werkzeuge bauen


Wir wollen die Gelegenheit nutzen und das erarbeitete Wissen zum Bau von eigenen
Werkzeugen einsetzen, um mit diesen später die Entwicklung von Programmen zu ver-
einfachen.
2.7 Eigene Werkzeuge bauen 129

2.7.1 Fehlerbehandlung

Selbst gut getestete Programme haben Fehler. Manche von ihnen machen sich erst be-
merkbar, wenn die Software beim Benutzer im Einsatz ist. Solche Probleme können
nur dann systematisch behoben werden, wenn sinnvolle Meldungen bei Fehlverhalten
erzeugt werden. Die besten Fehlermeldungen sind diejenigen, die am schnellsten zur Feh-
lerbehebung führen. Eine generelle Regel, wie Fehlermeldungen auszusehen haben, gibt
es leider nicht. Eine Mindestanforderung jedoch ist, dass die Stelle im Quelltext regis-
triert wird, an der ein Problem aufgetreten ist. Jede zusätzliche Information macht dann
die Identifizierung des Fehlers einfacher. Zu einem Entwicklungsprozess gehört auch der
Entwurf einer Strategie zur Erkennung und Behandlung von Fehlern, die während der
Ausführung eines Programms auftreten. Wir haben im ersten Kapitel die Vorteile der
Ausnahmebehandlung kennengelernt und wollen diesen Mechanismus durch Einführung
von wiederverwendbaren Funktionen systematisieren.

Kurzbeschreibung Wir möchten mit einer Reihe von Funktionen Fehlermeldungen er-
zeugen, die folgende Informationen enthalten:

Dateiname
Funktionsname
Zeile im Quelltext
Text mit zusätzlichen Informationen (optional)

Quelltext Innerhalb einer Struktur definieren wir zwei statische Funktionen (Listing
2.166 und Listing 2.167), die eine Zeichenkette zusammensetzen, welche die oben aufge-
listeten Informationen enthält. Sie unterscheiden sich dadurch, dass der zweiten Funktion
(Listing 2.167) eine zusätzliche Zeichenkette mitgegeben wird, mit der ein Fehler noch
näher beschrieben werden kann. In beiden Fällen werden die Nachrichten durch den
Einsatz von std::stringstream zusammengesetzt.

/**
* @brief The Error struct erzeugt Fehlermeldungen
*/
struct Error {

Listing 2.165 cppsrc/nmx-xerror/hpp/xerror–01

/**
* @brief msg Text einer Fehlermeldung zusammengesetzt aus
* @param file Dateinamen
* @param line Zeilennummer
* @param func Funktionsnamen
* @return Fehlermeldung als Zeichenkette
*/
inline static std :: string msg( const char *file , int line , const
char *func) {
std :: stringstream myoutput ;
myoutput << file << "\n" << line << "\n" << func;
130 2 Klassen in C++

return myoutput .str ();


}

Listing 2.166 cppsrc/nmx-xerror/hpp/xerror–02

/**
* @brief info Text einer Fehlermeldung zusammengesetzt aus
* @param msg allgemeine Nachricht
* @param file Dateinamen
* @param line Zeilennummer
* @param func Funktionsnamen
* @return Fehlermeldung als Zeichenkette
*/
template <class T>
inline static std :: string msgx( const T &msg , const char *file , int
line , const char *func) {
std :: stringstream myoutput ;
myoutput << msg << "\n" << file << "\n" << line << "\n" << func;
return myoutput .str ();
}
}; // Error

Listing 2.167 cppsrc/nmx-xerror/hpp/xerror–03

Mit statischen Funktionen einer weiteren Struktur können Fehler durch das Auswerfen
von Ausnahmen gemeldet werden.

/**
* @brief The Check struct Testfunktionen zum Auffinden von Fehlern
*/
struct Check {

Listing 2.168 cppsrc/nmx-xerror/hpp/xerror–04

Die ersten beiden Funktionen werfen bei Erfüllung bzw. Nichterfüllung einer Bedin-
gung eine Ausnahme aus. Es ist möglich den Typ der Ausnahme über einen Template-
Parameter festzulegen und somit differenziert auf Fehler zu reagieren.

/**
* @brief error_if Fehler wenn Bedingung erfüllt ist
* @param s Nachricht
* @param arg ( Bedingung ) wenn true wird eine Ausnahme ausgeworfen
*/
template <class X = std :: runtime_error >
void inline static error_if ( const std :: string &s, bool arg) {
if (arg) {
throw X(s);
}
}

Listing 2.169 cppsrc/nmx-xerror/hpp/xerror–05


2.7 Eigene Werkzeuge bauen 131

/**
* @brief error_if_not Fehler wenn Bedingung nicht erfüllt ist
* @param s Nachricht
* @param arg ( Bedingung ) wenn false wird Ausnahme ausgeworfen
*/
template <class X = std :: runtime_error >
void inline static error_if_not ( const std :: string &s, bool arg) {
if (! arg) {
throw X(s);
}
}

Listing 2.170 cppsrc/nmx-xerror/hpp/xerror–06

Die nächste Funktion prüft mehrere Bedingungen auf einmal. Sie ist zum Überprüfen
von Eingabeparametern gut geeignet.

/**
* @brief input teste mehrere Bedingungen auf einmal
* @param s Nachricht
* @param args Bedingungen für Eingabeparameter wenn eine nicht
* erfüllt ist wird eine Ausnahme ausgeworfen
*/
template <class X = std :: invalid_argument >
void inline static all( const std :: string &s,
std :: initializer_list <bool > lst) {
size_t counter = 0;
// iteriere über alle Bedingungen . Jede Bedingung ist ein
// Element der Liste und wird deswegen einem Index zugeordnet .
for (auto itr = lst. begin (); itr != lst.end (); itr ++) {
if (!* itr) {
// wenn eine nicht erfüllt , schreibe den Index in
// die Nachricht
std :: stringstream sstream ;
sstream << s << " invalid argument :" << counter ;
throw X( sstream .str ());
}
counter ++;
}
}

Listing 2.171 cppsrc/nmx-xerror/hpp/xerror–07

Ein ungültiger Zeiger ist oft die Ursache von Fehlern. Mit folgender Funktion kann ein
Zeiger getestet werden.

/**
* @brief null_ptr teste Zeiger auf Gültigkeit
* @param s Nachricht
* @param ptr Zeiger
*/
template <class T, class X = std :: invalid_argument >
void inline static ptr( const std :: string &s, T *ptr) {
if (ptr == nullptr ) {
std :: stringstream sstream ;
sstream << " nullpointer :" << s;
132 2 Klassen in C++

throw X( sstream .str ());


}
}

Listing 2.172 cppsrc/nmx-xerror/hpp/xerror–08

Wir überlassen es dem C++ Präprozessor, Nachrichten mithilfe der Error-Struktur


(Listing 2.165) zu generieren. Der Datei- und Funktionsname sowie die Zeile im
Quelltext werden mithilfe der Makros __FILE__, __LINE__ und der Compiler-Variablen
__PRETTY_FUNCTION__ automatisch erzeugt.

/**
* generiert einen Text mit Dateinamen , Zeile , Funktionsnamen
*/

# define nmx_msg nmx :: Error :: msg(__FILE__ , __LINE__ , __PRETTY_FUNCTION__ )

Listing 2.173 cppsrc/nmx-xerror/hpp/xerror–09

/**
* generiert einen Text mit Dateinamen , Zeile , Funktionsnamen und einer
* zusätzlichen Nachricht
*/
# define nmx_msg_txt (msg) nmx :: Error :: msgx(msg , __FILE__ , __LINE__ ,
__PRETTY_FUNCTION__ )

Listing 2.174 cppsrc/nmx-xerror/hpp/xerror–10

/**
* generiert einen Text mit Dateinamen , Zeile , Funktionsnamen
*und einer zusätzliche Nachricht z.B. aus einem bool 'schen Ausdruck
*/
# define nmx_msgx (msg) nmx :: Error :: msgx (#msg , __FILE__ , __LINE__ ,
__PRETTY_FUNCTION__ )

Listing 2.175 cppsrc/nmx-xerror/hpp/xerror–11

Beispiel

Ein std::array der Länge 5 wird instanziiert und mit Werten gefüllt. Anschließend wer-
den die Elemente auf dem Bildschirm ausgegeben. Bevor jedes Element gelesen wird,
findet eine Bereichsüberprüfung statt. Da versucht wird, mit einem Index außerhalb des
zulässigen Bereichs auf das Feld zuzugreifen, wird eine Ausnahme ausgeworfen. Die Feh-
lermeldung (Listing 2.177) wird mithilfe des Makros nmx_msgx generiert.

/**
* @brief ex1 Fehlerbehandlung Beispielanwendung
*/
2.7 Eigene Werkzeuge bauen 133

inline void ex1 () {


try {
constexpr size_t N = 5;
std :: array <double , N> a;
// fülle Feld
for ( size_t idx = 0; idx < N; idx ++) {
a[idx] = 0.2 * idx;
}
// schreibe Elemente auf dem Bildschirm
for ( size_t idx = 0; idx < N + 2; idx ++) {
// generiere mit Makro eine Nachricht (wenn Index unzulässig )
Check :: error_if_not <std :: out_of_range >( nmx_msgx (idx < N),
idx < N);
std :: cout << a[idx] << std :: setw (5);
}
} catch (std :: out_of_range err) {
std :: cout << "\ nerror :out of range\n" << err.what () << "\n";
}
}
} // namespace nmx :: apps :: x037

Listing 2.176 cppsrc/apps-x037/hpp/x037–01

Das Programm wird ausgeführt. Die Elemente werden auf dem Bildschirm ausgegeben.
Sobald versucht wird auf das Element mit Index 5, welches nicht existiert, zuzugreifen,
wird eine Ausnahme ausgeworfen. Es wird eine Fehlermeldung angezeigt, in der auch die
Bedingung, die nicht erfüllt wurde, enthalten ist.

0 0.2 0.4 0.6 0.8


error : out of range
idx < N
../../ cppdevsrc / numexp /apps/x037.h
25
void nmx :: apps :: x037 :: ex1 ()

Listing 2.177

2.7.2 Eine Klasse zum Formatieren von Daten

Die von einem Programm berechneten Daten werden meistens als Tabellen oder Dia-
gramme dargestellt. Dies bedeutet, dass sie in einem bestimmten Format in Dateien
gespeichert werden müssen.

Ein einführendes Beispiel

Betrachten wir ein Beispiel, in dem zwei Felder auf dem Bildschirm in jeweils eine Reihe
ausgegeben werden müssen. Die Trennzeichen zwischen den Elementen sowie das Zeichen
am Zeilenende sollen für jedes Feld anders sein. Damit nicht zweimal eine Schleife pro-
134 2 Klassen in C++

grammiert werden muss, führen wir eine λ-Funktion ein, der neben dem Feld auch die
beiden Zeichen für die Trennung der Elemente und das Zeilenende übergeben werden.

/**
* @brief ex1 Beispiel für die formatierte Ausgabe von Arrays
*/
inline void ex1 () {
// Datentyp und Länge von std :: array wird automatisch ermittelt
std :: array x{ 1, 2, 3, 4, 5 }, y{ 6, 7, 8, 9, 10 };

// Ausgabe - Funktion und Ausgabe


auto println = []( const auto &arg , const std :: string &sep , const
std :: string &lend) {
std :: cout << arg [0];
for ( size_t idx = 1; idx < arg.size (); idx ++) {
std :: cout << sep << arg[idx ];
}
std :: cout << lend;
};
println (x, ", ", "\n");
println (y, " &", " \\\\\ n");
}

Listing 2.178 cppsrc/apps-x028/hpp/x028–01

Wir führen die Funktion aus und erhalten das gewünschte Ergebnis.

1, 2, 3, 4, 5
6 &7 &8 &9 &10\\

Listing 2.179

Durch die Einführung der λ-Funktion ist die Programmierung von Schleifen für jede
Ausgabe eines Feldes überflüssig. Es ist jedoch möglich, die Formatierung der Daten wei-
ter zu automatisieren. Die Idee ist, die Daten zusammen mit Formatierungsanweisungen
einem Ausgabestrom zu übergeben.

Entwicklung einer Klasse

Kurzbeschreibung Möchten wir Daten als LATEX-Tabellen speichern (Listing 2.180),


so ist die Formatierung eine andere als die einer einfachen CSV-Datei (Listing 2.181).
Während bei einer LATEX-Tabelle die Spalten mit einem & getrennt werden, ist bei einer
CSV-Datei ein Komma notwendig. Zusätzlich muss bei LATEX-Tabellen darauf geachtet
werden, dass am Zeilenende ein \\ steht. Wir wollen diese Unterschiede über Parameter
der Klasse steuern.

1.00e -01&9.81 e +01&9.81 e+00& -9.81e +00&1.97e -01\\


2.00e -01&9.81 e +01&1.96 e+01& -1.96e +01&3.81e -01\\
3.00e -01&9.81 e +01&2.94 e+01& -2.94e +01&5.40e -01\\

Listing 2.180 cppsrc/src–cppxbase/table.tex


2.7 Eigene Werkzeuge bauen 135

1.00e -01 ,9.81e+01 ,9.81e+00 , -9.81e+00 ,1.97e -01


2.00e -01 ,9.81e+01 ,1.96e+01 , -1.96e+01 ,3.81e -01
3.00e -01 ,9.81e+01 ,2.94e+01 , -2.94e+01 ,5.40e -01

Listing 2.181 cppsrc/src–cppxbase/plot.csv

Quelltext Bei Dateien, die Daten in einem bestimmten Format speichern, ist es üblich,
für die Dateiendung spezielle Bezeichner zu wählen, z.B. für eine LATEX-Datei tex und für
eine CSV-Datei csv. Wir wollen dies in der neuen Klasse berücksichtigen und reservieren
eine Variable _extension, die zusammen mit einer weiteren Variablen _name automa-
tisch einen Dateinamen zusammensetzt. So würde für _extension=tex und _name=plot
der Dateiname plot.tex gebildet werden. Die Anzahl der Nachkommastellen für die ge-
speicherten Zahlen kann über den Ausgabestrom gesteuert werden. Wir definieren einen
Standardwert innerhalb der Klasse.

/**
* @brief The Format class Formatierte Ausgabe von Arrays
*/
class Format
{
// Name , Trennzeichen , Zeichen am Ende jeder Zeile , Zusatzname
std :: string _name , _sep , _lend , _extension ;

public :
// Anzahl der Ziffern
std :: streamsize precision = 3;

Listing 2.182 cppsrc/nmx-xfmt/hpp/xfmt–01

Über den Konstruktor werden allen internen Variablen Werte zugewiesen. Die Zahlen
werden standardmäßig in wissenschaftlicher Form und mit drei Nachkommastellen dar-
gestellt (Listing 2.183, Listing 2.184).

/**
* @brief Format Konstruktor
* @param sep Trennzeichen
* @param lend Zeichen am Ende einer Reihe
* @param prec Anzahl der Nachkommastellen
*/
inline Format ( const std :: string &sep , //
const std :: string &lend ,
std :: streamsize prec = 3)
: _name { "none" }
, _sep{ sep }
, _lend { lend }
, _extension { "none" }
, precision { prec } {}

Listing 2.183 cppsrc/nmx-xfmt/hpp/xfmt–02


136 2 Klassen in C++

/**
* @brief Format Konstruktor
* @param name Dateiname
* @param sep Trennzeichen
* @param lend Zeichen am Ende einer Reihe
* @param ext Dateisuffix
* @param prec Anzahl der Nachkommastellen
*/
inline Format ( const std :: string &name , //
const std :: string &sep ,
const std :: string &lend ,
const std :: string &ext ,
std :: streamsize prec = 3)
: _name { name }
, _sep{ sep }
, _lend { lend }
, _extension { ext }
, precision { prec } {}

Listing 2.184 cppsrc/nmx-xfmt/hpp/xfmt–03

Die Trennzeichen für Spalten und die Zeichen am Zeilenende können als Wertepaar aus-
gegeben werden.

/**
* @brief operator () Ausgabe von
* Trennzeichen , Zeichen am Ende jeder Zeile
*/
inline auto operator () () const { return std :: make_pair (_sep ,
_lend); }

Listing 2.185 cppsrc/nmx-xfmt/hpp/xfmt–04

Die Elemente eines Feldes werden in einer Reihe ausgegeben.

/**
* @brief write_line Daten in einem Array werden in
* einer Zeile ausgegeben
* @param ofs Ausgabestrom
* @param crow Array
*/
template <class T>
void write_line (std :: ostream &ofs , const T &crow) const {
ofs << crow [0];
for ( size_t idx = 1; idx < crow.size (); idx ++) {
ofs << _sep << crow[idx ];
}
ofs << _lend ;
}

Listing 2.186 cppsrc/nmx-xfmt/hpp/xfmt–05

Eine Anzahl von Feldern wird einem Ausgabestrom übergeben. Jedes der Felder wird
als Reihe ausgegeben. Das variadische Funktionstemplate erlaubt es mit beliebig vielen
2.7 Eigene Werkzeuge bauen 137

Feldern zu arbeiten. Zur Übersetzungszeit wird die Anzahl der Felder ermittelt und der
entsprechende Code zur Übergabe an den Ausgabestrom generiert.

/**
* @brief as_rows beliebige Anzahl von Arrays
* werden als Reihen ausgegeben
* @param ofs Ausgabestrom
* @param x Array
*/
template <class X, class ... Y>
void as_rows (std :: ostream &ofs , const X &x, const Y &... y) const {
write_line (ofs , x);
// sizeof ... : die Anzahl der Elemente vom Typ Y
if constexpr ( sizeof ...(y) > 0) {
// iteriere über alle Felder
for (const auto &arg : { y... }) {
// eine Zeile für jeden Container
write_line (ofs , arg);
}
}
}

Listing 2.187 cppsrc/nmx-xfmt/hpp/xfmt–06

Die Elemente von mehreren Feldern werden spaltenweise geschrieben.

/**
* @brief as_columns beliebige Anzahl von Arrays
* werden als Spalten einer Tabelle dargestellt
* @param ofs Ausgabestrom
* @param x Array
*/
template <class X, class ... Y>
void as_columns (std :: ostream &ofs , const X &x, const Y &... y)
const {
for ( size_t idx = 0; idx < x.size (); idx ++) {
ofs << x[idx ];
if constexpr ( sizeof ...(y) > 0) {
for (const auto &arg : { y... }) {
ofs << _sep << arg[idx ];
}
}
ofs << _lend ;
}
}

Listing 2.188 cppsrc/nmx-xfmt/hpp/xfmt–07

Ein verschachtelter Container, z.B. std::vector<std::vector<double>> wird reihenweise


einem Ausgabestrom übergeben.

/**
* @brief as_columns Array von Arrays wird als Tabelle ausgegeben .
* @param ofs Ausgabestrom
* @param data Array von Arrays
*/
138 2 Klassen in C++

template <class T>


void as_columns (std :: ostream &ofs , const T &data) const {
const size_t dim = data [0]. size ();
for ( size_t idx = 0; idx < dim; idx ++) {
ofs << data [0][ idx ];
for ( size_t jdx = 1; jdx < data.size (); jdx ++) {
ofs << _sep << data[jdx ][ idx ];
}
ofs << _lend ;
}
}

Listing 2.189 cppsrc/nmx-xfmt/hpp/xfmt–08

Beispiele

Wir formulieren das Beispiel aus Listing 2.178 neu.

/**
* @brief ex2 Beispiel für die formatierte Ausgabe eines Arrays
*/
inline void ex2 () {
Format fmt1{ "fmt1", " ,\t", "\n", "dat" }, //
fmt2{ "fmt2", "&\t", " \\\\\ n", "dat" };
std :: array x{ 1, 2, 3, 4, 5 }, y{ 6, 7, 8, 9, 10 };
fmt1. set_defaults (std :: cout); // es genügt ein Aufruf
fmt1. write_line (std :: cout , x);
fmt2. write_line (std :: cout , y);
}

Listing 2.190 cppsrc/apps-x028/hpp/x028–02

Zwei Felder werden in jeweils eine Reihe ausgegeben. Die Formatierung ist für beide
dieselbe. Die Arbeit wird mit einem einzigen Funktionsaufruf ausgeführt.

/**
* @brief ex3 Beispiel für die formatierte Ausgabe eines Arrays
*/
inline void ex3 () {
Format fmt{ "fmt1", "\t", "\n", "dat" };
std :: array x{ 1, 2, 3, 4, 5 }, y{ 6, 7, 8, 9, 10 };
fmt. set_defaults (std :: cout);
fmt. as_rows (std :: cout , x, y);
}

Listing 2.191 cppsrc/apps-x028/hpp/x028–03

Drei Felder werden reihenweise in wissenschaftlicher Darstellung und im CSV-Format aus-


gegeben.

/**
* @brief ex4 Beispiel für die formatierte Ausgabe eines Arrays
*/
2.7 Eigene Werkzeuge bauen 139

inline void ex4 () {


Format fmt{ "fmt1", " ,\t", "\n", "csv" };
std :: array x{ 1, 2, 3, 4, 5 }, //
y{ 6, 7, 8, 9, 10 }, //
z{ 11, 12, 13, 14, 15 };
fmt. set_defaults (std :: cout);
fmt. as_columns (std :: cout , x, y, z);
}

Listing 2.192 cppsrc/apps-x028/hpp/x028–04

Ein verschachtelter Container wird im CSV-Format ausgegeben.

/**
* @brief ex5 Beispiel für die formatierte Ausgabe eines Arrays
*/
inline void ex5 () {
Format fmt{ "fmt1", " ,\t", "\n", "csv" };
std :: vector <std :: vector <double >> table{ { 1, 2, 3, 4, 5 },
{ 6, 7, 8, 9, 10 },
{ 11, 12, 13, 14, 15 } };
fmt. set_defaults (std :: cout);
fmt. as_columns (std :: cout , table );
}

Listing 2.193 cppsrc/apps-x028/hpp/x028–05

2.7.3 Schreiben von Daten in Dateien

Wir haben den Prozess zur formatierten Ausgabe von Feldern automatisiert und führen
in diesem Abschnitt eine weitere Klasse ein, die in Zusammenarbeit mit der im letzten
Abschnitt entwickelten Formatierungsklasse das Schreiben von Daten in Dateien verein-
facht.

Kurzbeschreibung Die Klasse wird überprüfen, ob die Datei geöffnet wurde und über
eine Instanz der Formatierungsklasse (Listing 2.182) einen Dateinamen generieren. Wir
möchten, dass alle Dateien in einem Basisverzeichnis bzw. in einem Unterverzeichnis
des Basisverzeichnisses abgelegt werden. Der Name dieses Basisordners soll über die ein-
geführten Konfigurationsparameter (Abschnitt 1.9, Listing 1.50) festgelegt werden. Die
neue Klasse soll nur aus statischen Funktionen bestehen, die Ausgabeströme erzeugen.

Quelltext Damit nicht immer neue Formatierungsobjekte instanziiert werden müssen,


definieren wir vier Instanzen für gängige Formatierungen innerhalb der Klasse.

/**
* @brief The Output struct Hilfsklasse zur Erzeugung von Ausgabeströmen
* zum Schreiben in Dateien
*/
class Output
{
140 2 Klassen in C++

public :
inline static const Format latex { " table", "&", "\\\\\n", "tex" };
inline static const Format plot{ "plot", ",", "\n", "csv" };
inline static const Format terminal { " terminal ", "\t", "\n", "out"
};
inline static const Format csv{ "csv", ",", "\n", "csv" };

Listing 2.194 cppsrc/nmx-xoutput/hpp/xoutput–01

Mit folgender Elementfunktion wird ein Ausgabestrom erzeugt und zusätzlich überprüft,
ob die Zieldatei korrekt geöffnet wurde.

/**
* @brief get_output_stream Öffnen einer Ausgabedatei
* falls die Datei nicht geöffnet werden kann wird ein Fehler
* ausgeworfen
* @param fname Name der Ausgabedatei
* @return Ausgabestrom
*/
static std :: ofstream get_stream ( const std :: string &fname) {
std :: ofstream _ofs( fname . c_str ());
if (! _ofs) {
std :: string _msg = " error open file:" + fname;
throw std :: runtime_error ( nmx_msg_txt (_msg));
}
return _ofs;
}

Listing 2.195 cppsrc/nmx-xoutput/hpp/xoutput–02

Eine Datei wird, in einem Unterverzeichnis des Basisverzeichnisses abspeichert. Der Aus-
gabeordner muss existieren.

/**
* @brief get_output_stream Öffnen einer Ausgabedatei
* in einem Unterverzeichnis , Falls die Datei nicht
* geöffnet werden kann , wird ein Fehler ausgeworfen
* @param dirname Name des Unterverzeichnisses
* @param fname Name der Ausgabedatei
* @return Ausgabestrom
*/
static std :: ofstream get_stream ( const std :: string &dirname , const
std :: string & fname ) {
std :: stringstream _stream ;
_stream << settings :: Config :: data_directory //
<< "/" << dirname //
<< "/" << fname ; //
return get_stream ( _stream .str ());
}

Listing 2.196 cppsrc/nmx-xoutput/hpp/xoutput–03

Mit einem Formatierungsobjekt und einem Verzeichnisnamen wird ein Ausgabestrom


generiert. Die dazugehörige Datei wird im vorgegebenen Zielverzeichnis gespeichert.
2.7 Eigene Werkzeuge bauen 141

/**
* @brief get_output_stream Öffnen einer Ausgabedatei
* in einem Unterverzeichnis . Falls die Datei nicht
* geöffnet werden kann , wird ein Fehler ausgeworfen
* @param dirname Name des Unterverzeichnisses
* @param fmt Formatierungsanweisung für den Ausgabestrom , der Name
* der Datei erhält einen Zusatz abhängig von diesem Parameter
* @return Ausgabestrom
*/
static std :: ofstream get_stream ( const std :: string &dirname , const
Format &fmt) {
std :: stringstream _stream ;
_stream << settings :: Config :: data_directory //
<< "/" << dirname //
<< "/" << fmt.name () //
<< "." << fmt.ext (); //
std :: ofstream ofs = get_stream ( _stream .str ());
fmt. set_defaults (ofs);
return ofs;
}

Listing 2.197 cppsrc/nmx-xoutput/hpp/xoutput–04

Ein Ausgabestrom für eine Zieldatei wird erzeugt. Der Dateiname wird durch eine weitere
Zeichenkette oder Zahl ergänzt. Damit können Namenskonflikte, die in der automatischen
Namensgenerierung ihre Ursache haben, vermieden werden.

/**
* @brief get_output_stream Öffnen einer Ausgabedatei
* in einem Unterverzeichnis . Falls die Datei nicht
* geöffnet werden kann , wird ein Fehler ausgeworfen
* @param dirname Name des Unterverzeichnisses oder der Datei
* @param fmt Formatierungsanweisung für den Ausgabestrom , der Name
* der Datei erhält einen Zusatz abhängig von diesem Parameter
* @param id Namenszusatz
* @return Ausgabestrom
*/
template <class T>
static std :: ofstream get_stream ( const std :: string &dirname , const
Format &fmt , const T &id) {
std :: stringstream _stream ;
_stream << settings :: Config :: data_directory //
<< "/" << dirname //
<< "/" << fmt.name () //
<< "-" << id << "." << fmt.ext ();

// erzeuge Ausgabestrom
std :: ofstream ofs = get_stream ( _stream .str ());
fmt. set_defaults (ofs);
return ofs;

Listing 2.198 cppsrc/nmx-xoutput/hpp/xoutput–05

Intern wird ein Formatierungsobjekt für die Ausgabe von Feldern gespeichert. Es existiert
nur eine Kopie, die über den <<-Operator verändert werden kann (Listing 2.199).
142 2 Klassen in C++

/**
* @brief _array internes Formatierungsobjekt
*/
inline static Format _array = Output :: csv;

Listing 2.199 cppsrc/nmx-xoutput/hpp/xoutput–06

/**
* @brief operator << überladener Ausgabeoperator internes
* Formatierungsobjekt wird gesetzt .
* @param os Ausgabestrom
* @param fmt Formatierungsobjekt
* @return Referenz auf Ausgabestrom
*/
inline std :: ostream &operator <<( std :: ostream &os , const Format &fmt) {
Output :: _array = fmt;
return os;
}

Listing 2.200 cppsrc/nmx-xoutput/hpp/xoutput–09

/**
* @brief array lese internes Formatierungsobjekt
* @return Referens auf gespeichertes Formatierungsobjekt
*/
inline static const Format & array () { return _array ; }

Listing 2.201 cppsrc/nmx-xoutput/hpp/xoutput–08

Beispiele

Das folgende Beispiel zeigt, wie eine Tabelle, zusammengesetzt aus zwei verschachtel-
ten std::vector-Containern, in eine Datei geschrieben wird. Einem Formatierungsobjekt
werden zwei Zeichenketten __func__ (Variable für den Funktionsnamen) und csv überge-
ben. Damit wird der Dateiname ex15.csv generiert und in einem Ordner unserer Wahl
innerhalb des Basisverzeichnisses gespeichert.

/**
* @brief ex15 schreibe Tabelle in eine Datei im csv - Format
*/
inline void ex15 () {
Format fmt{ __func__ , ",", "\n", "csv" };
std :: vector <std :: vector <double >> table{ //
{ 1, 2, 3, 4 },
{ 6, 7, 8, 9 },
{ 11, 12, 13, 14 }
};

// Name der Datei ex15.csv im Verzeichnis x019


auto csvstream = Output :: get_stream ("x019", fmt);
2.8 Übungsaufgaben 143

fmt. set_defaults ( csvstream );


fmt. as_columns (csvstream , table );
}

Listing 2.202 cppsrc/apps-x019/hpp/x019–01

2.8 Übungsaufgaben
1. Am unteren Ende eines homogenen Stabs der Masse m und Länge l wird eine Kugel
vom Radius R und Masse M befestigt. Das obere Ende des Stabs wird so befestigt,
dass das System um diesen Punkt frei drehbar ist (physikalisches Pendel). Entwerfen
Sie eine Klasse, welche die Eigenschaften des Systems abbildet.
2. Entwerfen Sie friend-Funktionen, welche die Addition, Subtraktion und Multiplika-
tion für Polynome (Abschnitt 2.3.1) implementieren. Ergänzen Sie diese Funktionen
um eine weitere, welche die Ableitung eines Polynoms berechnet.
3. Entwerfen Sie eine Klasse für ein Polynom dritten Grades und implementieren Sie
mithilfe der gsl-Routinen gsl_poly_solve_cubic, gsl_poly_complex_solve_cubic Ele-
mentfunktion zur Berechnung der Nullstellen.
4. Entwerfen Sie eine Basisklasse zur Beschreibung von regelmäßigen Polygonen. Leiten
Sie von dieser Klassen für ein Dreieck, Viereck, Fünf- und Sechseck ab.
5. Entwerfen Sie einen textbasierten interaktiven Test zum Rechnen mit ganzen Zahlen.
Es sollen dabei nur die vier Grundrechenarten zugelassen sein.
6. Ändern Sie die Klasse Factorial (Listing 2.145) so ab, dass die Berechnung der zwi-
schengespeicherten Werte nicht beim ersten Aufruf stattfindet, sondern erst, wenn
diese angefordert werden.
7. Entwerfen Sie eine Klasse, welche für eine vorgegebene Funktion der Form y = f (x)
Funktionswerte im CSV- und LATEX-Format speichert. Verwenden Sie zur Speicherung
der Daten die Klasse Output (Listing 2.194).
8. Entwerfen Sie eine Klasse, welche einen Text aus einer Datei lädt, die im Text vor-
kommenden Wörter zählt und die Ergebnisse im CSV-Format speichert.
9. Ein Teilchen bewegt sich entlang einer geraden Strecke. Der Ort des Teilchens als
Funktion der Zeit lautet x(t) = at3 + bt2 + ct, mit a = 3.2 m/s3 , b = −1 m/s2
und c = 10 m/s. Schreiben Sie ein Computerprogramm, welches für t = 0, . . . , 10 s
mit ∆t = 1 s den Ort, die Geschwindigkeit und die Beschleunigung des Teilchens
berechnet. Setzen Sie die Polynom-Klasse aus Abschnitt 2.3.1 sowie die Funktion zur
Berechnung der Ableitung eines Polynoms aus Aufgabe 2 ein.
3 Tabellen und Views

Übersicht
3.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
3.2 Eine Klasse für Zahlentabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
3.3 Eine Klasse für Sichten auf Tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
3.4 Erstellen von Views auf Zahlentabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
3.5 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
3.6 Beispiele aus der Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
3.7 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

3.1 Einleitung
Die im letzten Kapitel eingeführten Klassen zur Formatierung von Daten und ihre Spei-
cherung in Dateien werden wir in diesem Kapitel um eine weitere Klasse zur Verwaltung
von Zahlentabellen ergänzen. Sie sollen hauptsächlich zum Speichern von Ergebnissen aus
numerischen Berechnungen eingesetzt werden. Das Hinzufügen von Daten, ihre Speiche-
rung in Dateien sowie eine einfache Suche werden Teil der Funktionalität der Klasse sein.
Views sind Sichten auf Daten. Sie sind eng mit Zahlentabellen verknüpft und machen es
möglich, mit Untermengen der Daten zu arbeiten, ohne Kopien anlegen zu müssen.

3.2 Eine Klasse für Zahlentabellen


Kurzbeschreibung Die Anzahl der Spalten soll bei der Instanziierung einer Tabelle
feststehen, die der Reihen, aber nicht, denn die Tabelle soll nach Bedarf wachsen können.
Wir führen ein Klassentemplate ein, in dem der Template-Parameter N die Anzahl der
Spalten festlegt. Jede Zeile wird ein std::array der Länge N sein, die wiederum Element
eines std::vector-Containers sein wird (Abb. 3.1). Dies hat den Vorteil, dass die ganze
Speicherverwaltung von den stl-Containern übernommen wird.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_3
146 3 Tabellen und Views



 10 11 12 13


 ... ...
std::vector 30 31 32 33 std::array<double,4>

 ... ...


 50 51 52 53

Abb. 3.1: Eine Zahlentabelle als ein std::vector mit std::array als Elemente

/**
* @brief The Data class Eine Klasse für Zahlentabellen mit fester
* Anzahl von Spalten . Die Anzahl der Reihen wächst automatisch . Intern
* wird ein std :: vector von std :: array angelegt
* @param N Anzahl der Spalten
*/
template < size_t N>
class Data
{
public :
using Row = std :: array <double , N >;
using Container = std :: vector <Row >;
using Column = std :: vector <double >;
using View = View <Data <N>>;

private :
Container _data ;

public :

Listing 3.1 cppsrc/nmx-xdata/hpp/xdata–01

Der Standardkonstruktor erzeugt eine leere Tabelle.

/**
* @brief Data Standardkonstruktor die Tabelle enthält keine Daten
*/
inline Data () {}

Listing 3.2 cppsrc/nmx-xdata/hpp/xdata–02

Wir haben die Möglichkeit, auf den internen Container zuzugreifen und direkt mit ihm
zu arbeiten. Dies ist z.B. bei der Nutzung der stl-Algorithmen von Vorteil; andernfalls
müsste die Klasse zusätzlich stl-konforme Iteratoren definieren.

/**
* @brief data lesender Zugriff auf die interne Struktur
* @return Referenz auf interne Struktur
*/
inline const auto &data () const { return _data; }

Listing 3.3 cppsrc/nmx-xdata/hpp/xdata–03


3.2 Eine Klasse für Zahlentabellen 147

/**
* @brief data schreibender Zugriff auf die interne Struktur
* @return Referenz auf interne Struktur
*/
inline auto &data () { return _data ; }

Listing 3.4 cppsrc/nmx-xdata/hpp/xdata–04

Auf die erste und letzte Zeile der Tabelle kann mittels der Elementfunktionen Listing 3.5
bis Listing 3.8 zugegriffen werden.

/**
* @brief first erste Zeile ( schreibender Zugriff )
* @return Referenz auf erste Zeile
*/
inline Row & first () { return * _data . begin (); }

Listing 3.5 cppsrc/nmx-xdata/hpp/xdata–05

/**
* @brief first erste Zeile ( lesender Zugriff )
* @return Referenz auf erste Zeile
*/
inline const Row & first () const { return *_data.begin (); }

Listing 3.6 cppsrc/nmx-xdata/hpp/xdata–06

/**
* @brief last letzte Zeile ( schreibender Zugriff )
* @return Referenz auf letzte Zeile
*/
inline Row &last () { return *( _data .end () - 1); }

Listing 3.7 cppsrc/nmx-xdata/hpp/xdata–07

/**
* @brief last letzte Zeile ( lesender Zugriff )
* @return Referenz auf letzte Zeile
*/
inline const Row &last () const { return *( _data.end () - 1); }

Listing 3.8 cppsrc/nmx-xdata/hpp/xdata–08

Auf die Reihen der Tabelle kann mit den Elementfunktionen Listing 3.9 und Listing 3.10
über einen Index zugegriffen werden.

/**
* @brief row Zugriff auf Zeile per Index (lesen)
* @param idx Zeilenindex
* @return Referenz auf Zeile
148 3 Tabellen und Views

*/
inline const auto &row( size_t idx) const {
// wenn ungültiger Index : out_of_range Ausnahme
return _data .at(idx);
}

Listing 3.9 cppsrc/nmx-xdata/hpp/xdata–09

/**
* @brief row Zugriff auf Zeile per Index ( schreiben )
* @param idx Zeilenindex
* @return Referenz auf Zeile
*/
inline auto &row( size_t idx) {
// wenn ungültiger Index : out_of_range Ausnahme
return _data .at(idx);
}

Listing 3.10 cppsrc/nmx-xdata/hpp/xdata–10

Wie viele Reihen und Spalten die Tabelle hat, kann über die nächsten beiden Funktionen
abgefragt werden.

/**
* @brief rows
* @return Anzahl der Reihen
*/
inline size_t rows () const { return _data.size (); }

Listing 3.11 cppsrc/nmx-xdata/hpp/xdata–11

/**
* @brief columns
* @return Anzahl der Spalten
*/
inline size_t columns () const { return N; }

Listing 3.12 cppsrc/nmx-xdata/hpp/xdata–12

Eine neue Zeile wird in die Tabelle eingefügt. Da Row ein Synonym für ein std::array
gleicher Länge wie die Anzahl der Tabellenspalten ist (Listing 3.1), muss keine Längen-
überprüfung stattfinden.

/**
* @brief add füge eine neue Zeile hinzu
* @param lst Array mit Zahlen
*/
inline void add( const Row &lst) { _data. push_back (lst); }

Listing 3.13 cppsrc/nmx-xdata/hpp/xdata–13


3.2 Eine Klasse für Zahlentabellen 149

Eine neue Reihe kann auch über die Angabe einer Liste von Zahlen hinzugefügt werden.
Die Liste darf die vorgesehene Länge N nicht überschreiten, sie kann aber kürzer sein.
Damit besteht die Möglichkeit, die Tabelle in mehreren Etappen zu füllen.

/**
* @brief add füge eine neue Reihe hinzu. Die Anzahl der
* Elemente darf kleiner als die Anzahl der Spalten sein
* @param lst Liste mit Zeilenelementen
*/
inline void add(std :: initializer_list <double > lst) {
Check :: error_if <std :: out_of_range >( nmx_msgx ( __func__ ), //
lst.size () > N);
// instanziiere neue Zeile
Row crow;
// kopiere Elemente
std :: copy(std :: begin (lst), std :: end(lst), std :: begin(crow));
// addiere neue Reihe zur Tabelle
add(crow);
}

Listing 3.14 cppsrc/nmx-xdata/hpp/xdata–14

Es folgen zwei Versionen des Operators += zum Hinzufügen von Zeilen. Intern werden die
Methoden aus Listing 3.13 und Listing 3.14 aufgerufen.

/**
* @brief operator += Hinzufügen einer Reihe
* @param lst Array mit Zeilenelementen
* @return Referenz auf aufrufendes Objekt
*/
inline auto & operator +=( const Row &lst) {
add(lst);
return *this;
}

Listing 3.15 cppsrc/nmx-xdata/hpp/xdata–15

/**
* @brief operator += Hinzufügen einer Reihe
* @param lst Liste mit Zeilenelementen
* @return Referenz auf aufrufendes Objekt
*/
inline auto & operator +=( std :: initializer_list <double > lst) {
try {
add(lst);
} catch (...) {
// wenn ein Fehler auftritt , enthält die Nachricht
// den richtigen Funktionsnamen
throw std :: out_of_range ( nmx_msgx ( __func__ ));
}
return *this;
}

Listing 3.16 cppsrc/nmx-xdata/hpp/xdata–16


150 3 Tabellen und Views

Die Daten der Tabelle werden einem Ausgabestrom übergeben. Jede Reihe wird mithilfe
der Elementfunktion Listing 2.186, formatiert ausgegeben.

/**
* @brief save Schreibe Tabelle in Datei
* @param ofs Ausgabestrom
* @param fmt Formatierungsanweisung
*/
inline void save(std :: ostream &ofs , Format fmt) const {
for (const auto &row : _data ) {
fmt. write_line (ofs , row);
}
}

Listing 3.17 cppsrc/nmx-xdata/hpp/xdata–17

Vor der Übergabe an den Ausgabestrom kann bei Bedarf auf jede Zeile eine Funktion
angewandt werden.

/**
* @brief save Schreibe Tabelle in Datei
* @param ofs Ausgabestrom
* @param fmt Formatierungsanweisung
* @param fn Funktion zur Bearbeitung der Zeilen
* vor dem Schreiben in die Datei
*/
template <class FN >
void save(std :: ostream &ofs , Format fmt , FN fn) const {
for (const auto &row : _data ) {
fmt. write_line (ofs , fn(row));
}
}

Listing 3.18 cppsrc/nmx-xdata/hpp/xdata–18

Eine Kopie einer Spalte (Listing 3.19) oder eines Teils einer Spalte (Listing 3.20) wird
erstellt.

/**
* @brief column Kopie einer Spalte
* @param cidx Spaltenindex
* @return Spalte als Column ( Synonym für std :: vector )
*/
auto column ( size_t cidx) const {
// erzeuge Vektor mit Länge gleich der Anzahl der Reihen
Column clmn(rows ());
for ( size_t idx = 0; idx < _data.size (); idx ++) {
clmn[idx] = _data [idx ][ cidx ];
}
return clmn;
}

Listing 3.19 cppsrc/nmx-xdata/hpp/xdata–19


3.2 Eine Klasse für Zahlentabellen 151

/**
* @brief column erstelle Kopie einer Spalte
* @param cidx Spaltenindex
* @param fn Testfunktion
* @return Kopie der Spalte cidx
*/
template <class FN >
inline Column column ( size_t cidx , const FN &fn) {
Column clmn;
for (const auto &row : _data ) {
if (fn(row)) {
clmn. push_back (row[cidx ]);
}
}
return clmn;
}
}; // Data

Listing 3.20 cppsrc/nmx-xdata/hpp/xdata–28

Eine ganze Spalte einer Tabelle kann mit einem Aufruf verändert oder mit Werten gefüllt
werden, wenn sie leer ist (Listing 3.21).

/**
* @brief set_column Die Werte einer Spalte werden eingefügt
* oder ersetzt . Wenn die Tabelle leer ist wird erst Speicher
* reserviert
* @param clmn Spalte die kopiert werden soll
* @param idx Spaltenindex
*/
inline void set_column ( const Column clmn , size_t idx) {
if (_data . empty ()) { // Tabelle ist leer
for ( size_t jdx = 0; jdx < clmn.size (); jdx ++) {
Row crow; // erzeuge neue Reihe
crow[idx] = clmn[jdx ]; // setze Wert
_data . push_back (crow); // füge Reihe hinzu
}
} else { // Tabelle nicht leer
if (clmn.size () != rows ()) {
// Fehler : die neue Spalte hat nicht die gleiche Länge
// mit der alten Spalte
throw std :: range_error ( nmx_msgx ( __func__ ));
}
for ( size_t jdx = 0; jdx < rows (); jdx ++) {
_data [jdx ][ idx] = clmn[jdx ]; // ersetze Wert
}
}
}

Listing 3.21 cppsrc/nmx-xdata/hpp/xdata–20

Alle oder bestimmte Zeilen einer Tabelle können geändert werden. Intern wird über eine
Schleife ein Funktionsobjekt auf jede Zeile der Tabelle angewandt (Listing 3.22). Das
Funktionsobjekt kann so programmiert werden, dass es nur dann aktiviert wird, wenn
eine bestimmte Bedingung erfüllt wird.
152 3 Tabellen und Views

/**
* @brief apply ändere alle oder bestimmte Elemente
* @param fn Funktion , die als Eingabeargument eine
* Tabellenzeile erwartet
*/
template <class FN >
void apply(FN fn) {
for (auto &row : _data ) {
fn(row);
}
}

Listing 3.22 cppsrc/nmx-xdata/hpp/xdata–21

Daten aus einer CSV-Datei werden in eine Tabelle geladen. Die Implementierung basiert
auf dem Beispiel Listing 1.78 aus Abschnitt 1.13.

/**
* @brief read_csv lese Daten aus csv -Datei
* @param ifs Eingabestrom
*/
inline void read_csv (std :: ifstream &ifs) {
// teste Status von Eingabestrom
Check :: error_if (nmx_msg , !ifs. is_open ());
std :: string line;
while ( getline (ifs , line)) {
std :: stringstream stream (line);
std :: string token ;
size_t idx = 0;
char *ptr;
std :: array <double , N> buffer ;
while ( getline (stream , token , ',')) {
buffer [idx] = std :: strtod ( token. c_str (), &ptr);
idx ++;
}
add( buffer );
}
}

Listing 3.23 cppsrc/nmx-xdata/hpp/xdata–22

Wir schließen vorerst die Beschreibung der Zahlentabelle ab, um eine Klasse zur Erzeu-
gung von Sichten (Views) auf Zahlentabellen vorzustellen.

3.3 Eine Klasse für Sichten auf Tabellen


Eine View ist ein Objekt, das auf die Daten einer Zahlentabelle zeigt. Dabei kann es sich
um den ganzen oder einen Teil der Zahlentabelle handeln. Es soll nicht erlaubt sein, auf
Teile einer Zeile zu zeigen, sondern immer nur auf die ganze Zeile. Das Wort «zeigen»
bedeutet hier, dass keine Kopie der Daten angelegt wird und damit jede Manipulation
einer Sicht sich auch auf die Daten, auf die sie zeigt, Auswirkung hat. Wir implemen-
3.3 Eine Klasse für Sichten auf Tabellen 153

10 11 12 13
20 21 22 23 View 10 11 12 13
30 31 32 33 30 31 32 33
40 41 42 43 40 41 42 43
50 51 52 53

Abb. 3.2: Sicht auf eine Zahlentabelle, View zeigt auf die Zeilen 0,2,3

tieren eine View als Klassentemplate mit einem Template-Parameter, der dem Typ des
Containers entspricht, auf dessen Daten die View zeigen soll. Intern wird ein Zeiger auf
eine Instanz einer Tabelle und eine Indexliste gespeichert. Die Indexliste gibt vor, auf
welche Reihen zugegriffen werden darf. Sie muss sortiert sein und darf keine doppelten
Einträge haben. Ein zur Speicherung der Indexliste geeigneter Container ist std::set,
weil dieser Elemente in einer sortierten Reihenfolge nur einmal speichert.

/**
* @brief The View class Sicht auf Teilmenge eines Containers
* @param CONTAINER Typ des Containers
*/
template <class CONTAINER >
class View
{
private :
// Zeiger auf einen Container
const CONTAINER * _data = nullptr ;

// sortierte Indexliste keine doppelten Einträge


std ::set <size_t > _indexlist ;

public :

Listing 3.24 cppsrc/nmx-xview/hpp/xview–01

Der Konstruktor erwartet eine Instanz eines Containers und setzt einen Zeiger auf sie.
Die Indexliste ist noch leer (Listing 3.25).

/**
* @brief View Konstruktor
* @param input Container
*/
inline View( const CONTAINER & input )
: _data { & input } {}

Listing 3.25 cppsrc/nmx-xview/hpp/xview–02

Eine Kopie einer View kann mithilfe des Kopierkonstruktors erzeugt werden.

/**
* @brief View Kopierkonstruktor
* @param v View
*/
inline View( const View &v)
154 3 Tabellen und Views

: _data { v. _data }
, _indexlist { v. _indexlist } {}

Listing 3.26 cppsrc/nmx-xview/hpp/xview–03

Ein move-Konstruktor ist ebenfalls implementiert. Er übernimmt neben dem Zeiger auf
den Container auch die Indexliste der Vorlage.

/**
* @brief View Move - Konstruktor
* @param v View
*/
inline View(View &&v)
: _data { nullptr } {
// vertausche Zeiger und Indexliste
std :: swap(_data , v. _data );
std :: swap( _indexlist , v. _indexlist );
//v._data ist ungültig und v. _indexlist ist leer
}

Listing 3.27 cppsrc/nmx-xview/hpp/xview–04

Es folgen die dazugehörigen Zuweisungsoperatoren.

/**
* @brief operator = Zuweisungsoperator
* @param v View
* @return Referenz auf aufrufendes Objekt
*/
inline auto & operator =( const View &v) {
if (this != &v) {
_data = v. _data ;
_indexlist = v. _indexlist ;
}
return *this;
}

Listing 3.28 cppsrc/nmx-xview/hpp/xview–05

/**
* @brief operator =
* @param v View
* @return Referenz auf das aufrufende Objekt
*/
inline auto & operator =( View &&v) {
if (this != &v) {
std :: swap(_data , v. _data );
std :: swap( _indexlist , v. _indexlist );
v._data = nullptr ;
v. _indexlist . clear ();
}
return this;
}

Listing 3.29 cppsrc/nmx-xview/hpp/xview–06


3.3 Eine Klasse für Sichten auf Tabellen 155

Wir können testen, ob die Indexliste leer ist.

/**
* @brief empty
* @return true wenn die View auf keine Daten zeigt
*/
inline bool empty () const { return _indexlist .empty (); }

Listing 3.30 cppsrc/nmx-xview/hpp/xview–07

Zeigt die View auf gültige Daten, so gibt die nächste Funktion true zurück.

/**
* @brief is_valid
* @return true wenn die View auf einen Container zeigt
*/
inline bool is_valid () const { return _data != nullptr ; }

Listing 3.31 cppsrc/nmx-xview/hpp/xview–08

Einer View wird ein einzelner Index (Listing 3.32) oder gleichzeitig mehrere Indizes (Lis-
ting 3.33) übergeben.

/**
* @brief add addiere Elemente
* @param idx Index
*/
inline void add( size_t idx) { _indexlist . insert (idx); }

Listing 3.32 cppsrc/nmx-xview/hpp/xview–09

/**
* @brief add addiere Elemente über Index -Liste
* @param idxlst Indexliste
*/
template <class T>
void add( const T & idxlst ) {
_indexlist . insert ( idxlst . begin () , idxlst .end ());
}

Listing 3.33 cppsrc/nmx-xview/hpp/xview–10

Die Reihen einer Zahlentabelle, auf welche die View zeigt, werden einem Ausgabestrom
übergeben.

/**
* @brief save Speichere Container auf den die View zeigt
* @param ofs Ausgabestrom
* @param fmt Formatierung
*/
inline void save(std :: ofstream &ofs , Format fmt) const {
for (const auto idx : _indexlist ) {
const auto &crow = _data ->row(idx);
156 3 Tabellen und Views

fmt. write_line (ofs , crow);


}
}

Listing 3.34 cppsrc/nmx-xview/hpp/xview–11

Die Daten einer View werden vor der Übergabe an einen Ausgabestrom von einem Funk-
tionsobjekt bearbeitet.

/**
* @brief save Speichere Container auf den die View zeigt
* @param ofs Ausgabestrom
* @param fmt Formatierung
* @param fn Funktion zur Bearbeitung der Reihe
*/
template <class FN >
void save(std :: ofstream &ofs , Format fmt , FN fn) const {
for (const auto idx : _indexlist ) {
const auto crow = fn(_data ->row(idx));
fmt. write_line (ofs , crow);
}
}

Listing 3.35 cppsrc/nmx-xview/hpp/xview–12

Eine Kopie einer Spalte wird erstellt. Es werden nur die Elemente aufgenommen, auf
welche die View Zugriff hat.

/**
* @brief column Kopie einer Spalte
* @param cidx Spaltenindex
* @return Kopie Spalte (im Standardfall ein std :: vector )
*/
inline auto column ( size_t cidx) const {
// der Datentyp der Spalte wird über den Template - Parameter
// festgelegt . ( reserviere Speicher )
typename CONTAINER :: Column clmn( _indexlist .size ());
auto clmnitr = clmn. begin ();
for (const auto &idx : _indexlist ) {
// idx : Reihe auf welche die View Zugriff hat
// cidx: die gewünschte Spalte
* clmnitr = _data ->row(idx)[cidx ];
// zeige auf nächstes Element
clmnitr ++;
}
return clmn;
}
}; // View

} // namespace nmx

Listing 3.36 cppsrc/nmx-xview/hpp/xview–13


3.4 Erstellen von Views auf Zahlentabellen 157

3.4 Erstellen von Views auf Zahlentabellen


Wir fahren mit einer Reihe von Elementfunktionen der Klasse Data fort, die für die
Erstellung von Sichten auf Zahlentabellen spezialisiert sind. Wir beginnen mit einer View,
welche Zugriff auf die ganze Zahlentabelle hat.

/**
* @brief view die ganze Tabelle als view
* @return eine Instanz vom Typ View
*/
inline auto view () const { return View (* this); }

Listing 3.37 cppsrc/nmx-xdata/hpp/xdata–23

Wir definieren ein Filter als eine Funktion oder als ein Funktionsobjekt, welches Zeilen
einer Tabelle einliest und diese einer View nach bestimmten Kriterien hinzufügt. Nützlich
ist dies z.B. für die Darstellung von berechneten Daten in Tabellen. Ist die berechnete
Datenmenge zu groß, wird die Tabelle schnell unübersichtlich. Eine Sicht auf eine re-
präsentative Untermenge der Daten könnte dann als Tabelle dargestellt werden. Mit der
Elementfunktion (Listing 3.38) wird eine View auf die ganze oder eine Teilmenge einer
Zahlentabelle erzeugt. Eine Testfunktion liest jede Reihe der Tabelle und fügt, wenn ein
vorgegebenes Kriterium erfüllt wird, den entsprechenden Index zu der View hinzu. Die
Testfunktion, welche die Rolle eines Filters spielt, kann jede Art von Funktionsobjekt
sein, welches eine Tabellenzeile einliest und entweder true oder false zurückgibt. Mit
zwei optionalen Parametern kann festgelegt werden, ob der View die erste und/oder die
letzte Tabellenzeile hinzugefügt wird.

/**
* @brief view Teile der Tabelle als View
* @param fn Funktionsobjekt zur Auswahl der Tabellenreihen
* @param f optional füge erste Tabellenzeile hinzu
* @param l optional füge letzte Tabellenzeile hinzu
* @return eine Instanz vom Typ View
*/
template <class PREDICATE >
auto view( PREDICATE fn , bool f = true , bool l = true) const {
View view (* this);
if (f) { // füge erste Zeile der Tabelle hinzu?
view.add( static_cast <size_t >(0));
}
for ( size_t idx = 0; idx < rows (); idx ++) {
if (fn( _data [idx ])) {
view.add(idx); // addiere Reihenindex zur View
}
}
if (l) { // füge letzte Zeile der Tabelle hinzu?
view.add( _data .size () - 1);
}
return view;
}

Listing 3.38 cppsrc/nmx-xdata/hpp/xdata–24


158 3 Tabellen und Views

Eine View kann auch durch die Angabe einer Liste von Iteratoren, welche auf ausgewählte
Elemente eines Containers zeigen, erzeugt werden. Optional können die erste und letzte
Zeile des Containers hinzugefügt werden. Intern wird der Index eines Elements mit der
stl-Funktion std::distance ermittelt.

/**
* @brief view Teile der Tabelle als View
* @param lst eine Liste mit Iteratoren , welche auf Elemente
* des Containers zeigen
* @param f füge erste Tabellenzeile hinzu ( optional )
* @param l füge letzte Tabellenzeile hinzu ( optional )
* @return eine Instanz vom Typ View
*/
inline auto view(std :: initializer_list < typename
Container :: iterator > lst ,
bool f = true ,
bool l = true) {
View view (* this);

if (f) { // füge erste Zeile der Tabelle hinzu?


view.add( static_cast <size_t >(0));
}
for (const auto &itr : lst) {
// berechne Index
const auto cidx = std :: distance (_data .begin (), itr);
// addiere Reihenindex zur View
view.add( static_cast <size_t >( cidx));
}
if (l) { // füge letzte Zeile der Tabelle hinzu?
view.add( _data .size () - 1);
}
return view;
}

Listing 3.39 cppsrc/nmx-xdata/hpp/xdata–25

Beginnend ab einer bestimmten Reihe der Zahlentabelle werden, mit einer vorgegebenen
Schrittweite, weitere Reihen einer View hinzugefügt.

/**
* @brief view Teile der Tabelle als View
* @param step Schrittweite
* @param startidx erste Stelle im Container
* @param last füge letzte Tabellenzeile hinzu ( optional )
* @return eine Instanz vom Typ View
*/
inline auto select_rows ( size_t step , size_t startidx = 0, bool last
= false) const {
View view (* this);

for ( size_t idx = startidx ; idx < _data.size (); idx += step) {
// addiere Reihenindex zur View
view.add(idx);
}

if (last) { // füge letzte Zeile der Tabelle hinzu ?


3.5 Beispiele 159

view.add( _data .size () - 1);


}
return view;
}

Listing 3.40 cppsrc/nmx-xdata/hpp/xdata–26

Eine View wird, mit einer vorgegebenen Anzahl von äquidistanten Reihen einer Zahlen-
tabelle, aufgebaut.

/**
* @brief get_total_rows generiere View mit eine Instanz vom Typ
View
* bestimmter Anzahl von Spalten
* @param n Anzahl der Reihen
*/
inline auto select_total_rows ( size_t n, size_t start = 0, bool l =
true) const {
// berechne Abstand zwischen den Zeilen
const size_t stepSize = std :: ceil (( rows ()) / (n - 1));
return select_rows (stepSize , start , l);
}

Listing 3.41 cppsrc/nmx-xdata/hpp/xdata–27

3.5 Beispiele
3.5.1 Generieren einer Tabelle und Speicherung in eine Datei

Eine leere Tabelle wird erzeugt und mit vier Zeilen aus jeweils drei Elementen gefüllt. Ein
Formatierungsobjekt (siehe Abschnitt 2.7.2) sorgt dafür, dass die Daten im Verzeichnis
x020 und unter dem automatisch generierten Namen ex16.csv gespeichert werden.

/**
* @brief ex16 Aufbau einer Tabelle und Ausgabe in Datei
*/
inline void ex16 () {
Data <3> data; // Tabelle mit drei Spalten
// fülle Tabelle mit Daten
data += { 1, 2, 3 }; // Reihe 0
data += { 10, 20, 30 }; // Reihe 1
data += { 100 , 200 , 300 }; // Reihe 2
data += { 1000 , 2000 , 3000 }; // Reihe 3
// Formatierung
Format fmt{ __func__ , ",", "\n", "csv" };
// Ausgabe
auto csvstream = Output :: get_stream ("x020", fmt);
data.save(csvstream , fmt);
}

Listing 3.42 cppsrc/apps-x020/hpp/x020–01


160 3 Tabellen und Views

1.000 e+00 ,2.000e+00 ,3.000e+00


1.000 e+01 ,2.000e+01 ,3.000e+01
1.000 e+02 ,2.000e+02 ,3.000e+02
1.000 e+03 ,2.000e+03 ,3.000e+03

Listing 3.43 data/x020/ex16.csv

3.5.2 Speicherung einer veränderten Tabelle in eine Datei

Eine Tabelle enthält pro Reihe einen Winkel in Bogenmaß und den Sinus und Kosinus
dieses Winkels. Gespeichert werden die Winkel in Gradmaß. Die Daten werden vor dem
Speichern in die Datei umgerechnet. Das Anlegen einer Kopie ist somit nicht nötig, wie
in Listing 3.44 zu sehen ist.

/**
* @brief ex17 Ausgabe einer Tabelle in Datei. Jede Zeile wird vor
* dem Speichern bearbeitet
*/
inline void ex17 () {
using Data = Data <3 >;
// Tabelle mit drei Spalten
Data data;

// fülle Tabellen mit Daten


for (const auto x : { 6, 3, 2 }) {
const double phi = Math :: PI / x;
data += { phi , sin(phi), cos(phi) };
}

// Formatierung
Format fmt{ __func__ , ",", "\n", "csv" };

// Ausgabe
auto csvstream = Output :: get_stream ("x020", fmt);
data.save(csvstream , fmt , []( const auto &crow) {
return Data :: Row{ Math :: to_degrees (crow [0]) , crow [1], crow [2] };
});
}

Listing 3.44 cppsrc/apps-x020/hpp/x020–02

3.000 e+01 ,5.000e -01 ,8.660e -01


6.000 e+01 ,8.660e -01 ,5.000e -01
9.000 e+01 ,1.000e+00 ,6.123e -17

Listing 3.45 data/x020/ex17.csv


3.5 Beispiele 161

3.5.3 Bearbeitung einer Tabelle

Eine Tabelle mit vier Spalten wird erzeugt. Jede Spalte stellt einen dreidimensionalen
Vektor dar. Wir wollen in einer neuen Tabelle die normierten Vektoren speichern.

/**
* @brief ex18 Bearbeitung einer Tabelle . Berechnung der Normen von
* Vektoren
*/
inline void ex18 () {
// Tabelle mit vier Spalten
using Data = Data <4 >;
Data table;
table += { -4, 3, 7, 1 };
table += { -2, 8, 2, 0 };
table += { 9, 1, 1, 2 };

// speichere Normen der Vektoren


Data :: Row results ;
// addieren Quadrate der Elemente
table.apply ([& results ]( const Data :: Row &crow) {
for ( size_t idx = 0; idx < crow.size (); idx ++) {
results [idx] += std :: pow(crow[idx], 2);
}
});

for (auto &res : results ) {


res = std :: sqrt(res);
}

// Kopiere Tabelle
Data normtable = table ;
// normiere Vektoren
for (auto &crow : normtable .data ()) {
for ( size_t idx = 0; idx < crow.size (); idx ++) {
crow[idx] /= results [idx ];
}
}
// füge als letzte Reihe die Norm hinzu
normtable += results ;

// formatierte Ausgabe
Format fmt{ __func__ , " ,\t", "\n", "csv" };
fmt. precision = 3;
auto csvstream = Output :: get_stream ("x020", fmt);
// ursprüngliche Tabelle
table.save(csvstream , fmt);
csvstream << " -------------------------------" << std :: endl;
// Tabelle mit normierten Vektoren und Normen
normtable .save(csvstream , fmt);
}

Listing 3.46 cppsrc/apps-x020/hpp/x020–03


162 3 Tabellen und Views

Nach Ausführung der Funktion erhalten wir die ursprüngliche Tabelle und die neue mit
den normierten Vektoren. Die letzte Reihe der neuen Tabelle enthält die Normen der
Vektoren.

-4.000e+00, 3.000 e+00 , 7.000 e+00 , 1.000 e+00


-2.000e+00, 8.000 e+00 , 2.000 e+00 , 0.000 e+00
9.000 e+00, 1.000 e+00 , 1.000 e+00 , 2.000e+00
-------------------------------
-3.980e-01, 3.487e -01 , 9.526e -01 , 4.472e -01
-1.990e-01, 9.300e -01 , 2.722e -01 , 0.000 e+00
8.955e-01, 1.162e -01 , 1.361e -01 , 8.944e -01
1.005 e+01, 8.602 e+00 , 7.348 e+00 , 2.236e+00

Listing 3.47 data/x020/ex18.csv

3.5.4 Suchen und Bearbeiten von Elementen einer Tabelle

Alle Elemente einer Tabelle, die größer als 6 sind, werden mit −1 multipliziert. Die
bearbeitete Tabelle wird in eine Datei gespeichert.

/**
* @brief ex19 Suchen und Bearbeiten von Elementen einer Tabelle
*/
inline void ex19 () {
// Tabelle mit vier Spalten
using Data = Data <4 >;
Data table;

table += { 1, 2, 3, 4 };
table += { 5, 6, 7, 8 };
table += { 9, 10, 11, 12 };

// ändere alle Zahlen , die größer als 6 sind


table.apply ([]( Data :: Row &crow) { //
std :: replace_if (ALL(crow), []( double x) { return x > 6; }, -1);
});

// Formatierung
Format fmt{ __func__ , ",", "\n", "csv" };

// Ausgabe
auto csvstream = Output :: get_stream ("x020", fmt);
table.save(csvstream , fmt);
}

Listing 3.48 cppsrc/apps-x020/hpp/x020–04

1.000 e+00 ,2.000e+00 ,3.000e+00 ,4.000e+00


5.000 e+00 ,6.000e+00 , -1.000e+00 , -1.000e+00
-1.000e+00 , -1.000e+00 , -1.000e+00 , -1.000e+00

Listing 3.49 data/x020/ex19.csv


3.5 Beispiele 163

3.5.5 Laden einer Tabelle aus Datei

Eine Tabelle wird mit Daten aus einer Datei (CSV-Format) gefüllt. Diejenigen Elemente,
die kleiner als −6 sind, werden gleicht 0 gesetzt. Die Tabelle wird anschließend in eine
Datei gespeichert.

/**
* @brief ex20 lade Tabelle aus csv -Datei ändere Werte
* und speichere Tabelle wieder ab
*/
inline void ex20 () {
using Data = Data <4 >;
Data table;

// generiere Namen
std :: string filename = settings :: Config :: data_directory + //
"/x020/ex19.csv";

// lade Tabelle
std :: ifstream ifs( filename );
table. read_csv (ifs);

// ändere alle Zahlen , die kleiner als 6 sind


table.apply ([]( Data :: Row &crow) { //
std :: replace_if (ALL(crow), []( double x) { return x < 6; }, 0);
});

// Ausgabe
Format fmt{ __func__ , " ,\t", "\n", "csv" }; // Formatierung
auto csvstream = Output :: get_stream ("x020", fmt);
table.save(csvstream , fmt);
}

Listing 3.50 cppsrc/apps-x020/hpp/x020–05

0.000 e+00, 0.000 e+00 , 0.000 e+00 , 0.000e+00


0.000 e+00, 6.000 e+00 , 0.000 e+00 , 0.000e+00
0.000 e+00, 0.000 e+00 , 0.000 e+00 , 0.000e+00

Listing 3.51 data/x020/ex20.csv

3.5.6 Sichten auf Tabellen

Eine Wertetabelle für die Funktionen f1 (x) = x3 und f2 (x) = x5 mit −5 ≤ x < 5 und
∆x = 1 wird erzeugt. Die View zeigt nur auf Tabellenreihen mit positiven Funktions-
werten.

/**
* @brief ex21 Filter für Funktionswerte (Sicht auf Tabelle )
*/
inline void ex21 () {
164 3 Tabellen und Views

using Data = Data <3 >;


Data table;

// Erzeuge Daten
for ( double x = -5; x < 5; x += 1) {
table += { x, pow(x, 3) , pow(x, 5) };
}

// Filter : alle positiven Funktionswerte


const auto view = table .view(
[]( const Data :: Row &crow) {
return crow [1] > 0 && crow [2] > 0; //
},
false ,
false );

// Ausgabe
Format fmt{ __func__ , " ,\t", "\n", "csv" }; // Formatierung
auto csvstream = Output :: get_stream ("x020", fmt);
view.save(csvstream , fmt);
}

Listing 3.52 cppsrc/apps-x020/hpp/x020–06

1.000 e+00, 1.000 e+00 , 1.000 e+00


2.000 e+00, 8.000 e+00 , 3.200 e+01
3.000 e+00, 2.700 e+01 , 2.430 e+02
4.000 e+00, 6.400 e+01 , 1.024 e+03

Listing 3.53 data/x020/ex21.csv

In einem weiteren Beispiel wird eine Sicht auf eine Tabelle erzeugt. Jede zweite Reihe
wird in die View aufgenommen. Jede Reihe der Tabelle enthält die Werte x, x3 , sin x mit
−4 ≤ x ≤ 4 und ∆x = 1. Die View zeigt auf die Reihen mit −3 ≤ x ≤ 3 und ∆x = 2.

/**
* @brief ex22 Filter für Funktionswerte : Filter für jede zweite
* Reihe der Tabelle
*/
inline void ex22 () {
using Data = Data <3 >;
Data table;

// Erzeuge Daten
for ( double x = -4; x < 4; x += 1) {
table += { x, pow(x, 3) , sin(x) };
}

// Filter : nehme jede zweite Reihe beginne ab Reihe 1


// ignoriere letzten Eintrag
const auto view = table . select_rows (2, 1, false);

// formatierte Ausgabe
Format fmt{ __func__ , " ,\t", "\n", "csv" };
auto csvstream = Output :: get_stream ("x020", fmt);
table.save(csvstream , fmt);
3.5 Beispiele 165

csvstream << " --------------" << std :: endl;


view.save(csvstream , fmt);
}

Listing 3.54 cppsrc/apps-x020/hpp/x020–07

-4.000e+00, -6.400e+01 , 7.568e -01


-3.000e+00, -2.700e+01 , -1.411e -01
-2.000e+00, -8.000e+00 , -9.093e -01
-1.000e+00, -1.000e+00 , -8.415e -01
0.000 e+00, 0.000 e+00 , 0.000 e+00
1.000 e+00, 1.000 e+00 , 8.415e -01
2.000 e+00, 8.000 e+00 , 9.093e -01
3.000 e+00, 2.700 e+01 , 1.411e -01
--------------
-3.000e+00, -2.700e+01 , -1.411e -01
-1.000e+00, -1.000e+00 , -8.415e -01
1.000 e+00, 1.000 e+00 , 8.415e -01
3.000 e+00, 2.700 e+01 , 1.411e -01

Listing 3.55 data/x020/ex22.csv

3.5.7 Kopie einer Spalte

Wir stellen den Sinus und Kosinus für −π ≤ x < π und ∆x = π4 in eine Tabelle dar.
Eine View zeigt auf die Reihen der Tabelle, für welche cos x < 0 ist. Eine Kopie der
Spalte mit den Werten für den Kosinus wird erstellt. Die Zahlentabelle, die View sowie
die Kopie der Spalte werden in eine Datei geschrieben.

/**
* @brief ex23 Kopie einer Spalte
*/
inline void ex23 () {
using Data = Data <3 >;
Data table;

// erzeuge Daten
for ( double x = -Math :: PI; x < Math :: PI; x += 0.25 * Math ::PI) {
table += { x, std :: cos(x), std :: sin(x) };
}

// View zeigt auf die Reihen mit cos x < 0


const auto view = table .view(
[]( const auto &r) { //
return r[1] < 0;
},
false ,
false );

// Ausgabe
Format fmt{ __func__ , " ,\t", "\n", "csv" }; // Formatierung
auto csvstream = Output :: get_stream ("x020", fmt);
166 3 Tabellen und Views

table.save(csvstream , fmt);
csvstream << " --------------" << std :: endl;
view.save(csvstream , fmt);
csvstream << " --------------" << std :: endl;
Data :: Column clmn = view. column (1);
fmt. write_line (csvstream , clmn);
}

Listing 3.56 cppsrc/apps-x020/hpp/x020–08

-3.142e+00, -1.000e+00 , -1.225e -16


-2.356e+00, -7.071e -01 , -7.071e -01
-1.571e+00, 6.123e -17 , -1.000e+00
-7.854e-01, 7.071e -01 , -7.071e -01
0.000 e+00, 1.000 e+00 , 0.000 e+00
7.854e-01, 7.071e -01 , 7.071e -01
1.571 e+00, 6.123e -17 , 1.000 e+00
2.356 e+00, -7.071e -01 , 7.071e -01
--------------
-3.142e+00, -1.000e+00 , -1.225e -16
-2.356e+00, -7.071e -01 , -7.071e -01
2.356 e+00, -7.071e -01 , 7.071e -01
--------------
-1.000e+00, -7.071e -01 , -7.071e -01

Listing 3.57 data/x020/ex23.csv

3.6 Beispiele aus der Physik


3.6.1 Teilchen wird vertikal nach oben geworfen

Ein Teilchen wird vom Boden mit einer Anfangsgeschwindigkeit v0 = 20 m/s vertikal
nach oben geworfen (g = 9.81 m/s2 ).

1. Wie lange dauert es, bis das Teilchen seine maximale Höhe erreicht? Wie lange braucht
es, um zur Anfangsposition zurückzukehren, und mit welcher Geschwindigkeit kommt
es dort an?
2. Erstellen Sie Bewegungsdaten für Ort und Geschwindigkeit des Teilchens als Funktion
der Zeit. An welcher Stelle im Datensatz wechselt die Geschwindigkeit ihr Vorzeichen?
Finden Sie einen Näherungswert für den Zeitpunkt, zu dem das Teilchen auf den
Boden fällt.
3.6 Beispiele aus der Physik 167

Lösung

Wir legen die positive z-Achse entlang der Bewegungsrichtung. Die Bewegung des Teil-
chens wird durch folgende Gleichungen

⃗v = v0⃗ez − gt⃗ez (3.1a)


1
⃗z = z0⃗ez + v0 t⃗ez − gt2⃗ez (3.1b)
2
mit den Anfangsbedingungen z(0) = 0, v(0) = v0⃗ez beschrieben. Wenn t1 der Zeitpunkt
ist, zu dem das Teilchen seine maximale Höhe z1 erreicht, erhalten wir für v = 0 in Gl.
3.1a und Gl. 3.1b
v0 v2
t1 = , z1 = 0 . (3.2)
g 2g
Für die Zeit t2 , zu der das Teilchen wieder auf den Boden fällt, erhalten wir mit z = 0
in Gl. 3.1b neben t2 = 0 auch t2 = 2vg 0 und damit für die Geschwindigkeit v2 = −v0 .

Das Computerprogramm

Kurzbeschreibung Wir werden den Ort und die Geschwindigkeit als Funktion der Zeit
mit Gl. 3.1a und Gl. 3.1b berechnen und die Daten in eine Zahlentabelle speichern.
Anschließend werden wir eine View erstellen, welche auf die Stelle zeigt, in der die Ge-
schwindigkeit ihr Vorzeichen wechselt. Zur View werden wir auch die letzte Reihe der
Tabelle hinzufügen.

Quelltext und Daten Wir wählen für die Berechnung sehr kleine Zeitschritte, um gute
Näherungswerte für die Zeitpunkte t1 und t2 zu erhalten. Mithilfe des stl-Algorithmus
std::adjacent_find finden wir, die für uns interessante Reihen der Tabelle und fügen
diese zu einer View hinzu (std::adjacent_find findet die Stelle in einem Container, in
der zwei direkt benachbarte Elemente eine vorgegebene Bedingung erfüllen).

/**
* @brief ex23 Teilchen wird vertikal nach oben geworfen
*/
inline void ex23 () {
using namespace gravitation ;
// Tabelle mit drei Spalten t,z,v
using Data = Data <3 >;
Data table;

std :: ofstream output = Output :: get_stream ("x021",


Output ::csv , //
__func__ );
output << std :: setprecision (3);

// Anfangsbedingungen
const double z0 = 0.0 , v0 = 20.0;
const double g = Earth ::g;

// rechne mit kleinen Zeitschritten


168 3 Tabellen und Views

double t = 0, dt = 1e-3, z = z0 , v = v0;


while (z >= 0) {
table += { t, z, v };
t += dt;
z = z0 + v0 * t - 0.5 * g * pow(t, 2);
v = v0 - g * t;
}

// finde Vorzeichenwechsel der Geschwindigkeit


auto itr = std :: adjacent_find (ALL( table.data ()), //
[]( const auto &x, const auto &y) { //
return x[2] * y[2] < 0;
});

// addiere gefundene Zeile und nächste sowie die letzte Zeile


View view = table .view ({ itr , itr + 1 }, false , true);

// Ausgabe
output << std :: setprecision (3);
output << std :: setw (5) << "t" << std :: setw (10) << "z" <<
std :: setw (10) << "v" << std :: endl;
output << " -------------------------------" << std :: endl;
view.save(output , Output :: csv);
output << " -------------------------------" << std :: endl;
// die exakten Werte
Data :: Row r1{ v0 / Earth ::g, 0.5 * pow(v0 , 2) / Earth ::g, 0 };
Data :: Row r2{ 2 * v0 / Earth ::g, 0, -v0 };
Output :: csv. as_rows (output , r1 , r2);
}

Listing 3.58 cppsrc/apps-x021/hpp/x021–01

Nach Ausführung der Funktion erhalten wir folgende Ausgabe:

t z v
-------------------------------
2.039 e+00 ,2.039e+01 ,4.241e -03
2.040 e+00 ,2.039e+01 , -5.566e -03
4.078 e+00 ,1.729e -02 , -1.999e+01
-------------------------------
2.039 e+00 ,2.039e+01 ,0.000e+00
4.079 e+00 ,0.000e+00 , -2.000e+01

Listing 3.59 data/x021/csv–ex23.csv

Die ersten drei Reihen sind die gefundenen Näherungswerte und die beiden folgenden die
exakten Ergebnisse.

3.6.2 Kraft beschleunigt eine Masse auf reibungsfreiem Boden


⃗ , die einen Winkel φ mit der Horizontalen bildet, wird auf einen ruhenden
Eine Kraft F
Block der Masse m angewandt. Die Bewegung findet auf reibungsfreiem Boden statt.
3.6 Beispiele aus der Physik 169

1. Wir groß ist die Beschleunigung des Blocks? Welche Strecke legt der Block innerhalb
der ersten t1 Sekunden zurück? Welche Arbeit leistet die Kraft innerhalb dieser Zeit?
2. Erstellen Sie für m = 2 kg, |F ⃗ | = 10 N, t1 = 10 s und für die Winkel φ =
◦ ◦ ◦ ◦
10 , 30 , 45 , 60 eine Tabelle, die in jeder Reihe die x- und y-Komponente der Kraft,
die von ihr verrichtete Arbeit und die vom Block zurückgelegte Strecke enthält. Spei-
chern Sie die Tabelle im LATEX-Format ab.

Lösung

Das Koordinatensystem Wir legen die positive x-Achse entlang der Bewegungsrichtung
des Blocks und senkrecht zur ihr nach oben zeigend die positive y-Achse.

Die Bewegungsgleichung Wir wenden das zweite Newton’sche Gesetz an.

m⃗r¨ = N
⃗ +F
⃗ + w,
⃗ (3.3)
⃗ die Normalkraft und w
wobei N ⃗ die Gewichtskraft ist. Wir multiplizieren diese Gleichung
nacheinander skalar mit ⃗ex , dann mit ⃗ey und erhalten:
{
mẍ = F cos φ
mÿ = N + F sin φ − mg = 0
oder 

 F
 ẍ = cos φ (3.5a)
m

 N F
 ÿ = + sin φ − g = 0 (3.5b)
m m
Die Geschwindigkeit als Funktion der Zeit erhalten wir durch Integration von Gl. 3.5a
nach der Zeit unter Berücksichtigung der Anfangsbedingung v(0) = 0:
∫ t
F F
v(t) = cos φ dt = cos φ t. (3.6)
0 m m
Eine weitere Integration nach der Zeit, zusammen mit x(0) = 0, liefert die Ortskoordi-
nate: ∫ t
F 1F
x(t) = cos φ t′ dt′ = cos φ t2 (3.7)
0 m 2m
Die von der Kraft verrichtete Arbeit berechnen wir über folgendes Integral:
∫ x ∫ x
WF = F · d⃗r =
⃗ F cos φ dx′ = F cos φ x (3.8)
0 0

Das Computerprogramm

Kurzbeschreibung
Implementierung der Formeln Gl. 3.6, Gl. 3.7, Gl. 3.8 und Speicherung der Daten in
einer Zahlentabelle mit sieben Spalten
Ausgabe der Daten in eine Datei im LATEX-Format
170 3 Tabellen und Views

Quelltext und Daten Innerhalb einer Schleife werden durch Anwendung der herge-
leiteten Formeln Daten für unterschiedliche Winkel berechnet und einer Zahlentabelle
hinzugefügt.

/**
* @brief ex24 Kraft beschleunigt eine Masse auf reibungsfreien Boden
*/
inline void ex24 () {
const double mass = 2.0;
const double force = 10.0;
Data <7> table ;
const double t = 10.0;
// für jeden Winkel ...
for ( double phi : { 10, 30, 45, 60 }) {
// ... berechne Daten
const double phirad = Math :: to_radians (phi);
const double fx = force * cos( phirad );
const double fy = force * sin( phirad );
const double ax = fx / mass;
const double vx = ax * t;
const double x = 0.5 * ax * pow(t, 2);
const double work = fx * x;
// ... und füge eine neue Reihe zur Tabelle hinzu
table += { phi , fx , fy , ax , vx , x, work };
}
// speichere Daten in Datei
std :: ofstream csvstream = Output :: get_stream ("x021", Output :: latex ,
__func__ );
table.save(csvstream , Output :: latex );
}

Listing 3.60 cppsrc/apps-x021/hpp/x021–02

⃗ = 10 N wirkt für t = 10 s auf Masse m = 2 kg unter einen Winkel φ


Tab. 3.1: Kraft F
φ[deg] Fx [N] Fy [N] ax [m/s2 ] vx [m/s] x[m] W [J]
1.000e+01 9.848e+00 1.736e+00 4.924e+00 4.924e+01 2.462e+02 2.425e+03
3.000e+01 8.660e+00 5.000e+00 4.330e+00 4.330e+01 2.165e+02 1.875e+03
4.500e+01 7.071e+00 7.071e+00 3.536e+00 3.536e+01 1.768e+02 1.250e+03
6.000e+01 5.000e+00 8.660e+00 2.500e+00 2.500e+01 1.250e+02 6.250e+02

3.6.3 Geschoss trifft auf ruhenden Holzblock

Ein Geschoss der Masse m1 bewegt sich geradlinig mit einer horizontalen Geschwindigkeit
v und trifft auf einen ruhenden Holzblock mit der Masse M und bleibt in ihm stecken.

1. Mit welcher Geschwindigkeit ⃗v ′ bewegen sich der Holzblock und das Geschoss direkt
nach dem Stoß?
2. Wenn m = 200 g, erstellen Sie eine LATEX-Tabelle, in der für drei unterschiedliche
Holzblockmassen M1 = 1 kg, M2 = 5 kg und M3 = 10 kg und Geschossgeschwindig-
3.6 Beispiele aus der Physik 171

keiten v1 = 200 m/s, v2 = 400 m/s und v3 = 600 m/s die Geschwindigkeiten direkt
nach dem Stoß des Systems Holzblock und Geschoss aufgelistet werden. Ergänzen Sie
die Daten mit den kinetischen Energien des Systems vor und nach dem Stoß sowie
den Energieverlust in Prozent.

Lösung

Das Koordinatensystem Wir legen die positive x-Achse entlang der Bewegungsrichtung
des Geschosses.

Die Beschreibung des Stoßvorgangs Auf das System wirken keine äußeren Kräfte. Der
Gesamtimpuls des Systems bleibt in der Zeit kurz vor und direkt nach dem Stoß erhalten.

m⃗v = (M + m) ⃗v ′ ⇒ v⃗ex = (M + m) v ′⃗ex (3.9)

oder
m
v′ = v. (3.10)
m+M
Für die kinetische Energie des Systems vor und nach dem Stoß sowie den Energieverlust
in Prozent gelten die Formeln:

1 1 |K ′ − K|
K= mv 2 , K′ = (m + M )v ′2 , 100 % (3.11)
2 2 K

Das Computerprogramm

Kurzbeschreibung

Implementierung der Formeln (Gl. 3.10) und (Gl. 3.11)


Berechnung der Daten und Speicherung in einer Zahlentabelle
Ausgabe der Daten in eine Datei im LATEX-Format

Quelltext und Daten Mit zwei ineinander geschachtelten Schleifen berechnen wir, in
Abhängigkeit von M und v, Daten und speichern sie in eine Zahlentabelle. Diese wird
anschließend in eine Datei im LATEX-Format geschrieben (Tab. 3.2).

/**
* @brief ex25 Geschoss trifft auf ruhenden Holzblock
*/
inline void ex25 () {
// Datenobjekt
using Data = Data <6 >;
Data table;
// Masse des Geschosses
const double m = 200.0 _g;
// erzeuge Daten für drei Holzblockmassen und
// drei Kugelgeschwindigkeiten
for (auto M : { 1.0 , 5.0 , 10.0 }) {
const double factor = m / (m + M);
172 3 Tabellen und Views

for (auto v : { 200.0 , 400.0 , 600.0 }) {


const auto vp = factor * v;
// kinetische Energie vor dem Stoß
const auto ekinB = 0.5 * m * pow(v, 2);
// kinetische Energie nach dem Stoß
const auto ekinA = 0.5 * (M + m) * pow(vp , 2);
const auto ekinDiff = 100 * abs(ekinA - ekinB) / ekinB;
// speichere Daten im Datenobjekt
table += { M, v, vp , ekinB , ekinA , ekinDiff };
}
}
// erzeuge Ausgabestrom und speichere die Daten im LaTeX - Format
std :: ofstream ofs = Output :: get_stream ("x021", Output :: latex ,
__func__ );
table.save(ofs , Output :: latex );
}

Listing 3.61 cppsrc/apps-x021/hpp/x021–03

Tab. 3.2: Geschoss mit Masse m = 200 g trifft auf ruhenden Block.
K ′ −K
M [kg] v[m/s] v ′ [m/s] K[J] K ′ [J] K
%
1.000e+00 2.000e+02 3.333e+01 4.000e+03 6.667e+02 8.333e+01
1.000e+00 4.000e+02 6.667e+01 1.600e+04 2.667e+03 8.333e+01
1.000e+00 6.000e+02 1.000e+02 3.600e+04 6.000e+03 8.333e+01
5.000e+00 2.000e+02 7.692e+00 4.000e+03 1.538e+02 9.615e+01
5.000e+00 4.000e+02 1.538e+01 1.600e+04 6.154e+02 9.615e+01
5.000e+00 6.000e+02 2.308e+01 3.600e+04 1.385e+03 9.615e+01
1.000e+01 2.000e+02 3.922e+00 4.000e+03 7.843e+01 9.804e+01
1.000e+01 4.000e+02 7.843e+00 1.600e+04 3.137e+02 9.804e+01
1.000e+01 6.000e+02 1.176e+01 3.600e+04 7.059e+02 9.804e+01

3.6.4 Kraft und potenzielle Energie im Gravitationsfeld der Erde

Ein Teilchen der Masse m befindet sich in einer Höhe z über der Erdoberfläche.

1. Welche Kraft übt die Erde auf das Teilchen aus? Wie groß ist die potenzielle Energie
des Systems Erde-Teilchen?
2. Tragen Sie in eine Tabelle die Kraft und die potenzielle Energie als Funktion der Höhe
ein, beginnend von der Erdoberfläche bis zu einem zweifachen Erdradius. Speichern
Sie die Tabelle im LATEX-Format ab.

Lösung

Das Koordinatensystem Wir wählen die positive z-Achse senkrecht zur Erdoberfläche.
Sie soll in Richtung des Teilchens zeigen.
3.6 Beispiele aus der Physik 173

Formeln für die Kraft und die potenzielle Energie Bei großen Abständen von der
Erdoberfläche kann die Gravitationskraft nicht mehr als konstant angesehen werden. Die
Kraft und die potenzielle Energie berechnen wir mit den Formeln

⃗ = −G M m ⃗ez ,
F U = −G
Mm
, (3.12)
z2 z
wobei M die Masse der Erde, G die universelle Gravitationskonstante und z der Abstand
vom Erdmittelpunkt ist.

Das Computerprogramm

Kurzbeschreibung

Implementierung der Formeln (Gl. 3.12)


Berechnung und Speicherung der Daten in einer Zahlentabelle mit drei Spalten
Ausgabe der Daten in eine Datei

Quelltext und Daten Wir setzen m = 10 kg und berechnen Daten für z = R bis
z = 2R in Schritten von ∆z = 1000 km, wobei R der Erdradius ist. Die Daten werden
in einer Zahlentabelle gespeichert, die dann im LATEX-Format in eine Datei geschrieben
wird (Tab. 3.3).

/**
* @brief ex26 Kraft und potenzielle Energie im Gravitationsfeld der
Erde
*/
void ex26 () {
using namespace gravitation ; // für die universelle
Gravitationskonstante
Data <3> table ;
// Abstände xmin = R bis xmax = 2* R...
const auto xmin = Earth :: radius ;
const auto xmax = 2 * xmin;
// ... mit Schrittweite
const auto dx = 1000.0 _km;
const auto mass = 10.0;
const auto factor = Astronomy ::G * Earth :: mass * mass;

for ( double x = xmin; x < xmax; x += dx) {


const auto force = -factor / pow(x + xmin , 2);
const auto pEnergy = -factor / (x + xmin);
// speichere Abstand , Kraft , potenzielle Energie in Zahlentabelle
table += { x, force , pEnergy };
}
// erzeuge Ausgabestrom und speichere die Daten im LaTeX - Format
std :: ofstream ofs = Output :: get_stream ("x021", Output :: latex ,
__func__ );
table.save(ofs , Output :: latex );
}

Listing 3.62 cppsrc/apps-x021/hpp/x021–04


174 3 Tabellen und Views

Tab. 3.3: Gravitationskraft und potenzielle Energie eines Teilchens der Masse m = 10 kg
als Funktion vom Abstand zur Erdoberfläche
z[m] F [N ] U [J]
6.378e+06 -2.449e+01 -3.124e+08
7.378e+06 -2.106e+01 -2.897e+08
8.378e+06 -1.830e+01 -2.701e+08
9.378e+06 -1.605e+01 -2.529e+08
1.038e+07 -1.419e+01 -2.378e+08
1.138e+07 -1.264e+01 -2.244e+08
1.238e+07 -1.133e+01 -2.125e+08

3.6.5 Massenschwerpunkt einer diskreten Massenverteilung

Für ein System von Teilchen, die sich an festen Orten befinden, soll mittels eines Com-
puterprogramms der Massenschwerpunkt für folgende Konfiguration berechnet werden:
Drei Massenpunkte mit Massen m1 = 1 kg, m2 = 2 kg, m3 = 3 kg, und Ortsvektoren
⃗r1 = 0, ⃗r2 = 4 ⃗ex m, ⃗r3 = 2 ⃗ex m + 3.46 ⃗ey m.

Lösung

Die Ortskoordinate des Massenschwerpunkts ist durch


∑N
mi⃗ri
⃗rcm = ∑i N (3.13)
i mi

gegeben, wobei mi die Massen des Systems und ⃗ri ihre Ortsvektoren sind.

Das Computerprogramm

Kurzbeschreibung

Speicherung der Massen der Teilchen und ihrer Koordinaten in eine Tabelle
Anwendung der Formel Gl. 3.13 zur Berechnung des Massenschwerpunktes
Einfügen der Ergebnisse in die Tabelle
Ausgabe der Daten in eine Datei im LATEX-Format (Tab. 3.4)

Quelltext und Daten Eine Tabelle mit drei Spalten wird mit den Daten der drei Mas-
sen gefüllt. Es folgt eine λ-Funktion, welche die Formel (Gl. 3.13) implementiert. Zu-
sätzlich erhält die λ-Funktion eine Referenz auf zwei Variablen, in denen die gesuchten
Ergebnisse gespeichert werden sollen. Die λ-Funktion wird auf jede Reihe der Tabelle
angewandt. Bei jeder zusätzlichen Masse und ihrer Ortskoordinaten werden die Daten
für den Schwerpunkt (Zähler und Nenner in Gl. 3.13) aktualisiert.
3.6 Beispiele aus der Physik 175

/**
* @brief ex27 Massenschwerpunkt von drei Teilchen
*/
void ex27 () {
using Data = Data <3 >;
Data data;

// Tabelle mit {m,x,y}


data += { 1, 0, 0 }; // Masse 1
data += { 2, 4, 0 }; // Masse 2
data += { 3, 2, 3.46 }; // Masse 3

// Ortvektor und Masse des Schwerpunktes


std :: array cmPosition { 0.0 , 0.0 };
double cmMass = 0;

// Masse und Ortsvektor werden bei jedem Aufruf aktualisiert .


auto calcCM = [& cmMass , & cmPosition ]( const Data :: Row &row) {
cmMass += row [0]; // addiere neue Masse hinzu
cmPosition [0] += row [0] * row [1]; // m_i * x_i
cmPosition [1] += row [0] * row [2]; // m_i * y_i
};

// Wende Funktion auf jede Reihe des Datenobjekts an


data.apply( calcCM );
cmPosition [0] /= cmMass ; // xcm
cmPosition [1] /= cmMass ; // ycm

// Gesamtmasse und Schwerpunktskoordinaten in letzte Reihe


data += { cmMass , cmPosition [0] , cmPosition [1] };

// Ausgabe
std :: ofstream ofs = Output :: get_stream ("x021", Output :: latex ,
__func__ );
data.save(ofs , Output :: latex );
}

} // namespace nmx :: apps :: x021

Listing 3.63 cppsrc/apps-x021/hpp/x021–05

Tab. 3.4: Massen und Koordinaten eines Systems von drei Teilchen und ihres Schwer-
punktes. Die letzte Reihe enthält die Masse und die Koordinaten des Massenschwerpunk-
tes
m [kg] x [m] y [m]
1.000e+00 0.000e+00 0.000e+00
2.000e+00 4.000e+00 0.000e+00
3.000e+00 2.000e+00 3.460e+00
6.000e+00 2.333e+00 1.730e+00
176 3 Tabellen und Views

3.7 Übungsaufgaben
1. Erstellen Sie eine Datentabelle für das Polynom f (x) = x5 − x4 − x3 − x2 − x + 1 für
x ∈ [−1, 2] mit ∆x = 0.02.
a) Suchen Sie in der Tabelle Näherungswerte für den maximalen und minimalen Wert.
b) Filtern Sie die Tabellenwerte für die f (x) > 0.
c) Speichern Sie die Werte im CSV- und LATEX-Format in Dateien ab. Für die LATEX-
Daten wählen Sie eine Schrittweite von ∆x = 0.5.
2. Ein Teilchen der Masse m = 2 kg wird aus einer Höhe H = 10 m fallen gelassen. Er-
stellen Sie eine Zahlentabelle mit Bewegungsdaten für das Teilchen. Dabei sollen fol-
gende Größen enthalten sein: Zeit, Höhe, Geschwindigkeit, kinetische und potenzielle
Energie. Berechnen Sie aus dem Mittelwert der Differenzen ∆v ∆t einen Näherungswert
für die Beschleunigung des Teilchens. Setzen Sie die Fallbeschleunigung g als bekannt
voraus.
3. Eine Masse m = 1 kg ist am Ende einer idealen Feder mit der Federkonstante k =
100 N/m befestigt und schwingt reibungsfrei auf horizontalem Boden.
a) Berechnen Sie die Auslenkung, die Geschwindigkeit und die Federkraft sowie die
potenzielle, kinetische und Gesamtenergie als Funktion der Zeit. Erstellen Sie eine
Tabelle für die Dauer einer Periode.
b) Finden Sie mittels einfacher Suche im generierten Datensatz einen Näherungswert
für die Zeit, zu der die potenzielle und die kinetische Energie gleich sind. Verglei-
chen Sie das Ergebnis mit dem exakten Wert.
4. Vier Massenpunkte mit Massen m1 = 2 kg, m2 = 6 kg, m3 = 1 kg und m4 = 10 kg,
befinden sich jeweils auf den Ecken eines Quadrats mit Seitenlänge l = 2 m. Berechnen
Sie mithilfe eines Computerprogramms, die Gesamtmasse und den Ortsvektor des
Massenschwerpunktes.
5. Zwei Autos befinden sich in einem Abstand d und bewegen sich mit konstanten Ge-
schwindigkeiten v1 und v2 geradlinig aufeinander zu. Ein Insekt fliegt mit einer dem
Betrag nach konstanten Geschwindigkeit v3 zwischen den Autos hin und her.
a) Welche Strecke legt das Insekt zurück, bis sich die beiden Autos treffen? Wie lange
dauert seine Bewegung?
b) Zeichnen Sie für d, v1 , v2 und v3 Ihrer Wahl mit einem Computerprogramm die
Bewegungsdaten für die beiden Autos und das Insekt auf. Stellen Sie die Daten in
einer LATEX-Tabelle dar.
4 Klassen zur Modellierung von
physikalischen Systemen

Übersicht
4.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
4.2 Eine Basisklasse für Computermodelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
4.3 Eine Komponente zur Speicherung von Rechenergebnissen . . . . . . . . . . . . . . . . 180
4.4 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
4.5 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193

4.1 Einleitung
Betrachten wir eine Physikaufgabe als eine stark idealisierte Beschreibung eines Na-
turvorgangs und demzufolge eines physikalischen Systems, so sind die bekannten und
unbekannten Größen die Parameter, welche das System beschreiben. Sie bieten damit
eine Vorlage, um Klassen zu programmieren, die diese Prozesse abbilden. In Anlehnung
an die Vorgehensweise im vorherigen Kapitel wären damit die Eigenschaften der Klasse
schnell festgelegt und es bliebe zu klären, welche dieser Eigenschaften des zu beschrei-
benden Systems mit Variablen und welche mit Elementfunktionen abgebildet werden.
Für eine Aufgabe existieren manchmal mehrere Lösungswege, z.B. exakte Lösungen oder
Näherungsverfahren. Es stellt sich dann die Frage, wie eine einzige Klasse von vornherein
so programmiert werden kann, dass sie alle möglichen Lösungswege berücksichtigt bzw.
um Lösungswege erweitert werden kann. Es zeigt sich, dass dies sehr aufwendig ist, wenn
die Klasse nicht ständig Änderungen unterzogen werden soll. Hier ist es sinnvoll über
Komponenten nachzudenken, welche die alternativen Lösungswege implementieren.

Modellklasse
Aufgabe
N Parameter
Abgeleitete Klasse Abgeleitete Klasse
...
Lösungsweg 1 Lösungsweg N
Abb. 4.1: Eine Aufgabe wird mithilfe einer Modellklasse abgebildet. Die Lösungswege
werden in abgeleiteten Klassen implementiert.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_4
178 4 Klassen zur Modellierung von physikalischen Systemen

4.2 Eine Basisklasse für Computermodelle


Wir nehmen uns vor, alle Klassen zur Beschreibung von physikalischen Prozessen, von ei-
ner gemeinsamen Basisklasse abzuleiten. Wir werden diese Klassen Modellklassen nennen
und beabsichtigen durch die gemeinsame Basisklasse, bestimmte Eigenschaften automa-
tisch an alle Modelle zu vererben. Welche sind aber diese gemeinsamen Eigenschaften?
Wir fordern, dass jedes Modell einen Namen hat, um mit diesem, Dateien zu benennen
und dort Rechenergebnisse zu speichern. Der Name gehört zu einem Modell und nicht
zur einer Instanz; deswegen wird dieser als eine statische Variable eingeführt.

/**
* @brief The XModel class Basisklasse speichert eine ID in Form
* einer Zeichenkette enthält Hilfsfunktionen zur Speicherung von Daten
*/
class XModel
{
private :
// speichere ID für alle Instanzen dieser Klasse
static inline std :: string _name ;

public :

Listing 4.1 cppsrc/nmx-xmodel/hpp/xmodel–01

Es soll nicht erlaubt sein, ein Modell ohne Namen zu erzeugen. Der Standardkonstruktor
wird deswegen gelöscht.

/**
* @brief XModelBase kein Standardkonstruktor
*/
XModel () = delete ;

Listing 4.2 cppsrc/nmx-xmodel/hpp/xmodel–02

Der Name eines Modells wird in Kleinbuchstaben (tolower) konvertiert und abgespei-
chert. Dies brauch nur einmal zu geschehen, da es sich um eine statische Variable handelt.

/**
* @brief XModelBase Konstruktor
* @param name Klassen -ID
*/
XModel ( const char *name) {
if (_name . empty ()) {
_name = name;
std :: transform (ALL( _name ), _name.begin (), tolower );
}
}

Listing 4.3 cppsrc/nmx-xmodel/hpp/xmodel–03

Um die Arbeit mit den Modellklassen zu erleichtern, definieren wir statische Funktionen,
die Ausgabeströme zum Schreiben in Dateien generieren. Die Dateinamen setzen sich aus
4.2 Eine Basisklasse für Computermodelle 179

den Namen der Modellklasse und einem Suffix, das dem Format der Daten entspricht,
zusammen. Somit können Dateien erzeugt werden, die dieselben Daten im CSV- oder im
LATEX-Format speichern.

/**
* @brief get_output_stream
* @param fmt Formatierung
* @return Ausgabestrom
*/
inline static std :: ofstream get_output_stream ( Format fmt) {
return Output :: get_stream ( class_name (), fmt);
}

Listing 4.4 cppsrc/nmx-xmodel/hpp/xmodel–04

Zu den Namen kann ein zusätzlicher Bezeichner hinzugefügt werden, um Namenskonflikte


zu vermeiden.

/**
* @brief get_output_stream
* @param fmt Formatierung
* @param id Namenszusatz
* @return Ausgabestrom
*/
template <class T>
inline static std :: ofstream get_output_stream ( Format fmt , const T
&id) {
return Output :: get_stream ( class_name (), fmt , id);
}

Listing 4.5 cppsrc/nmx-xmodel/hpp/xmodel–05

Wir gehen einen Schritt weiter und führen Elementfunktionen ein, mit denen Daten in
Dateien gespeichert werden, deren Namen automatisch generiert werden. Hier besteht
die Möglichkeit, analog zu Listing 4.5 und Listing 4.6, dies mit zwei Varianten zu tun.

/**
* @brief save Speicherung von Daten in eine Datei
* @param data Datenobjekt
* @param fmt Formatierung
*/
template <class T>
inline static void save( const T &data , Format fmt) {
std :: ofstream output = get_output_stream (fmt);
data.save(output , fmt);
}

Listing 4.6 cppsrc/nmx-xmodel/hpp/xmodel–06

/**
* @brief save Speicherung von Daten in eine Datei
* @param data Datenobjekt
* @param fmt Formatierung
180 4 Klassen zur Modellierung von physikalischen Systemen

* @param id Namenszusatz
*/
template <class T, class ID >
inline static void save( const T &data , Format fmt , const ID &id) {
std :: ofstream output = get_output_stream (fmt , id);
data.save(output , fmt);
}

Listing 4.7 cppsrc/nmx-xmodel/hpp/xmodel–07

Es folgt schließlich eine Methode, welche den Namen des Modells liefert.

/**
* @brief class_name
* @return Klassenname oder ID
*/
inline static std :: string class_name () { //
if (_name . empty ()) {
throw std :: invalid_argument ( nmx_msg );
}
return _name ;
}
}; // XModel

Listing 4.8 cppsrc/nmx-xmodel/hpp/xmodel–08

Wir haben die Programmierung der kurzen, aber sehr nützlichen Klasse abgeschlossen
und kümmern uns als nächstes um eine Komponente, welche für die Speicherung von
Rechenergebnissen zuständig ist.

4.3 Eine Komponente zur Speicherung von


Rechenergebnissen
Wir greifen an dieser Stelle die Idee aus Abschnitt 2.2.5 auf. Dort hatten wir statt mehrere
Variablen vom selben Typ einzeln einzuführen, diese in Feldern gespeichert. Statt eines
Index hatten wir mit Elementen von Aufzählungstypen (enum) auf diese zugegriffen. Für
den Fall, dass eine Klasse eine kleine Anzahl von Rechenergebnissen speichern muss,
führen wir folgendes Klassentemplate ein.

/**
* @brief The CResult class Speicherung von Rechenergebnissen
* @param N Anzahl der Ergebnisse
* @param IDX Aufzählung
* @param VTYPE Datentyp der Ergebnisse ( Standardwert double )
*/
template < size_t N, class IDX , class VTYPE = double >
class CResult
{
private :
// speichere Elemente vom Typ VTYPE
4.3 Eine Komponente zur Speicherung von Rechenergebnissen 181

std :: array <VTYPE , N> _result ;

protected :

Listing 4.9 cppsrc/nmx-xmodel/hpp/xmodel–09

Es besteht intern aus einem Feld, dessen Länge über den ersten Template-Parameter der
Klasse festgelegt wird. Der zweite Template-Parameter ist ein Platzhalter für einen Auf-
zählungstypen, mit dem auf die einzelnen Elemente des Feldes zugegriffen werden kann.
Der dritte Parameter legt den gemeinsamen Datentyp, der zu speichernden Ergebnisse
fest.
Im protected-Bereich befinden sich Elementfunktionen, mit denen entweder per Index
einzelnen Elementen Werte zugewiesen werden können (Listing 4.10) oder alle Elemente
über die Angabe eines anderen Containers festgelegt werden (Listing 4.11).

/**
* @brief operator () einem Element wird ein Wert zugewiesen
* @param idx Aufzählung
* @param val Werte des Elements an dr Stelle idx
*/
inline void set_result (IDX idx , VTYPE val) { //
_result [ static_cast <size_t >( idx)] = val;
}

Listing 4.10 cppsrc/nmx-xmodel/hpp/xmodel–10

/**
* @brief set_result allen Elementen werden Werte zugewiesen
* @param v Container mit N Elementen
*/
template <class T>
inline void set_result ( const T &v) {
// der Container muss die gleiche Länge wie das intern
// gespeicherte Feld haben
Check :: error_if <std :: out_of_range >( nmx_msg , v.size () != N);
std :: copy(v. begin () , v.end () , std :: begin( _result ));
}

Listing 4.11 cppsrc/nmx-xmodel/hpp/xmodel–11

Es besteht zusätzlich die Möglichkeit, über eine Liste (Listing 4.12) oder einen assoziati-
ven Container (Listing 4.13), die Elemente des Feldes anzugeben. Im ersten Fall muss die
Liste alle Werte enthalten, während im zweiten Fall über die Angabe des Index gezielt
einzelne Werte festgelegt werden können.

/**
* @brief set_result allen Elementen werden Werte zugewiesen
* @param lst Liste mit Werten
*/
inline void set_result (std :: initializer_list <VTYPE > lst) { //
Check :: error_if <std :: out_of_range >( nmx_msg , lst.size () != N);
182 4 Klassen zur Modellierung von physikalischen Systemen

std :: copy(lst. begin () , lst.end (), std :: begin ( _result ));


}

Listing 4.12 cppsrc/nmx-xmodel/hpp/xmodel–12

/**
* @brief set_result setze Werte über assoziativen Container
* @param args Container der Form { { IDX ,VTYPE } ,... }
*/
inline void set_result (std :: unordered_map <IDX , VTYPE > args) {
Check :: error_if <std :: out_of_range >( nmx_msg , args.size () < N);
for (const auto &val : args) {
set_result (val.first , val. second );
}
}

Listing 4.13 cppsrc/nmx-xmodel/hpp/xmodel–13

Es folgen der überladene Klammeroperator und zwei Methoden, mit denen die gespei-
cherten Elemente gelesen werden können. Durch die Umwandlung mit static_cast des
Aufzählungstyps in ein size_t ist es möglich auch mit enum class zu arbeiten.

/**
* @brief operator () Zugriff auf Element
* @param idx Aufzählung
* @return Wert des Elements an der Stelle idx
*/
inline auto operator ()(IDX idx) const { //
return _result [ static_cast <size_t >( idx)];
}

Listing 4.14 cppsrc/nmx-xmodel/hpp/xmodel–14

/**
* @brief get Zugriff auf Element
* @param idx Aufzählung
* @return Wert des Elements an der Stelle idx
*/
inline auto get(IDX idx) const { //
return _result [ static_cast <size_t >( idx)];
}

Listing 4.15 cppsrc/nmx-xmodel/hpp/xmodel–15

/**
* @brief result
* @return Referenz auf Feld mit gespeicherten Daten
*/
inline const auto & result () const { return _result ; }

Listing 4.16 cppsrc/nmx-xmodel/hpp/xmodel–16


4.4 Beispiele 183

4.4 Beispiele
Wir werden mithilfe von zwei Beispielen aus der Physik den Einsatz von Modellklassen
demonstrieren. Das erste Beispiel befasst sich mit der Statik eines Stabes, der an ei-
ner glatten Wand gelehnt ist. Das zweite Beispiel behandelt den Fall eines ballistischen
Pendels.

4.4.1 Stab lehnt gegen eine senkrechte Wand

Ein homogener Stab der Länge l und Masse m wird unter einem Winkel ϑ an eine
senkrechte Wand gelehnt. Der maximale Haftreibungskoeffizient (statischer Reibungs-
koeffizient) zwischen Boden und Stab ist µ. Die Reibung zwischen Stab und Wand sei
vernachlässigbar.

B l sin ϑ B ⃗B
N
l
ϑ 2
sin ϑ
ϑ
l
M l cos ϑ M

l
cos ϑ w
⃗ ⃗A
N
2

⃗ ⃗ey
R
A A ⃗e
⃗ez x

(a) (b)

Abb. 4.2: (a) Ein Stab der Länge l lehnt unter einem Winkel ϑ an einer senkrechten
⃗ die Normalkräfte N
Wand und befindet sich im Gleichgewicht. (b) Die Haftreibung R, ⃗ A,

NB und die Gewichtskraft w⃗

1. Berechnen Sie den maximalen Winkel ϑmax , unter dem der Stab an die Wand gelehnt
werden kann, ohne dass er anfängt zu gleiten.
2. Berechnen Sie für eine Reihe von Haftreibungskoeffizienten, die auf den Stab wir-
kenden Kräfte sowie den Winkel ϑmax . Fassen Sie die Ergebnisse in eine Tabelle
zusammen und erstellen Sie Diagramme für NA , NB , R und ϑmax als Funktionen
des Haftreibungskoeffizienten µ.

Lösung

Das Koordinatensystem Wir legen das Koordinatensystem so, dass ⃗ex parallel zum
Boden und ⃗ey parallel zur Wand verläuft. ⃗ez steht senkrecht zur Buchebene und bildet
mit den beiden anderen Einheitsvektoren ein Rechtssystem (Abb. 4.2b).
184 4 Klassen zur Modellierung von physikalischen Systemen

Die Gleichgewichtsbedingungen Am Punkt A wirken die Haftreibung R ⃗ und die Nor-



malkraft NA . Da zwischen Wand und Stab keine Reibung existiert, übt die Wand nur die
Normalkraft NB auf den Stab aus. Die Gleichgewichtsbedingung besagt, dass die Sum-
me aller äußeren Kräfte und die Summe aller Drehmomente bezüglich eines beliebigen
Punktes null sein müssen. ∑

 ⃗i = 0
F (4.1a)


i


 ⃗i = 0 (4.1b)


D
i

(Gl. 4.1a) in Komponentendarstellung:


∑

 ⃗i ⃗ex = 0 ⇒ NB − R = 0
F (4.2a)


i


 ⃗i ⃗ey = 0 ⇒ NA − w = 0 (4.2b)


F
i

Die Summe der Drehmomente bezüglich des Drehpunktes A lautet


3
⃗ri × F
⃗i = 0 (4.3)
i=1

⇒ AM
⃗ ×w ⃗ ×N
⃗ + AB ⃗B = 0
       
− 2l sin ϑ 0 −l sin ϑ NB
       
⇒ l       
 2 cos ϑ  × −mg  +  l cos ϑ  ×  0  = 0 (4.4)
0 0 0 0

oder (Abb. 4.2)


l
mg sin ϑ⃗ez − NB l cos ϑ⃗ez = 0. (4.5)
2
Wir lösen nach NB auf und erhalten:
mg
NB = tan ϑ (4.6)
2
Dieses Ergebnis liefert zusammen mit (Gl. 4.2a) einen Ausdruck für die Reibungskraft:

mg
R= tan ϑ (4.7)
2
Bedingung für den Winkel ϑ Die Haftreibung hat keinen konstanten Wert. Sie ändert
sich, wenn NB sich ändert, und sorgt dafür, dass der Stab im Gleichgewicht bleibt. Ist
jedoch eine obere Grenze
Rmax = µNA = µmg (4.8)

überschritten, fängt der Stab an zu gleiten. Es muss also immer

NB ≤ Rmax (4.9)
4.4 Beispiele 185

gelten, was zusammen mit (Gl. 4.6)

mg
tan ϑ ≤ µmg (4.10)
2
bedeutet und schließlich zu
tan ϑ ≤ 2µ (4.11)

führt. Wir stellen fest, dass der Winkel weder von der Masse noch von der Länge des
Stabs abhängt.

Das Computerprogramm

Kurzbeschreibung Wir führen eine neue Modellklasse ein, welche auch für die Berech-
nung der Ergebnisse zuständig sein wird. Es folgt die Liste der Daten, die der Modellklasse
übergeben werden, und derjenigen, die von ihr berechnet werden.

Eingabe: Länge l, Masse m des Stabes und der maximale Haftreibungskoeffizient µ


zwischen Boden und Stab
⃗ die Normalkraft vom Boden auf den Stab NA , die
Ausgabe: die Reibungskraft R,
Normalkraft NB von der Wand auf den Stab und der Winkel ϑmax

Der Quelltext Die Modellklasse speichert im public-Bereich die Länge und die Masse
des Stabes sowie den Haftreibungskoeffizienten (Listing 4.18). Diese Daten werden über
den Konstruktor initialisiert und können nicht mehr geändert werden (Listing 4.19).
Die Ergebnisse der Berechnungen werden in einem Feld gespeichert, welches durch die
Basisklasse CResult bereitgestellt wird. Der Zugriff auf die Elemente des Feldes erfolgt
über einen Aufzählungstyp.

/**
* @brief The Idx enum Zugriff auf berechnete Daten
*/
enum class Idx {
MAXTHETA = 0, // maximaler Winkel damit der Stab nicht wegrutscht
NA = 1, // Normalkraft : Boden auf Stab
NB = 2, // Normalkraft : Wand auf Stab
STATIC_FRICTION = 3, // Haftreibung
WEIGHT = 4 // Gewicht des Stabs
};

Listing 4.17 cppsrc/apps-x040/hpp/x040–01

/**
* @brief The X5000 class Stab lehnt gegen eine senkrechte Wand
*/
class X040 : public nmx :: XModel , public CResult <5, Idx >
{
public :
// Eingabeparameter
const double length ; // Länge des Stabs
186 4 Klassen zur Modellierung von physikalischen Systemen

const double mass; // Masse des Stabs


const double friction_coefficient ; // Haftreibungskoeffizient

public :

Listing 4.18 cppsrc/apps-x040/hpp/x040–02

Eine bequeme Art einen Namen für eine Modellklasse zu vergeben, ist die Compiler-
Variable __func__ einzusetzen.

/**
* @brief XModel Konstruktor
* @param l Länge des Stabs
* @param w Masse des Stabs
* @param fc Haftreibungskoeffizient
*/
inline X040( double l, double m, double fc)
: XModel ( __func__ )
, length { l }
, mass{ m }
, friction_coefficient { fc } {
Check :: all(nmx_msg , { length > 0, mass > 0,
friction_coefficient > 0 });
}

Listing 4.19 cppsrc/apps-x040/hpp/x040–03

Es folgt die Berechnung von ϑmax , w, NA , NB und R.

/**
* @brief exec interne Parameter werden berechnet
*/
inline void exec () {
using namespace gravitation ;
const auto tanMaxTheta = 2 * ( friction_coefficient );
// rufe Methode der Basisklasse CResult auf , um die Werte
// zu speichern
set_result (Idx :: MAXTHETA , atan( tanMaxTheta ));
set_result (Idx :: WEIGHT , -mass * Earth ::g);
set_result (Idx ::NA , -get(Idx :: WEIGHT ));
set_result (Idx ::NB , 0.5 * (mass) *Earth ::g * tanMaxTheta );
set_result (Idx :: STATIC_FRICTION , -get(Idx :: NB));
}
}; // namespace nmx :: x5000

Listing 4.20 cppsrc/apps-x040/hpp/x040–04

Die Daten Für l = 2 m und m = 10 kg werden Beispieldaten für eine Reihe von Haft-
reibungskoeffizienten berechnet und einer Zahlentabelle hinzugefügt. Die Daten werden
sowohl im Tabellen- als auch im Grafikformat gespeichert (Tab. 4.1, Abb. 4.3). Die Da-
teinamen werden mit Funktionen der Modellklasse generiert.
4.4 Beispiele 187

/**
* @brief run Berechnung von Beispieldaten Stab lehnt gegen
* eine senkrechte Wand
*/
inline void run () {
Data <5> data;
// Tabellendaten werden berechnet
for ( double mu = 0.1; mu < 0.6; mu += 0.1) {
// Modellklasse wird initialisiert
X040 xobj{ 2.0 , 10.0 , mu };
xobj.exec (); // Ergebnisse werden berechnet
data += { xobj. friction_coefficient ,
xobj(Idx :: NA),
xobj(Idx :: NB),
xobj(Idx :: STATIC_FRICTION ),
xobj(Idx :: MAXTHETA ) };
}
// Schreibe Daten in Datein (LaTeX -, CSV - Format )
X040 :: save(data , Output :: latex );
X040 :: save(data , Output :: plot);
}

Listing 4.21 cppsrc/apps-x040/hpp/x040–05

Tab. 4.1: Stab lehnt gegen eine senkrechte Wand. Werte der Normalkräfte vom Boden
und der Wand und der maximale Winkel ϑmax in Abhängigkeit vom Haftreibungskoef-
fizienten
µ NA [N] NB [N] R [N] ϑmax [rad]
1.000e-01 9.807e+01 9.807e+00 -9.807e+00 1.974e-01
2.000e-01 9.807e+01 1.961e+01 -1.961e+01 3.805e-01
3.000e-01 9.807e+01 2.942e+01 -2.942e+01 5.404e-01
4.000e-01 9.807e+01 3.923e+01 -3.923e+01 6.747e-01
5.000e-01 9.807e+01 4.903e+01 -4.903e+01 7.854e-01

0.8 100 NA
NB
R
NA , NB , R[N ]

0.6 50
ϑ[rad]

0.4 0

0.2 −50

0.1 0.2 0.3 0.4 0.5 0.1 0.2 0.3 0.4 0.5
µ µ

(a) (b)
Abb. 4.3: (a) Der maximale Winkel und (b) die auf den Stab wirkende Kräfte als Funk-
tion des Haftreibungskoeffizienten
188 4 Klassen zur Modellierung von physikalischen Systemen

4.4.2 Das ballistische Pendel

Eine Kugel mit der Masse m1 wird mit einer Geschwindigkeit ⃗v0 , die einen Winkel φ0
mit der Horizontalen bildet, auf den Pendelkörper eines ballistischen Pendels der Masse
m2 abgefeuert. Der Pendelkörper ist an einer Stange vernachlässigbarer Masse befestigt,
die sich am anderen Ende frei drehen kann. Die Kugel dringt in die Masse m2 ein und
bleibt stecken, ohne dass das System aus Pendelkörper und Kugel einen Massenverlust
erleidet.

O
O O

ϑmax d
l

m1 m1
⃗j
φ0 ymax
⃗ey ⃗ey m1 ⃗v ′0 U =0
⃗v0 xmax
⃗ex ⃗ex
m2 m2

(a) (b) (c)


Abb. 4.4: Kugel trifft auf den Pendelkörper eines ballistischen Pendels (a) vor dem Stoß,
(b) direkt nach dem Stoß und (c) bei maximalen Ausschlag

1. Wie groß ist der maximale Ausschlag des Pendels in Abhängigkeit von der Anfangs-
geschwindigkeit der Kugel?
2. Wie groß ist der Kraftstoß ⃗j, der von der Stange auf den Pendelkörper ausgeübt wird?
3. Erstellen Sie Daten für den maximalen Ausschlag des Systems in Abhängigkeit von
der Anfangsgeschwindigkeit der Kugel.

Lösung

Der Stoßvorgang Im Folgenden werden wir stellvertretend für das System aus Block
und Kugel der Einfachheit halber nur den Block schreiben. Für den zweidimensionalen
Stoßprozess gilt
m1⃗v0 = (m1 + m2 ) ⃗v ′0 + ⃗j, (4.12)
∫t
wobei ⃗v ′0 die Geschwindigkeit des Systems direkt nach dem Stoß und ⃗j = 0 F ⃗ dt′ der
Kraftstoß ist (Abb. 4.4). Multiplizieren wir diese Gleichung skalar mit ⃗ex und ⃗ey , erhalten
wir
{
m1 v0 cos φ0 = (m1 + m2 )v0 ′ (4.13a)
−m1 v0 sin φ0 = |⃗j|, (4.13b)
4.4 Beispiele 189

was bedeutet, dass der Impuls nur entlang der x-Richtung erhalten bleibt. Aus (Gl. 4.13a)
folgt für die Geschwindigkeit des Blocks nach dem Stoß
m1 v0 cos φ0
v0′ = . (4.14)
m1 + m2
Bewegung nach dem Stoß Aus Abb. 4.4c folgt für die Höhe des Blocks, wenn der
maximale Ausschlag erreicht ist

ymax = l − d = l − l cos ϑmax = l(1 − cos ϑmax ). (4.15)

Die mechanische Energie bleibt für den Vorgang nach dem Stoß erhalten. Anfangs hat der
Block nur kinetische Energie (Kmax ). Ist die maximale Höhe erreicht, liegt nur potenzielle
Energie (Umax ) vor.

E = U + K = Kmax = Umax (4.16a)


1
⇒ E = (m1 + m2 )v0′ , ϑ = 0
2
(4.16b)
2
⇒ E = (m1 + m2 )gymax , ϑ = ϑmax . (4.16c)
Wir kombinieren Gl. 4.16b, Gl. 4.16c mit Gl. 4.14 und Gl. 4.15 und erhalten
1 m2 v 2 cos2 φ0
(m1 + m2 )gl (1 − cos ϑmax ) = (m1 + m2 ) 1 0
2 (m1 + m2 )2
( )2
1 m1 v0 cos φ0
⇒ 1 − cos ϑmax =
2gl m1 + m2
oder ( )2
1 m1 v0 cos φ0
cos ϑmax = 1 − . (4.17)
2gl m1 + m2

Das Computerprogramm

Kurzbeschreibung Wir leiten von XModel (Listing 4.1) und CResult (Listing 4.14) eine
neue Modellklasse ab. Es folgt eine Liste aller Parameter, welche der Klasse übergeben
werden, und derjenigen, die von der Klasse berechnet werden.
Eingabe: Masse der Kugel m1 , Masse des Blocks m2 , der Betrag v0 und der Winkel
φ0 der Geschwindigkeit der Kugel; Länge der Stange des Pendels l
Ausgabe: Die maximalen Werte für Ausschlag ϑmax , Höhe ymax und kinetische En-
ergie Kmax sowie der Kraftstoß

Quelltext Die Modellklasse initialisiert m1 , m2 und l über den Konstruktor (Listing


4.24). Es handelt sich um charakteristische Eigenschaften des Systems, die nicht verändert
werden dürfen und im public-Bereich der Klasse abgespeichert werden (Listing 4.23).
Zur Speicherung der Ergebnisse wird ein Feld verwendet, welches durch die Basisklasse
CResult zur Verfügung gestellt wird. Um den Zugriff auf die Elemente des Feldes zu
vereinfachen, führen wir einen Aufzählungstypen ein (Listing 4.22). Die Gesamtmasse
des Systems sowie die Geschwindigkeit der Kugel werden im private-Bereich gespeichert.
190 4 Klassen zur Modellierung von physikalischen Systemen

/**
* @brief The Idx enum Zugriff auf die Ergebnisse mittels Index
*/
enum Idx {
EKIN_MAX = 0, // maximale kinetische Energie
EPOT_MAX = 1, // maximale potenzielle Energie
Y_MAX = 2, // maximale Höhe
V0P = 3, // Geschwindigkeit direkt nach dem Stoß
THETA_MAX = 4, // maximaler Winkel
IMPULS = 5 // Kraftstoß
};

Listing 4.22 cppsrc/apps-x032/hpp/x032–01

/**
* @brief The X630 class Das ballistische Pendel
*/
class X032 : public XModel , public CResult <6, Idx >
{
private :
double _mass ; // Gesamtmasse =Masse der Kugel + Masse des Blocks
std :: array <double , 2> _bullet_velocity ; // Kugelgeschwindigkeit
public :
// Eingabeparameter
const double mbullet ; // Masse der Kugel
const double mblock ; // Masse des Blocks
const double length ; // Länge des Pendels

public :

Listing 4.23 cppsrc/apps-x032/hpp/x032–02

/**
* @brief X630 Konstruktor
* @param m1 Masse der Kugel
* @param m2 Masse des Blocks
* @param l Länge des Pendels
*/
inline X032( double m1 , double m2 , double l)
: XModel { __func__ }
, mbullet { m1 }
, mblock { m2 }
, length { l } {
// sind alle Eingaben gültig ?
Check :: all(nmx_msg , { m1 > 0, m2 > 0, l > 0 });
_mass = m1 + m2; // berechne die Gesamtmasse
}

Listing 4.24 cppsrc/apps-x032/hpp/x032–03

Das Verhalten des Systems wird für eine vorgegebene Kugelgeschwindigkeit untersucht.
Die Ergebnisse werden berechnet und abgespeichert. Auch die Geschwindigkeit der Kugel
wird registriert und kann mit einer Elementfunktion gelesen werden (Listing 4.26).
4.4 Beispiele 191

/**
* @brief exec berechne alle fehlenden Werte
* @param v0 Geschwindigkeit der Kugel vor dem Stoß
* @param phi0 Winkel der Geschwindigkeit mit der Horizontalen
*/
inline void exec( double v0 , double phi0) {
_bullet_velocity = { v0 , phi0 }; // speichere Geschwindigkeit
const double g = Earth ::g;
// berechne cos( theta_max )
const double tmp = pow (( mbullet * v0 * cos(phi0)) / _mass , 2);
const double cosThetaMax = 1 - 1 / (2 * g * length ) * tmp;
// Wertezuweisung der internen Variablen
set_result (V0P , mbullet * v0 * cos(phi0) / _mass);
set_result (EKIN_MAX , 0.5 * _mass * pow(get(V0P), 2));
set_result (EPOT_MAX , get( EKIN_MAX ));
set_result (Y_MAX , get( EKIN_MAX ) / (g * _mass ));
set_result (THETA_MAX , acos( cosThetaMax ));
set_result (IMPULS , -mbullet * v0 * sin(phi0));
}

Listing 4.25 cppsrc/apps-x032/hpp/x032–04

/**
* @brief bullet_velocity Kugelgeschwindigkeit
* @return Feld (Betrag , Winkel )
*/
inline const auto & bullet_velocity () const { //
return _bullet_velocity ;
}
}; // X032

Listing 4.26 cppsrc/apps-x032/hpp/x032–05

Daten Zur Berechnung der Beispieldaten benutzen wir eine Kugelmasse m = 20 g, eine
Pendelmasse m = 4 kg und eine Pendellänge l = 1.5 m. Wir variieren den Einfallswinkel
und den Betrag der Geschwindigkeit der Kugel und berechnen den maximalen Ausschlag,
die maximale Höhe, die maximale kinetische Energie und den Kraftstoß. Für jede Einfalls-
geschwindigkeit wird eine separate Datei erzeugt (Tab. 4.2, Abb. 4.5). Die Dateinamen
werden aus dem Modellnamen und dem Geschwindigkeitsbetrag zusammengesetzt.

/**
* @brief run Berechnung von Beispieldaten ( ballistisches Pendel )
*/
inline void run () {
// Modellklasse : Masse der Kugel , Masse des Blocks , Länge des Pendels
X032 xobj{ 20.0_g , 4.0 , 1.5 };

// für eine Reihe von Geschwindigkeitsbeträgen


for ( double v0 = 200; v0 < 700; v0 += 100) {
// für jede Kugelgeschwindigkeit ( Betrag ) wird eine Zahlentabelle
// mit 6 Spalten erzeugt
Data <6> data;
// für eine Reihe von Winkeln
192 4 Klassen zur Modellierung von physikalischen Systemen

for ( double p0 = -60.0 _deg; p0 <= 10.0 _deg; p0 += 10.0 _deg) {


xobj.exec(v0 , p0);
data += { v0 ,
Math :: to_degrees (p0),
Math :: to_degrees (xobj(Idx :: THETA_MAX )), //
xobj(Idx :: Y_MAX ),
xobj(Idx :: EKIN_MAX ),
xobj(Idx :: IMPULS ) };
}
// öffne Dateien mit Namen bestehend aus Namen der Modellklasse
// den Geschwindigkeitsbetrag und Suffix
// (z.B. x032 -200. tex und x032 -200. csv)
//( eindeutiger Name der Datei durch den Geschwindigkeitsbetrag )
X032 :: save(data , Output :: latex , v0);
X032 :: save(data , Output :: plot , v0);
}
}

Listing 4.27 cppsrc/apps-x032/hpp/x032–06

Tab. 4.2: Ballistisches Pendel: Kraftstoß, maximale Werte für Winkel, kinetische Energie
und Höhe als Funktion der Anfangsgeschwindigkeit der Kugel
v0 [m/s] φ0 [deg] ϑmax [deg] ymax [m] Kmax [J] j [Ns]
6.000e+02 -6.000e+01 2.244e+01 1.136e-01 4.478e+00 1.039e+01
6.000e+02 -5.000e+01 2.897e+01 1.877e-01 7.400e+00 9.193e+00
6.000e+02 -4.000e+01 3.469e+01 2.666e-01 1.051e+01 7.713e+00
6.000e+02 -3.000e+01 3.939e+01 3.407e-01 1.343e+01 6.000e+00
6.000e+02 -2.000e+01 4.290e+01 4.012e-01 1.582e+01 4.104e+00
6.000e+02 -1.000e+01 4.507e+01 4.406e-01 1.737e+01 2.084e+00
6.000e+02 9.542e-15 4.580e+01 4.543e-01 1.791e+01 -1.998e-15

40

20 35

30
ϑmax [deg]

ϑmax [deg]

15
25

10 20
v0 = 200m/s v0 = 400m/s
v0 = 300m/s 15 v0 = 500m/s
−60 −50 −40 −30 −20 −10 0 −60 −50 −40 −30 −20 −10 0
φ0 [deg] φ0 [deg]

(a) (b)
Abb. 4.5: Maximaler Ausschlag des Pendels als Funktion des Einfallswinkels der Kugel
für verschiedene Kugelgeschwindigkeiten
4.5 Übungsaufgaben 193

4.5 Übungsaufgaben
1. Eine Kugel mit der Masse m1 trifft mit einer Geschwindigkeit v0 und unter einem
Winkel φ0 auf einen ruhenden Holzblock der Masse m2 , der auf einem Tisch mit der
Höhe H ruht. Die Kugel bleibt im Holzblock stecken und der Block rutscht eine Strecke
l, bis er vom Tisch fällt. Der Reibungskoeffizient für die Bewegung des Klotzes auf dem
Tisch ist µ. Schreiben Sie ein Computerprogramm, welches als Eingabeparameter v0 ,
φ0 , H, l und µ einließt und die Bahn des Blocks berechnet, nachdem dieser den Tisch
verlassen hat. Erstellen Sie die Daten für unterschiedliche Geschwindigkeitsbeträge
v0 und Einfallswinkel φ0 und für l = 2 m, H = 1.25 m, m1 = 200 g, m2 = 2 kg und
µ = 0.3.
2. Ein homogener Zylinder mit der Masse m und Radius r rollt ohne zu gleiten eine
schiefe Ebene mit dem Neigungswinkel φ hinab. Zum Zeitpunkt t0 = 0 beträgt die
Höhe des Kontaktpunktes des Zylinders mit der schiefen Ebene vom Boden H. Das
Trägheitsmoment des Zylinders bezüglich der Drehachse, die durch den Schwerpunkt
verläuft, lautet Izz = 12 mr2 .
a) Entwerfen Sie ein Modell, welches die Bewegung des Zylinders beschreibt.
b) Erstellen Sie eine Tabelle und Diagramme mit den Bewegungsdaten des Zylinders.
Benutzen Sie folgende Parameter m = 1 kg, r = 50 cm, φ = 30◦ und H = 1 m.
c) Wie muss das Modell programmiert werden, damit auch die Bewegung einer ho-
mogenen Kugel damit beschrieben werden kann?
Teil II

Schnittstellen zur GNU Scientific


Library
5 Die Vektor-Klasse

Übersicht
5.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
5.2 Vom gsl-Programm zur Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
5.3 Entwurf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
5.4 Kommunikation zwischen gsl und Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . 201
5.5 Komponente zum Lesen und Schreiben von Vektoren . . . . . . . . . . . . . . . . . . . . . 204
5.6 Komponente zum Rechnen mit Vektoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
5.7 Schnittstellen zur stl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
5.8 Minima und Maxima . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
5.9 Eigenschaften von Vektoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
5.10 BLAS-Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
5.11 Sicht auf einen Vektor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
5.12 Die Vektor-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
5.13 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
5.14 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240

5.1 Einleitung
Ein Feld oder Array ist eine Datenstruktur, in der mehrere Elemente desselben Datentyps
hintereinander im Speicher angelegt werden. Der Speicher kann entweder dynamisch (zur
Laufzeit des Programms) oder statisch (während der Kompilierung) erzeugt und die
Elemente können per Index über ihre Position im Feld angesprochen werden. Vektoren
sind in der GNU Scientific Library Strukturen, die mit einer Reihe von zugehörigen
Funktionen zur Verwaltung von C-Feldern spezialisiert sind. Da es möglich ist, Felder mit
Elementen von jeweils einem speziellen Datentyp (z.B. int oder double) anzulegen, stellt
die gsl pro Datentyp einen Satz von Funktionen zur Verfügung, die einer bestimmten
Namenskonvention folgen. So beginnen alle Funktionen für die Bearbeitung von int-
Feldern mit gsl_vector_int_... und solche, die auf double-Felder spezialisiert sind, mit
gsl_vector_... ohne den Zusatz double. Wir wollen im Rahmen dieses Buches mit double-
Feldern arbeiten und werden die Funktionen dieser Gruppe in Klassen kapseln.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_5
198 5 Die Vektor-Klasse

5.2 Vom gsl-Programm zur Klasse


In einer leicht modifizierten Version eines Beispiels, welches auf der Webseite der GNU
Scientific Library zu finden ist, wird Speicher für einen Vektor der Länge 5 erzeugt, mit
Werten gefüllt und dann die Elemente auf dem Bildschirm ausgegeben. Danach werden
die Elemente mit den Indizes 2 und 3 vertauscht. Das Ergebnis wird auf dem Bildschirm
ausgegeben und anschließend der für den Vektor reservierter Speicher wieder freigegeben.

/**
* @brief ex0 Beispiel mit Vektoren (gsl - Programm )
*/
inline void ex0 () {
const size_t n = 5;
// reserviere Speicher für Vektor
gsl_vector *v = gsl_vector_alloc (n);
// initialisiere Elemente
for ( size_t idx = 0; idx < n; idx ++) {
gsl_vector_set (v, idx , idx);
}
// schreibe Elemente auf dem Bildschirm
for ( size_t idx = 0; idx < n; idx ++) {
std :: cout << gsl_vector_get (v, idx) << " ";
}
std :: cout << " -> ";
// vertausche Elemente mit Index 2 und 3
gsl_vector_swap_elements (v, 2, 3);
// schreibe Elemente auf dem Bildschirm
for ( size_t idx = 0; idx < n; idx ++) {
std :: cout << gsl_vector_get (v, idx) << " ";
}
// Speicherfreigabe
gsl_vector_free (v);
}

Listing 5.1 cppsrc/apps-x029/hpp/x029–01

0 1 2 3 4 -> 0 1 3 2 4

Listing 5.2

Das Studium des Quelltextes zeigt, dass alle Funktionen - unabhängig davon für welchen
Zweck sie bestimmt sind - als erstes Eingabeargument einen Zeiger auf einen gsl_vector
erwarten. Ein gsl_vector ist eine Struktur, die folgendermaßen definiert ist.

typedef struct {
size_t size; // die Länge des Feldes
size_t stride ; // Schrittweite
double * data; // Zeiger auf das erste Element
gsl_block * block ; // C-Feld
int owner; // Eigentümer der Daten
} gsl_vector ;

Listing 5.3 cppsrc/src–cppxbase/gslvec4.cpp


5.2 Vom gsl-Programm zur Klasse 199

Mit dieser Struktur werden C-Felder verwaltet. So kann z.B. ein gsl_vector Eigentümer
eines Feldes sein (owner=1), was ihm erlaubt, den Speicher des Feldes freizugeben. Durch
die Angabe einer Schrittweite ist es möglich, nur auf bestimmte Elemente zuzugreifen
usw. Neben dem gsl_vector existiert auch eine gsl_vector_view. Es handelt sich hierbei
um eine Struktur, die Zugriff auf die Daten eines Feldes hat, aber nicht ihr Eigentümer
ist (owner=0).

typedef struct
{
gsl_vector vector ;
} _gsl_vector_view ;

typedef _gsl_vector_view gsl_vector_view ;

Listing 5.4 cppsrc/src–cppxbase/gslvecview.cpp

Einer View oder Sicht stehen praktisch dieselben Informationen wie einem gsl_vector
zur Verfügung. Der Unterschied ist, dass eine Sicht als Nichteigentümer der Daten ein
Feld weder erzeugen noch freigeben kann. Eine Methode, um dasselbe wie in Listing 5.1
mit einer View zu tun, zeigt das nächste Beispielprogramm.

/**
* @brief ex00 Beispiel (gsl - Programm )
*/
inline void ex00 () {
const size_t n = 5;
// erzeuge C-Feld
double data [] = { 0, 1, 2, 3, 4 };

// erzeuge Sicht auf C-Feld


gsl_vector_view v = gsl_vector_view_array (data , n);

// schreibe Elemente auf dem Bildschirm


for ( size_t idx = 0; idx < n; idx ++) {
std :: cout << gsl_vector_get (&v.vector , idx) << "\t";
}
std :: cout << std :: endl;
// vertausche Elemente mit Index 2 und 3
gsl_vector_swap_elements (&v.vector , 2, 3);

// schreibe Elemente auf dem Bildschirm


for ( size_t idx = 0; idx < n; idx ++) {
std :: cout << gsl_vector_get (&v.vector , idx) << "\t";
}
std :: cout << std :: endl;
}

Listing 5.5 cppsrc/apps-x029/hpp/x029–02

Hier wird auf dem C-üblichen Weg statischer Speicher für ein Feld reserviert. Mit einer
gsl_vector_view wird auf die Daten zugegriffen und über dieser Sicht kann dann mit den
gsl-Funktionen genau wie im ersten Beispiel gearbeitet werden. Eine View in der GNU
Scientific Library ist nicht identisch mit der im Abschnitt 3.3 eingeführten Klasse.
200 5 Die Vektor-Klasse

5.3 Entwurf
Nach dem Studium der Beispiele stellen wir fest, dass eine Klasse einen Zeiger auf einen
gsl_vector intern speichern müsste, um über ihn mit der GNU Scientific Library zu kom-
munizieren. Somit würde eine Instanz einer Klasse genau einem gsl_vector entsprechen.
Folgender Ansatz tut genau dies:

/**
* erster Entwurf einer Klasse für gsl - Vektoren
*/
class GslVector
{
private :
// Zeiger auf gsl - Struktur
gsl_vector *_vec;

public :
// Konstruktor
// n : Länge des Vektors
GslVector ( size_t n){
// reserviere Speicher
_vec = gsl_vector_alloc (n);
}

// Destruktor
~ GslVector () {
// gebe Speicher wieder frei
gsl_vector_free (_vec);
}

// setze Element mit Index i gleich x


void set( size_t i, double x) {
gsl_vector_set (_vec , i, x);
}

// lese Element mit index i


double get( size_t i) const {
return gsl_vector_get (_vec , i);
}

// hier können weitere Elementfunktionen hinzugefügt werden


};

Listing 5.6 cppsrc/src–cppxbase/gslvecclass1.cpp

Die Klasse ist, trotzt ihres kleinen Umfangs voll funktionsfähig und wir können ein Pro-
gramm schreiben, welches einen Vektor erzeugt ihn mit Werten füllt und anschließend
diese auf den Bildschirm ausgibt.

int main(void)
{
const size_t n = 4;
GslVector v(n);

// fülle Vektor
5.4 Kommunikation zwischen gsl und Komponenten 201

for ( size_t idx = 0; idx < n; idx ++){


v.set(idx , 0.1 * idx);
}

// schreibe Element auf dem Bildschirm


for ( size_t idx = 0; idx < n; idx ++) {
std :: cout << v.get(idx) << std :: endl;
}

return 0;
}

Listing 5.7 cppsrc/src–cppxbase/gslvecclass1.cpp

Es fällt sofort auf, dass die Speicherfreigabe automatisch stattfindet, was ein enormer
Vorteil gegenüber der C-Version des Programms ist. Wir könnten nun so fortfahren und
alle gsl-Funktionen dieser Gruppe der Klasse hinzufügen und somit die komplette Funk-
tionalität über die Klasse abbilden. Diese würde wegen des großen Umfangs der gsl
enorm wachsen. Wir wollen die Kapselung der gsl_vector-Struktur beibehalten, jedoch
die Methoden der zu programmierenden Klasse nach Gruppen ordnen und in kleinen
Bausteinen, die auch Klassen sein werden, unterbringen. Aus diesen Komponenten soll
dann ein Vektor-Datentyp zusammengesetzt werden (Abb. 5.1). Wir hätten somit eine
Aufgabeteilung erreicht, die es einfacher macht, diese kleinen Komponenten zu pflegen.
Wir gewinnen aber mehr als das. Mit den erstellten Komponenten werden wir eine Klas-
se zur Abbildung einer gsl_vector_view bauen. Zur Implementierung der Schnittstellen
werden wir wie in Abschnitt 2.6 vorgehen.

Komponente 1
..
.
Vector Komponente k View

Komponente n
Abb. 5.1: Die Klassen für einen Vektor und eine Sicht werden gezielt mithilfe von Kom-
ponenten zusammengesetzt.

5.4 Kommunikation zwischen gsl und Komponenten


Unsere Absicht, durch Komponenten eine Vektor-Klasse zusammenzusetzen, macht es
notwendig zu entscheiden, wie diese Komponenten mit den gsl-Funktionen kommuni-
zieren werden. Ein erster Gedanke, wäre dem Muster von Listing 5.6 zu folgen und für
jede Komponente einen gsl_vector-Zeiger intern zu speichern. Dieser Ansatz würde be-
deuten, den Quelltext von Listing 5.6 zu reproduzieren und jedes Mal durch geeignete
Elementfunktionen zu ergänzen. Wir wollen aber dieses Problem eleganter lösen. Ein
202 5 Die Vektor-Klasse

Ansatz wäre eine Basisklasse zu erstellen, die einen Zeiger intern enthält, und von dieser
dann alle Komponenten abzuleiten (Abb. 5.2). Wir würden so diesen Teil nur einmal
programmieren müssen.

B
gsl_vector*

I1 I2 ... In−1 In

Vector
Abb. 5.2: Alle Komponenten I1 , I2 , . . . In erben von einer Basisklasse B einen
gsl_vector-Zeiger.

Dieser Versuch führt aber zu dem Problem, dass über jede Komponente die Vektor-
Klasse jeweils eine Kopie eines gsl_vector-Zeigers erhält, was eine Mehrdeutigkeit zur
Folge hat (Diamond-Problem). Eine Lösung wäre, mit virtuellen Basisklassen zu arbei-
ten. Wir haben uns aber vorgenommen, die Komponenten als statische Schnittstellen
zu implementieren, und kehren zur leicht abgeänderten ursprünglichen Idee mit einer
Basisklasse pro Komponente zurück. Da die Komponenten für den Zusammenbau ei-
nes Datentyps gedacht sind, soll die aus den Komponenten zusammengesetzte Klasse
als einzige über einen gsl_vector-Zeiger verfügen. Die Komponenten sollen als Teil des
resultierenden Datentyps mittels einer Methode diesen Zeiger anfordern (Abb. 5.3).

B1 B2 ... Bn−1 Bn

I1 I2 ... In−1 In

Vektor
gsl_vector*

Abb. 5.3: Jede Komponente I1 , I2 , . . . In erbt (Pfeile) von einer Basisklasse


B1 , B2 , . . . Bn die Möglichkeit, mittels einer Elementfunktion (gestrichelte Linien) den
gsl_vector-Zeiger anzufordern. Die Basisklassen enthalten keinen gsl_vector-Zeiger.

Dieser Ansatz scheint mit sehr viel Programmieraufwand verbunden zu sein, wenn
pro Komponente eine dazugehörige Basisklasse programmiert werden muss. Dies ist aber
nicht der Fall, denn wir werden alle Basisklassen mittels eines einzigen Klassentemplates
vom Compiler generieren lassen und führen zu diesem Zweck einen Template-Parameter N
vom Typ int ein (Listing 5.8). Für jedes N wird vom Compiler eine neue Klasse generiert.
5.4 Kommunikation zwischen gsl und Komponenten 203

/**
* @brief The IGslContainer struct Basisklasse für
* gsl - Vektoren und Matrizen
* @param T : abgeleitete Klasse
* @param GSLOBJ : gsl_vector oder gsl_matrix
* @param N : ganze Zahl ( Diamond Problem )
*/
template <class T, class GSLOBJ , int N>
struct IGslContainer {

Listing 5.8 cppsrc/nmx-xvector0/hpp/xvector0–01

Der Template-Parameter T wird wie im Abschnitt 2.6 gezeigt die abgeleitete Klasse sein,
sodass ein Aufruf wie in Listing 2.141 und Listing 2.142 möglich ist. Ausgehend von
dieser Basisklasse werden wir alle Komponenten erstellen. Da eine Matrix ähnlich wie
ein Vektor in der gsl abgebildet ist, denken wir einen Schritt voraus und führen noch
einen weiteren Template-Parameter GSLOBJ ein. Für die Abbildung eines gsl-Vektors
wird dieser Template-Parameter gleich gsl_vector gesetzt.
Die beiden nächsten Elementfunktionen geben einen Zeiger auf einen gsl_vector zu-
rück. Sie leiten den Aufruf an die abgeleitete Klasse weiter.

/**
* @brief ermöglicht direkten Zugriff auf gsl - Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline const GSLOBJ *gsl () const {
// konvertiere Objekt welches diese Methode aufruft in einem
// Objekt vom Typ T
return static_cast <const T *>( this)->gsl ();
}

Listing 5.9 cppsrc/nmx-xvector0/hpp/xvector0–02

/**
* @brief gsl_vec ermöglicht direkten Zugriff auf gsl - Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline GSLOBJ *gsl () {
// konvertiere Objekt welches diese Methode aufruft in einem
// Objekt vom Typ T
return static_cast <T *>( this)->gsl ();
}
}; // IGslContainer

} // namespace nmx :: gsl

Listing 5.10 cppsrc/nmx-xvector0/hpp/xvector0–03


204 5 Die Vektor-Klasse

5.5 Komponente zum Lesen und Schreiben von


Vektoren
Aufgabe dieser Komponente bzw. Schnittstelle ist Basisfunktionalität, wie Methoden
zur Abfrage der Länge oder Klammeroperatoren zum Lesen und Schreiben einzelner
Elemente eines Vektors, zu implementieren. Durch die Basisklasse wird gewährleistet,
dass ein Zeiger auf einen gsl_vector zur Verfügung steht.

/**
* @brief The IVBase struct Basisfunktionalität für eine Vektor - Klasse
*/
template <class T>
class IVBase : public IGslContainer <T, gsl_vector , 1>
{
// benutze Elementfunktionen der Basisklasse
using IGslContainer <T, gsl_vector , 1>:: gsl;

public :

Listing 5.11 cppsrc/nmx-xvector1/hpp/xvector1–01

Die Länge des Feldes wird abgelesen.

/**
* @brief size
* @return die Länge des Vektors
*/
inline size_t size () const { //
try {
return gsl () ->size;
} catch (...) {
// wenn kein Speicher angefordert wurde
return 0;
}
}

Listing 5.12 cppsrc/nmx-xvector1/hpp/xvector1–02

Mit den beiden folgenden überladenen Klammeroperatoren können die Werte des Vektors
gelesen oder überschrieben werden. Intern nutzen wir eine gsl-Funktion, die mittels Index
einen Zeiger auf die entsprechende Stelle des Feldes liefert.

/**
* @brief operator [] Zugriff auf die Elemente des Vektors
* @param idx Index
* @return Element an der Stelle idx
*/
double operator []( size_t idx) const { //
return * gsl_vector_const_ptr (gsl () , idx);
}

Listing 5.13 cppsrc/nmx-xvector1/hpp/xvector1–03


5.5 Komponente zum Lesen und Schreiben von Vektoren 205

/**
* @brief operator [] Zugriff auf die Elemente des Vektors
* @param idx Index
* @return Referenz Element an der Stelle idx
*/
double & operator []( size_t idx) { //
return * gsl_vector_ptr (gsl () , idx);
}

Listing 5.14 cppsrc/nmx-xvector1/hpp/xvector1–04

Die Elemente eines Vektors werden einem Strom zur Ausgabe in einer Datei oder auf
dem Bildschirm übergeben. Der Vektor wird zeilenweise ausgegeben. Die Formatierungs-
anweisung wird im Standardfall über eine globale Variable (Listing 2.201) gesteuert.

/**
* @brief save Ausgabe in Datei oder auf dem Bildschirm
* @param os Ausgabestrom
* @param fmt Formatierung
*/
inline void save(std :: ostream &os , Format fmt = Output :: array ())
const {
// schreibe erstes Element z.B. x0 ...
os << gsl_vector_get (gsl () , 0);
for ( size_t idx = 1; idx < size (); idx ++) {
// ... ,x1 ,x2 ,...
os << fmt.sep () << gsl_vector_get (gsl (), idx);
}
os << fmt.lend ();
}

Listing 5.15 cppsrc/nmx-xvector1/hpp/xvector1–05

Wir überladen den Ausgabeoperator für Instanzen dieser Klasse. Intern leiten wir den
Vektor an die Elementfunktion Listing 5.15 weiter.

/**
* @brief operator <<
* @param os Ausgabestrom
* @param p Formatierung
* @return generierter Ausgabestrom
*/
inline friend std :: ostream &operator <<( std :: ostream &os , const T
&p) {
p.save(os);
return os;
}

Listing 5.16 cppsrc/nmx-xvector1/hpp/xvector1–06

Zur Transformation der Elemente des Vektors durchlaufen wir diese innerhalb einer
Schleife und wenden auf jedes Element eine Funktion an, die als Eingabeargument über-
geben wird (Listing 5.17). Die Funktion muss die Form f (x) = y haben.
206 5 Die Vektor-Klasse

/**
* @brief transform Transformation der Elemente eines Vektors
* @param fn Funktionsobjekt
* @return Referenz auf das aufrufende Objekt
*/
template <class FN >
inline T & transform (FN fn) {
for ( size_t idx = 0; idx < size (); idx ++) {
double val = gsl_vector_get (gsl (), idx);
gsl_vector_set (gsl () , fn(val), idx);
}
return static_cast <T & >(* this);
}
}; // IVBase

} // namespace nmx :: gsl :: vec

Listing 5.17 cppsrc/nmx-xvector1/hpp/xvector1–07

5.6 Komponente zum Rechnen mit Vektoren


Die nächste Komponente wird Rechenoperationen für gsl_vector-Instanzen implementie-
ren. Es handelt sich dabei um kombinierte Rechen- und Zuweisungsoperatoren wie z.B.
die elementweise Addition und Zuweisung v1 += v2. Wir beginnen mit einem C-ähnlichen
Beispiel, welches als Ausgangsbasis zur Erstellung der Komponente dienen soll. Zu jedem
Element eines Vektors wird eine Konstante addiert und anschließend werden die neuen
Elemente mit einer Konstante multipliziert. Es folgt die Erstellung eines C-Feldes und
einer View, die auf dieses Feld zeigt. Die View und damit das C-Feld wird elementweise
auf den ursprünglichen Vektor addiert.

/**
* @brief ex1 Rechnen mit gsl - Vektoren
*/
inline void ex1 () {
const size_t n = 3;
// erzeuge Vektor mit 3 Elementen
gsl_vector *v = gsl_vector_alloc (n);

// Elemente werden initialisiert


for ( size_t idx = 0; idx < n; idx ++) {
gsl_vector_set (v, idx , idx);
}

// Vektor wird auf dem Bildschirm geschrieben


for ( size_t idx = 0; idx < n; idx ++) {
std :: cout << gsl_vector_get (v, idx) << "\t";
}
std :: cout << std :: endl;

// zu allen Elementen wird eine Zahl addiert


5.6 Komponente zum Rechnen mit Vektoren 207

gsl_vector_add_constant (v, 10);


// alle Elemente werden mit einer Zahl multipliziert
gsl_vector_scale (v, 10);

// der Vektor wird auf dem Bildschirm geschrieben


for ( size_t idx = 0; idx < n; idx ++) {
std :: cout << gsl_vector_get (v, idx) << "\t";
}
std :: cout << std :: endl;

// erzeuge C-Feld
double f[] = { -10, -20, -30 };

gsl_vector_view view = gsl_vector_view_array (f, n);

// zu den Elemente des Vektors werden die Elemente


// des C- Feldes addiert
gsl_vector_add (v, &view. vector );

// der Vektor wird auf dem Bildschirm geschrieben


for ( size_t idx = 0; idx < n; idx ++) {
std :: cout << gsl_vector_get (v, idx) << "\t";
}

std :: cout << std :: endl;


// Freigabe des Speichers
gsl_vector_free (v);
}

Listing 5.18 cppsrc/apps-x029/hpp/x029–03

Wir erhalten nach Ausführung der Funktion folgendes Ergebnis.

0 1 2
100 110 120
90 90 90

Listing 5.19

Ähnlich wie auch in der ersten Komponente wird diese über die Basisklasse mit Funk-
tionen zum Zugriff auf die gsl_vector-Struktur ausgestattet.

/**
* @brief The IVCalc struct Komponente implementiert Operatoren
* += , -= ,*= ,/=
*/
template <class T>
class IVCalc : public IGslContainer <T, gsl_vector , 2>
{
using IGslContainer <T, gsl_vector , 2>:: gsl;

protected :

Listing 5.20 cppsrc/nmx-xvector2/hpp/xvector2–01


208 5 Die Vektor-Klasse

Wir wollen die Erstellung von überflüssigem Quelltext vermeiden und führen eine Hilfs-
funktion ein, mit der alle Operatoren implementiert werden.

/**
* @brief apply_fn Hilfsfunktion
* @param v Vektor oder View oder Zahl
* @param fn gsl - Funktion
* @return Referenz auf das aufrufende Objekt
*/
template <class X, class FN >
inline T & apply_fn ( const X &v, FN fn) {
if constexpr (std :: is_same_v <X, double >) {
fn(gsl () , v); //z.B. gsl_vector_add_constant ( , v)
} else {
fn(gsl () , v.gsl ()); //z.B. gsl_vector_add ( , v)
}
return static_cast <T & >(* this);
}

public :

Listing 5.21 cppsrc/nmx-xvector2/hpp/xvector2–02

Dieser Hilfsfunktion kann entweder eine Konstante oder ein Vektor bzw. eine View über-
geben werden. Alle diese Fälle werden über den ersten Template-Parameter abgedeckt.
Das zweite Eingabeargument ist eine gsl-Funktion, die intern aufgerufen wird. Dem
Beispiel aus Listing 5.18 entnehmen wir, dass zwei Kategorien von Aufrufen existie-
ren. Beide erwarten als erstes Eingabeargument einen gsl_vector. Sie unterscheiden
sich aber beim zweiten Eingabeargument, denn dieses kann entweder eine Zahl (z.B.
gsl_vector_add_constant) oder ein Vektor (z.B. gsl_vector_add) sein. Wir handeln beide
Fälle mittels der if constexpr-Anweisung ab, mit der schon bei der Übersetzung des Pro-
gramms zwei unabhängige Funktionen durch den Compiler generiert werden. Mithilfe der
eingeführten Hilfsfunktion ist es einfach, die überladenen Operatoren zu implementieren.

/**
* @brief Implementierung der Operatoren += , -= ,*= ,/=
* @param v Vektor oder View
* @return Referenz auf das aufrufende Objekt
*/
inline T & operator +=( const T &v) { //
return apply_fn (v, gsl_vector_add );
}
inline T &operator -=( const T &v) { //
return apply_fn (v, gsl_vector_sub );
}
inline T & operator *=( const T &v) { //
return apply_fn (v, gsl_vector_mul );
}
inline T & operator /=( const T &v) { //
return apply_fn (v, gsl_vector_div );
}

Listing 5.22 cppsrc/nmx-xvector2/hpp/xvector2–03


5.6 Komponente zum Rechnen mit Vektoren 209

Analog folgt eine Gruppe mit Operationen zwischen aufrufendem Objekt und einer Zahl.

/**
* @brief Implementierung der Operatoren +=,-= ,*=
* @param x Zahl
* @return Referenz auf das aufrufende Objekt
*/
inline T & operator +=( double x) { //
return apply_fn (x, gsl_vector_add_constant );
}
inline T &operator -=( double x) { //
return apply_fn (-x, gsl_vector_add_constant );
}
inline T & operator *=( double x) { //
return apply_fn (x, gsl_vector_scale );
}

Listing 5.23 cppsrc/nmx-xvector2/hpp/xvector2–04

Die Division mit einer Konstanten könnte auch wie die anderen Operatoren implementiert
werden. Wir machen hier eine Ausnahme und testen, ob eine Division mit 0 stattfindet.

/**
* @brief operator /= dividiere alle Elemente mit einer Zahl
* @param x Zahl
* @return Referenz auf das aufrufende Objekt
*/
inline T & operator /=( double x) {
Check :: error_if (nmx_msg , x == 0.0);
gsl_vector_scale (gsl () , 1 / x);
return static_cast <T & >(* this);
}
}; // IVCalc

} // namespace nmx :: gsl :: vec

Listing 5.24 cppsrc/nmx-xvector2/hpp/xvector2–05

Es folgt eine weitere Komponente zum Rechnen mit Vektoren. Es werden die Operatoren
für das binäre Plus, Minus usw. implementiert.

/**
* @brief The IVCalc1 class Implementierung von +,-,*,/ (binär)
*/
template <class TOUT , class TIN >
struct IVCalc1 {

Listing 5.25 cppsrc/nmx-xvector3/hpp/xvector3–01

Auch hier soll eine Hilfsfunktion zur Implementierung der Operatoren eingesetzt werden.
Ihr werden eine Reihe von Vektoren bzw. Sichten zusammen mit einer Rechenvorschrift
210 5 Die Vektor-Klasse

übergeben. Diese wird dann elementweise innerhalb einer Schleife ausgeführt. Für die
Vektoren      
a0 b0 c0
     
 a1   b1   c1 
     
a =  . , b =  . , c =  . 
 ..   ..   .. 
     
an bn cn
und die Rechenvorschrift
f (x, y, z) = 3x − 4y + z

wäre das Ergebnis ein neuer Vektor z mit Komponenten

zi = 3ai − 4bi + ci .

Wir implementieren diese Funktion als variadisches Template, denn so haben wir die
Möglichkeit, mit beliebig vielen Vektoren zu arbeiten.

/**
* @brief apply Anwendung einer Rechenvorschrift auf eine
* beliebige Anzahl von Vektoren
* @param fn Rechenvorschrift
* @param v Vektor oder View
* @param x Vektor oder View
* @return Vektor
*/
template <class FN , class ... X>
inline friend TOUT apply (FN fn , const TIN &v, const X &... x) {
TOUT vout(v.size ());
for ( size_t idx = 0; idx < v.size (); idx ++) {
vout[idx] = fn(v[idx], x[idx ]...);
}
return vout;
}

Listing 5.26 cppsrc/nmx-xvector3/hpp/xvector3–02

Der Parameter TIN kann entweder ein Vektor oder eine Sicht auf einen Vektor sein. TOUT
dagegen ist immer ein Vektor. Es wird dadurch möglich gemacht, mit Views zu rechnen.
Das Ergebnis der Rechnung kann jedoch keine View, sondern muss ein Vektor sein, da
das Ergebnis ein neuer Datensatz ist. Es folgt die Implementierung der Operatoren.

/**
* @brief Implementierung der Operatoren +,-,*,/
* @param v1 Vektor oder View
* @param v2 Vektor oder View
* @return Vektor
*/
inline friend TOUT operator +( const TIN &v1 , const TIN &v2) {
auto v = apply (std :: plus <double >() , v1 , v2);
return v;
}
5.7 Schnittstellen zur stl 211

inline friend TOUT operator -( const TIN &v1 , const TIN &v2) {
return apply (std :: minus <double >() , v1 , v2);
}

inline friend TOUT operator *( const TIN &v1 , const TIN &v2) {
return apply (std :: multiplies <double >() , v1 , v2);
}

inline friend TOUT operator /( const TIN &v1 , const TIN &v2) {
return apply (std :: divides <double >() , v1 , v2);
}

inline friend TOUT operator -( const TIN &v) { //


return apply (std :: negate <double >() , v);
}

Listing 5.27 cppsrc/nmx-xvector3/hpp/xvector3–03

Die eingeführte Hilfsfunktion kann auch für die Gruppe von Operatoren eingesetzt wer-
den, in denen die Multiplikation und Division eines Vektors mit einer Zahl implementiert
wird. Hier wird die Zahl über die eckige Klammer (capture-list) an die λ-Funktion über-
geben.

/**
* @brief Implementierung der Operatoren *,/
* @param c Zahl
* @param v2 Vektor oder View
* @return Vektor
*/
inline friend TOUT operator *( double c, const TIN &v2) {
return apply ([c]( double x) { return x * c; }, v2);
}

inline friend TOUT operator /( const TIN &v1 , double c) {


return apply (
[c]( double x) {
Check :: error_if (nmx_msg , c == 0.);
return x / c;
},
v1);
}

Listing 5.28 cppsrc/nmx-xvector3/hpp/xvector3–04

5.7 Schnittstellen zur stl


Die gsl ist eine numerische Bibliothek und arbeitet indexbasiert, d.h. auf die Elemente
von Vektoren und Matrizen wird hauptsächlich mithilfe von Schleifen und über Indizes
zugegriffen. Die stl verfolgt bekanntlich den Ansatz über Iteratoren (Abschnitt 1.4).
212 5 Die Vektor-Klasse

Wir wollen eine rudimentäre stl-Unterstützung einführen, indem wir einen Zeiger auf
das erste und einen weiteren auf ein Element nach dem letzten setzen.

/**
* @brief The IVStl class Schnittstelle zur Standard Template Library
*/
template <class T>
class IVStl : public IGslContainer <T, gsl_vector , 3>
{
// benutze Elementfunktionen der Basisklasse
using IGslContainer <T, gsl_vector , 3>:: gsl;

public :
inline double * begin () { return gsl () ->data; }
inline double *end () { return gsl () ->data + gsl () ->size; }

inline const double * begin () const { return gsl () ->data; }


inline const double *end () const { //
return gsl () ->data + gsl () ->size;
}
};

Listing 5.29 cppsrc/nmx-xvector4/hpp/xvector4–01

5.8 Minima und Maxima


Die Abfrage nach dem größten bzw. kleinsten Element eines Vektors und die dazugehö-
rigen Indizes können über Funktionen der gsl abgefragt werden. Wir erstellen für diese
Gruppe von Funktionen eine neue Komponente.

/**
* @brief The IVMinMax class Suche minimales , maximales Element
*/
template < typename T>
class IVMinMax : public IGslContainer <T, gsl_vector , 4>
{
using IGslContainer <T, gsl_vector , 4>:: gsl;

public :
// suche minimales Element
inline double max () const { return gsl_vector_max (gsl ()); }

// suche maximales Element


inline double min () const { return gsl_vector_min (gsl ()); }

// suche Index des minimalen Elements


inline size_t min_idx () const { //
return gsl_vector_min_index (gsl ());
}

// suche Index des maximalen Elements


inline size_t max_idx () const { //
5.9 Eigenschaften von Vektoren 213

return gsl_vector_max_index (gsl ());


}

// suche minimales UND maximales Element


inline auto min_max () const {
double minval , maxval ;
gsl_vector_minmax (gsl () , &minval , & maxval );
return std :: make_pair (minval , maxval );
}

// suche Indizes für minimales und maximales Element


inline auto min_max_idx () const {
size_t minidx , maxidx ;
gsl_vector_minmax_index (gsl () , &minidx , & maxidx );
return std :: make_pair (minidx , maxidx );
}

Listing 5.30 cppsrc/nmx-xvector5/hpp/xvector5–01

Mit den zwei letzten Elementfunktionen werden der maximale und minimale Wert sowie
die dazugehörigen Indizes ermittelt. In beiden Fällen erfolgen die Rückgabewerte als
std::pair.

5.9 Eigenschaften von Vektoren


Eine weitere Komponente liefert Informationen über die Elemente eines Vektors. So kann
z.B. abgefragt werden, ob alle Elemente gleich, größer oder kleiner als 0 sind.

/**
* @brief The IVEqual class Informationen über die Elemente
* eines Vektors und Vergleichsoperatoren
*/
template <class T>
class IVProperties : public IGslContainer <T, gsl_vector , 5>
{
using IGslContainer <T, gsl_vector , 5>:: gsl;

public :
// sind alle Elemente 0?
inline bool is_null () const { //
return gsl_vector_isnull (gsl ()) == 1;
}

// sind alle elemente >0 ?


inline bool is_pos () const { //
return gsl_vector_ispos (gsl ()) == 1;
}

// sind alle Elemente <0


inline bool is_neg () const { //
return gsl_vector_isneg (gsl ()) == 1;
}
214 5 Die Vektor-Klasse

// sind alle Elemente nicht negativ ?


inline bool is_nonneg () const { //
return gsl_vector_isnonneg (gsl ()) == 1;
}

Listing 5.31 cppsrc/nmx-xvector6/hpp/xvector6–01

Es folgen die zwei Vergleichsoperatoren:

/**
* @brief operator == prüfe ob zwei Vektoren gleich sind
* @param v1 Vektor
* @param v2 Vektor
* @return true wenn gleich
*/
inline friend bool operator ==( const T &v1 , const T &v2) {
return gsl_vector_equal (v1.gsl (), v2.gsl ()) == 1;
}

Listing 5.32 cppsrc/nmx-xvector6/hpp/xvector6–02

/**
* @brief operator == prüfe ob zwei Vektoren gleich sind
* @param v1 Vektor
* @param v2 Vektor
* @return true wenn gleich
*/
inline friend bool operator !=( const T &v1 , const T &v2) {
return gsl_vector_equal (v1.gsl (), v2.gsl ()) != 1;
}

Listing 5.33 cppsrc/nmx-xvector6/hpp/xvector6–03

5.10 BLAS-Schnittstelle
Die BLAS (Basic Linear Algebra Subprograms) ist eine numerische Bibliothek, mit der
Vektor- und Matrixoperationen implementiert werden. Die gsl stellt Schnittstellen zu
dieser Bibliothek zur Verfügung. Mit folgender Komponente werden wir Funktionen der
BLAS wie z.B. die Berechnung des Skalarproduktes oder die Norm eines Vektors, imple-
mentieren. Diese Komponente besteht nur aus friend-Funktionen und hat keinen inneren
Zustand. Es gibt deswegen keinen Grund, eine Struktur oder Klasse von der Basisklas-
se IGslContainer abzuleiten (Listing 5.34). Wir beginnen mit dem Skalarprodukt xT y
zweier Vektoren, wobei xT der transponierte Vektor
√∑ ist (Listing 5.35). Es folgen die Be-
N 2
rechnung der euklidischen Norm eines Vektors i xi (Listing 5.36), des normierten
Vektors ⃗n = |⃗⃗vv| (Listing 5.37) sowie der Summe der Beträge der Elemente eines Vektors
∑N
i |xi | (Listing 5.38).
5.10 BLAS-Schnittstelle 215

/**
* @brief The IVBlas struct Schnittstellen zur BLAS für das Rechnen
* mit Vektoren oder Views
* @param T Vektor oder View
*/
template < typename T>
struct IVBlas {

Listing 5.34 cppsrc/nmx-xvector7/hpp/xvector7–01

/**
* @brief dot Skalarprodukt
* @param v1 Vektor oder View
* @param v2 Vektor oder View
* @return double ( Skalarprodukt )
*/
inline friend double dot( const T &v1 , const T &v2) {
double result ;
gsl_blas_ddot (v1.gsl () , v2.gsl (), & result );
return result ;
}

Listing 5.35 cppsrc/nmx-xvector7/hpp/xvector7–02

/**
* @brief nrm2 Norm eines Vektors
* @param v Vektor oder View
* @return double (Norm)
*/
inline friend double nrm2( const T &v) { //
return gsl_blas_dnrm2 (v.gsl ());
}

Listing 5.36 cppsrc/nmx-xvector7/hpp/xvector7–03

/**
* @brief normalize Vektor wird normiert
* @param v Vektor oder View
* @return Kopie der Vektors ( normiert )
*/
inline friend T normalize ( const T &v) {
// berechne Norm des Vektors
const double magn = nrm2(v);
Check :: error_if (nmx_msg , magn == 0.0);
// Kopie eines Vektors
T vout(v);
// Kopie des Vektors wird normiert
vout /= magn;
return vout;
}

Listing 5.37 cppsrc/nmx-xvector7/hpp/xvector7–04


216 5 Die Vektor-Klasse

/**
* @brief asum Summe der Beträge der Elemente
* @param v Vector oder View
* @return Summe der Beträge der Elemente
*/
inline friend double asum( const T &v) { //
return gsl_blas_dasum (v.gsl ());
}

Listing 5.38 cppsrc/nmx-xvector7/hpp/xvector7–05

In allen Fällen wird es durch den Template-Parameter (Listing 5.34) möglich gemacht,
die Funktionen auf Vektoren und auf Views anzuwenden.

5.11 Sicht auf einen Vektor


Die Erstellung aller Komponenten hatte zum Ziel, eine Klasse zur Abbildung einer
gsl_vector_view und einer für eine gsl_vector-Struktur zu erstellen. Wir beginnen mit
der Erstellung der View-Klasse. Wir sehen, wie die Komponenten die neu erstellte Klasse
als Template-Parameter enthalten und somit zu Schnittstellen dieser Klasse werden.

/**
* @brief The VView class Klasse für Sichten zusammengesetzt
* aus Komponenten
*/
class VView : public IVBase <VView >,
public IVCalc <VView >,
public IVProperties <VView >,
public IVCalc1 <Vector , VView >,
public IVBlas <VView >

{
private :
gsl_vector_view _view ; //gsl - Struktur

public :

Listing 5.39 cppsrc/nmx-xvector/hpp/xvector–01

Instanzen einer View werden über zwei Wege erstellt. Die erste ist, dem Konstruktor,
eine entsprechende gsl-Struktur und die zweite, ein statisch erstelltes Feld zu übergeben.

/**
* @brief VView Konstruktor
* @param v gsl -View
*/
VView( gsl_vector_view v) { _view = v; }

Listing 5.40 cppsrc/nmx-xvector/hpp/xvector–02


5.12 Die Vektor-Klasse 217

/**
* @brief VView Konstruktor
* @param dim Anzahl der Elemente eines C- Feldes
* @param f C-Feld
*/
VView( size_t dim , double f[]) { //
_view = gsl_vector_view_array (f, dim);
}

Listing 5.41 cppsrc/nmx-xvector/hpp/xvector–03

Die nächsten beiden Elementfunktion geben den intern gespeicherten gsl_vector zurück.

/**
* @brief gsl_vec ermöglicht direkten Zugriff auf gsl Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline const gsl_vector *gsl () const { return &_view. vector ; }

Listing 5.42 cppsrc/nmx-xvector/hpp/xvector–04

/**
* @brief gsl_vec ermöglicht direkten Zugriff auf gsl Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline gsl_vector *gsl () { return &_view . vector ; }
}; // VView

Listing 5.43 cppsrc/nmx-xvector/hpp/xvector–05

5.12 Die Vektor-Klasse


Die Klasse Vector speichert intern einen Zeiger auf einen gsl_vector und erbt alle Eigen-
schaften der Komponenten, die in diesem Kapitel eingeführt wurden.

/**
* @brief The Vector class Klasse für gsl - Vektoren zussamengesetzt
* aus Komponenten
*/
class Vector : public IVBase <Vector >,
public IVCalc <Vector >,
public IVStl <Vector >,
public IVProperties <Vector >,
public IVCalc1 <Vector , Vector >,
public IVBlas <Vector >,
public IVMinMax <Vector >
{
public :
using View = VView ; // Synonym
218 5 Die Vektor-Klasse

private :
gsl_vector * _vector = nullptr ; // gsl - Struktur

public :

Listing 5.44 cppsrc/nmx-xvector/hpp/xvector–06

Der Standardkonstruktor erzeugt einen leeren Vektor. Dieser kann zu einem späteren
Zeitpunkt mit Daten gefüllt werden. Für die Instanz wird kein Speicher reserviert.

/**
* @brief Vector leerer Vektor
*/
inline Vector () {}

Listing 5.45 cppsrc/nmx-xvector/hpp/xvector–07

Der nächste Konstruktor erzeugt einen Vektor einer bestimmten Länge. Alle Elemente
erhalten denselben Wert.

/**
* @brief Vector Konstruktor : Vektor mit reservierten Speicher
* @param dim Länge des Vektors
*/
inline explicit Vector ( size_t dim , double val = 0) {
// reserviere Speicher
_vector = gsl_vector_alloc (dim);
// setze alle Elemente gleich einem vorgegebenen Wert
gsl_vector_set_all (_vector , val);
}

Listing 5.46 cppsrc/nmx-xvector/hpp/xvector–08

Eine intuitive Methode, Instanzen eines Vektors zu erzeugen, bietet der nächste Konstruk-
tor. Hier werden die Elemente innerhalb von geschweiften Klammern einfach aufgelistet.
Der Speicher wird durch den Aufruf des Konstruktors aus Listing 5.46 reserviert.

/**
* @brief Vector Konstruktor : kopiere eine Liste von Zahlen
* @param lst Zahlenliste
*/
inline Vector (std :: initializer_list <double > lst)
: Vector (lst.size ()) {
std :: copy(ALL(lst), begin ());
}

Listing 5.47 cppsrc/nmx-xvector/hpp/xvector–09

Ähnlich arbeitet der nächste Konstruktor, der aber statt eine Liste von Zahlen ein einfa-
ches C-Feld erwartet (Listing 5.48). Hier muss dem Konstruktor zusätzlich die Anzahl der
zu kopierenden Elemente übergeben werden. Diese darf die gesamte Anzahl der Elemente
nicht überschreiten.
5.12 Die Vektor-Klasse 219

/**
* @brief Vector Konstruktor : kopiere Inhalt eines C- Feldes
* @param n Anzahl der Elemente
* @param f C-Feld
*/
inline Vector ( size_t n, const double f[])
: Vector (n) {
std :: copy(f, f + n, begin ());
}

Listing 5.48 cppsrc/nmx-xvector/hpp/xvector–10

Es folgt die Implementierung des Kopierkonstruktors, mit dem eine Kopie eines bereits
existierenden Vektors erzeugt wird.

/**
* @brief Vector Kopierkonstruktor
* @param v der zu kopierende Vektor
*/
inline Vector ( const Vector &v)
: Vector (v.size ()) {
std :: copy(ALL(v), begin ());
}

Listing 5.49 cppsrc/nmx-xvector/hpp/xvector–11

Der move-Konstruktor kopiert nicht, sondern übernimmt die Daten eines anderen Vektors.
Die Vorlage ist anschließend leer.

/**
* @brief Vector move - Konstruktor
* @param v übernehme Daten dieses Vektors
*/
inline Vector ( Vector &&v) {
std :: swap(_vector , v. _vector );
v. _vector = nullptr ;
}

Listing 5.50 cppsrc/nmx-xvector/hpp/xvector–12

Der Destruktor gibt den Speicher, der für den gsl_vector reserviert wurde, wieder frei.

/**
* Destruktor
*/
inline ~ Vector () {
if ( _vector != nullptr ) {
gsl_vector_free ( _vector );
}
}

Listing 5.51 cppsrc/nmx-xvector/hpp/xvector–13


220 5 Die Vektor-Klasse

Es folgen zwei Versionen des Zuweisungsoperators: die eine, um einen anderen Vektor
zu kopieren (Listing 5.52), und die andere, um die Daten eines Vektors zu übernehmen
(Listing 5.53). Genauso wie im Fall des move-Konstruktors ist die Vorlage anschließend
leer.

/**
* @brief operator = kopiere die Werte eines Vektors
* @param v der zu kopierende Vektor
* @return Referenz auf das aufrufende Objekt
*/
inline Vector & operator =( const Vector &v) {
if (&v != this) {
if ( _vector != nullptr ) {
gsl_vector_free ( _vector );
}
_vector = gsl_vector_alloc (v.size ());
std :: copy(ALL(v), begin ());
}
return *this;
}

Listing 5.52 cppsrc/nmx-xvector/hpp/xvector–14

/**
* @brief operator = übernehme Daten eines Vektors
* @param v dieser Vektor ist anschließend leer
* @return Referenz auf das aufrufende Objekt
*/
inline Vector & operator =( Vector &&v) {
if (&v != this) {
if ( _vector != nullptr ) {
// gebe alten Speicher frei
gsl_vector_free ( _vector );
// der Zeiger ist ab jetzt ungültig
_vector = nullptr ;
}
// vertausche Zeiger
std :: swap(_vector , v. _vector );
}
return *this;
}

Listing 5.53 cppsrc/nmx-xvector/hpp/xvector–15

Es folgen zwei Elementfunktionen, die einen Zeiger auf den intern gespeicherten
gsl_vector liefern. Alle Komponenten leiten die entsprechenden Aufrufe an diese bei-
den Methoden weiter.

/**
* @brief gsl ermöglicht direkten Zugriff auf gsl - Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline const gsl_vector *gsl () const {
// ist der Zeiger gültig ?
5.12 Die Vektor-Klasse 221

Check :: ptr(nmx_msg , _vector );


return _vector ;
}

Listing 5.54 cppsrc/nmx-xvector/hpp/xvector–16

/**
* @brief gsl ermöglicht direkten Zugriff auf gsl - Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline gsl_vector *gsl () {
// ist der Zeiger gültig ?
Check :: ptr(nmx_msg , _vector );
return _vector ;
}

Listing 5.55 cppsrc/nmx-xvector/hpp/xvector–17

Es wird eine View mit n Elementen erzeugt, die bei der Position offset beginnt.

/**
* @brief view erzeuge View auf Daten
* @param offset beginne ab dieser Position
* @param n die View zeigt auf n Elemente
* @return View auf die Daten des aufrufenden Objekts
*/
inline View view( size_t offset , size_t n) {
return View( gsl_vector_subvector (gsl (), offset , n));
}

Listing 5.56 cppsrc/nmx-xvector/hpp/xvector–18

Mit dieser Elementfunktion wird ebenfalls eine View mit n Elementen (beginnend bei der
Position offset) erzeugt, allerdings wird mit einer vorgegebenen Schrittweite auf diese
zugegriffen.

/**
* @brief view erzeuge View auf Daten
* @param offset beginne ab dieser Position
* @param stride Schrittweite
* @param n Anzahl der Elemente
* @return View auf die Daten des aufrufenden Objekts
*/
inline View view( size_t offset , size_t stride , size_t n) {
return View( gsl_vector_subvector_with_stride (gsl (), //
offset ,
stride ,
n));
}

Listing 5.57 cppsrc/nmx-xvector/hpp/xvector–19

Ist ein Vektor leer, so gibt die Elementfunktion Listing 5.58 true zurück. Leer bedeutet,
dass entweder kein Speicher reserviert wurde oder aber der Vektor keine Elemente enthält.
222 5 Die Vektor-Klasse

Ob der interne Zeiger auf gültige Werte zeigt, kann mit der Elementfunktion Listing 5.59
getestet werden.

/**
* @brief empty
* @return true wenn gsl - Struktur instanziiert wurde und Elemente
* beinhaltet
*/
inline bool empty () const { //
return _vector != nullptr && size () != 0;
}

Listing 5.58 cppsrc/nmx-xvector/hpp/xvector–20

/**
* @brief is_init
* @return true wenn gsl - Struktur instanziiert wurde
*/
inline bool is_init () const { return _vector != nullptr ; }

Listing 5.59 cppsrc/nmx-xvector/hpp/xvector–21

5.13 Beispiele
Für die Beispiele in diesem Abschnitt werden wir folgende Hilfsfunktion zur Generierung
eines Ausgabestroms verwenden.

/**
* @brief get_output_stream Ausgabestrom für die Beispielprogramme
* @param name Dateiname
* @param fmt Formatierung ( optional )
* @return Ausgabestrom
*/
static inline std :: ofstream get_output_stream ( const std :: string &name ,
const Format &fmt =
Output :: csv) {
return Output :: get_stream ("x029", fmt , name);
}

Listing 5.60 cppsrc/apps-x029/hpp/x029–04

5.13.1 Einsatz von Konstruktoren

Beispiel Vektoren werden über Zahlenlisten und den Kopierkonstruktor erstellt. Eine
View wird erzeugt und zeigt auf ein statisches C-Feld. Alle Objekte werden in eine Datei
geschrieben.
5.13 Beispiele 223

/**
* @brief vec1 Konstruktoren Vektor - Klasse
*/
inline void vec0 () {
using Vector = gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed ;
// erzeuge Vektor aus einer Liste von Zahlen
Vector v1{ 1.0 , 2.0 , 3.0 };
ofs << v1;
// erzeuge zweiten Vektor aus einer Liste von Zahlen
Vector v2{ 2, 3, 4 };
ofs << v2;
// Kopierkonstruktor v3 = v1
gsl :: Vector v3{ v1 };
ofs << v3;
v3 = v2;
ofs << v3;
// erzeuge Vektor mit C-Array
double f[] = { 10, 20, 30 };
Vector :: View v4(3, f);
ofs << v4;
}

Listing 5.61 cppsrc/apps-x029/hpp/x029–05

1.000 ,2.000 ,3.000


2.000 ,3.000 ,4.000
1.000 ,2.000 ,3.000
2.000 ,3.000 ,4.000
10.000 ,20.000 ,30.000

Listing 5.62 data/x029/csv–vec0.csv

Beispiel Ein Vektor wird über eine Zahlenliste erzeugt. Anschließend werden seine Da-
ten mit std::move einem anderen Vektor übertragen. Die Vorlage ist anschließend leer.

/**
* @brief vec11 Daten werden von einem Vektor auf einen anderen
* übertragen
*/
inline void vec11 () {
using namespace gsl;
// öffne Ausgabestrom
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed ;
// erzeuge Vektor und schreibe ihn in die Datei
Vector v1{ 1, 2, 3, 4 };
ofs << v1;
// übertrage Inhalt
Vector v2 = std :: move(v1);

if (v1.empty ()) {
ofs << "v1 is empty " << std :: endl;
} else {
224 5 Die Vektor-Klasse

ofs << v1;


}
ofs << v2;
}

Listing 5.63 cppsrc/apps-x029/hpp/x029–06

1.000 ,2.000 ,3.000 ,4.000


v1 is empty
1.000 ,2.000 ,3.000 ,4.000

Listing 5.64 data/x029/csv–vec11.csv

5.13.2 Einsatz von Views

Beispiel Ein Vektor mit den Elementen 0, 1, 2, 3 . . . 9 wird initialisiert. Es werden zwei
Sichten auf diesen Vektor erzeugt. Die erste beginnt bei dem Element mit Index 3 und
hat die Länge 4 und die zweite bei dem Index 3 mit einer Länge 4 und Schrittweite 2.

/**
* @brief vec1 Konstruktoren Vektor - Klasse und Views
*/
inline void vec1 () {
using Vector = gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed ;

// erzeuge Vektor aus einer Liste von Zahlen


Vector v1 (10);
for ( size_t idx = 0; idx < v1.size (); idx ++) {
v1[idx] = idx;
}
ofs << v1;
// view auf Vektor beginnend ab der Stelle 3 und
// insgesamt 4 Elementen
auto view = v1.view (3, 4);
ofs << view;
// view auf Vektor beginnend ab der Stelle 3 und
// insgesamt 4 Elementen und Schrittweite 2
auto view1 = v1.view (3, 2, 4);
ofs << view1 ;
}

Listing 5.65 cppsrc/apps-x029/hpp/x029–07

0.000 ,1.000 ,2.000 ,3.000 ,4.000 ,5.000 ,6.000 ,7.000 ,8.000 ,9.000
3.000 ,4.000 ,5.000 ,6.000
3.000 ,5.000 ,7.000 ,9.000

Listing 5.66 data/x029/csv–vec1.csv


5.13 Beispiele 225

5.13.3 Mathematische Operationen mit Vektoren

Beispiel Ein Vektor v1 der Länge 4 wird mit den Elementen 1, 2, 3, 4 gefüllt. Es werden
2 2
folgende Rechenoperationen ausgeführt: v2i = sin(v1i ), v3i = cos(v1i ), v4i = v2i + v3i
2 2
und v5i = cos (v2i ) + sin (v3i ), wobei i = 0, 1, 2, 3 ist.

/**
* @brief vec2 mathematische Operationen mit Vektoren
*/
inline void vec2 () {
using Vector = gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed << std :: setprecision (2);
// erzeuge Nullvektor
Vector v1 (4);
ofs << v1;

// die Elemente erhalten die Werte 0,1,2,3


v1. transform ([]( double x) {
(void) x;
double static val = 0;
return val ++;
});
ofs << v1;

// berechne den sin der Elemente von v1 und speichere diese in v2


Vector v2 = apply ([]( double x) { return sin(x); }, v1);
ofs << v2;
Vector v3 = apply ([]( double x) { return cos(x); }, v1);
ofs << v3;

auto fn = []( double x, double y) { //


return std :: pow(x, 2) + std :: pow(y, 2);
};
Vector v4 = apply (fn , v2 , v3);
ofs << v4;

auto fn1 = []( double x, double y) { //


return std :: pow(cos(x), 2) + std :: pow(sin(y), 2);
};
Vector v5 = apply (fn1 , v1 , v1);
ofs << v5;
}

Listing 5.67 cppsrc/apps-x029/hpp/x029–08

0.00 ,0.00 ,0.00 ,0.00


0.00 ,1.00 ,2.00 ,3.00
0.00 ,0.84 ,0.91 ,0.14
1.00 ,0.54 , -0.42 , -0.99
1.00 ,1.00 ,1.00 ,1.00
1.00 ,1.00 ,1.00 ,1.00

Listing 5.68 data/x029/csv–vec2.csv


226 5 Die Vektor-Klasse

5.13.4 Zugriff auf einzelne Elemente, Minima und Maxima

Beispiel Ein Vektor der Länge 6 wird mithilfe des Klammeroperators mit Werten
sin(xi )/xi gefüllt, wobei xi = 1, 2, . . . 7. Zuerst werden der minimale und maximale
Wert durch Suchen mithilfe einer Schleife gefunden, dann durch Anwendung der stl-
Algorithmen und schließlich durch Aufruf der Elementfunktionen der Vektor-Klasse.

/**
* @brief vec5 minimales maximales Element
*/
inline void vec5 () {
using gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );
constexpr size_t dim = 6;
gsl :: Vector v(dim);
// Vektor wird gefüllt
for ( size_t idx = 0; idx < dim; idx ++) {
v[idx] = sin(idx + 1) / (idx + 1);
}
// Suche nach Minimum Maximum mit Schleife
double maxelement = v[0] , minelement = v[0];
for ( size_t jdx = 0; jdx < dim; jdx ++) {
const double jelement = v[jdx ];
maxelement = std :: max( maxelement , jelement );
minelement = std :: min( minelement , jelement );
}
// Suche nach Minimum Maximum mit stl
auto imaxlement = std :: max_element (std :: begin(v), //
std :: end(v));
auto iminlement = std :: min_element (std :: begin(v), //
std :: end(v));
// Ergebnisse
ofs << v;
ofs << "max loop:" << maxelement << std :: endl;
ofs << "min loop:" << minelement << std :: endl;
ofs << "max method :" << v.max () << std :: endl;
ofs << "min method :" << v.min () << std :: endl;
ofs << "max stl:" << * imaxlement << std :: endl;
ofs << "min stl:" << * iminlement << std :: endl;
}

Listing 5.69 cppsrc/apps-x029/hpp/x029–09

Das Programm produziert folgende Ausgabe:

8.415e -01 ,4.546e -01 ,4.704e -02 , -1.892e -01 , -1.918e -01 , -4.657e -02
max loop :8.415e -01
min loop : -1.918e -01
max method :8.415e -01
min method : -1.918e -01
max stl :8.415e -01
min stl : -1.918e -01

Listing 5.70 data/x029/csv–vec5.csv


5.13 Beispiele 227

5.13.5 Beispiele aus der Vektorrechnung

Beispiel Werden zwei Punkte ⃗r0 und ⃗r1 einer Geraden gegeben, so lautet ihre Parame-
terform:
⃗r = ⃗r0 + λ (⃗r1 − ⃗r0 ) , λ ∈ R

Für die beiden Vektoren    


3 −1
⃗r0 =  , ⃗r1 =  
1 6

wäre das      
x 3 −4
  =   + λ  .
y 1 5

Das dazugehörige Programm berechnet die Koordinaten von zwei Punkten für λ1 = 1
und λ2 = 2.

/**
* @brief vec6 Parameterdarstellung einer Gerade
*/
inline void vec6 () {
using gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );
const gsl :: Vector r0{ 3, 1 };
const gsl :: Vector r1{ -1, 6 };

// Funktion Parameterform
auto paramForm = [r0 , r1 ]( double l) { return r0 + l * (r1 - r0); };

const double l1 = 1, l2 = 2;
const auto v1 = paramForm (l1);
const auto v2 = paramForm (l2);

// Ausgabe
ofs << r0 << r1;
ofs << " lambda 1=" << l1 << std :: endl;
ofs << v1;
ofs << " lambda 2=" << l2 << std :: endl;
ofs << v2;
}

Listing 5.71 cppsrc/apps-x029/hpp/x029–10

3.000 e+00 ,1.000e+00


-1.000e+00 ,6.000e+00
lambda 1=1.000 e+00
-1.000e+00 ,6.000e+00
lambda 2=2.000 e+00
-5.000e+00 ,1.100e+01

Listing 5.72 data/x029/csv–vec6.csv


228 5 Die Vektor-Klasse

Beispiel Für zwei Vektoren


   
3 ⃗ 4
⃗a =  , b =   (5.1)
5 −2

wollen wir
       
7 1 6 −3
⃗v1 = ⃗a + ⃗b =  , ⃗v2 = ⃗a − ⃗b =  , ⃗v3 = 2⃗a =  , ⃗v4 = −⃗a =   (5.2)
3 7 10 −5

vom Computer berechnen lassen und zusätzlich zeigen, dass die Dreiecksungleichungen

|⃗a + ⃗b| ≤ |⃗a| + |⃗b|, |⃗a − ⃗b| ≥ ||⃗a| − |⃗b||

für ⃗a und ⃗b erfüllt sind.

/**
* @brief vec3 algebraische Operationen
*/
inline void vec3 () {
using Vector = gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );
Format fmt{ ",", "\t" };
ofs << fmt << std :: fixed ;

const Vector a{ 3, 5 };
const Vector b{ 4, -2 };
auto v1 = a + b, v2 = a - b, v3 = 2 * a, v4 = -a;
ofs << a << b << v1 << std :: endl << v2 << v3 << v4 << std :: endl;

// teste Dreiecksungleichungen
if (nrm2(v1) <= abs(nrm2(v1)) + abs(nrm2(v2))) {
ofs << "true , ";
} else {
ofs << "false , ";
}
if (nrm2(v2) >= abs(abs(nrm2(v1)) - abs(nrm2(v2)))) {
ofs << "true" << std :: endl;
} else {
ofs << " false " << std :: endl;
}
}

Listing 5.73 cppsrc/apps-x029/hpp/x029–11

Das Programm produziert folgende Ausgabe.

3.000 ,5.000 4.000 , -2.000 7.000 ,3.000


-1.000 ,7.000 6.000 ,10.000 -3.000 , -5.000
true , true

Listing 5.74 data/x029/csv–vec3.csv


5.13 Beispiele 229

Beispiel Das Skalarprodukt zweier Vektoren und der Winkel zwischen ihnen wird be-
rechnet. Für die beiden Vektoren
   
5 −1
⃗c =  , d⃗ =   (5.3)
2 6

⃗ 2 = 37, cos φ = 0.21369 mit φ = 1.3554 rad = 77.66◦ .


gilt ⃗c · d⃗ = 7, |⃗c|2 = 29, |d|

/**
* @brief vec4 Skalarprodukt cos
*/
inline void vec4 () {
using Vector = gsl :: Vector ;

std :: ofstream ofs = get_output_stream ( __func__ );


const Vector c{ 5, 2 };
const Vector d{ -1, 6 };

const double n1 = pow(nrm2(c), 2);


const double n2 = pow(nrm2(d), 2);
const double cdotd = dot(c, d);
const double cosphi = cdotd / (nrm2(c) * nrm2(d));

ofs << c << d;


ofs << "n1=" << n1 << ","
<< "n2=" << n2 << ","
<< "cdotd =" << cdotd << ","
<< "cos(phi)=" << cosphi << std :: endl;
}

Listing 5.75 cppsrc/apps-x029/hpp/x029–12

Wir erhalten folgende Ausgabe:

5.000 e+00 ,2.000e+00


-1.000e+00 ,6.000e+00
n1 =2.900 e+01,n2 =3.700 e+01 , cdotd =7.000 e+00, cos(phi) =2.137e -01

Listing 5.76 data/x029/csv–vec4.csv

Beispiel Wir wollen das Kreuzprodukt ⃗c zweier Vektoren ⃗a und ⃗b berechnen:


        
3 2 3 2 −5
         
⃗a =   ⃗  
−1, b =  3 , ⃗c = −1 ×  3  =  7 
    
. (5.4)
2 −1 2 −1 11

Wir implementieren als erstes eine Funktion zur Berechnung des Kreuzproduktes zweier
dreidimensionalen Vektoren.

/**
* @brief cross_product Kreuzprodukt von dreidimensionalen Vektoren
230 5 Die Vektor-Klasse

* @param v1 Vektor (Länge 3)


* @param v2 Vektor (Länge 3)
* @return Vektor (Länge 3)
*/
gsl :: Vector cross_product ( const gsl :: Vector &v1 , const gsl :: Vector &v2)
{
gsl :: Vector vout (3);
vout [0] = v1 [1] * v2 [2] - v1 [2] * v2 [1];
vout [1] = v1 [0] * v2 [2] - v1 [2] * v2 [0];
vout [2] = v1 [0] * v2 [1] - v1 [1] * v2 [0];
return vout;
}

Listing 5.77 cppsrc/apps-x029/hpp/x029–13

Es folgt das Programm zur Berechnung von ⃗c = ⃗a × ⃗b.

/**
* @brief vec7 Kreuzprodukt zweier Vektoren
*/
inline void vec7 () {
using gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );

const gsl :: Vector a{ 3, -1, 2 }, b{ 2, 3, -1 };

// berechne Kreuzprodukt
const auto c = cross_product (a, b);

// Ausgabe
ofs << a << b;
ofs << "c = a x b=" << c;
}

Listing 5.78 cppsrc/apps-x029/hpp/x029–14

3.000 e+00 , -1.000e+00 ,2.000e+00


2.000 e+00 ,3.000e+00 , -1.000e+00
c = a x b= -5.000e+00 , -7.000e+00 ,1.100e+01

Listing 5.79 data/x029/csv–vec7.csv

5.13.6 Drehmoment durch Kraft auf Teilchen bezüglich des


Ursprungs

Eine Kraft F⃗ = 2⃗ex + 3⃗ey wirkt auf ein Teilchen, das sich am Ort ⃗r = 5⃗ex − 5⃗ey be-
züglich des Ursprungs befindet. Es soll mit einem Computerprogramm das Drehmoment
berechnet werden. (Alle Größen in SI-Einheiten.)
5.13 Beispiele 231

Lösung Aus der Definition des Drehmoments ⃗τ = ⃗r × F


⃗ folgt
 
   
5 2 0
     
⃗τ =   ×   
−5 3  0  N m.
=  (5.5)
0 0 25

Quelltext und Daten Zur Berechnung des Kreuzproduktes setzen wir die Funktion
Listing 5.77 ein.

/**
* @brief vec8 Drehmoment durch Kraft auf Teilchen bezüglich
* des Ursprungs
*/
inline void vec8 () {
using Vector = gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );
const Vector r{ 5, -5, 0 }, f{ 2, 3, 0 };
const auto tau = cross_product (r, f);
ofs << r << f;
ofs << "tau = r x f=" << tau;
}

Listing 5.80 cppsrc/apps-x029/hpp/x029–15

5.000 e+00 , -5.000e+00 ,0.000e+00


2.000 e+00 ,3.000e+00 ,0.000e+00
tau = r x f= -0.000e+00 ,0.000e+00 ,2.500e+01

Listing 5.81 data/x029/csv–vec8.csv

5.13.7 Drehimpuls eines Teilchens bezüglich des Ursprungs

Ein Teilchen der Masse m = 100 g bewegt sich mit einer Geschwindigkeit ⃗v (t) =
(0, 0, −9.81t⃗ez ) und seine Position wird durch ⃗r(t) = (0, 10⃗ey , 30 − 4.905t2⃗ez ) beschrie-
ben. Alle Größen sind in SI-Einheiten angegeben. Es soll der Drehimpuls des Teilchens
bezüglich des Ursprungs für t = 1 s berechnet werden.

Lösung
⃗ = ⃗r × p
L ⃗ = m⃗r × ⃗v (5.6)
oder
⃗ex ⃗ey ⃗ez
⃗ = 0.1 0 10 30 − 4.905t2 = −9.81t⃗ex
L (5.7)
0 0 −9.81t
⃗ = −0.9.81⃗ex kg m2 /s
Für t = 1 s ist L
232 5 Die Vektor-Klasse

Quelltext Wir nutzen die Funktion für das Kreuzprodukt (Listing 5.77).

/**
* @brief vec9 Drehimpuls eines Teilchens bezüglich des Ursprungs
*/
inline void vec9 () {
using gsl :: Vector ;
std :: ofstream ofs = get_output_stream ( __func__ );
const double mass = 100.0 _g;
const double t = 1.0;

// Geschwindigkeits - und Ortsvektor


const gsl :: Vector v{ 0, 0, -9.81 * t };
const gsl :: Vector r{ 0, 10.0 , 30.0 - 4.905 * pow(t, 2) };

const auto p = mass * v;


const auto L = cross_product (r, p);
ofs << r << p << L;
}

Listing 5.82 cppsrc/apps-x029/hpp/x029–16

0.000 e+00 ,1.000e+01 ,2.509e+01


0.000 e+00 ,0.000e+00 , -9.810e -01
-9.810e+00 , -0.000e+00 ,0.000e+00

Listing 5.83 data/x029/csv–vec9.csv

5.13.8 Kräfte wirken auf Massenpunkt (in kartesischen


Koordinaten)

Auf einen Massenpunkt wirken folgende Kräfte (in N): F ⃗0 = −3⃗ey , F


⃗1 = 20⃗ex − 10⃗ey ,
F2 = 1⃗ex + 20⃗ey , F3 = 10⃗ex , F4 = 5⃗ex + 5⃗ey , F5 = −10⃗ex − 10⃗ey
⃗ ⃗ ⃗ ⃗

1. Berechnen Sie die auf den Massenpunkt wirkende Gesamtkraft in kartesischen Koor-
dinaten.
2. Erstellen Sie ein Computerprogramm, welches die Gesamtkraft berechnet und alle
Kräfte inklusive der Gesamtkraft im LATEX-Format als Tabelle speichert.

⃗ = ∑5 F
Lösung Die Gesamtkraft auf das Teilchen ist F ⃗
i=0 i oder
   
        
0 20 5 −10 1 26 10
⃗ = +
F + + + +  =  . (5.8)
−3 −10 20 0 5 −10 2

Quelltext und Daten Für jede Kraft wird eine gsl::Vector-Instanz angelegt. Alle zu-
sammen werden in einem std::array gespeichert. Es kann somit bequem mithilfe einer
Schleife die Summe aller Kräfte berechnet werden.
5.13 Beispiele 233

/**
* @brief vec12 Kräfte wirken auf Massenpunkt
* ( kartesischen Koordinaten )
*/
inline void vec12 () {
// alle Kräfte werden in einem std :: array gespeichert
std :: array <gsl :: Vector , 7> forces ;
forces [0] = { 0, -3 };
forces [1] = { 20, -10 };
forces [2] = { 1, 20 };
forces [3] = { 10, 0 };
forces [4] = { 5, 5 };
forces [5] = { -10, -10 };
forces [6] = { 0, 0 };

// die Summe der Kräfte wird berechnet


for ( size_t idx = 0; idx < 6; idx ++) {
forces [6] += forces [idx ];
}

// Darstellung der Daten im LaTeX - Format


const auto [sep , lend] = Output :: latex ();
std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);
ofs << std :: fixed << std :: setprecision (2);
for ( size_t idx = 0; idx < 2; idx ++) {
idx == 0 ? ofs << "$x$" : ofs << "$y$";
for ( size_t jdx = 0; jdx < 7; jdx ++) {
ofs << " " << sep << forces [jdx ][ idx ];
}
ofs << lend;
}
}

Listing 5.84 cppsrc/apps-x029/hpp/x029–18

Tab. 5.1: Kräfte, die auf Massenpunkt wirken, und die resultierende Gesamtkraft
i F0 [N] F1 [N] F2 [N] F3 [N] F4 [N] F5 [N] F [N]
x 0.00 20.00 1.00 10.00 5.00 -10.00 26.00
y -3.00 -10.00 20.00 0.00 5.00 -10.00 2.00

5.13.9 Kräfte wirken auf Massenpunkt (in Polarkoordinaten)

Auf einen Massenpunkt wirken drei Kräfte |F ⃗0 | = 80 N, |F


⃗1 | = 120 N und |F
⃗2 | = 150 N,
◦ ◦ ◦
die jeweils einen Winkel φ0 = 40 , φ1 = 70 und φ2 = 145 mit der Horizontalen bilden.

1. Befindet sich der Massenpunkt im Gleichgewicht?


2. Stellen Sie in einer Tabelle jede Kraft und die Gesamtkraft in kartesischen und in
Polkoordinaten dar.
234 5 Die Vektor-Klasse

Lösung

Wir zerlegen jede Kraft in ihre x- und y-Komponenten

⃗i = Fi cos φi⃗ex + Fi sin φi⃗ey ,


F i = 1, 2, 3 (5.9)


und berechnen die Gesamtkraft F

2 ∑
2
⃗ =
F Fi cos φi⃗ex + Fi sin φi⃗ey . (5.10)
i=0 i=0

Quelltext und Daten Die Kräfte werden in Polarkoordinaten angegeben. Zur Be-
rechnung der Gesamtkraft rechnen wir die Polarkoordinaten in kartesische Koordina-
ten um. Dazu führen wir eine λ-Funktion ein. Eine weitere λ-Funktion rechnet kar-
tesische Koordinaten in Polarkoordinaten um. Beide rufen intern die gsl-Funktionen
gsl_sf_polar_to_rect und gsl_sf_rect_to_polar auf.

/**
* @brief vec13 Kräfte wirken auf Massenpunkt (in Polarkoordinaten )
*/
inline void vec13 () {
// Umrechnung von Polarkoordinaten in kartesischen Koordinaten
auto polar_to_rect = []( double r, double phi) {
gsl_sf_result x, y;
gsl_sf_polar_to_rect (r, phi , &x, &y);
return gsl :: Vector ({ x.val , y.val });
};

// Umrechnung von kartesischen Koordinaten in Polarkoordinaten


auto rect_to_polar = []( double x, double y) {
gsl_sf_result r, phi;
gsl_sf_rect_to_polar (x, y, &r, &phi);
return gsl :: Vector ({ r.val , phi.val });
};

// die Kräfte werden in einem std :: array gespeichert


std :: array <gsl :: Vector , 4> forces ;
forces [0] = polar_to_rect (80.0 , 40.0 _deg);
forces [1] = polar_to_rect (120.0 , 70.0 _deg);
forces [2] = polar_to_rect (150.0 , 145.0 _deg);
forces [3] = forces [0] + forces [1] + forces [2]; // die Gesamtkraft

// die Daten werden im LaTeX - Format gespeichert


std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);
ofs << std :: setprecision (2) << std :: fixed ;
const auto [sep , lend] = Output :: latex ();
for ( size_t idx = 0; idx < 4; idx ++) {
const auto x = forces [idx ][0] , y = forces [idx ][1];
const auto polar = rect_to_polar (x, y);
ofs << idx << sep << x << sep << y << sep //
<< polar [0] << sep << Math :: to_degrees (polar [1]) << lend;
}
}

Listing 5.85 cppsrc/apps-x029/hpp/x029–19


5.13 Beispiele 235

Tab. 5.2: Kräfte (i = 0, 1, 2), die auf Massenpunkt wirken die resultierende Gesamtkraft
(i = 3)
i Fix [N] Fiy [N] |Fi | [N] φi [deg]
0 61.28 51.42 80.00 40.00
1 41.04 112.76 120.00 70.00
2 -122.87 86.04 150.00 145.00
3 -20.55 250.22 251.06 94.69

Der Massenpunkt befindet sich nicht im Gleichgewicht.

5.13.10 Gravitationsfeld zweier Massen

Zwei kugelförmige Massen m1 und m2 mit Radien R1 und R2 befinden sich in einem
Abstand 2d voneinander und sind fest an ihrem Ort gebunden. Eine Probemasse mit
Masse m befindet sich innerhalb des Feldes, welches von diesen beiden erzeugt wird.

m m
y F1
r1 F2
r r2 F
ex ex
0 ex x 0 ex
m1 m2 m1 m2
−d d

(a) (b)
Abb. 5.4: Zwei Massen üben eine Kraft auf eine Probemasse aus. (a) Das Koordina-
tensystem und die Ortsvektoren (b) Die auf die Probemasse von den einzelnen Massen
ausgeübten Kräfte und die Gesamtkraft

1. Leiten Sie einen Ausdruck her für die Feldstärke des Gravitationsfeld in einer Ebene,
welches von beiden Massen erzeugt wird.
2. Erstellen Sie ein Computerprogramm, welches die Richtung der Kraft darstellt, die
das Feld auf die Probemasse mit m = 1 kg ausübt.

Lösung

Das Koordinatensystem Wir legen das Koordinatensystem so, dass die x-Achse durch
die Mittelpunkte der beiden Massen verläuft (Abb. 5.4a).

Berechnung der Kräfte m1 soll die Koordinaten (−d, 0) und m2 die Koordinaten (d, 0)
haben. Die Kraft, die von m1 auf m ausgeübt wird, lautet (Abb. 5.4a):

⃗1 = −G mm1 ⃗r1
F (5.11)
|⃗r1 |2 |⃗r1 |
236 5 Die Vektor-Klasse

mit
⃗ = ⃗r + d⃗
⃗r1 = ⃗r − (−d) (5.12)
Analog folgt für m2 und m:
⃗2 = −G mm2 ⃗r2
F (5.13)
|⃗r2 |2 |⃗r2 |
und
⃗r2 = ⃗r − d⃗ (5.14)
In Komponenten gilt für Gl. 5.11 und Gl. 5.13:
  



 m1 m x + d



F⃗1 = −G [ ]3/2   (5.15a)
 (x + d)2 + y 2 y
 



 m m x − d
 ]3/2  
⃗2 = −G [ 2

 F (5.15b)
 (x − d)2 + y 2 y

Damit folgt für die Komponenten der Gesamtkraft:


 ( )

 m1 m2

 Fx = −Gm [ ]3/2 (x + d) + [ ]3/2 (x − d) (5.16a)

 (x + d)2 + y 2 (x − d)2 + y 2
( )



 m 1 m 2
 Fy = −Gm [
 ]3/2 + [ ]3/2 y (5.16b)
(x + d)2 + y 2 (x − d)2 + y 2

Das Computerprogramm

Kurzbeschreibung Innerhalb einer Modellklasse werden wir sowohl die vorgegebenen


als auch die zu berechnenden physikalischen Größen speichern. Alle Berechnungen sollen
mit Elementfunktionen der Modellklasse durchgeführt werden. Wir fassen die wichtigsten
Eigenschaften zusammen:

Eingabe der beiden Massen m1 und m2 , des Abstands d sowie der Probemasse m
Berechnung der normierten Gesamtkraft an äquidistanten Punkten in einem Bereich
zwischen xmin und xmax für die x-Richtung und zwischen ymin und ymax für die
y-Richtung
Ausgabe der Daten in einer Datei zur grafischen Darstellung im TikZ-Format

Quelltext Die Klasse speichert neben den physikalischen Parametern auch die Grenzen
des Bereichs, in dem die normierten Kraftvektoren gezeichnet werden sollen. Alle diese
Werte werden mit dem Konstruktor (Listing 5.87) initialisiert und können nicht geändert
werden.

/**
* @brief The X130 class Gravitationsfeld zweier Massen
*/
5.13 Beispiele 237

class X130 : public XModel


{
using Data = Data <4 >;
using Vector = gsl :: Vector ;

private :
// G m1 m3 and G m2 m3
double _factor1 , _factor2 ;
// Koordinaten der Massen m1 und m2
Vector _d1 , _d2;
// Datenobjekt
Data _data;

public :
// Eingabeparameter
const double m1 , m2 , m3;
const double d, dx , dy;
const double xlim , ylim;

public :

Listing 5.86 cppsrc/apps-x130/hpp/x130–01

Mit clang-format off und clang-format on wird innerhalb des Quelltextes ein Bereich
markiert, der nicht von clang-format formatiert wird.

/**
* @brief X130 Konstruktor
* @param m1 Masse 1 ( Quelle )
* @param m2 Masse 2 ( Quelle )
* @param d Abstand zwischen m1 und m2
* @param m3 Probemasse
* @param xlim Darstellung von [-xlim ,xlim]
* @param dx Schrittweite in x- Richtung
* @param ylim Darstellung von [-ylim ,ylim]
* @param dy Schrittweite in y- Richtung
*/
// clang - format off
X130( double m1 , double m2 , double d, double m3 = 1,
double xlim = 5, double dx = 1,
double ylim = 5, double dy = 1): XModel ( __func__ )
, m1{ m1 } , m2{ m2 }, m3{ m3 }
, d{ 0.5*d }, dx{ dx } , dy{ dy }
, xlim{ xlim }, ylim{ ylim } {}
// clang - format on

Listing 5.87 cppsrc/apps-x130/hpp/x130–02

Die Kraft von m1 auf die Probemasse (Gl. 5.15a) wird durch folgende Funktion berechnet:

/**
* @brief force1 Berechnung der Kraft von m1 auf die Probemasse
* @param v Ortsvektor der Probemasse
* @return Kraft
*/
inline auto force1 ( const Vector &v) const {
auto r = v - _d1; // Vektor von m1 zur Probemasse
238 5 Die Vektor-Klasse

const double norm = nrm2(r);


Check :: error_if (nmx_msg , norm == 0.0);

// berechne Kraft
double den = pow(norm , 3);
auto fResult = ( _factor1 / den) * r;
return fResult ;
}

Listing 5.88 cppsrc/apps-x130/hpp/x130–03

Analog gilt für die Berechnung der Kraft von m2 auf die Probemasse (Gl. 5.15b):

/**
* @brief force2 Berechnung der Kraft von m2 auf die Probemasse
* @param v Ortsvektor der Probemasse
* @return Kraft
*/
inline auto force2 ( const Vector &v) const {
auto r = v - _d2; // Vektor von m2 zur Probemasse
const double norm = nrm2(r);
Check :: error_if (nmx_msg , norm == 0.0);

// berechne Kraft
double den = pow(norm , 3);
auto fResult = ( _factor2 / den) * r;
return fResult ;
}

Listing 5.89 cppsrc/apps-x130/hpp/x130–04

Es folgt die Berechnung der normierten Gesamtkraft für alle Punkte im festgelegten
Zeichenbereich.

/**
* @brief exec Berechnung der normierten Kraftvektoren
*/
inline void exec () {
using namespace gravitation ;
Check :: all(nmx_msg , { m1 > 0, m2 > 0, d > 0, m3 > 0 });

// konstante Faktoren der Kräfte von m1 und m2 auf die Probemasse


_factor1 = -Astronomy ::G * m1 * m3;
_factor2 = -Astronomy ::G * m2 * m3;

// Ortsvektorten von m1 und m2


_d1 = { -d, 0.0 };
_d2 = -_d1;

// Berechne normierte Gesamtkraft für jeden Punkt (x,y)


for ( double _x = -xlim; _x < xlim; _x += dx) {
for ( double _y = -ylim; _y < ylim; _y += dy) {
Vector r{ _x , _y };
try {
// Kräfte von m1 und m2 auf Probemasse
auto f1 = force1 (r), f2 = force2 (r);
auto f = normalize (f1 + f2);
5.13 Beispiele 239

// speichere Daten in Tabelle


_data += { _x , _y , f[0], f[1] };
} catch ( const std :: exception &err) {
std :: cout << _x << "," << _y << std :: endl;
std :: cout << err.what () << std :: endl;
}
}
}
}

Listing 5.90 cppsrc/apps-x130/hpp/x130–05

Die Speicherung der Daten erfolgt im TikZ-Format. Es werden die x- und y- Koordinate
der Stelle im Zeichenbereich und die zwei Komponenten des normierten Kraftvektors in
jeweils eine Zeile ausgegeben.

/**
* @brief save_data Speicherung der Daten , z.B. -1 -1 5 8
*/
inline void save_data () const {
std :: ofstream mysplot = get_output_stream ( Output ::plot , m1);
mysplot << std :: scientific << std :: setprecision (2);

// Formatierung kompatibel zu TiKZ ( erste Zeile)


mysplot << "x y u v\n";

// Formatierung kompatibel zu TiKZ ( Daten )


const auto sep = " ", lend = "\n";
for (const auto &crow : _data .data ()) {
mysplot << crow [0] //
<< sep << crow [1] //
<< sep << crow [2] //
<< sep << crow [3] << lend;
}
}
}; // X130

Listing 5.91 cppsrc/apps-x130/hpp/x130–06

Daten Wir erzeugen zwei Grafiken: die erste für das Gravitationsfeld, welches von zwei
gleichen Massen erzeugt wird, und die zweite, in der m1 ein ganzzahliges Vielfaches von
m2 ist (Abb. 5.5).

/**
* @brief run Gravitationsfeld welches von zwei Massen m1 und m2
* erzeugt wird
*/
inline void run () {
// Probemasse
const double m3 = 1.0;
// Erzeugt zusammen mit m1 das Feld
const double m2 = 1e4;
// Abstand zwischen m1 und m2
const double d = 10.0;
// Bereich in dem die Vektoren gezeichnet werden
240 5 Die Vektor-Klasse

const double xlim = 10, dx = 1.5;


const double ylim = 10, dy = 1.5;

// setze m1 = m2
double m1 = 1e4;
X130 xobj0{ m1 , m2 , d, m3 , xlim , dx , ylim , dy };
xobj0.exec ();
xobj0. save_data ();

// m1 ist ein Vielfaches von m2


m1 = 16e4;
X130 xobj1{ m1 , m2 , d, m3 , xlim , dx , ylim , dy };
xobj1.exec ();
xobj1. save_data ();
}

} // namespace nmx :: apps :: x130

Listing 5.92 cppsrc/apps-x130/hpp/x130–07

10 10

5 5

0 0

−5 −5

−10 −10

−10 −5 0 5 10 −10 −5 0 5 10

(a) (b)
Abb. 5.5: Gravitationsfeld zweier Massen (a) m1 = m2 = 104 kg (b) m1 = 16 ×
104 kg, m2 = 104 kg

5.14 Übungsaufgaben
y mit ⃗x = ⃗ex + 2⃗ey − 3⃗ez und ⃗
1. Berechnen Sie für die zwei Vektoren ⃗x und ⃗ y =
12⃗ex − ⃗ey + ⃗ez :
a) den Vektor ⃗r = ⃗x + ⃗
y und den Einheitsvektor parallel zu ⃗r,
b) den Winkel φ zwischen den zwei Vektoren ⃗x und ⃗ y,
c) das normalisierte Kreuzprodukt ⃗x × ⃗
y.
2. Abb. 5.6 zeigt fünf in einer Ebene liegenden Kräfte, welche an einem Massenpunkt
greifen. Erstellen Sie eine CSV-Datei, welche die Beträge für die Kräfte und die einge-
zeichneten Winkel enthält. Lesen Sie mit einem Computerprogramm diese Datei und
berechnen Sie die Gesamtkraft.
5.14 Übungsaufgaben 241

3. Zwei Massenpunkte A und B bewegen sich auf zwei geraden Strecken mit Geschwin-
digkeiten vA = 2 m/s und vB = 4 m/s. Berechnen Sie die relative Geschwindigkeit
(Betrag und Richtung) von A bezüglich B für den Fall, dass der Winkel zwischen den
beiden Strecken φ = 30◦ ist.

⃗2
F
⃗3
F
⃗1
F
ϑ3
ϑ2
⃗4
F
ϑ4
ϑ1 ⃗0
F

Abb. 5.6: Aufgabe 2


6 Die Matrix-Klasse

Übersicht
6.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
6.2 Ein C-ähnliches Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
6.3 Basisfunktionalität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
6.4 Rechnen mit Matrizen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
6.5 Minimales und maximales Element einer Matrix . . . . . . . . . . . . . . . . . . . . . . . . . 252
6.6 Austausch von Reihen und Spalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
6.7 Sichten und Kopien von Reihen und Spalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
6.8 Schnittstelle zur BLAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
6.9 Eigenschaften von Matrizen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
6.10 Die Matrix-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
6.11 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
6.12 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283

6.1 Einleitung
Eine Matrix ist eine zweidimensionale Struktur von Elementen desselben Datentyps, die
in Reihen und Spalten angeordnet sind und über zwei Indizes angesprochen werden,
wobei der Index für die erste Spalte und Reihe 0 ist.
 
a00 · · · a0j · · · a0n
 
 .. .. . .. .. 
 . . .. . . 
 
 
A =  ai1 · · · aij · · · ain  (6.1)
 
 .. .. . .. .. 
 . . .. . . 
 
am0 · · · amj · · · amn

Die GNU Scientific Library verfügt über eine Reihe von Funktionen zum Rechnen mit
Matrizen. Die Namen für Matrixfunktionen werden nach demselben Muster wie bei den
entsprechenden Vektorfunktionen vergeben. Wir beginnen mit einem C-ähnlichen Bei-
spiel und konzentrieren uns dabei auf Matrizen mit double-Elementen.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_6
244 6 Die Matrix-Klasse

6.2 Ein C-ähnliches Beispiel


Das Programm dieses Abschnitts ist dem entsprechenden Programm für Vektoren (Lis-
ting 5.1) ähnlich. Die Funktion erzeugt eine 4×4-Matrix mit Elementen aij = sin2 (i+j),
druckt diese aus, vertauscht dann die Reihen 2 und 3 und gibt schließlich die neue Matrix
auf dem Bildschirm aus.

/**
* @brief ex1 Rechnen mit Matrizen (gsl - Routinen )
*/
inline void ex1 () {
// Spalten und Reihen der Matrix
const size_t nrows = 4, nclmns = 4;

// reserviere Speicher für die Matrix


gsl_matrix *m = gsl_matrix_alloc (nrows , nclmns );

// Initialisierung der Elemente


for ( size_t i = 0; i < nrows ; i++) {
for ( size_t j = 0; j < nclmns ; j++) {
gsl_matrix_set (m, i, j, std :: pow(sin(i + j), 2));
}
}
// Ausgabe der Matrix auf dem Bildschirm
std :: cout << std :: setprecision (10) << std :: scientific ;
for ( size_t i = 0; i < nrows ; i++) {
for ( size_t j = 0; j < nclmns ; j++) {
std :: cout << std :: setw (10) //
<< gsl_matrix_get (m, i, j) << "\t";
}
std :: cout << "\n";
}

// vertausche Reihen 2 und 3


gsl_matrix_swap_rows (m, 2, 3);

// Ausgabe der geänderten Matrix auf dem Bildschirm


std :: cout << " --------------------" << std :: endl;
for ( size_t i = 0; i < nrows ; i++) {
for ( size_t j = 0; j < nclmns ; j++) {
std :: cout << std :: setw (10) //
<< gsl_matrix_get (m, i, j) << "\t";
}
std :: cout << "\n";
}

// Freigabe des Speichers


gsl_matrix_free (m);
}

Listing 6.1 cppsrc/apps-x030/hpp/x030–01

Nach Ausführung der Funktion erhalten wir folgende Ausgabe:

0.0000000000 e+00 7.0807341827e -01 8.2682181043e -01 1.9914856675e -02


7.0807341827e -01 8.2682181043e -01 1.9914856675e -02 5.7275001690e -01
6.2 Ein C-ähnliches Beispiel 245

8.2682181043e -01 1.9914856675e -02 5.7275001690e -01 9.1953576454e -01


1.9914856675e -02 5.7275001690e -01 9.1953576454e -01 7.8073020634e -02
--------------------
0.0000000000 e+00 7.0807341827e -01 8.2682181043e -01 1.9914856675e -02
7.0807341827e -01 8.2682181043e -01 1.9914856675e -02 5.7275001690e -01
1.9914856675e -02 5.7275001690e -01 9.1953576454e -01 7.8073020634e -02
8.2682181043e -01 1.9914856675e -02 5.7275001690e -01 9.1953576454e -01

Listing 6.2

Wir stellen als erstes fest, dass auch hier darauf geachtet werden muss, den reservierten
Speicher wieder freizugeben. Es fällt zusätzlich auf, dass die Ein- und Ausgabe der Ele-
mente über Schleifen erfolgt - die obwohl einfach zu programmieren - den meisten Platz
im Quelltext einnehmen. Wir wollen mit einer Klasse das Arbeiten mit gsl-Matrizen
vereinfachen und die Quelltexte übersichtlicher machen.
Eine Matrix wird in der gsl mithilfe folgender Struktur abgebildet.

typedef struct
{
size_t size1; // Reihen
size_t size2; // Spalten
size_t tda; // Anzahl der Elemente einer Reihe im Speicher
double * data; // Zeiger auf das erste Element der Matrix
gsl_block * block ; // reservierter Speicher
int owner; // Eigentümer
} gsl_matrix ;

Listing 6.3

Die Variablen size1 und size2 geben die Anzahl der Reihen und Spalten an. Die Ele-
mente werden in Form eines eindimensionalen Feldes im Speicher abgelegt. Für Matrizen
existieren auch Sichten (Listing 6.4) auf die Daten, die es erlauben, mit dem ganzen
Datensatz oder einen Teil davon zu arbeiten, ohne eine Kopie erstellen zu müssen.

typedef struct
{
gsl_matrix matrix ;
} _gsl_matrix_view ;

typedef _gsl_matrix_view gsl_matrix_view ;

Listing 6.4

Der dem Listing 6.1 entsprechende Quelltext, mit einer gsl_matrix_view realisiert, sieht
folgendermaßen aus.

/**
* @brief ex2 Rechnen mit Matrizen (gsl - Routinen und gsl - Sichten )
*/
inline void ex2 () {
// Spalten und Reihen der Matrix
const size_t nrows = 4, nclmns = 4;
246 6 Die Matrix-Klasse

// statisches C-Feld
double data [16];

// eine Sicht auf das C-Feld. Die Daten werden als Matrix verwaltet
gsl_matrix_view m = gsl_matrix_view_array (data , nrows , nclmns );

// Initialisierung der Elemente


for ( size_t i = 0; i < nrows ; i++) {
for ( size_t j = 0; j < nclmns ; j++) {
gsl_matrix_set (&m.matrix , i, j, std :: pow(sin(i + j), 2));
}
}

// Ausgabe der geänderten Matrix auf dem Bildschirm


std :: cout << std :: setprecision (10) << std :: scientific ;
for ( size_t i = 0; i < nrows ; i++) {
for ( size_t j = 0; j < nclmns ; j++) {
std :: cout << std :: setw (10) //
<< gsl_matrix_get (&m.matrix , i, j) << "\t";
}
std :: cout << "\n";
}

std :: cout << " --------------------" << std :: endl;

// vertausche Reihen 2 und 3


gsl_matrix_swap_rows (&m.matrix , 2, 3);

// Ausgabe der geänderten Matrix auf dem Bildschirm


for ( size_t i = 0; i < nrows ; i++) {
for ( size_t j = 0; j < nclmns ; j++) {
std :: cout << std :: setw (10) << //
gsl_matrix_get (&m.matrix , i, j) << "\t";
}
std :: cout << "\n";
}
}

Listing 6.5 cppsrc/apps-x030/hpp/x030–02

Für ein statisch erstelltes, eindimensionales C-Feld wird eine Sicht erzeugt, die diese Da-
ten als eine 4 × 4-Matrix verwaltet. Dies führt nicht zu Konflikten, denn die Anordnung
im Speicher sagt nichts darüber aus, wie auf die Daten zugegriffen werden kann. Wenn
r die Anzahl der Reihen und s die der Spalten ist, dann kann jedes Element eines eindi-
mensionalen Feldes über eine Indexpaar (i, j) mit folgender Formel angesprochen werden:
i ∗ s + j.

6.3 Basisfunktionalität
Wir folgen beim Entwurf der Klasse dieselbe Strategie wie im Fall der Vektor-Klasse
und erzeugen eine Matrix bzw. eine Sicht auf eine Matrix durch das Zusammenfügen von
6.3 Basisfunktionalität 247

Komponenten. Alle werden von der Basisklasse IGslContainer abgeleitet (Abschnitt 5.3,
Listing 5.8). Wir beginnen mit der Implementierung von Elementfunktionen zur Abfrage
der Anzahl der Reihen und Spalten sowie einer Methode zum Testen, ob die Matrix leer
ist.

/**
* @brief The IMBase class Basisfunktionalität
* für eine gsl - Matrix in Form einer Komponente
* T ist die Klasse , die mithilfe dieser Komponente
* gebaut wird
*/
template <class T>
class IMBase : public IGslContainer <T, gsl_matrix , 1>
{
// verwende die Funktionen der Basisklasse
using IGslContainer <T, gsl_matrix , 1>:: gsl;

public :

Listing 6.6 cppsrc/nmx-xmatrix0/hpp/xmatrix0–01

/**
* @brief rows
* @return Anzahl der Reihen
*/
inline size_t rows () const { return gsl () ->size1; }

Listing 6.7 cppsrc/nmx-xmatrix0/hpp/xmatrix0–02

/**
* @brief columns
* @return Anzahl der Spalten
*/
inline size_t columns () const { return gsl () ->size2; }

Listing 6.8 cppsrc/nmx-xmatrix0/hpp/xmatrix0–03

Eine leere Matrix ist diejenige, für die kein Speicher reserviert wurde.

/**
* @brief empty
* @return true wenn kein Speicher reserviert wurde
*/
inline bool empty () const { //
return gsl () == nullptr ? true : rows () == 0 && columns () == 0;
}

Listing 6.9 cppsrc/nmx-xmatrix0/hpp/xmatrix0–04

Durch die Überladung des Klammeroperators kann auf jedes Element über die Angabe
der Reihe und Spalte sowohl lesend als auch schreibend zugegriffen werden.
248 6 Die Matrix-Klasse

/**
* @brief operator () lesender Zugriff
* @param idx Reihe
* @param jdx Spalte
* @return Wert an der Stelle i,j
*/
inline double operator ()( size_t idx , size_t jdx) const {
return gsl_matrix_get (gsl () , idx , jdx);
}

Listing 6.10 cppsrc/nmx-xmatrix0/hpp/xmatrix0–05

/**
* @brief operator () schreibender Zugriff
* @param i Reihe
* @param j Spalte
* @return Referenz auf das Element i,j
*/
inline double & operator ()( size_t i, size_t j) {
return * gsl_matrix_ptr (gsl () , i, j); //
}

Listing 6.11 cppsrc/nmx-xmatrix0/hpp/xmatrix0–06

Die Ausgabe einer Matrix auf dem Bildschirm oder in einer Datei erfolgt über einen Aus-
gabestrom. Diesem werden die Elemente (angeordnet nach Spalten und Reihen) mithilfe
von zwei Schleifen übergeben. Die Formatierung der Elemente wird im Standardfall über
die Variable aus Listing 2.201 gesteuert.

/**
* @brief save Speichert reihenweise eine Matrix
* @param os Ausgabestrom
* @param fmt Formatierungsanweisung ( optional )
*/
inline void save(std :: ostream &os , Format fmt = Output :: array ())
const {
for ( size_t _idx = 0; _idx < rows (); _idx ++) {
os << gsl_matrix_get (gsl () , _idx , 0);
for ( size_t _jdx = 1; _jdx < columns (); _jdx ++) {
os << fmt.sep () << gsl_matrix_get (gsl (), _idx , _jdx);
}
os << fmt.lend ();
}
}

Listing 6.12 cppsrc/nmx-xmatrix0/hpp/xmatrix0–07

Wir überladen den <<-Operator zur Weiterleitung einer Matrix an einen Ausgabestrom.
Intern wird die Methode aus Listing 6.12 aufgerufen.

/**
* @brief operator << leitet die Daten der abgeleiteten Klasse
* an einem Ausgabestrom weiter
6.4 Rechnen mit Matrizen 249

* @param os Ausgabestrom
* @param p Instanz der abgeleiteten Klasse
* @return Referenz auf den Ausgabestrom
*/
inline friend std :: ostream &operator <<( std :: ostream &os , const T
&p) {
p.save(os);
return os;
}

Listing 6.13 cppsrc/nmx-xmatrix0/hpp/xmatrix0–08

Die Elemente einer Matrix können über die Angabe einer Funktion der Form y = f (x)
transformiert werden. Dadurch, dass eine Referenz auf das aufrufende Objekt zurückge-
liefert wird, können mehrere Aufrufe hintereinandergeschaltet werden.

/**
* @brief transform eine Funktion wird auf alle Elemente der
* Matrix angewandt
* @param fn Funktion der Form f(x) = y
* @return Referenz auf die Instanz der abgeleiteten Klasse
*/
template <class FN >
inline T & transform (FN fn) {
for ( size_t idx = 0; idx < rows (); idx ++) {
for ( size_t jdx = 0; jdx < columns (); jdx ++) {
double val = gsl_matrix_get (gsl (), idx , jdx);
gsl_matrix_set (gsl () , idx , jdx , fn(val));
}
}
return static_cast <T & >(* this);
}

Listing 6.14 cppsrc/nmx-xmatrix0/hpp/xmatrix0–09

6.4 Rechnen mit Matrizen


Wir erstellen eine Komponente zur Implementierung der Operatoren +=,-=,*=,/+ für das
Rechnen mit Matrizen.

/**
* @brief The IMCalc class kombinierte Rechenoperationen und Zuweisungen
*/
template <class T>
class IMCalc : public IGslContainer <T, gsl_matrix , 2>
{
// benutze Elementfunktionen der Basisklasse
using IGslContainer <T, gsl_matrix , 2>:: gsl;

protected :

Listing 6.15 cppsrc/nmx-xmatrix1/hpp/xmatrix1–01


250 6 Die Matrix-Klasse

Die Implementierung von +=,-= usw. erfolgt über eine Hilfsfunktion, die als Template
formuliert sowohl mit Matrizen als auch mit Views und Konstanten arbeiten kann.

/**
* @brief apply_fn Hilfsfunktion
* @param v Instanz einer Matrix oder View oder Zahl
* @param fn gsl - Funktion kombiniert das aufrufende Objekt
* mit einer Instanz v vom Typ X
* @return Referenz auf das aufrufende Objekt
*/
template <class X, class FN >
inline T & apply_fn ( const X &v, FN fn) {
if constexpr (std :: is_same_v <X, double >) {
// v ist eine Zahl
fn(gsl () , v);
} else {
// v ist ein Vektor oder eine View
fn(gsl () , v.gsl ());
}
// Referenz auf das aufrufende Objekt als Instanz vom Typ T
// ( downcast )
return static_cast <T & >(* this);
}

public :

Listing 6.16 cppsrc/nmx-xmatrix1/hpp/xmatrix1–02

Folgender Quelltextabschnitt zeigt, wie durch Einführung der Hilfsfunktion die Imple-
mentierung dieser Gruppe der Operatoren vereinfacht wird.

/**
* @brief operator += , -= ,*= ,/+
* @param v View oder Vektor
* @return Referenz auf das aufrufende Objekt
*/
inline T & operator +=( const T &v) { //
return apply_fn (v, gsl_matrix_add );
}
inline T &operator -=( const T &v) { //
return apply_fn (v, gsl_matrix_sub );
}
inline T & operator *=( const T &v) { //
return apply_fn (v, gsl_matrix_mul_elements );
}
inline T & operator /=( const T &v) { //
return apply_fn (v, gsl_matrix_div_elements );
}

Listing 6.17 cppsrc/nmx-xmatrix1/hpp/xmatrix1–03

Ähnliches gilt auch für die nächste Gruppe von Elementfunktionen. Der Compiler erzeugt
aus dem Funktionstemplate die richtige Variante, mit dem Resultat, dass auch für diese
Operatoren kein zusätzlicher Programmieraufwand nötig ist.
6.4 Rechnen mit Matrizen 251

/**
* @brief operator += , -= ,*=
* @param x reelle Zahl
* @return Referenz auf das aufrufende Objekt
*/
inline T & operator +=( double x) { //
return apply_fn (x, gsl_matrix_add_constant );
}
inline T &operator -=( double x) { //
return apply_fn (-x, gsl_matrix_add_constant );
}
inline T & operator *=( double x) { //
return apply_fn (x, gsl_matrix_scale );
}

Listing 6.18 cppsrc/nmx-xmatrix1/hpp/xmatrix1–04

Das Programmieren des binären Plus und Minus ist eine einfache Sache.

/**
* @brief operator + addiere zwei Matrizen
* @param m1 Matrix
* @param m2 Matrix
* @return m1+m2
*/
inline friend T operator +( const T &m1 , const T &m2) {
T m{ m1 };
m += m2;
return m;
}

Listing 6.19 cppsrc/nmx-xmatrix1/hpp/xmatrix1–05

/**
* @brief operator - subtrahiere zwei Matrizen
* @param m1 Matrix
* @param m2 Matrix
* @return m1 -m2
*/
inline friend T operator -( const T &m1 , const T &m2) {
T m{ m1 };
m -= m2;
return m;
}
}; // IMCalc

} // namespace nmx :: gsl :: mat

Listing 6.20 cppsrc/nmx-xmatrix1/hpp/xmatrix1–06


252 6 Die Matrix-Klasse

6.5 Minimales und maximales Element einer Matrix


Die Suche nach dem größten bzw. kleinsten Element und den dazugehörigen Indizes ist
Aufgabe dieser Komponente. Ihre Elementfunktionen rufen intern gsl-Routinen auf.

/**
* @brief The IMMinMax class Suche das minimale und maximale Element
* einer Matrix
*/
template <class T>
class IMMinMax : public IGslContainer <T, gsl_matrix , 3>
{
// benutze Elementfunktionen der Basisklasse
using IGslContainer <T, gsl_matrix , 3>:: gsl;

public :

Listing 6.21 cppsrc/nmx-xmatrix2/hpp/xmatrix2–01

Über die beiden folgenden Elementfunktionen wird das größte bzw. kleinste Element
einer Matrix abgefragt.

/**
* @brief min suche minimales Element
* @return das minimale Element
*/
inline double min () const { return gsl_matrix_min (gsl ()); }

Listing 6.22 cppsrc/nmx-xmatrix2/hpp/xmatrix2–02

/**
* @brief max suche maximales Element
* @return das maximale Element
*/
inline double max () const { return gsl_matrix_max (gsl ()); }

Listing 6.23 cppsrc/nmx-xmatrix2/hpp/xmatrix2–03

Durch einen Funktionsaufruf können beide Werte auf einmal abgefragt werden. Das Er-
gebnis wird in Form eines std::pair von der Funktion zurückgeliefert.

/**
* @brief min_max suche gleichzeitig minimales und maximales Element
* @return beide Werte als std :: pair
*/
inline auto min_max () const {
double minval , maxval ;
gsl_matrix_minmax (gsl () , &minval , & maxval );
return std :: make_pair (minval , maxval );
}

Listing 6.24 cppsrc/nmx-xmatrix2/hpp/xmatrix2–04


6.5 Minimales und maximales Element einer Matrix 253

Es wird die Position des maximalen (Listing 6.25) und des minimalen Werts (Listing
6.26) innerhalb der Matrix ermittelt.

/**
* @brief max_index Position des maximalen Elements
* @return (i,j) als std :: pair
*/
inline auto max_index () const {
size_t idx , jdx;
gsl_matrix_max_index (gsl () , &idx , &jdx);
return std :: make_pair (idx , jdx);
}

Listing 6.25 cppsrc/nmx-xmatrix2/hpp/xmatrix2–05

/**
* @brief min_index Position des minimalen Elements
* @return (i,j) als std :: pair
*/
inline auto min_index () const {
size_t idx , jdx;
gsl_matrix_min_index (gsl () , &idx , &jdx);
return std :: make_pair (idx , jdx);
}

Listing 6.26 cppsrc/nmx-xmatrix2/hpp/xmatrix2–06

Mit einem Aufruf können beide Indexpaare abgefragt werden. Da hier insgesamt vier
Indizes benötigt werden, ist der Rückgabewert ein verschachteltes std::pair.

/**
* @brief min_max_idx Position des minimalen und maximalen
* Elements
* @return geschachteltes std :: pair
*/
inline auto min_max_idx () const {
size_t minidx , minjdx , maxidx , maxjdx ;
gsl_matrix_minmax_index (gsl () , //
&minidx ,
&minjdx ,
&maxidx ,
& maxjdx );
return std :: make_pair (std :: make_pair (minidx , minjdx ), //
std :: make_pair (maxidx , maxjdx ));
}
}; // IMMinMax

} // namespace nmx :: gsl :: mat

Listing 6.27 cppsrc/nmx-xmatrix2/hpp/xmatrix2–07


254 6 Die Matrix-Klasse

6.6 Austausch von Reihen und Spalten


Durch Angabe der Indizes können die Reihen und Spalten einer Matrix ausgetauscht wer-
den bzw. es kann die Transponierte einer Matrix gebildet werden. Folgende Komponente
verleiht der Matrix-Klasse diese Funktionalität.

/**
* @brief The IMExchange struct
*/
template <class T>
struct IMExchange : public IGslContainer <T, gsl_matrix , 4> {
// benutze Elementfunktionen der Basisklasse
using IGslContainer <T, gsl_matrix , 4>:: gsl;

Listing 6.28 cppsrc/nmx-xmatrix3/hpp/xmatrix3–01

Zwei Reihen (Listing 6.29) bzw. Spalten (Listing 6.30) einer Matrix werden vertauscht.

/**
* @brief swap_rows vertausche Reihen
* @param i i-te Reihe
* @param j j-te Reihe
*/
void swap_rows ( size_t i, size_t j) { //
gsl_matrix_swap_rows (gsl () , i, j);
}

Listing 6.29 cppsrc/nmx-xmatrix3/hpp/xmatrix3–02

/**
* @brief swap_columns vertausche Spalten
* @param i i-te Spalte
* @param j j-te Spalte
*/
void swap_columns ( size_t i, size_t j) { //
gsl_matrix_swap_columns (gsl () , i, j);
}

Listing 6.30 cppsrc/nmx-xmatrix3/hpp/xmatrix3–03

Für eine quadratische Matrix werden Elemente einer Reihe mit den Elementen eine Spalte
vertauscht.

/**
* @brief swap_row_columns vertausche Reihe mit Spalte
* @param i i-te Reihe
* @param j j-te Spalte
*/
void swap_row_columns ( size_t i, size_t j) { //
gsl_matrix_swap_rowcol (gsl () , i, j);
}

Listing 6.31 cppsrc/nmx-xmatrix3/hpp/xmatrix3–04


6.7 Sichten und Kopien von Reihen und Spalten 255

Es folgen zwei Elementfunkionen zur Bildung der transponierten bzw. einer Kopie der
transponierten Matrix.

/**
* @brief transpose Matrix wird transponiert
*/
void transpose () { gsl_matrix_transpose (gsl ()); }

Listing 6.32 cppsrc/nmx-xmatrix3/hpp/xmatrix3–05

/**
* @brief transpose_copy
* @param m die transponiert Matrix wird auf
* das aufrufende Objekt kopiert
*/
void transpose_copy ( const T &m) { //
gsl_matrix_transpose_memcpy (gsl (), m.gsl ());
}

Listing 6.33 cppsrc/nmx-xmatrix3/hpp/xmatrix3–06

6.7 Sichten und Kopien von Reihen und Spalten


Es gibt zwei Möglichkeiten, mit Zeilen und Spalten einer Matrix zu arbeiten. Die eine
ist, eine Sicht auf eine Spalte bzw. Reihe zu erzeugen, und die zweite Kopien zu erstellen.
Während die Änderung einer Sicht auch die Matrix ändert, hat die Änderung einer Kopie
keinen Einfluss auf die Matrix.

/**
* @brief The IRowColumns struct erzeugt Sichten auf Reihen
* und Spalten einer Matrix
*/
template <class T>
struct IRowColumnsViews : public IGslContainer <T, gsl_matrix , 5> {
// benutze Elementfunktionen der Basisklasse
using IGslContainer <T, gsl_matrix , 5>:: gsl;

Listing 6.34 cppsrc/nmx-xmatrix4/hpp/xmatrix4–01

Mit den beiden folgenden Elementfunktionen werden Sichten auf ganze Reihen bzw.
Spalten erzeugt.

/**
* @brief row_view Sicht auf eine Reihe
* @param n n-te Reihe
* @return eine Sicht auf die n-te Reihe
*/
Vector :: View row_view ( size_t n) { //
256 6 Die Matrix-Klasse

return Vector :: View( gsl_matrix_row (gsl (), n));


}

Listing 6.35 cppsrc/nmx-xmatrix4/hpp/xmatrix4–02

/**
* @brief column_view erzeugt eine Sicht auf eine Spalte
* @param n n-te Spalte
* @return eine Sicht auf die n-te Spalte
*/
Vector :: View column_view ( size_t n) { return
Vector :: View( gsl_matrix_column (gsl (), n)); }

Listing 6.36 cppsrc/nmx-xmatrix4/hpp/xmatrix4–03

Eine Sicht auf die Reihe l wird ab der Spalte 1 mit insgesamt k Elementen, erzeugt.
 
a00 a01 a02 . . . a0k . . . a0n
 
 .. .. .. 
 . 
 . . 
 
 al0 al1 al2 . . . alk . . . aln 
 | {z } 
 
 View 
 .. .. .. 
 . 
 . . 
an0 an1 an2 . . . ank . . . ann

/**
* @brief sub_row_view Sicht auf Reihe einer Matrix
* @param i i-te Reihe ...
* @param offset ... beginnend ab dieser Stelle
* @param n Anzahl der Elemente
* @return Sicht auf Reihe einer Matrix
*/
Vector :: View sub_row_view ( size_t i, size_t offset , size_t n) {
return Vector :: View( gsl_matrix_subrow (gsl () , i, offset , n));
}

Listing 6.37 cppsrc/nmx-xmatrix4/hpp/xmatrix4–04

Es folgt eine Elementfunktion, die eine Sicht auf einen Teil einer Spalte erzeugt.

/**
* @brief sub_column_view Sicht auf Spalte einer Matrix
* @param i i-te Spalte ...
* @param offset ... beginnend ab dieser Stelle
* @param n Anzahl der Elemente
* @return Sciht auf Spalte der Matrix
*/
Vector :: View sub_column_view ( size_t i, size_t offset , size_t n) {
return Vector :: View( gsl_matrix_subcolumn (gsl (), i, offset , n));
}

Listing 6.38 cppsrc/nmx-xmatrix4/hpp/xmatrix4–05


6.7 Sichten und Kopien von Reihen und Spalten 257

Mit folgender Komponente werden Funktionen zum Erstellen von Kopien von Reihen
und Spalten erstellt. Zusätzlich können ganze Reihen und Spalten verändert werden.

/**
* @brief The IRowColumns struct Kopien von Spalten und Reihen erzeugen
*/
template <class T>
struct IRowColumns : public IGslContainer <T, gsl_matrix , 10> {
// benutze Elementfunktionen der Basisklasse
using IGslContainer <T, gsl_matrix , 10 >:: gsl;

Listing 6.39 cppsrc/nmx-xmatrix4/hpp/xmatrix4–06

/**
* @brief row Kopie einer Reihe erzeugen
* @param n n-te Reihe
* @return Kopie der n-ten Reihe als Vektor
*/
gsl :: Vector row( size_t n) const {
// lese die Anzahl der Spalten über die von dieser Komponenten
// abgeleitete Klasse
size_t clmns = static_cast <const T *>( this)->columns ();
// erzeuge leeren Vektor mit vorgegebener Länge
Vector vout( clmns );
// kopiere Reihe
gsl_matrix_get_row (vout.gsl () , gsl (), n);
return vout;
}

Listing 6.40 cppsrc/nmx-xmatrix4/hpp/xmatrix4–07

/**
* @brief column Kopie einer Spalte
* @param n n-te Spalte
* @return Kopie der n-ten Spalte als Vektor
*/
gsl :: Vector column ( size_t n) const {
// lese die Anzahl der Reihen über die von dieser Komponenten
// abgeleitete Klasse
size_t rows = static_cast < const T *>( this)->rows ();
// erzeuge leeren Vektor mit vorgegebener Länge
Vector vout(rows);
// kopiere Spalte
gsl_matrix_get_col (vout.gsl () , gsl (), n);
return vout;
}

Listing 6.41 cppsrc/nmx-xmatrix4/hpp/xmatrix4–08

/**
* @brief set_column Spalte wird den Elementen eines
* Vektors gleichgesetzt
* @param v Vektor
258 6 Die Matrix-Klasse

* @param n Index der Spalte


*/
void set_column ( const gsl :: Vector &v, size_t n) { //
gsl_matrix_set_col (gsl () , n, v.gsl ());
}

Listing 6.42 cppsrc/nmx-xmatrix4/hpp/xmatrix4–09

/**
* @brief set_row Reihe wird den Elementen eines
* Vektors gleichgesetzt
* @param v Vektor
* @param n Index der Spalte
*/
void set_row ( const gsl :: Vector &v, size_t n) { //
gsl_matrix_set_row (gsl () , n, v.gsl ());
}

Listing 6.43 cppsrc/nmx-xmatrix4/hpp/xmatrix4–10

6.8 Schnittstelle zur BLAS


Wir implementieren BLAS-Aufrufe für Matrix-Vektor-Rechenoperationen

y = αAx + βy, (gsl_blas_dgemv) (6.2)

und Matrix-Matrix-Rechenoperationen

C = αAB + βC, (gsl_blas_dgemm), (6.3)

wobei α, β reelle Zahlen, A, B Matrizen und x, y Vektoren sind. Ein Parameter vom Typ
CBLAS_TRANSPOSE_t legt fest, ob mit einer Matrix selbst (CblasNoTrans), der Transponierten
(CblasTrans) oder, wenn die Matrix komplex ist, ihrer Adjungierten (CblasConjTrans)
gerechnet wird. Die Klasse besteht nur aus friend-Funktionen; deswegen gibt es keinen
Grund, diese von der üblichen Basisklasse abzuleiten.

/**
* @brief The IMBlas class Schnittstelle zur BLAS mit Funktionen für
* Matrix -Vektor - und Matrix -Matrix - Multiplikation
*/
template <class M, class V>
struct IMBlas {

Listing 6.44 cppsrc/nmx-xmatrix5/hpp/xmatrix5–01

Implementierung von Gl. 6.2: Der Vektor y wird der Funktion übergeben, verändert
und als Ergebnis ausgegeben. Für diesen und alle folgenden Aufrufe gilt, dass, wenn
CblasTrans übergeben wird statt CblasNoTrans, die Matrix A transponiert wird.
6.8 Schnittstelle zur BLAS 259

/**
* @brief dgemv y = a*m + b*y
* @param t CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param a reelle Zahl
* @param m Matrix
* @param x Vektor
* @param b reelle Zahl
* @param y Vektor Eingabe und Ausgabe
*/
inline friend void dgemv ( CBLAS_TRANSPOSE_t t, //
double a,
const M &m,
const V &x,
double b,
V & yinout ) {
gsl_blas_dgemv (t, a, m.gsl () , x.gsl (), b, yinout .gsl ());
}

Listing 6.45 cppsrc/nmx-xmatrix5/hpp/xmatrix5–02

Implementierung von v = αAx + βy , wobei x, y Vektoren, α, β reelle Zahlen und A


eine Matrix ist: Dieser Aufruf unterscheidet sich vom Aufruf Listing 6.45 darin, dass das
Ergebnis nicht in den Vektor y geschrieben wird.

/**
* @brief dgemv
* @param t CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param a reelle Zahl
* @param m Matrix
* @param v Vektor
* @param b reelle Zahl
* @return Vektor Ergebnis von a m*v + b*y
*/
inline friend V amxby ( CBLAS_TRANSPOSE_t t, //
double a,
const M &m,
const V &x,
double b,
const V &y) {
Vector out(y);
gsl_blas_dgemv (t, a, m.gsl () , x.gsl (), b, out.gsl ());
return out;
}

Listing 6.46 cppsrc/nmx-xmatrix5/hpp/xmatrix5–03

Es folgt ein weiterer Spezialfall von Listing 6.45. Dieser implementiert die Rechenopera-
tion v = αAx.

/**
* @brief dgemv
* @param t CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param a reelle Zahl
* @param m Matrix
* @param x Vektor
260 6 Die Matrix-Klasse

* @return a m x
*/
inline friend V amx( CBLAS_TRANSPOSE_t t, double a, const M &m,
const V &x) { //
Vector out{ x };
gsl_blas_dgemv (t, a, m.gsl () , x.gsl (), 0, out.gsl ());
return out;
}

Listing 6.47 cppsrc/nmx-xmatrix5/hpp/xmatrix5–04

Implementierung der Matrix-Vektor-Multiplikation. Der Parameter vom Typ CBLAS


_TRANSPOSE_t ist optional, sodass die Funktion nur mit zwei Argumenten aufgerufen
werden kann.

/**
* @brief dot Matrix - Vektor Multiplikation
* @param m Matrix
* @param v Vektor
* @param type CblasNoTrans oder CblasTrans oder CblasConjTrans
* @return m v
*/
inline friend V dot( const M &m, const V &v,
CBLAS_TRANSPOSE_t type = CblasNoTrans ) { //
return amx(type , 1, m, v);
}

Listing 6.48 cppsrc/nmx-xmatrix5/hpp/xmatrix5–05

Es folgt die Gruppe von Rechenoperationen (Gl. 6.3). Auch hier werden zur einfacheren
Handhabung Spezialfälle implementiert. Wir beginnen mit der Umsetzung von Gl. 6.3,
in der die Matrix C übergeben und das Ergebnis in dieser geschrieben wird.

/**
* @brief dgemm
* @param t1 CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param t2 CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param a reelle Zahl
* @param m1 Matrix
* @param m2 Matrix
* @param b reelle Zahl
* @param minout Matrix Eingabe Ausgabe
* @return m = a m1 m2 + b m
*/
inline friend void dgemm ( CBLAS_TRANSPOSE_t t1 ,
CBLAS_TRANSPOSE_t t2 ,
double a,
const M &m1 ,
const M &m2 ,
double b,
M & minout ) {
gsl_blas_dgemm (t1 , t2 , a, m1.gsl (), m2.gsl (), b, minout .gsl ());
}

Listing 6.49 cppsrc/nmx-xmatrix5/hpp/xmatrix5–06


6.8 Schnittstelle zur BLAS 261

Es folgt eine leicht abgeänderte Version der Routine Listing 6.49. Hier wird das Ergebnis
nicht in die Matrix C geschrieben, sondern als neue Matrix D von der Elementfunktion
zurückgegeben (D = αAB + βC).

/**
* @brief dgemm
* @param t1 CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param t2 CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param a reelle Zahl
* @param m1 Matrix
* @param m2 Matrix
* @param b reelle Zahl
* @param m3 Matrix
* @return m = a m1 m2 + b m3
*/
inline friend M dgemm ( CBLAS_TRANSPOSE_t t1 , //
CBLAS_TRANSPOSE_t t2 ,
double a,
const M &m1 ,
const M &m2 ,
double b,
const M &m3) {
M out{ m3 };
gsl_blas_dgemm (t1 , t2 , a, m1.gsl (), m2.gsl (), b, out.gsl ());
return out;
}

Listing 6.50 cppsrc/nmx-xmatrix5/hpp/xmatrix5–07

Implementierung von C = αAB als weiterer Spezialfall von Listing 6.49:

/**
* @brief dgemm
* @param t1 CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param t2 CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param a reelle Zahl
* @param m1 Matrix
* @param m2 Matrix
* @return a * m1 * m2
*/
inline friend M dgemm ( CBLAS_TRANSPOSE_t t1 ,
CBLAS_TRANSPOSE_t t2 , //
double a,
const M &m1 ,
const M &m2) {
M out(m1.rows () , m2. columns ());
gsl_blas_dgemm (t1 , t2 , a, m1.gsl (), m2.gsl (), 0, out.gsl ());
return out;
}

Listing 6.51 cppsrc/nmx-xmatrix5/hpp/xmatrix5–08

Als letzte Funktion dieser Klasse implementieren wir die Matrix-Matrix-Mutliplikation


ebenfalls als Spezialfall von Listing 6.49.
262 6 Die Matrix-Klasse

/**
* @brief dot Matrix - Matrix Multiplikation
* @param m1 Matrix
* @param m2 Matrix
* @param t1 CblasNoTrans oder CblasTrans oder CblasConjTrans
* @param t2 CblasNoTrans oder CblasTrans oder CblasConjTrans
* @return Matrix
*/
inline friend M dot( const M &m1 , //
const M &m2 ,
CBLAS_TRANSPOSE_t t1 = CblasNoTrans ,
CBLAS_TRANSPOSE_t t2 = CblasNoTrans ) {
return dgemm (t1 , t2 , 1, m1 , m2);
}

Listing 6.52 cppsrc/nmx-xmatrix5/hpp/xmatrix5–09

6.9 Eigenschaften von Matrizen


Informationen zu einer Matrix, ob z.B. alle Elemente nur Null oder nur negativ bzw.
positiv sind, können mit Funktionen dieser Komponente abgefragt werden.

/**
* @brief The IMProperties struct
*/
template <class M>
struct IMProperties : public IGslContainer <M, gsl_matrix , 6> {
// benutze Elementfunktionen der Basisklasse
using IGslContainer <M, gsl_matrix , 6>:: gsl;

// Informationen bezüglich der Elemente einer Matrix


// sind alle Elemente 0?
inline bool is_null () const { //
return gsl_matrix_isnull (gsl ()) == 1;
}
// sind alle Elemente > 0 ?
inline bool is_pos () const { //
return gsl_matrix_ispos (gsl ()) == 1;
}
// sind alle Elemente < 0 ?
inline bool is_neg () const { //
return gsl_matrix_isneg (gsl ()) == 1;
}
// sind alle Elemente nicht negativ ?
inline bool is_nonneg () const { //
return gsl_matrix_isnonneg (gsl ()) == 1;
}

Listing 6.53 cppsrc/nmx-xmatrix6/hpp/xmatrix6–01

Es werden zusätzlich die Vergleichsoperatoren für Matrizen implementiert (Listing 6.54,


Listing 6.55).
6.10 Die Matrix-Klasse 263

/**
* @brief operator == prüfe ob zwei Vektoren gleich sind
* @param v1 Vektor
* @param v2 Vektor
* @return true wenn gleich
*/
inline friend bool operator ==( const M &v1 , const M &v2) {
return gsl_matrix_equal (v1.gsl (), v2.gsl ()) == 1;
}

Listing 6.54 cppsrc/nmx-xmatrix6/hpp/xmatrix6–02

/**
* @brief operator == prüfe ob zwei Vektoren gleich sind
* @param v1 Vektor
* @param v2 Vektor
* @return true wenn gleich
*/
inline friend bool operator !=( const M &m1 , const M &m2) {
return gsl_matrix_equal (m1.gsl (), m2.gsl ()) != 1;
}

Listing 6.55 cppsrc/nmx-xmatrix6/hpp/xmatrix6–03

6.10 Die Matrix-Klasse


Wir implementieren mithilfe der in diesem Kapitel eingeführten Komponenten eine Klas-
se für Sichten auf gsl-Matrizen (MatrixView) und eine Klasse zur Abbildung von gsl-
Matrizen. Jeder Aufruf einer gsl-Routine erwartet als erstes Argument einen Zeiger auf
eine gsl_matrix. Wie im Listing 6.4 zu sehen ist, verfügt eine gsl_matrix_view über eine
Kopie einer gsl_matrix. Wir implementieren eine Klasse für eine Sicht auf eine Matrix,
indem wir intern eine gsl_matrix_view speichern.

/**
* @brief The MView class Sicht auf eine gsl - Matrix
*/
class MatrixView : public IMBase < MatrixView >, public IMCalc <MatrixView >
{
private :
gsl_matrix_view _view ;

public :

Listing 6.56 cppsrc/nmx-xmatrix/hpp/xmatrix–01

Eine Instanz dieser Klasse kann entweder durch die Übergabe einer gsl_matrix_view-
Struktur oder über ein vorhandenes Feld erzeugt werden (Listing 6.57, Listing 6.58).
264 6 Die Matrix-Klasse

/**
* @brief MView Konstruktor
* @param v View
*/
MatrixView ( gsl_matrix_view v) { _view = v; }

Listing 6.57 cppsrc/nmx-xmatrix/hpp/xmatrix–02

/**
* @brief MView Konstruktor Sicht auf C-Feld
* @param nr Anzahl der Reihen
* @param nc Anzahl der Spalten
* @param f C-Feld
*/
MatrixView ( size_t nr , size_t nc , double f[]) { //
_view = gsl_matrix_view_array (f, nr , nc);
}

Listing 6.58 cppsrc/nmx-xmatrix/hpp/xmatrix–03

Mit den beiden folgenden Elementfunktionen wird ein Zeiger auf eine gsl_matrix zurück-
gegeben.

/**
* @brief gsl ermöglicht direkten Zugriff auf gsl Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline const gsl_matrix *gsl () const { return &_view. matrix ; }

Listing 6.59 cppsrc/nmx-xmatrix/hpp/xmatrix–04

/**
* @brief gsl ermöglicht direkten Zugriff auf gsl Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline gsl_matrix *gsl () { return &_view . matrix ; }
}; // MatrixView

Listing 6.60 cppsrc/nmx-xmatrix/hpp/xmatrix–05

Die Matrix-Klasse speichert einen Zeiger auf einer gsl_matrix. Damit wird auch hier eine
Zuordnung zwischen einer Instanz dieser Klasse und einer gsl-Matrix sichergestellt.

/**
* @brief The Matrix class Schnittstelle zur gsl Bibliothek
*/
class Matrix : public IMBase <Matrix >,
public IMProperties <Matrix >,
public IMCalc <Matrix >,
public IMBlas <Matrix , Vector >,
public IMExchange <Matrix >,
public IMMinMax <Matrix >,
6.10 Die Matrix-Klasse 265

public IRowColumns <Matrix >,


public IRowColumnsViews <Matrix >
{
public :
using View = MatrixView ;

private :
gsl_matrix * _matrix = nullptr ;

public :

Listing 6.61 cppsrc/nmx-xmatrix/hpp/xmatrix–06

Der Standardkonstruktor erzeugt eine leere Matrix, ohne Speicher zu reservieren.

/**
* @brief Matrix Konstruktor erzeugt eine leere Matrix ohne
* Speicherreservierung
*/
inline Matrix () {}

Listing 6.62 cppsrc/nmx-xmatrix/hpp/xmatrix–07

Eine Nullmatrix mit reserviertem Speicher für eine vorgegebene Anzahl von Reihen und
Spalten wird erzeugt.

/**
* @brief Matrix Konstruktor erzeugt eine Nullmatrix
* @param rows Anzahl der Reihen
* @param columns Anzahl der Spalten
*/
explicit inline Matrix ( size_t rows , size_t columns ) { //
_matrix = gsl_matrix_calloc (rows , columns );
}

Listing 6.63 cppsrc/nmx-xmatrix/hpp/xmatrix–08

Eine Matrix mit reserviertem Speicher für eine vorgegebene Anzahl von Reihen und
Spalten wird erzeugt. Alle Elemente werden einem vorgegebenen Wert gleichgesetzt.

/**
* @brief Matrix Konstruktor in der alle Elemente denselben
* Wert haben
* @param rows Anzahl der Reihen
* @param columns Anzahl der Spalten
* @param x alle Element erhalten den Wert x
*/
explicit inline Matrix ( size_t rows , size_t columns , double val)
: Matrix (rows , columns ) {
gsl_matrix_set_all (gsl () , val);
}

Listing 6.64 cppsrc/nmx-xmatrix/hpp/xmatrix–09


266 6 Die Matrix-Klasse

Durch die explizite Angabe der Elemente einer Matrix in Form von verschachtelten Listen
kann mit diesem Konstruktor eine Instanz der Klasse erzeugt werden. Die Anzahl der
Spalten und Reihen sowie der benötigte Speicher werden automatisch ermittelt.

/**
* @brief Matrix Konstruktor
* @param lst verschachtelte Liste von Vektoren werden zu Reihen
* der Matrix z.B. { {1 ,2 ,3} ,{4 ,5 ,6} ,{6 ,7 ,8}}
*/
inline Matrix (std :: initializer_list <std :: initializer_list <double >>
lst)
: Matrix () {
// Anzahl der verschachtelten Listen :
// für { {1 ,2 ,3} ,{4 ,5 ,6} ,{6 ,7 ,8}} ist die Anzahl 3
size_t irows = lst.size ();
size_t iclmns = 0;
// suche maximale Spaltenzahl
for (const auto &row : lst) {
iclmns = std :: max(iclmns , row.size ());
}
// erzeuge Speicher , alle Element sind 0
_matrix = gsl_matrix_calloc (irows , iclmns );
// fülle Vektor
size_t i = 0, j = 0;
// jedes l ist eine verschachtelte Liste
for (const auto &l : lst) {
for (const auto &val : l) {
// setze Element (i,j) = val
gsl_matrix_set (gsl () , i, j, val);
++j;
}
j = 0;
++i;
}
}

Listing 6.65 cppsrc/nmx-xmatrix/hpp/xmatrix–10

Es folgen der Kopier- und der move-Konstruktor.

/**
* @brief Matrix Kopierkonstruktor
* @param m Matrix Vorlage
*/
inline Matrix ( const Matrix &m)
: Matrix (m.rows () , m. columns ()) {
gsl_matrix_memcpy (gsl () , m.gsl ());
}

Listing 6.66 cppsrc/nmx-xmatrix/hpp/xmatrix–11

/**
* @brief Matrix move Konstruktor
* @param m Matrix Vorlage
*/
6.10 Die Matrix-Klasse 267

inline Matrix ( Matrix &&m)


: Matrix () {
std :: swap(_matrix , m. _matrix );
m. _matrix = nullptr ;
}

Listing 6.67 cppsrc/nmx-xmatrix/hpp/xmatrix–12

Der Destruktor gibt den reservierten Speicher wieder frei.

/**
* @brief ~ Matrix
*/
inline ~ Matrix () {
if ( _matrix != nullptr ) {
gsl_matrix_free ( _matrix );
}
}

Listing 6.68 cppsrc/nmx-xmatrix/hpp/xmatrix–13

Mit den beiden folgenden Elementfunktionen wird der Zeiger auf die intern gespeicherte
gsl_matrix-Struktur angefordert. Alle Komponenten leiten die entsprechenden Aufrufe
an diese beiden Methoden weiter.

/**
* @brief gsl_vec ermöglicht direkten Zugriff auf gsl Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline const gsl_matrix *gsl () const { return *& _matrix ; }

Listing 6.69 cppsrc/nmx-xmatrix/hpp/xmatrix–14

/**
* @brief gsl_vec ermöglicht direkten Zugriff auf gsl Funktionen
* @return Zeiger auf gsl - Struktur
*/
inline gsl_matrix *gsl () { return *& _matrix ; }

Listing 6.70 cppsrc/nmx-xmatrix/hpp/xmatrix–15

Es folgen die Zuweisungsoperatoren.

/**
* @brief operator = erzeuge Kopie
* @param m Matrix die kopiert wird
* @return Referenz auf sich selbst
*/
inline Matrix & operator =( const Matrix &m) {
if (this != &m) {
if ( _matrix != nullptr ) {
// gebe Speicher frei
gsl_matrix_free (gsl ());
268 6 Die Matrix-Klasse

}
// erzeuge neuen Speicher
_matrix = gsl_matrix_alloc (m.rows () , m. columns ());
// kopiere Matrix
gsl_matrix_memcpy (gsl () , m.gsl ());
}
return *this;
}

Listing 6.71 cppsrc/nmx-xmatrix/hpp/xmatrix–16

/**
* @brief operator = Move - Zuweisungsoperator
* @param m Matrix Vorlage
* @return Referenz auf sich selbst
*/
inline Matrix & operator =( Matrix &&m) {
if (this != &m) {
if ( _matrix != nullptr ) {
// gebe Speicher frei
gsl_matrix_free (gsl ());
_matrix = nullptr ;
}
// vertausche Zeiger auf gsl - Matrix
std :: swap(_matrix , m. _matrix );
}
return *this;
}

Listing 6.72 cppsrc/nmx-xmatrix/hpp/xmatrix–17

Mit einer statischen Funktion können Einheitsmatrizen erzeugt werden.

/**
* @brief identity Erzeugt eine NxN Einheitsmatrix
* @param N Anzahl der Reihen und Spalten
* @return NxN Einheitsmatrix
*/
inline static Matrix identity ( size_t N) {
Matrix m{ N, N };
gsl_matrix_set_identity (m.gsl ());
return m;
}

Listing 6.73 cppsrc/nmx-xmatrix/hpp/xmatrix–18

6.11 Beispiele
Alle Beispiele in diesem Abschnitt rufen zur Speicherung der Daten folgende Funktion
auf.
6.11 Beispiele 269

/**
* @brief get_output_stream
* @param name Name der Datei
* @param fmt Formatierung der Daten
*/
inline auto get_output_stream ( const char *name , Format fmt =
Output :: csv) {
auto ofs = Output :: get_stream ("x030", fmt , name);
fmt. set_defaults (ofs);
return ofs;
}

Listing 6.74 cppsrc/apps-x030/hpp/x030–04

6.11.1 Initialisierungen und Zuweisungen

Beispiel Eine 2×2-Matrix wird erzeugt. Alle Elemente erhalten den Wert 5. Die Matrix
wird mithilfe von Schleifen und dem Klammeroperator in eine Datei gespeichert.

/**
* @brief mat1 Initialisierungen und Zuweisungen
*/
inline void mat1 () {
std :: ofstream ofs = get_output_stream ( __func__ );
const size_t rows = 2, columns = 2;
// erzeuge eine 2x2 Matrix und setze alle Elemente gleich 5
gsl :: Matrix m(2, 2, 5.0);
// schreibe Matrix in Datei
for ( size_t i = 0; i < rows; i++) {
for ( size_t j = 0; j < columns ; j++) {
ofs << m(i, j) << "\t";
}
ofs << std :: endl;
}
}

Listing 6.75 cppsrc/apps-x030/hpp/x030–05

5.000 e+00 5.000 e+00


5.000 e+00 5.000 e+00

Listing 6.76 data/x030/csv–mat1.csv

Beispiel Eine 3 × 3-View wird für ein statisches C-Feld der Länge 9 erzeugt. Die Daten
werden über die View mithilfe von Schleifen und des Klammeroperators in eine Datei
geschrieben.

/**
* @brief mat2 Erzeuge View aus C-Feld
*/
270 6 Die Matrix-Klasse

inline void mat2 () {


std :: ofstream ofs = get_output_stream ( __func__ );
constexpr size_t rows = 3, columns = 3;
// die zu kopierende Daten
double f[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// Erzeuge eine 3x3 Sicht ( Matrix ) auf die Daten
gsl :: Matrix :: View m{ 3, 3, f };
// schreibe Matrix in Datei
for ( size_t i = 0; i < rows; i++) {
for ( size_t j = 0; j < columns ; j++) {
ofs << m(i, j) << "\t";
}
ofs << std :: endl;
}
}

Listing 6.77 cppsrc/apps-x030/hpp/x030–06

1.000 e+00 2.000 e+00 3.000 e+00


4.000 e+00 5.000 e+00 6.000 e+00
7.000 e+00 8.000 e+00 9.000 e+00

Listing 6.78 data/x030/csv–mat2.csv

Beispiel Eine 3 × 3-Matrix wird über eine std::initializer_list erzeugt. Die Matrix
wird in eine Datei geschrieben.

/**
* @brief mat3 Erzeuge eine Matrix aus verschachtelten
* Listen von Zahlen
*/
inline void mat3 () {
std :: ofstream ofs = get_output_stream ( __func__ );
// drei Vektoren ergeben folgende 3x3 - Matrix
gsl :: Matrix m{ { 1, 2, 3 }, { 10, 20, 30 }, { 100, 200, 300 } };
ofs << m;
}

Listing 6.79 cppsrc/apps-x030/hpp/x030–07

1.000 e+00 ,2.000e+00 ,3.000e+00


1.000 e+01 ,2.000e+01 ,3.000e+01
1.000 e+02 ,2.000e+02 ,3.000e+02

Listing 6.80 data/x030/csv–mat3.csv

Beispiel Eine 3 × 3-Matrix wird durch die Eingabe ihrer Elemente mittels einer std::
initializer_list erzeugt und in eine Datei geschrieben. Die Matrix wird kopiert und
die Kopie wird in dieselbe Datei geschrieben.
6.11 Beispiele 271

/**
* @brief mat4 Einsatz des Kopierkonstruktors
*/
inline void mat4 () {
std :: ofstream ofs = get_output_stream ( __func__ );
// erzeuge eine 3x3 Matrix
gsl :: Matrix m1{ { 1, 2, 3 }, { 10, 20, 30 }, { 100, 200, 300 } };
// schreibe Matrix in Datei
ofs << m1;
// kopiere m1
gsl :: Matrix m2{ m1 };
ofs << " ---------------------" << std :: endl << m2;
}

Listing 6.81 cppsrc/apps-x030/hpp/x030–08

1.000 e+00 ,2.000e+00 ,3.000e+00


1.000 e+01 ,2.000e+01 ,3.000e+01
1.000 e+02 ,2.000e+02 ,3.000e+02
---------------------
1.000 e+00 ,2.000e+00 ,3.000e+00
1.000 e+01 ,2.000e+01 ,3.000e+01
1.000 e+02 ,2.000e+02 ,3.000e+02

Listing 6.82 data/x030/csv–mat4.csv

Beispiel Wir erzwingen durch eine std::move-Anweisung die Übertragung des komplet-
ten Inhalts der Matrix m1 auf die Matrix m2. m1 enthält anschließend keine Daten.

/**
* @brief mat5 Einsatz des move - Konstruktors
*/
inline void mat5 () {
std :: ofstream ofs = get_output_stream ( __func__ );
// erzeuge 3x3 Matrix
gsl :: Matrix m1{ { 1, 2, 3 }, { 10, 20, 30 }, { 100, 200, 300 } };
ofs << m1;
// übertrage Inhalt der Matrix
gsl :: Matrix m2 = std :: move(m1);
ofs << " ---------------------" << std :: endl;
if (m1.empty ()) {
ofs << "m1 is empty " << std :: endl;
} else {
ofs << "m1 is not empty " << std :: endl;
}
ofs << " ---------------------" << std :: endl << m2;
}

Listing 6.83 cppsrc/apps-x030/hpp/x030–09

1.000 e+00 ,2.000e+00 ,3.000e+00


1.000 e+01 ,2.000e+01 ,3.000e+01
1.000 e+02 ,2.000e+02 ,3.000e+02
272 6 Die Matrix-Klasse

---------------------
m1 is empty
---------------------
1.000 e+00 ,2.000e+00 ,3.000e+00
1.000 e+01 ,2.000e+01 ,3.000e+01
1.000 e+02 ,2.000e+02 ,3.000e+02

Listing 6.84 data/x030/csv–mat5.csv

Beispiel Die Inhalte zweier Matrizen werden mithilfe einer temporären Matrix ver-
tauscht. Die Matrizen werden vor und nach der Vertauschung mit einer λ-Funktion in
eine Datei geschrieben.

/**
* @brief mat6 Einsatz des Zuweisungsoperators
*/
inline void mat6 () {
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed << std :: setprecision (2);

// lambda Funktion zum schreiben der Daten in eine Datei


auto print = [& ofs ]( const char *txt , const gsl :: Matrix &m) {
ofs << " --------" << txt << " ----------" << std :: endl;
ofs << m;
};

gsl :: Matrix m1{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };


print("m1", m1);
gsl :: Matrix m2{ { 10, 20, 30 }, { 40, 50, 60 }, { 70, 80, 90 } };
print("m2", m2);

// vertausche Matrizen
gsl :: Matrix tmp = m2;
m2 = m1;
m1 = tmp;

// Ausgabe
print("m1", m1);
print("m2", m2);
}

Listing 6.85 cppsrc/apps-x030/hpp/x030–10

--------m1 ----------
1.00 ,2.00 ,3.00
4.00 ,5.00 ,6.00
7.00 ,8.00 ,9.00
--------m2 ----------
10.00 ,20.00 ,30.00
40.00 ,50.00 ,60.00
70.00 ,80.00 ,90.00
--------m1 ----------
10.00 ,20.00 ,30.00
40.00 ,50.00 ,60.00
70.00 ,80.00 ,90.00
6.11 Beispiele 273

--------m2 ----------
1.00 ,2.00 ,3.00
4.00 ,5.00 ,6.00
7.00 ,8.00 ,9.00

Listing 6.86 data/x030/csv–mat6.csv

Beispiel Das folgende Beispiel imitiert die Funktionalität der std::swap-Funktion, die
genauso wie im vorherigen Beispiel die Daten zweier Matrizen vertauscht. Die letzten
Zeilen zeigen, wie derselbe Datenaustausch mit std::swap programmiert werden kann.

/**
* @brief mat7 swap - Funktion
*/
inline void mat7 () {
using Matrix = gsl :: Matrix ;
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed << std :: setprecision (2);

Matrix m1{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
ofs << " ----------m1 -----------" << std :: endl;
m1.save(ofs);

Matrix m2{ { 10, 20, 30 }, { 40, 50, 60 }, { 70, 80, 90 } };


ofs << " ----------m2 ----------" << std :: endl;
m2.save(ofs);

gsl :: Matrix tmp = std :: move(m1);


m1 = std :: move(m2);
m2 = std :: move(tmp);

ofs << " ----------m1 -----------" << std :: endl << m1;
ofs << " ----------m2 ----------" << std :: endl << m2;
std :: swap(m1 , m2);
ofs << " ----------m1 -----------" << std :: endl << m1;
ofs << " ----------m2 ----------" << std :: endl << m2;
}

Listing 6.87 cppsrc/apps-x030/hpp/x030–11

Die Ausgabe nach Ausführung der Funktion lautet:

----------m1 -----------
1.00 ,2.00 ,3.00
4.00 ,5.00 ,6.00
7.00 ,8.00 ,9.00
----------m2 ----------
10.00 ,20.00 ,30.00
40.00 ,50.00 ,60.00
70.00 ,80.00 ,90.00
----------m1 -----------
10.00 ,20.00 ,30.00
40.00 ,50.00 ,60.00
70.00 ,80.00 ,90.00
----------m2 ----------
274 6 Die Matrix-Klasse

1.00 ,2.00 ,3.00


4.00 ,5.00 ,6.00
7.00 ,8.00 ,9.00
----------m1 -----------
1.00 ,2.00 ,3.00
4.00 ,5.00 ,6.00
7.00 ,8.00 ,9.00
----------m2 ----------
10.00 ,20.00 ,30.00
40.00 ,50.00 ,60.00
70.00 ,80.00 ,90.00

Listing 6.88 data/x030/csv–mat7.csv

6.11.2 Direktes Aufrufen von GNU Scientific Library Funktionen

Beispiel Speicher wird für eine 3×3-Matrix reserviert und daraus mittels direktem Auf-
ruf einer gsl-Funktion eine Einheitsmatrix erzeugt und, wieder mittels gsl-Funktionen,
in eine Datei geschrieben. Zur Ermittlung der Anzahl der Reihen und Spalten wird auf
die gsl_matrix-Struktur (Listing 6.3) zugegriffen.

/**
* @brief mat8 direkter Zugriff auf die gsl - Routinen
*/
inline void mat8 () {
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed << std :: setprecision (2);

gsl :: Matrix m{ 3, 3 };
gsl_matrix_set_identity (m.gsl ());

for ( size_t i = 0; i < m.gsl () ->size1; i++) {


for ( size_t j = 0; j < m.gsl () ->size2; j++) {
ofs << gsl_matrix_get (m.gsl (), i, j) //
<< Output :: csv.sep ();
}
ofs << Output :: csv.lend ();
}
}

Listing 6.89 cppsrc/apps-x030/hpp/x030–12

1.00 ,0.00 ,0.00 ,


0.00 ,1.00 ,0.00 ,
0.00 ,0.00 ,1.00 ,

Listing 6.90 data/x030/csv–mat8.csv


6.11 Beispiele 275

6.11.3 Eigenschaften der Matrix, Zugriff auf Elemente und


Zuweisung von Werten

Beispiel Die Elemente einer 3 × 3-Einheitsmatrix werden verändert. Die Matrix wird
vor und nach der Änderung der Elemente in eine Datei geschrieben.

/**
* @brief mat9 verändere Elemente mithilfe des Klammeroperators
*/
inline void mat9 () {
std :: ofstream ofs = get_output_stream ( __func__ );
// erzeuge Einheitsmatrix
gsl :: Matrix m = gsl :: Matrix :: identity (3);

ofs << " ---------------------" << std :: endl << m;


// verändere Elemente
m(2, 2) = 22;
m(1, 2) = 12;
ofs << " ---------------------" << std :: endl << m;
}

Listing 6.91 cppsrc/apps-x030/hpp/x030–13

---------------------
1.000 e+00 ,0.000e+00 ,0.000e+00
0.000 e+00 ,1.000e+00 ,0.000e+00
0.000 e+00 ,0.000e+00 ,1.000e+00
---------------------
1.000 e+00 ,0.000e+00 ,0.000e+00
0.000 e+00 ,1.000e+00 ,1.200e+01
0.000 e+00 ,0.000e+00 ,2.200e+01

Listing 6.92 data/x030/csv–mat9.csv

Beispiel Eine 3 × 4-Matrix wird durch explizite Angabe der Elemente erzeugt. Es wird
jeweils der Index für das kleinste und das größte Element ermittelt. Der Rückgabewert
der Funktionen ist jedes Mal ein std::pair. Mithilfe des Structured Bindings können dem
Rückgabewert gleichzeitig eigene Variablen zugeordnet werden.

/**
* @brief mat10 suche Minimum und Maximum
*/
inline void mat10 () {
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: scientific << std :: setprecision (2);
// erzeuge Matrix
gsl :: Matrix m{ { 0, 20, 129 , 3, 300 }, //
{ 311 , 0, 40, -1, -600 },
{ -100, 3, 0, 5, 123 },
{ -10, -342, -90, 1 } };

ofs << m << " ---------------\n";


276 6 Die Matrix-Klasse

// suche Reihe und Spalte des kleinsten Elements


auto [imin , jmin] = m. min_index ();
ofs << "min:" << imin << "," << jmin << "," << m.min () << "\n";

// suche Reihe und Spalte des größten Elements


auto [imax , jmax] = m. max_index ();
ofs << "max:" << imax << "," << jmax << "," << m.max () << "\n";
}

Listing 6.93 cppsrc/apps-x030/hpp/x030–14

0.00e+00 ,2.00e+01 ,1.29e+02 ,3.00e+00 ,3.00e+02


3.11e+02 ,0.00e+00 ,4.00e+01 , -1.00e+00 , -6.00e+02
-1.00e+02 ,3.00e+00 ,0.00e+00 ,5.00e+00 ,1.23e+02
-1.00e+01 , -3.42e+02 , -9.00e+01 ,1.00e+00 ,0.00e+00
---------------
min :1 ,4 , -6.00e+02
max :1 ,0 ,3.11e+02

Listing 6.94 data/x030/csv–mat10.csv

6.11.4 Algebraische Operationen

Beispiel Es folgt ein einfaches Beispielprogramm für die Addition und Subtraktion zwei-
er Matrizen. Sowohl die Matrizen als auch die Ergebnisse werden in eine Datei geschrie-
ben.

/**
* @brief mat11 algebraische Operationen mit Matrizen
*/
inline void mat11 () {
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed << std :: setprecision (2);

gsl :: Matrix m1{ { 1, 2, 3 }, { 5, 6, 7 }, { 8, 9, 10 } };


ofs << " ---------- m ----------" << std :: endl << m1;

gsl :: Matrix m2 = m1 + m1;


ofs << " ----------- m1+m1 ---------" << std :: endl << m2;

gsl :: Matrix m3 = m1 - m1;


ofs << " ----------- m1 -m1 --------" << std :: endl << m3;
}

Listing 6.95 cppsrc/apps-x030/hpp/x030–15

---------- m ----------
1.00 ,2.00 ,3.00
5.00 ,6.00 ,7.00
8.00 ,9.00 ,10.00
----------- m1+m1 ---------
6.11 Beispiele 277

2.00 ,4.00 ,6.00


10.00 ,12.00 ,14.00
16.00 ,18.00 ,20.00
----------- m1 -m1 --------
0.00 ,0.00 ,0.00
0.00 ,0.00 ,0.00
0.00 ,0.00 ,0.00

Listing 6.96 data/x030/csv–mat11.csv

Beispiel Zu den Elementen einer Matrix wird eine Konstante addiert.

/**
* @brief mat12 algebraische Operationen
*/
inline void mat12 () {
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed << std :: setprecision (2);

// erzeuge Matrix
gsl :: Matrix m1{ { 1, 2, 3 }, { 5, 6, 7 }, { 8, 9, 10 } };

ofs << " ---------------------" << std :: endl << m1;


// kopiere Matrix
gsl :: Matrix m2 = m1;

// addiere zu allen Elemente eine Zahl


m2 += 100.;
ofs << " ---------------------" << std :: endl << m2;
}

Listing 6.97 cppsrc/apps-x030/hpp/x030–16

---------------------
1.00 ,2.00 ,3.00
5.00 ,6.00 ,7.00
8.00 ,9.00 ,10.00
---------------------
101.00 ,102.00 ,103.00
105.00 ,106.00 ,107.00
108.00 ,109.00 ,110.00

Listing 6.98 data/x030/csv–mat12.csv

Beispiel Zwei 2 × 2-Matrizen werden multipliziert.

/**
* @brief mat13 BLAS Matrix - Matrix Multiplikation
*/
inline void mat13 () {
using Matrix = gsl :: Matrix ;
std :: ofstream ofs = get_output_stream ( __func__ );
// erzeuge Matrizen
Matrix m1{ { 4, 3 }, { 1, 1 } }, m2{ { 1, -3 }, { -1, 4 } };
278 6 Die Matrix-Klasse

// berechne Matrixprodukt
Matrix m3 = dot(m1 , m2);

const char *sep = " ---------------------";


ofs << m1 << sep << "\n" << m2 << sep << "\n" << m3;
}

Listing 6.99 cppsrc/apps-x030/hpp/x030–17

4.000 e+00 ,3.000e+00


1.000 e+00 ,1.000e+00
---------------------
1.000 e+00 , -3.000e+00
-1.000e+00 ,4.000e+00
---------------------
1.000 e+00 ,0.000e+00
0.000 e+00 ,1.000e+00

Listing 6.100 data/x030/csv–mat13.csv

Beispiel Das lineare Gleichungssystem


    
1 3 −2 x 5
    
3 5 6   y  = 7  (6.4)
    
2 4 3 z 8
hat die Lösung x = −15, y = 8, z = 2. Die Koeffizientenmatrix wird mit dem Lö-
sungsvektor multipliziert und das Ergebnis mit der rechten Seite des Gleichungssystems
verglichen.

/**
* @brief mat14 BLAS Matrix -Vektor - Multiplikation
*/
inline void mat14 () {
std :: ofstream ofs = get_output_stream ( __func__ );
// Koeffizientenmatrix
gsl :: Matrix m{ { 1, 3, -2 }, { 3, 5, 6 }, { 2, 4, 3 } };

// Lösung und / rechte Seite des Gleichungssystems


gsl :: Vector x{ -15.0 , 8.0 , 2.0 }, b{ 5, 7, 8 };

const char *sep = " ---------------------";


ofs << sep << std :: endl << m << sep << std :: endl << x;
ofs << sep << std :: endl << b;

// multipliziere Koeffizientenmatrix mit Lösung


gsl :: Vector vSolution = dot(m, x);
ofs << sep << std :: endl << vSolution ;
ofs << std :: boolalpha << ( vSolution == b) << std :: endl;
}

Listing 6.101 cppsrc/apps-x030/hpp/x030–18


6.11 Beispiele 279

Wir führen das Programm aus und erhalten:

---------------------
1.000 e+00 ,3.000e+00 , -2.000e+00
3.000 e+00 ,5.000e+00 ,6.000e+00
2.000 e+00 ,4.000e+00 ,3.000e+00
---------------------
-1.500e+01 ,8.000e+00 ,2.000e+00
---------------------
5.000 e+00 ,7.000e+00 ,8.000e+00
---------------------
5.000 e+00 ,7.000e+00 ,8.000e+00
true

Listing 6.102 data/gslexamples/csv–mat14.csv

6.11.5 Euler’sche Drehmatrizen

Jede Drehmatrix kann als Produkt von drei einfachen Drehmatrizen, Dx (φx ) (Drehung
um die x-Achse), Dy (φy ) (Drehung um die y-Achse), Dz (φz ) (Drehung um die z-Achse)
geschrieben werden, wobei
   
1 0 0 cos φ 0 − sin φ
   
Dx (φ) =   
0 cos φ − sin φ, Dy (φ) =  0 1 0  ,
0 sin φ cos φ sin φ 0 cos φ
 
cos φ − sin φ 0
 
Dz (φ) =  sin φ cos φ 0

. (6.5)
0 0 1

Es wird gezeigt, dass die Drehmatrix


 
0.35355 −0.61237 −0.70710
 
D= 0.57322 0.73919 −0.35355
 (6.6)
0.73919 −0.28033 0.61237

eine Drehung um die z-Achse um 60◦ , dann eine Drehung um die y-Achse um 45◦ und
schließlich eine Drehung um die x-Achse um 30◦ darstellt.

Lösung Wir müssen zeigen, dass Dx (0.5235)Dy (0.7853)Dz (1.0471) = D ist. Die Win-
kel sind im Bogenmaß angegeben.

Quelltext Wir implementieren innerhalb einer Funktion für jede Drehmatrix eine λ-
Funktion (Gl. 6.5). Der Compiler kann den Typ des Rückgabewerts nicht erraten, da nur
verschachtelte Zahlenlisten angegeben werden. Deswegen schreiben wir den Rückgabe-
typ explizit hin. Es folgen die Matrixmultiplikation der drei Rotationsmatrizen und der
Vergleich mit der Matrix (Gl. 6.6)
280 6 Die Matrix-Klasse

/**
* @brief mat15 Euler 'sche Drehmatrizen
*/
inline void mat15 () {
using Matrix = gsl :: Matrix ;
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: setprecision (8);
// Drehung um x-Achse
auto Dx = []( double phi) -> Matrix {
return { { 1, 0, 0 }, //
{ 0, cos(phi), -sin(phi) },
{ 0, sin(phi), cos(phi) } };
};
// Drehung um y-Achse
auto Dy = []( double phi) -> Matrix {
return { { cos(phi), 0, -sin(phi) }, //
{ 0, 1, 0 },
{ sin(phi), 0, cos(phi) } };
};
// Drehung um z-Achse
auto Dz = []( double phi) -> Matrix {
return { { cos(phi), -sin(phi), 0 }, //
{ sin(phi), cos(phi), 0 },
{ 0, 0, 1 } };
};
// Drehmatrix
Matrix D = { { 0.3536 , -0.6124 , -0.7071 },
{ 0.5732 , 0.7392 , -0.3536 },
{ 0.7392 , -0.2803 , 0.6124 } };
// Winkel
const auto phix = 30.0 _deg;
const auto phiy = 45.0 _deg;
const auto phiz = 60.0 _deg;
auto DxDyDz = dot(dot(Dx(phix), Dy(phiy)), Dz(phiz));

// Prüfe ob Matrizen gleich sind ( Vorsicht bei double - Zahlen )


if (D == DxDyDz ) {
ofs << "true" << std :: endl;
} else {
ofs << " false " << std :: endl;
}
ofs << " ----------------------------" << std :: endl;
// speichere Drehmatrix und Produkt der Drehmatrizen in Datei
D.save(ofs);
ofs << " ----------------------------" << std :: endl;
DxDyDz .save(ofs);
// Berechne Differenz
Matrix D1 = DxDyDz - D;
ofs << " ----------------------------" << std :: endl;
D1.save(ofs);
}

Listing 6.103 cppsrc/apps-x030/hpp/x030–19

Der Vergleich der Elemente mit dem ==-Operator zeigt nicht das gewünschte Ergebnis an.
Dies liegt daran, dass die Elemente der Drehmatrix nicht genau genug angegeben wurden.
6.11 Beispiele 281

Die berechnete Differenz zeigt jedoch, dass die Ergebnisse genügend nahe beieinander
liegen.

false
----------------------------
3.53600000e -01 , -6.12400000e -01 , -7.07100000e -01
5.73200000e -01 ,7.39200000e -01 , -3.53600000e -01
7.39200000e -01 , -2.80300000e -01 ,6.12400000e -01
----------------------------
3.53553391e -01 , -6.12372436e -01 , -7.07106781e -01
5.73223305e -01 ,7.39198920e -01 , -3.53553391e -01
7.39198920e -01 , -2.80330086e -01 ,6.12372436e -01
----------------------------
-4.66094067e -05 ,2.75643042e -05 , -6.78118655e -06
2.33047034e -05 , -1.08025988e -06 ,4.66094067e -05
-1.08025988e -06 , -3.00858899e -05 , -2.75643042e -05

Listing 6.104 data/x030/csv–mat15.csv

6.11.6 Manipulation von Reihen und Spalten einer Matrix

Beispiel Ausgehend vom Gleichungssystem


    
2 −1 5 x1 10
    
1 1 −3 x2  = −2
    
2 4 1 x3 1

transformieren wir die erweiterte Koeffizientenmatrix in eine obere Dreiecksmatrix.


     
2 −1 5 10 2 −1 5 10 2 −1 5 10
     
1 1 −3 −2    
  −→ 0 3 −11 −14 −→ 0 3 −11 −14
2 4 1 1 0 2 7 5 0 6 21 15
   
2 −1 5 10 2 −1 5 10
   

−→ 0 3 −11 −14 −→ 0 3 −11 −14
  (6.7)

0 0 43 43 0 0 1 1

Die einzelnen Schritte im Quelltext bestehen hauptsächlich aus der Erzeugung von Kopi-
en von Reihen der Matrix. Diese Kopien sind Vektoren. Mit diesen werden algebraische
Operationen durchgeführt und wieder in die Matrix eingesetzt.

/**
* @brief mat16 Manipulation von Reihen und Spalten einer Matrix
*/
inline void mat16 () {
using Matrix = gsl :: Matrix ;
std :: ofstream ofs = get_output_stream ( __func__ );
Matrix m{ { 2, -1, 5, 10 }, { 1, 1, -3, -2 }, { 2, 4, 1, 1 } };
282 6 Die Matrix-Klasse

ofs << m;

// multipliziere Reihe 1 mit 2


auto mr1 = 2 * m.row (1);
// und ersetze Reihe 1 durch das Ergebnis
m. set_row (mr1 , 1);
// Reihe 2 - Reihe 1
auto mr2 = m.row (2) - m.row (1);
// ersetze Reihe 2 durch das Ergebnis
m. set_row (mr2 , 2);
// Reihe1 - Reihe 0
mr1 = m.row (1) - m.row (0);
// ersetze Reihe 1 durch das Ergebnis
m. set_row (mr1 , 1);

const char *sep = " ----------------------------";


ofs << sep << std :: endl << m;
mr2 = 3 * m.row (2);
m. set_row (mr2 , 2);
ofs << sep << std :: endl << m;
auto v = m.row (1);
mr2 = m.row (2) - 2 * m.row (1);
m. set_row (mr2 , 2);
ofs << sep << std :: endl << m;
mr2 = m.row (2) / 43;
m. set_row (mr2 , 2);
ofs << sep << std :: endl << m;
}

Listing 6.105 cppsrc/apps-x030/hpp/x030–20

Es folgt die Ausgabe des Programms. Die Matrizen werden in der gleichen Reihenfolge
ausgegeben wie in Gl. 6.7.

2.000 e+00 , -1.000e+00 ,5.000e+00 ,1.000e+01


1.000 e+00 ,1.000e+00 , -3.000e+00 , -2.000e+00
2.000 e+00 ,4.000e+00 ,1.000e+00 ,1.000e+00
----------------------------
2.000 e+00 , -1.000e+00 ,5.000e+00 ,1.000e+01
0.000 e+00 ,3.000e+00 , -1.100e+01 , -1.400e+01
0.000 e+00 ,2.000e+00 ,7.000e+00 ,5.000e+00
----------------------------
2.000 e+00 , -1.000e+00 ,5.000e+00 ,1.000e+01
0.000 e+00 ,3.000e+00 , -1.100e+01 , -1.400e+01
0.000 e+00 ,6.000e+00 ,2.100e+01 ,1.500e+01
----------------------------
2.000 e+00 , -1.000e+00 ,5.000e+00 ,1.000e+01
0.000 e+00 ,3.000e+00 , -1.100e+01 , -1.400e+01
0.000 e+00 ,0.000e+00 ,4.300e+01 ,4.300e+01
----------------------------
2.000 e+00 , -1.000e+00 ,5.000e+00 ,1.000e+01
0.000 e+00 ,3.000e+00 , -1.100e+01 , -1.400e+01
0.000 e+00 ,0.000e+00 ,1.000e+00 ,1.000e+00

Listing 6.106 data/x030/csv–mat16.csv


6.12 Übungsaufgaben 283

6.12 Übungsaufgaben
1. Zeigen Sie mit einem Computerprogramm, dass A2 − 4A + 5I2 = 02 gilt, wobei
     
1 2 1 0 0 0
A= , I 2 =  , 0 2 =  .
−1 3 0 1 0 0

2. Zeigen sie, dass wenn


   
1 cos x sin x  1 cos x − sin x
A= √ ,B= √
2 sin x cos x 2 sin x cos x

A2 (x) + B 2 (x) = I ist, wobei I die 2 × 2-Einheitsmatrix ist. Erstellen Sie ein Compu-
terprogramm, welches für eine Reihe von Zufallswerten für x die Gleichung bestätigt.
3. Berechnen Sie für die Matrix A und den Vektor x
   
1 13 6 7 6
   
17 15 5 9  15
   
A= , x =  
10 16 20 20 5
   
11 1 4 19 9

a) M1 = aA · x + b, wobei a = 9 und b = −3 ist,


b) M2 = aAT · x + b, wobei a = 10 und b = 0 und AT die transponierte Matrix ist,
c) M3 = A · M1 , M4 = AT · M1T .
4. Lösen Sie das Gleichungssystem durch Transformation der erweiterten Koeffizienten-
matrix in eine obere Dreiecksmatrix. Speichern Sie das Gleichungssystem und die
Lösung als CSV-Datei ab.
    
4 −2 1 −2 x1 11
    
−2 4 −2 −5 x  −16
   2  
   =  
 1 −2 4 1  x3   17 
    
2 3 6 −8 x4 −4

Prüfen Sie, ob das berechnete Ergebnis richtig ist.


5. Benutzen Sie die gsl-Funktionen gsl_matrix_submatrix, um eine Sicht auf eine Matrix
zu erzeugen.  
20 3 9 8  
  18 4 19
15 18 4 19
  Sicht  
  −−−→  12 14 14

 4 12 14 14
  19 12 10
2 19 12 10
7 Lösung von linearen
Gleichungssystemen

Übersicht
7.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
7.2 LU-Zerlegung mit der GNU Scientific Library . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
7.3 Entwurf einer C++-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
7.4 Beispielanwendungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
7.5 Beispiele aus der Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
7.6 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319

7.1 Einleitung
Viele Probleme aus der Mathematik und Physik werden durch lineare Gleichungssyste-
me beschrieben. Dabei geht es um die Berechnung von n unbekannten Größen, wenn
m lineare Gleichungen vorliegen. Wenn n = m ist, haben wir es mit einem linearen
quadratischen Gleichungssystem zu tun, welches die allgemeine Form


 a11 x1 + a12 x2 + · · · + a1n xn = b1



 a21 x1 + a22 x2 + · · · + a2n xn = b2
 ..

 .



an1 x1 + an2 x2 + · · · + ann xn = bn

hat, wobei x1 , x2 , · · · , xn die zu berechnenden Unbekannten sind. Wenn zusätzlich aij , bi


mit i, j = 1, · · · , n reelle Zahlen sind, haben wir es mit einem reellen linearen quadra-
tischen Gleichungssystem zu tun, was ein wichtiger Spezialfall ist. In Matrixform kann
das Gleichungssystem auch folgendermaßen geschrieben werden:
    
a11 a12 · · · a1n x1 b1
    
 a21 a22 · · · a2n   x2   b2 
    
 . .. . . ..   . = . 
 .. . . .   .  .
  .   . 
an1 an2 · · · ann xn bn

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_7
286 7 Lösung von linearen Gleichungssystemen

Die am häufigsten verwendete Methode zur Lösung solcher Gleichungssysteme ist der
Gauß-Algorithmus. Ziel ist, das System durch Transformationen in eine Dreiecksform zu
überführen,
 ∗     ∗
a11 a∗12 · · · a∗1n x1 b1
    
 0 a∗22 · · · a∗2n   x2   b∗2 
    
 . .. . . ..   .  =  . ,
 .. . . .   .  .
  .   . 
0 0 · · · a∗nn xn b∗n

wobei die Koeffizienten a∗ij und die Komponenten b∗i aus den ursprünglichen Koeffizienten
aij und Komponenten bi durch äquivalente Umformungen hervorgegangen sind. Diese
bestehen im Wesentlichen darin, die Gleichungen mit konstanten Faktoren zu multipli-
zieren und sie miteinander zu addieren. Zusätzlich können auch Zeilenvertauschungen
vorgenommen werden, z.B. wenn a11 = 0. Dies hat zur Folge, dass die Koeffizienten-
matrix und die rechte Seite des Systems nicht mehr die ursprünglichen sind, aber das
Gleichungssystem, welches sie repräsentieren, dasselbe bleibt. Es ist sinnvoll, durch Zei-
lenvertauschungen eine Matrix zu bilden, in der das Element a11 durch das betragsgrößte
Element der Spalte ersetzt wird, welches wir auch Pivot-Element nennen. Ist das Glei-
chungssystem in eine Dreiecksgestalt überführt worden, berechnen wir die Lösung durch
Rücksubstitution. Beginnend mit der letzten Gleichung wird xn berechnet und in die
vorletzte Gleichung eingesetzt, was zur Berechnung von xn−1 führt, usw.
Mittels des Gauß-Algorithmus lässt sich die Koeffizientenmatrix in zwei Dreiecksma-
trizen L und U zerlegen, wobei L die untere Dreiecksmatrix ist, in der alle Elemente über
der Hauptdiagonalen null und die Diagonalelemente eins sind und U die obere Dreiecks-
matrix ist, in der alle Elemente unterhalb der Hauptdiagonalen null sind. Zwischen L, U
und A gilt die Relation
P A = LU, (7.1)

wobei P eine Permutationsmatrix ist, die jede Reihenvertauschung während der Berech-
nung der beiden Dreiecksmatrizen registriert.
Eine Permutationsmatrix ist eine Einheitsmatrix, in der die Zeilen bzw. Reihen ver-
tauscht sind. Wird eine m × m-Matrix mit einer m × m-Permutationsmatrix von links
multipliziert, so hat dies zur Folge, dass eine Zeilenvertauschung bei A stattfindet. Eine
Multiplikation der Matrix mit der Permutationsmatrix von rechts, hat eine Spaltenver-
tauschung von A zur Folge. Wir wollen dies anhand eines einfachen Beispiels deutlich
machen und multiplizieren eine 3 × 3-Permutationsmatrix P von links mit einer 3 × 3-
Matrix A:     
0 0 1 1 2 3 7 8 9
    
PA =    
0 1 0 4 5 6 = 4 5 6

1 0 0 7 8 9 1 2 3
P ist aus einer 3 × 3-Einheitsmatrix hervorgegangen, bei der die erste und letzte Reihe
vertauscht wurden mit dem Resultat, dass die erste und dritte Reihe von A vertauscht
7.2 LU-Zerlegung mit der GNU Scientific Library 287

werden. Entsprechend hat die Multiplikation von rechts von A mit P eine Vertauschung
der ersten und dritten Spalte zur Folge:
    
1 2 3 0 0 1 3 2 1
    
AP =    
4 5 6 0 1 0 = 6 5 4

7 8 9 1 0 0 9 8 7

Eine Faktorisierung der Koeffizientenmatrix dieser Art führt dann folgendermaßen zur
Lösung des Gleichungssystems

Ax = b ⇒ LU x = b ⇒ L (U x) = b ⇒ Ly = b. (7.2)
| {z }
=y

Da L eine untere Dreiecksmatrix ist, kann y durch Vorwärtssubstitution berechnet wer-


den. Dies führt über U x = y ohne weiteres zur Berechnung durch Rückwärtssubstitution
von x, da U eine obere Dreiecksmatrix ist.

7.2 LU-Zerlegung mit der GNU Scientific Library


Unsere Aufgabe wird es in diesem Kapitel sein, eine Klasse zu entwerfen, die lineare
Gleichungssysteme der Form Ax = b mittels einer LU-Zerlegung löst. Zusätzlich soll
diese Klasse Determinanten von Matrizen und ihre Inversen berechnen können.
Wir analysieren ein Beispielprogramm, welches auf der Webseite der GNU Scientific
Library zu finden ist. Daraus entnehmen wir die nötigen Informationen, um die Klasse
aufzubauen. In diesem Beispiel wird das Gleichungssystem
   
0.18 0.60 0.57 0.96 x1 1
   
0.41 0.24 0.99 0.58 x2  2
   
 
    (7.3)
0.14 0.30 0.97 0.66 x3  3
   
0.51 0.13 0.19 0.85 x4 4

mittels einer LU-Zerlegung gelöst. Die Daten der Koeffizientenmatrix werden in einem
linearen Feld mit sechzehn Elementen und die der rechten Seite in einem Feld der Länge
vier gespeichert. Die Routinen der gsl benötigen jedoch als Eingabe gsl-Matrizen und
Vektoren. Es werden deswegen Views auf diese Daten erzeugt, die es erlauben, die Felder
wie eine Matrix und wie einen Vektor zu behandeln. Für den Lösungsvektor wird ein
gsl-Vektor reserviert. Es folgt die LU-Zerlegung der Koeffizientenmatrix. Die Daten der
oberen und unteren Dreieckmatrix werden in die ursprüngliche Koeffizientenmatrix ge-
schrieben. Das Gleichungssystem wird gelöst und der Lösungsvektor auf dem Bildschirm
ausgegeben. Anschließend werden die angeforderten Ressourcen freigegeben.
288 7 Lösung von linearen Gleichungssystemen

/**
* @brief lu0 LU - Zerlegung einer Matrix Originalbeispiel mit
* Kommentaren . Lösung eines linearen Gleichungssystems A x= b
* https :// www.gnu.org/ software /gsl/doc/html/ linalg .html# examples
*/
inline void lu0 () {
// die Koeffizientenmatrix A
double a_data [] = { 0.18 , 0.60 , 0.57 , 0.96 , //
0.41 , 0.24 , 0.99 , 0.58 , //
0.14 , 0.30 , 0.97 , 0.66 , //
0.51 , 0.13 , 0.19 , 0.85 };

// die rechte Seite des Gleichungssystems b


double b_data [] = { 1.0 , 2.0 , 3.0 , 4.0 };

// eine view auf das lineare Feld


// ... kann wie eine gsl Matrix behandelt werden
gsl_matrix_view m = gsl_matrix_view_array (a_data , 4, 4);

// eine view auf die rechte Seite


// kann wie ein gsl Vektor behandelt werden
gsl_vector_view b = gsl_vector_view_array (b_data , 4);

// der Lösungsvektor
gsl_vector *x = gsl_vector_alloc (4);
// speichere Informationen zur Erzeugung der
// Permutationsmatrix
gsl_permutation *p = gsl_permutation_alloc (4);

// s = (-1)^n n die Anzahl der Vertauschungen


int s;
//LU - Zerlegung der Koeffizientenmatrix . Sie enthält nach der
// Zerlegung die Daten der oberen und unteren Dreiecksmatrix
gsl_linalg_LU_decomp (&m.matrix , p, &s);
// Lösung des Gleichungssystems
gsl_linalg_LU_solve (&m.matrix , p, &b.vector , x);
// schreibe Lösungsvektor
printf ("x = \n");
gsl_vector_fprintf (stdout , x, "%g");
// gebe gsl - Resourcen frei
gsl_permutation_free (p);
gsl_vector_free (x);
}

Listing 7.1 cppsrc/apps-x038/hpp/x038–05

Es stellt sich die Frage nach der Notwendigkeit der Einführung einer Klasse, denn das
Programm ist einfach zu verstehen und leicht zu programmieren. Wir möchten als erstes
durch die Klasse die Anforderung und Freigabe der Ressourcen automatisieren und da-
mit eine potenzielle Fehlerquelle eliminieren. Wenn zusätzlich mit viel weniger Quelltext
dasselbe Resultat erreicht wird, dann haben wir zwei sehr starke Argumente, welche für
die Einführung einer Klasse sprechen.
7.3 Entwurf einer C++-Klasse 289

7.3 Entwurf einer C++-Klasse


Intern werden die Koeffizientenmatrix und die rechte Seite des Gleichungssystems sowie
eine Permutationsstruktur, aus der die Permutationsmatrix generiert wird, gespeichert.

/**
* @brief The LU_Decomposition class Lösung eines linearen
* Gleichungssystems mittels der LU - Zerlegung
*/
class LU_Decomposition
{
public :
using Matrix = gsl :: Matrix ;
using Vector = gsl :: Vector ;

private :
// linke Seite des Gleichungssystems
Matrix _left ;
// rechte Seite des Gleichungssystems
Vector _right ;
// Lösungsvektor
Vector _solution ;

public :
// Reihen - und Zeilenvertauschung (gsl)
gsl_permutation * _permutation ;
// (-1)^n Anzahl der Vertauschungen (gsl)
int _permutation_sign ;

public :

Listing 7.2 cppsrc/nmx-xlu/hpp/xlu–01

Der Standardkonstruktor erzeugt eine leere Instanz der Klasse. Die Koeffizientenmatrix
und die rechte Seite des Gleichungssystems müssen über Elementfunktionen festgelegt
werden.

/**
* @brief LUDecomposition erzeuge eine leere Instanz
*/
inline LU_Decomposition () {}

Listing 7.3 cppsrc/nmx-xlu/hpp/xlu–02

Mit diesem Konstruktor wird mit der Erzeugung einer Instanz auch das Gleichungssystem
angegeben.

/**
* @brief LUDecomposition Konstruktor
* @param m Koeffizientenmatrix
* @param v rechte Seite des Gleichungssystems
*/
inline LU_Decomposition (const Matrix &m, const Vector &v)
: _left { m }
290 7 Lösung von linearen Gleichungssystemen

, _right { v }
, _solution ( _right .size ()) {}

Listing 7.4 cppsrc/nmx-xlu/hpp/xlu–03

Für die Freigabe des Speichers sorgen die Klassen Matrix und Vektor selbst, nicht aber
die Permutationsstruktur. Der Destruktor gibt den Speicher dieser gsl-Struktur frei.

/**
* Destruktor
*/
inline ~ LU_Decomposition () {
if ( _permutation != nullptr ) {
//gsl - Struktur wird freigegeben
gsl_permutation_free ( _permutation );
}
}

Listing 7.5 cppsrc/nmx-xlu/hpp/xlu–04

Das Gleichungssystem kann mit den beiden folgenden Funktionen festgelegt werden.

/**
* @brief set_left
* @param m die Koeffizientenmatrix
*/
inline void set_left ( const Matrix &m) { _left = m; }

Listing 7.6 cppsrc/nmx-xlu/hpp/xlu–05

/**
* @brief set_right
* @param v die rechte Seite des Gleichungssystems
*/
inline void set_right ( const Vector &v) { _right = v; }

Listing 7.7 cppsrc/nmx-xlu/hpp/xlu–06

Ein lesender Zugriff auf die Koeffizientenmatrix und die rechte Seite des Gleichungssys-
tems ist über die Methoden (Listing 7.8) und (Listing 7.9) möglich. Die Koeffizienten-
matrix enthält nach der LU-Zerlegung nicht mehr die ursprünglichen Daten, sondern die
beiden Dreiecksmatrizen.

/**
* @brief get_left
* @return Referenz auf die Koeffizientenmatrix
*/
inline const Matrix & get_left () const { return _left; }

Listing 7.8 cppsrc/nmx-xlu/hpp/xlu–07


7.3 Entwurf einer C++-Klasse 291

/**
* @brief get_right
* @return Referenz auf die rechte Seite des
* Gleichungssystems
*/
const Vector & get_right () const { return _right ; }

Listing 7.9 cppsrc/nmx-xlu/hpp/xlu–08

Der überladene Klammeroperator liefert die Lösung an der Stelle i = 0, 1, 2, . . . im Lö-


sungsvektor.

/**
* @brief operator () Zugriff auf die Elemente des Lösungsvektors
* @param idx Index
* @return Lösung an der Stelle idx
*/
inline double operator ()( size_t idx) const { //
return _solution [idx ];
}

Listing 7.10 cppsrc/nmx-xlu/hpp/xlu–09

Auf die einzelnen Elemente der Koeffizientenmatrix wird mit dieser Version des Klam-
meroperators lesend zugegriffen.

/**
* @brief operator () Zugriff auf die Koeffizientenmatrix
* @param i Index Reihe
* @param j Index Spalte
* @return das Element i,j 0 ,1 ,2 ,3 ,...
*/
inline double operator ()( size_t i, size_t j) const { //
return _left (i, j);
}

Listing 7.11 cppsrc/nmx-xlu/hpp/xlu–10

Eine LU-Zerlegung der Koeffizientenmatrix kann auch unabhängig von der Lösung des
Gleichungssystems durchgeführt werden.

/**
* @brief decompose Zerlegung der Koeffizientenmatrix
* in obere und untere Dreiecksmatrix
*/
inline void decompose () {
_permutation = gsl_permutation_alloc (std :: min(_left.rows (), //
_left. columns ()));
gsl_linalg_LU_decomp ( _left .gsl (),
_permutation , //
& _permutation_sign );
}

Listing 7.12 cppsrc/nmx-xlu/hpp/xlu–11


292 7 Lösung von linearen Gleichungssystemen

Ein Gleichungssystem, dessen Koeffizientenmatrix und rechte Seite bereits festgelegt wur-
den, wird mit dieser Elementfunktion gelöst.

/**
* @brief solve Berechnung des Lösungsvektors
*/
inline void solve () {
// zerlege Koeffizientenmatrix in obere und untere Dreiecksmatrix
decompose ();
// Lösungsvektor muss die gleiche Länge haben wie die
// rechte Seite
if ( _solution .size () != _right .size ()) {
_solution = Vector ( _right .size ());
}
// löse das Gleichungssystem
gsl_linalg_LU_solve ( _left .gsl () , //
_permutation ,
_right .gsl () ,
_solution .gsl ());
}

Listing 7.13 cppsrc/nmx-xlu/hpp/xlu–12

Das Gleichungssystem kann in einem Schritt mit einer Elementfunktion festgelegt und
gelöst werden.

/**
* @brief solve Berechnung des Lösungsvektors eines
* Gleichungssystems
* @param m die Koeffizientenmatrix
* @param v rechte Seite des Gleichungssystems
*/
inline void solve ( const Matrix &m, const Vector &v) {
set_left (m);
set_right (v);
solve ();
}

Listing 7.14 cppsrc/nmx-xlu/hpp/xlu–13

Eine Referenz auf den Lösungsvektor erhalten wir mit der nächsten Funktion. Es versteht
sich, dass kein schreibender Zugriff erlaubt ist, da sonst die Lösung geändert werden
könnte.

/**
* @brief solution
* @return Referenz auf den Lösungsvektor
*/
inline const Vector & solution () const { return _solution ; }

Listing 7.15 cppsrc/nmx-xlu/hpp/xlu–14


7.3 Entwurf einer C++-Klasse 293

Eine Kopie der unteren und der oberen Dreiecksmatrix erhalten wir mit zwei weiteren
Elementfunktionen (Listing 7.16 und Listing 7.17). Auch hier gilt, dass zuvor eine LU-
Zerlegung durchgeführt werden muss.

/**
* @brief lower_matrix
* @return Kopie der unteren Dreiecksmatrix
*/
inline Matrix lower_matrix () const {
// reserviere Speicher fur das Ergebnis
Matrix mout( _left .rows () , _left . columns ());

for ( size_t i = 0; i < _left .rows (); i++) {


for ( size_t j = 0; j < _left. columns (); j++) {
if (i < j) {
mout(i, j) = 0;
} else if (i == j) {
mout(i, j) = 1.0; // diagonale
} else
mout(i, j) = _left (i, j);
}
}

return mout;
}

Listing 7.16 cppsrc/nmx-xlu/hpp/xlu–15

/**
* @brief upper_matrix
* @return Kopie der oberen Dreiecksmatrix
*/
inline Matrix upper_matrix () const {
// reserviere Speicher fur das Ergebnis
Matrix mout( _left .rows () , _left . columns ());

for ( size_t i = 0; i < _left .rows (); i++) {


for ( size_t j = 0; j < _left. columns (); j++) {
if (i > j) {
mout(i, j) = 0;
} else
mout(i, j) = _left (i, j);
}
}

return mout;
}

Listing 7.17 cppsrc/nmx-xlu/hpp/xlu–16

Mithilfe der gespeicherten Permutation wird die Permutationsmatrix generiert (Listing


7.18). Sie hat dieselbe Anzahl von Reihen und Spalten wie die Koeffizientenmatrix. Wir
iterieren über alle Reihen und erhalten über die Permutation die Position des Elements,
welches gleich eins ist.
294 7 Lösung von linearen Gleichungssystemen

/**
* @brief permutation_matrix
* @return Permutationsmatrix
*/
inline Matrix permutation_matrix () {
// Anzahl der Elemente der Permutation
size_t psize = static_cast <size_t >( _permutation ->size);

Matrix pMatrix ( _left .rows () , _left. columns ());


for ( size_t idx = 0; idx < psize; idx ++) {
// für jede Reihe gebe die Position des Elements ,
// welches 1 ist
size_t pIdx = gsl_permutation_get ( _permutation , idx);
pMatrix (idx , pIdx) = 1;
}
return pMatrix ;
}

Listing 7.18 cppsrc/nmx-xlu/hpp/xlu–17

Wir führen eine weitere Elementfunktion der Klasse ein, welche die Elemente einer Per-
mutationsmatrix als Vektor ausgibt.

/**
* @brief permutation_vector Permutationsvektor wird aus
* Permutationstruktur generiert
* @return Permutationsvektor
*/
inline Vector permutation_vector () const {
// erzeuge Vektor für die Elemente der Permutation
gsl :: Vector vec( _permutation ->size);

for ( size_t idx = 0; idx < vec.size (); idx ++) {


vec[idx] = gsl_permutation_get ( _permutation , idx);
}
return vec;
}

Listing 7.19 cppsrc/nmx-xlu/hpp/xlu–20

Die Determinante einer Matrix wird mit einer statischen Funktion berechnet. Sie benötigt
deswegen keine Instanz der Klasse, um aufgerufen zu werden (Listing 7.20). Dasselbe gilt
auch für die Berechnung einer inversen Koeffizientenmatrix (Listing 7.21).

/**
* @brief det Berechnung der Determinante
* @param m die Matrix
* @return die Determinante
*/
inline static double det( const Matrix &m) {
LU_Decomposition lu;
lu._left = m;
//LU - Zerlegung der Matrix
lu. decompose ();
// Berechnung der Determinante
7.4 Beispielanwendungen 295

return gsl_linalg_LU_det (lu. _left.gsl () , lu. _permutation_sign );


}

Listing 7.20 cppsrc/nmx-xlu/hpp/xlu–18

/**
* @brief inverse Berechnung der Inversen einer Matrix
* @param m die Matrix
* @return die inverse Matrix
*/
inline static Matrix inverse ( const Matrix &m) {
// reserviere Speicher für die Inverse Matrix
Matrix mInverse (m.rows () , m. columns ());
LU_Decomposition lu;
lu._left = m;
//LU - Zerlegung der Matrix
lu. decompose ();
// Berechnung der Inversen Matrix
gsl_linalg_LU_invert (lu. get_left ().gsl (), //
lu. _permutation ,
mInverse .gsl ());
return mInverse ;
}

Listing 7.21 cppsrc/nmx-xlu/hpp/xlu–19

7.4 Beispielanwendungen
Für die Beispiele in diesem Abschnitt definieren wir folgende Synonyme.

using LU = gsl :: LU_Decomposition ;


using Matrix = LU :: Matrix ;
using Vector = LU :: Vector ;

Listing 7.22 cppsrc/apps-x038/hpp/x038–00

Ausgabeströme werden mit einer Hilfsfunktion (Listing 7.23) generiert.

/**
* @brief get_output_stream generiere Ausgabestrom
* @param name Dateiname
* @param fmt Formatierungsanweisung
*/
inline auto get_output_stream ( const char *name , Format fmt =
Output :: csv) {
auto ofs = Output :: get_stream ("x038", fmt , name);
ofs << std :: fixed << std :: setprecision (2);
return ofs;
}

Listing 7.23 cppsrc/apps-x038/hpp/x038–01


296 7 Lösung von linearen Gleichungssystemen

7.4.1 Lösung eines 3 × 3-linearen Gleichungssystems mit der


LU-Zerlegung

Wir wollen das lineare Gleichungssystem



 3.00x1 − 0.10x2 − 0.20x3 = 7.85

0.10x1 + 7.00x2 − 0.30x3 = −19.30 (7.4)


0.30x1 − 0.20x2 + 10.00x3 = 71.40

lösen und schreiben es in Matrixform


    
3.00 −0.10 −0.20 x1 7.85
    
0.10 7.00 −0.30 x1  = −19.30 . (7.5)
    
0.30 −0.20 10.00 x3 71.40

Es hat die Lösung


( )
xT = 3.00 −2.50 7.00 . (7.6)

Die LU-Zerlegung der Matrix lautet


 
3.00 −0.10 −0.20
 
0.03 7.00 −0.29, (7.7)
 
0.10 −0.03 10.01

wobei beide Dreiecksmatrizen in dieser Matrix enthalten sind.

Quelltext Die Struktur des Programms wird durch Nutzung der Klassen für Vekto-
ren und Matrizen sowie der LU-Zerlegung einfach. Selbst die Ausgabe erfolgt ohne die
Programmierung von Schleifen.

/**
* @brief lu1 Lösung eines linearen Gleichungssystems durch LU - Zerlegung
*/
inline void lu1 () {
// öffne Ausgabestrom
std :: ofstream ofs = get_output_stream ( __func__ );

// Koeffizientenmatrix
Matrix left{ { { 3, -0.1, -0.2 }, //
{ 0.1 , 7, -0.3 },
{ 0.3 , -0.2, 10 } } };

// rechte Seite des Gleichungssystems


Vector right { 7.85 , -19.3 , 71.4 };

// Berechnung der Lösung


LU lusolver { left , right };
lusolver .solve ();
const char *sep = " ---------------------\n";
7.4 Beispielanwendungen 297

auto mupper = lusolver . upper_matrix ();


auto mlower = lusolver . lower_matrix ();
auto pmatrix = lusolver . permutation_matrix ();
auto pdotleft = dot(pmatrix , left);
auto ldotu = dot( lusolver . lower_matrix (), lusolver . upper_matrix ());

// schreibe Ergebnisse in Datei


ofs << left << sep << right << sep << lusolver . solution ();
ofs << sep << lusolver . get_left ();
ofs << sep << mupper << sep << mlower << sep << pmatrix ;
ofs << sep << pdotleft << sep << ldotu;
}

Listing 7.24 cppsrc/apps-x038/hpp/x038–02

Daten Wir erhalten der Reihe nach die linke, die rechte Seite und den Lösungsvektor
von Gl. 7.5.

3.00 , -0.10 , -0.20


0.10 ,7.00 , -0.30
0.30 , -0.20 ,10.00
---------------------
7.85 , -19.30 ,71.40
---------------------
3.00 , -2.50 ,7.00

Listing 7.25 data/x038/csv–lu1.csv

Es folgen die untere und die obere Dreiecksmatrix zusammengefasst als eine Matrix und
aufgeteilt in zwei Matrizen.

3.00 , -0.10 , -0.20


0.03 ,7.00 , -0.29
0.10 , -0.03 ,10.01
---------------------
3.00 , -0.10 , -0.20
0.00 ,7.00 , -0.29
0.00 ,0.00 ,10.01
---------------------
1.00 ,0.00 ,0.00
0.03 ,1.00 ,0.00
0.10 , -0.03 ,1.00

Listing 7.26 data/x038/csv–lu1.csv

Schließlich schreibt das Programm die Permutationsmatrix P und die Matrixprodukte


P A und LU in dieselbe Datei.

1.00 ,0.00 ,0.00


0.00 ,1.00 ,0.00
0.00 ,0.00 ,1.00
---------------------
3.00 , -0.10 , -0.20
0.10 ,7.00 , -0.30
298 7 Lösung von linearen Gleichungssystemen

0.30 , -0.20 ,10.00


---------------------
3.00 , -0.10 , -0.20
0.10 ,7.00 , -0.30
0.30 , -0.20 ,10.00

Listing 7.27 data/x038/csv–lu1.csv

7.4.2 Lösung eines 3 × 3-linearen Gleichungssystems mit der


Cramer’schen Regel

Ist die Determinante der Koeffizientenmatrix ungleich null, so kann ein Gleichungssystem
mit der Cramer’schen Regel gelöst werden. Die einzelnen Lösungen werden mit der Formel

det(Ai )
xi = , i = 0, 1, 2 (7.8)
det(A)

berechnet, wobei A die Koeffizientenmatrix ist und jedes der Ai , i = 0, 1, 2 durch den
Austausch der i-ten Spalte der Koeffizientenmatrix mit dem Vektor der rechten Seite
entsteht.

Quelltext In der GNU Scientific Library ist die Cramer’sche Regel nicht implemen-
tiert, da alternative Methoden existieren, die weniger rechenintensiv sind. Wir wollen
dennoch das Gleichungssystem Gl. 7.5 mit dieser Methode lösen, um die Berechnung von
Determinanten mittels unserer Klasse zu demonstrieren.

/**
* @brief lu2 Lösung eines Gleichungssystems mit der Cramer 'schen Regel
*/
inline void lu2 () {
// die Cramer 'sche Regel für x_i als lambda - Funktion
auto cramer_i = []( size_t i, const Matrix &m, const Vector &v) {
// Kopiere linke Seite des GL - Systems
auto m1 = m;
// ersetze i-te Spalte durch rechte Seite
m1. set_column (v, i);
// Determinante Nenner
auto det = LU :: det(m);
// Determinante Zähler
auto det1 = LU :: det(m1);
// Lösung x_i
return det1 / det;
};

std :: ofstream ofs = get_output_stream ( __func__ );


Matrix left{ { { 3, -0.1, -0.2 }, //
{ 0.1 , 7, -0.3 },
{ 0.3 , -0.2, 10 } } };
Vector right { 7.85 , -19.3 , 71.4 };

// der Lösungsvektor
const size_t N = right .size ();
7.4 Beispielanwendungen 299

Vector solution (N);


for ( size_t i = 0; i < N; i++) {
solution [i] = cramer_i (i, left , right );
}
const char *sep = " ---------------------\n";
// Ausgabe
ofs << sep << left << sep << right << sep << solution ;
}

Listing 7.28 cppsrc/apps-x038/hpp/x038–03

Daten Wie erwartet, gibt das Programm, neben der Koeffizientenmatrix und der rech-
ten Seite des Gleichungssystems, den korrekten Lösungsvektor aus.

---------------------
3.00 , -0.10 , -0.20
0.10 ,7.00 , -0.30
0.30 , -0.20 ,10.00
---------------------
7.85 , -19.30 ,71.40
---------------------
3.00 , -2.50 ,7.00

Listing 7.29 data/x038/csv–lu2.csv

7.4.3 Lösung eines 3 × 3-linearen Gleichungssystems mit der


inversen Matrix

Eine weitere Möglichkeit ein Gleichungssystem zu lösen ist, von links mit der inversen
Koeffizientenmatrix, vorausgesetzt sie existiert, zu multiplizieren.

Ax = b ⇒ A−1 Ax = A−1 b ⇒ x = A−1 b, (7.9)

wobei A−1 die inverse von A ist.

Quelltext und Daten Die Berechnung der inversen Matrix gehört zum Funktionsum-
fang unserer Klasse und ist daher einfach zu programmieren. Das Programm wird neben
der Lösung von Gl. 7.5 auch die inverse Koeffizientenmatrix und das Ergebnis der Mul-
tiplikation A−1 A ausgeben (Listing 7.31).

/**
* @brief lu3 Lösung eines Gleichungssystems mittels der inversen Matrix
*/
inline void lu3 () {
// erzeuge Ausgabestrom
std :: ofstream ofs = get_output_stream ( __func__ );

// Koeffizientenmatrix
Matrix left{ { { 3, -0.1, -0.2 }, //
{ 0.1 , 7, -0.3 },
300 7 Lösung von linearen Gleichungssystemen

{ 0.3 , -0.2, 10 } } };

// die rechte Seite des Gleichungssystems


Vector right { 7.85 , -19.3 , 71.4 };

// Berechnung der inversen Koeffizientenmatrix


Matrix leftinverse = LU :: inverse (left);

// Lösung
Vector solution = dot( leftinverse , right);

const char *sep = " ---------------------\n";


// Ausgabe
ofs << sep << left << sep << right ;
ofs << sep << leftinverse << sep << solution ;

auto m = dot( leftinverse , left);


ofs << sep << std :: endl << m;
}

Listing 7.30 cppsrc/apps-x038/hpp/x038–04

---------------------
3.00 , -0.10 , -0.20
0.10 ,7.00 , -0.30
0.30 , -0.20 ,10.00
---------------------
7.85 , -19.30 ,71.40
---------------------
0.33 ,0.00 ,0.01
-0.01 ,0.14 ,0.00
-0.01 ,0.00 ,0.10
---------------------
3.00 , -2.50 ,7.00
---------------------
1.00 , -0.00 ,0.00
0.00 ,1.00 ,0.00
-0.00 ,0.00 ,1.00

Listing 7.31 data/x038/csv–lu3.csv

7.5 Beispiele aus der Physik


7.5.1 Masse an drei Seilen

Eine Kugel mit der Masse m ist, wie in Abb. 7.1a gezeigt, an Seilen mit vernachlässigbarer
Masse aufgehängt. Das System befindet sich im Gleichgewicht.

1. Leiten Sie die Formeln für die Seilspannungen her.


2. Berechnen Sie mit einem Computerprogramm alle Seilspannungen für die Winkel
φ = 10◦ , 20◦ . . . 80◦ und Massen m = 2 kg, m = 4 kg, m = 6 kg.
7.5 Beispiele aus der Physik 301

T2
T2y
ϕ
ϕ A
T2x T3
T1
A
T1

ey m B
ex m B
w
(a)
(b)
Abb. 7.1: (a) Masse im Gleichgewicht, befestigt an einem System von drei Seilen (b)
Kräfte, die auf die Masse und auf den Punkt A wirken.

Lösung

Die Gleichgewichtsbedingungen Die Masse m befindet sich im Gleichgewicht, d.h., die


Summe aller Kräfte ist null:
T⃗1′ + w
⃗ =0 (7.10)
oder
T1′ ⃗ey − mg⃗ey = 0. (7.11)

Die Kräfte an den Enden von masselosen Seilen sind vom Betrag gleich und haben ent-
gegengesetzte Richtungen. Somit gilt für das Seil AB:

|T⃗1 | = |T⃗1′ | (7.12)

Für die Summe aller Kräfte am Punkt A gilt:

T⃗1 + T⃗2 + T⃗3 = 0 (7.13)

oder { {
T3⃗ex − T2 cos φ⃗ex = 0 T3 − T2 cos φ = 0
⇒ (7.14)
−T1⃗ey + T2 sin φ⃗ey = 0 −T1 + T2 sin φ = 0.
Fassen wir alle Ergebnisse zusammen, erhalten wir das Gleichungssystem
{
−T2 cos φ + T3 = 0 (7.15a)
T2 sin φ + 0 = mg. (7.15b)
302 7 Lösung von linearen Gleichungssystemen

Die Berechnung der Seilspannungen Zur Lösung des Gleichungssystems wenden wir
die Cramer’sche Regel an:
− cos φ 1
D= = − sin φ
sin φ 0

und
0 1 − cos φ 0
D1 = = −mg, D2 = = −mg cos φ
mg 0 sin φ mg

Damit folgt für die drei Seilspannungen:




 T1 = mg (7.16a)



 D1 mg
T2 = = (7.16b)
 D sin φ



 D 2
 T3 = = mg cot φ (7.16c)
D

Das Computerprogramm

Kurzbeschreibung Die Beschreibung des Systems wird über eine Modellklasse und die
Berechnung der Seilspannungen über zwei abgeleitete Rechenmodelle realisiert. Wir ha-
ben so die Freiheit, mit Methoden unserer Wahl, unter Verwendung derselben Modell-
klasse, die Ergebnisse zu berechnen.

Modellklasse
– Eingabe: Winkel φ und die Masse m (Abb. 7.1)
– Ausgabe: Die Seilspannungen T1 , T2 und T3

Erste abgeleitete Klasse (Rechenmodell): Berechnung der Seilspannungen über die


Lösung des Gleichungssystems zusammengesetzt aus Gl. 7.11 und Gl. 7.14

Zweite abgeleitete Klasse (Rechenmodell): Berechnung der Seilspannungen mithilfe


der hergeleiteten Formeln (Gl. 7.16)

Quelltext Die Modellklasse liest und speichert über den Konstruktor (Listing 7.34) die
Programmparameter m und φ. Das Gewicht der Masse wird innerhalb des Konstruktors
berechnet und kann über eine Elementfunktion (Listing 7.35) gelesen werden. Auf die
berechneten Seilspannungen wird mithilfe der folgenden Aufzählung zugegriffen.

/**
* @brief The Idx enum Zugriff auf die Seilspannungen
*/
enum class Idx { T1 , T2 , T3 };

Listing 7.32 cppsrc/apps-x1800/hpp/x1800–01


7.5 Beispiele aus der Physik 303

/**
* @brief The X1800 class Masse im Gleichgewicht befestigt an
* einem System mit drei Seilen
*/
class X1800 : public XModel , public CResult <3, Idx >
{
private :
// das Gewicht der Masse
double _weight = 0;

public :
const double mass; // Masse der Kugel
const double phi; // Winkel

Listing 7.33 cppsrc/apps-x1800/hpp/x1800–02

/**
* @brief X1800 Konstruktor
* @param xmass Masse
* @param xphi Winkel
*/
inline X1800 ( double mass , double phi)
: XModel ( __func__ )
, mass{ mass }
, phi{ phi } {
Check :: all(nmx_msg , { mass > 0, phi > 0, phi < 90 });
_weight = mass * gravitation :: Earth ::g;
}

Listing 7.34 cppsrc/apps-x1800/hpp/x1800–03

/**
* @brief weight Zugriff auf das intern gespeicherte Gewicht
* @return das Gewicht
*/
inline double weight () const { return _weight ; }

Listing 7.35 cppsrc/apps-x1800/hpp/x1800–04

Die Seilspannungen können per Index (Listing 7.32) gelesen werden. Mit Initialisierung
einer Instanz sind noch keine gültigen Werte vorhanden.

/**
* @brief tension lese Seilspannung über Index ( Aufzählung )
* @param idx der Index ( Element der Aufzählung )
* @return die Seilspannung
*/
inline double tension (Idx idx) const { return get(idx); }
}; // X1800

Listing 7.36 cppsrc/apps-x1800/hpp/x1800–05


304 7 Lösung von linearen Gleichungssystemen

Es folgt die Implementierung des ersten Rechenmodells. Die Realisierung reduziert sich
auf die Programmierung einer einzigen Elementfunktion, da die Konstruktoren durch
eine using-Anweisung von der Basisklasse geerbt werden. Wir schreiben Gl. 7.11, Gl.
7.12 und Gl. 7.14 als Matrixgleichung und lösen diese mittels LU-Zerlegung.
    
1 0 0 T1 mg
    
 0 − cos φ 1 T2  =  0  (7.17)
    
−1 sin φ 0 T3 0

/**
* @brief The CModel1 struct Berechnung der Seilspannungen durch
* direkte Lösung des Gleichungssystems
*/
struct CModel1 : public X1800 {
// erbe Konstruktoren der Basisklasse
using X1800 :: X1800 ;

// Berechnung der Seilspannungen


inline void apply () {
using LU = gsl :: LU_Decomposition ;
LU lusolver ;
LU:: Matrix m = { { 1, 0, 0 }, //
{ 0, -cos(phi), 1 },
{ -1, sin(phi), 0 } };
lusolver . set_left (m);
lusolver . set_right ({ weight () , 0, 0 });
lusolver . solve ();
// übergebe direkt ganzen Vektor
set_result ( lusolver . solution ());
}
}; // CModel1

Listing 7.37 cppsrc/apps-x1800/hpp/x1800–06

Das zweite Rechenmodell implementiert die Formeln Gl. 7.16a, Gl. 7.16b und Gl. 7.16c.
Auch hier handelt es sich um eine einfache Struktur, welche eine Elementfunktion imple-
mentiert und die gesamte Funktionalität der Basisklasse erbt.

/**
* @brief The CModel2 struct Berechnung der Seilspannungen
* über die theoretisch hergeleitete Formeln
*/
struct CModel2 : public X1800 {
// erbe Konstruktoren der Basisklasse
using X1800 :: X1800 ;

// Berechnung der Seilspannungen


inline void apply () {
const auto w = weight ();
set_result ({ w, w / sin(phi), w / tan(phi) });
}
}; // CModel2

Listing 7.38 cppsrc/apps-x1800/hpp/x1800–07


7.5 Beispiele aus der Physik 305

Daten Für eine Reihe von Massen m werden, für alle Winkel φ, die Seilspannungen
mit den zwei Rechenmodellen berechnet und in einer gemeinsamen Datei abgespeichert
(Tab. 7.1, Abb. 7.2).

/**
* @brief run Berechnung von Beispieldaten mit zwei Rechenmodellen
*/
inline void run () {
using Data = nmx :: Data <7 >;
for (auto m : { 2.0 , 4.0 , 6.0 }) {
Data data;
for ( double phi = 10.0 _deg; phi < 90.0 _deg; phi += 10.0 _deg) {
// initialisiere zwei Rechenmodelle
CModel1 xobj1 (m, phi);
CModel2 xobj2 (m, phi);
// Lösung über LU - Zerlegung
xobj1 . apply ();
// Lösung über hergeleitete Formeln
xobj2 . apply ();
// füge Ergebnisse einer gemeinsamen Zahlentabelle hinzu
data += { Math :: to_degrees (phi), //
xobj1 . tension (Idx ::T1), xobj1. tension (Idx ::T2), //
xobj1 . tension (Idx ::T3), xobj2. tension (Idx ::T1),
xobj2 . tension (Idx ::T2), xobj2. tension (Idx ::T3) };
}
// speichere Daten im CSV - und LaTeX - Format . Dateinamenskonflikte
// werden durch das Hinzufügen der Masse m vermieden
X1800 :: save(data , Output :: plot , m);
X1800 :: save(data , Output :: latex , m);
}
}

Listing 7.39 cppsrc/apps-x1800/hpp/x1800–08

Tab. 7.1: Die Beträge der Seilspannungen T1 , T2 und T3 , berechnet mittels LU-
Zerlegung, und den Formeln aus der Aufgabenlösung T1c , T2c und T3c in Abhängigkeit
vom Winkel φ für m = 2 kg
φ [deg] T1 [N] T2 [N] T3 [N] T1c [N] T2c [N] T3c [N]
1.000e+01 1.961e+01 1.129e+02 1.112e+02 1.961e+01 1.129e+02 1.112e+02
2.000e+01 1.961e+01 5.735e+01 5.389e+01 1.961e+01 5.735e+01 5.389e+01
3.000e+01 1.961e+01 3.923e+01 3.397e+01 1.961e+01 3.923e+01 3.397e+01
4.000e+01 1.961e+01 3.051e+01 2.337e+01 1.961e+01 3.051e+01 2.337e+01
5.000e+01 1.961e+01 2.560e+01 1.646e+01 1.961e+01 2.560e+01 1.646e+01
6.000e+01 1.961e+01 2.265e+01 1.132e+01 1.961e+01 2.265e+01 1.132e+01
7.000e+01 1.961e+01 2.087e+01 7.139e+00 1.961e+01 2.087e+01 7.139e+00
8.000e+01 1.961e+01 1.992e+01 3.458e+00 1.961e+01 1.992e+01 3.458e+00
306 7 Lösung von linearen Gleichungssystemen

m = 2kg m = 2kg
m = 4kg 300 m = 4kg
300
m = 6kg m = 6kg

200
T2 [N ]

T3 [N ]
200

100
100

0
0
20 40 60 80 20 40 60 80
φ[deg] φ[deg]

(a) (b)
Abb. 7.2: Die Seilspannungen (a) T2 und (b) T3 als Funktionen des Winkels φ für
Massen m = 2 kg, m = 4 kg und m = 6 kg

7.5.2 Massen befestigt an Seilen und Haftreibung

Das System (Abb. 7.3a) befindet sich im Gleichgewicht. Der maximale Haftreibungsko-
effizient zwischen m2 und der starren Unterlage U ist µ. Die Masse der Seile sei vernach-
lässigbar.

φ φ

N T⃗3
m2 ⃗
R T⃗2 T⃗2′ A
A
T⃗1
w
⃗2
U T⃗1′

m1 w
⃗1
ey ey
ex ex

(a) (b)

Abb. 7.3: (a) Das System im Gleichgewicht (b) Auf die Masse m2 wirken die Haftreibung
⃗ das Gewicht w
R, ⃗ 2 , die Seilspannung T⃗2 und die Normalkraft N
⃗ . Auf die Masse m1 wirken

die Seilspannung T⃗1 und das Gewicht w ⃗ 1.

1. Wie groß darf die Masse m1 werden, damit das System bei vorgegebener Masse m2
und Haftreibungskoeffizienten µ sich noch im Gleichgewicht befindet?
2. Berechnen Sie für eine Masse m2 = 1 kg und µ = 0.1, 0.2, 0.3, 0.4 sowie φ =
10◦ , 30◦ , 40◦ den maximalen Wert für m1 . Stellen Sie die errechneten Daten in Ta-
bellen und Diagrammen dar.
7.5 Beispiele aus der Physik 307

Lösung

Das Koordinatensystem Wir legen die positive y-Achse entgegengesetzt zur Schwer-
kraft.

Die Gleichgewichtsbedingung Die Summe aller Kräfte auf m1 , m2 und am Punkt A


muss null sein. 

 ⃗ ⃗ ⃗ 2 + T⃗2 = 0 (7.18a)
R + N + w
⃗ ′
T1 + w
⃗1 = 0 (7.18b)


⃗ ⃗ ′ ⃗
T1 + T2 + T3 = 0, (7.18c)
wobei w⃗ 1 und w
⃗ 2 die Gewichte der Massen m1 und m2 sind. Wir schreiben für das
Gleichungssystem


 T2⃗ex − R⃗ex = 0



 N⃗e − m2 g⃗ey = 0

 y
T1′ ⃗ey − m1 g⃗ey = 0 (7.19)



 T3 sin φ⃗ey − T1⃗ey = 0




T3 cos φ⃗ex − T2′ ⃗ex = 0.
Aus der Definition der Haftreibung folgt:

|R| ⃗|
⃗ = µ|N (7.20)

Alle Seile sind masselos, es gilt:

T⃗2 + T⃗2′ = 0 und T⃗1 + T⃗1′ = 0 (7.21)

Formeln für die Masse m1 und die Seilspannungen Die Bedingungen Gl. 7.20 und Gl.
7.21 eingesetzt in Gl. 7.19, geben folgenden Satz von Gleichungen:


 T1 = m1
 (7.22a)
T2 m2 µ
 T1

 = tan φ (7.22b)
T2
Damit folgt für die Masse m1
m1 = µm2 tan φ (7.23)

und für die Seilspannungen




 T1 = µm2 g tan φ (7.24a)


T2 = µm2 g (7.24b)

 µm2 g

 T3 = . (7.24c)
cos φ
308 7 Lösung von linearen Gleichungssystemen

Das Computerprogramm

Kurzbeschreibung Wir verfolgen wie auch in allen anderen Fällen einen modularen
Ansatz und überlassen die Berechnung der unbekannten Größen einem Rechenmodell.
Genauer gesagt werden wir zwei Rechenmodelle erstellen, um zwei alternative Lösungs-
wege vorzustellen. Beide Rechenmodelle werden von einer gemeinsamen Modellklasse
abgeleitet.

Modellklasse (Basisklasse)
– Eingabe: Haftreibungskoeffizient µ, Masse m2 und Winkel φ (Abb. 7.3)
– Ausgabe: Seilspannungen T1 , T2 , T3 , Normalkraft N und Masse m1

Erstes Rechenmodell abgeleitet von der Modellklasse


– Berechnung von T1 , T2 , T3 , N und Masse m1 mithilfe der hergeleiteten Formeln

Zweites Rechenmodell abgeleitet von der Modellklasse


– Berechnung von T1 , T2 , T3 , N und Masse m1 durch Lösung des Gleichungssystems
Gl. 7.19

Quelltext Alle Eingabeparameter werden über den Konstruktor (Listing 7.42) eingele-
sen und im public-Bereich als konstante Attribute gespeichert (Listing 7.42). Die Lösun-
gen werden in einem Feld gespeichert auf dessen Elemente über einen Aufzählungstyp
zugegriffen werden kann.

/**
* @brief The Idx enum Aufzählung , Zugriff auf gespeicherte Variablen
*/
enum Idx { T1 , T2 , T3 , N, M1 };

Listing 7.40 cppsrc/apps-x1900/hpp/x1900–01

/**
* @brief The X1900 class Massen befestigt an Seilen und Haftreibung
* ( Modellklasse )
*/
class X1900 : public XModel , public CResult <5, Idx >
{
public :
// Eingabeparameter
const double mu; // Haftreibungskoeffizient
const double mass2 ; // Masse m2
const double phi; // Winkel

public :

Listing 7.41 cppsrc/apps-x1900/hpp/x1900–02


7.5 Beispiele aus der Physik 309

/**
* @brief X1900 Konstruktor
* @param muin Haftreibungskoeffizient
* @param mass2in Masse m2
* @param phiin Winkel
*/
inline X1900 ( double muin , double mass2in , double phiin)
: XModel ( __func__ )
, mu{ muin }
, mass2 { mass2in }
, phi{ phiin } {
Check :: all(nmx_msg , { mu > 0, mass2 > 0, phi > 0 });
}
}; // X1900

Listing 7.42 cppsrc/apps-x1900/hpp/x1900–03

Es folgt das erste Rechenmodell, welches die Formeln Gl. 7.23 und Gl. 7.24a, Gl. 7.24b,
Gl. 7.24c innerhalb einer Elementfunktion implementiert.

/**
* @brief The CModel struct berechnet die Daten auf Basis hergeleiteter
* Formeln ( Rechenmodell )
*/
struct CModel1 : public X1900 {
// erbe Konstruktoren der Basisklasse
using X1900 :: X1900 ;

// verwende hergeleitete Formeln


inline void apply () {
set_result (Idx ::T1 , mass2 * Earth ::g * mu * tan(phi));
set_result (Idx ::T2 , mass2 * Earth ::g * mu);
set_result (Idx ::T3 , mass2 * Earth ::g * mu / cos(phi));
set_result (Idx ::N, mass2 * Earth ::g);
set_result (Idx ::M1 , mu * mass2 * tan(phi));
}
}; // CModel1

Listing 7.43 cppsrc/apps-x1900/hpp/x1900–04

Das zweite Rechenmodell löst folgendes Gleichungssystem (Gl. 7.19, Gl. 7.20 und Gl.
7.21) mittels LU-Zerlegung.
    
0 1 0 −µ 0 T1 0
    
0 0 1 0     
 0   T 2  m 2 g 
    
0 1 − cos φ 0 0   T3  =  0  (7.25)
    
    
1 0 − sin φ 0 0   N   0 
    
1 0 0 0 −g m1 0
310 7 Lösung von linearen Gleichungssystemen

/**
* @brief The CModel2 struct löst das Gleichungssystem direkt mittels
* LU - Zerlegung ( Rechenmodell )
*/
struct CModel2 : public X1900 {
// erbe Konstruktoren der Basisklasse
using X1900 :: X1900 ;

// löse Gleichungssystem
inline void apply () {
using LU = gsl :: LU_Decomposition ;
//LU - Zerlegung : Koeffizientenmatrix
LU:: Matrix left{ { 0, 1, 0, -mu , 0 },
{ 0, 0, 0, 1, 0 },
{ 0, 1, -cos(phi), 0, 0 },
{ 1, 0, -sin(phi), 0, 0 },
{ 1, 0, 0, 0, -Earth ::g } };
//LU - Zerlegung : rechte Seite des Gleichungssystems
LU:: Vector right { 0, mass2 * Earth ::g, 0, 0, 0 };
//LU - Zerlegung : Berechnung der Lösung
LU lusolver { left , right };
lusolver . solve ();
// speichere Ergebnisse
set_result ({ lusolver (Idx :: T1),
lusolver (Idx :: T2),
lusolver (Idx :: T3),
lusolver (Idx ::N),
lusolver (Idx :: M1) });
}
}; // CModel2

Listing 7.44 cppsrc/apps-x1900/hpp/x1900–05

Daten Für einen festen Wert der Masse m2 berechnen wir Daten für verschiedene Haft-
reibungskoeffizienten und Winkel mithilfe der beiden Rechenmodelle. Die Ergebnisse wer-
den in Dateien abgespeichert (Tab. 7.2, Tab. 7.3 Abb. 7.4).

/**
* @brief run Massen befestigt an Seilen und statische
* Reibung ( Berechnung von Beispieldaten )
*/
inline void run () {
using Data = Data <7 >;
const double mass2 = 1.0;
for (auto mu : { 0.1 , 0.2 , 0.3 , 0.4 }) {
// reservierter Speicher für Daten
Data data;
for (auto phi : { 10.0 _deg , 30.0 _deg , 40.0 _deg }) {
// verwende hergeleitete Formeln
CModel1 xobj1 { mu , mass2 , phi };
xobj1 . apply ();
// löse Gleichungssystem
CModel2 xobj2 { mu , mass2 , phi };
xobj2 . apply ();
// füge Ergebnisse einer gemeinsamen Zahlentabelle hinzu
// clang - format off
7.5 Beispiele aus der Physik 311

data += { phi ,
xobj2 (Idx :: T1), xobj2(Idx ::T2), xobj2(Idx ::T3),
xobj2 (Idx ::N), xobj2(Idx ::M1), xobj1(Idx ::M1) };
// clang - format on
}
// speichere Daten im CSV - und LaTeX - Format . Dateinamenskonflikte
// werden durch das Hinzufügen des Haftreibungskoeffizienten
// vermieden
X1900 :: save(data , Output :: plot , mu);
X1900 :: save(data , Output :: latex , mu);
}
}

Listing 7.45 cppsrc/apps-x1900/hpp/x1900–06

Tab. 7.2: Daten für m2 = 1 kg, µ = 0.1 und unterschiedlichen Winkeln. Die beiden
letzten Spalten sind die Werte für die Masse m1 aus den beiden Rechenmodellen.
φ [rad] T1 [N] T2 [N] T3 [N] N [N] m1 [kg] m1 [kg]
1.745e-01 1.729e-01 9.807e-01 9.958e-01 9.807e+00 1.763e-02 1.763e-02
5.236e-01 5.662e-01 9.807e-01 1.132e+00 9.807e+00 5.774e-02 5.774e-02
6.981e-01 8.229e-01 9.807e-01 1.280e+00 9.807e+00 8.391e-02 8.391e-02

Tab. 7.3: Daten für m2 = 1 kg, µ = 0.4 und unterschiedlichen Winkeln. Die beiden
letzten Spalten sind die Werte für die Masse m1 aus den beiden Rechenmodellen.
φ [rad] T1 [N] T2 [N] T3 [N] N [N] m1 [kg] m1 [kg]
1.745e-01 6.917e-01 3.923e+00 3.983e+00 9.807e+00 7.053e-02 7.053e-02
5.236e-01 2.265e+00 3.923e+00 4.529e+00 9.807e+00 2.309e-01 2.309e-01
6.981e-01 3.292e+00 3.923e+00 5.121e+00 9.807e+00 3.356e-01 3.356e-01

µ = 0.1 µ = 0.3
µ = 0.2 µ = 0.4
0.15 0.3
m1 [kg]

m1 [kg]

0.1 0.2

5 · 10−2
0.1

0.2 0.3 0.4 0.5 0.6 0.7 0.2 0.3 0.4 0.5 0.6 0.7
φ[rad] φ[rad]

Abb. 7.4: Die Masse m1 in Abhängigkeit vom Winkel φ und dem Haftreibungskoeffizi-
enten µ
312 7 Lösung von linearen Gleichungssystemen

7.5.3 Beschleunigte schiefe Ebene

Eine schiefe Ebene mit Neigungswinkel φ und der Masse m2 ruht auf reibungsfreiem
horizontalen Boden. Ein Holzblock der Masse m1 gleitet auf der rauen, schrägen Seite
der Ebene herunter. Der Reibungskoeffizient zwischen Holzblock und Ebene sei µ.

⃗1
N
⃗1
R
⃗v1
⃗2
N


r1 ⃗′
R 1
⃗e2y ⃗e2y
ϑ ⃗e1x ⃗e1y w
⃗1 ⃗e1x ⃗e1y
⃗e2x w
⃗2 ⃗′
N 1 ϑ

r2 ⃗e2x

(a) (b)
Abb. 7.5: Ein Holzblock bewegt sich entlang einer beweglichen schiefen Ebene. Die rot
eingezeichneten Kräfte wirken auf die schiefe Ebene und die blauen auf den Holzblock.
⃗ 1 ist die Normalkraft von der schiefen Ebene auf den Block, N
N ⃗ 2 die Normalkraft vom
Boden auf die schiefe Ebene und R ⃗ 1 die Gleitreibung.

1. Berechnen Sie die Beschleunigungen für die schiefe Ebene und den Holzblock sowie
die Normalkraft von der schiefen Ebene auf den Holzblock. Wie groß ist die vom
Holzblock auf die schiefe Ebene ausgeübte Kraft?
2. Für einen festen Neigungswinkel der schiefen Ebene variieren Sie das Massenverhältnis
m2
λ = m1 sowie den Gleitreibungskoeffizienten µ und berechnen die Beschleunigungen
der beiden Objekte sowie die Kräfte, die diese gegenseitig aufeinander ausüben.

Lösung

Wahl des Koordinatensystems Wir wählen ein festes Koordinatensystem K2 mit


(⃗e2x , ⃗e2y ) und ein K1 mit (⃗e1x , ⃗e1y ), welches an die Ebene gebunden ist (Abb. 7.5).
Die Umrechnung zwischen K1 und K2 erfolgt über die Gleichungen
{
⃗e1x = cos ϑ⃗e2x + sin ϑ⃗e2y (7.26)
⃗e1y = − sin ϑ⃗e2x + cos ϑ⃗e2y . (7.27)
Das zweite Newton’sche Gesetz Wenn ⃗r1 die Ortskoordinate des Blocks im K1 und
⃗r2 die Ortskoordinate des rechten unteren Punktes der schiefen Ebene im Bezugssystem
K2 ist (Abb. 7.5a), dann gilt für die Bewegung der schiefen Ebene und des Blocks im
K2 :  ( )
 m1 ⃗r¨1 + ⃗r¨2 = w ⃗1 + R
⃗1 + N ⃗1 (7.28a)
 ⃗ ′ + R⃗′
m2⃗r¨2 = N
⃗2 + w
⃗2 + N 1 1 (7.28b)
7.5 Beispiele aus der Physik 313

oder
{
m1 (ẍ1⃗e1x + ẍ2⃗e2x ) = −m1 g⃗e2y + N1⃗e1y + R1⃗e1x (7.29a)
m2 x¨2⃗e2x = N2⃗e2y − m2 g⃗e2y − R1′ ⃗e1x − N1′ ⃗e1y (7.29b)
Aus dem dritten Newton’schen Gesetz folgt

⃗ 1′ | = |N
|N ⃗ 1| (7.30)

|R
⃗ 1 | = |R⃗ 1 | = µN1 . (7.31)

⃗ 1′ und N
Wir ersetzen R ⃗ 1′ in (Gl. 7.29b):
{
m1 x¨1⃗e1x + m1 x¨2⃗e2x = −m1 g⃗e2y + N1⃗e1y + µN1⃗e1x (7.32a)
m2 x¨2⃗e2x = N2⃗e2y − m2 g⃗e2y − µN1⃗e1x − N1⃗e1y . (7.32b)

Durch skalare Multiplikation dieser Gleichungen mit ⃗e2x und ⃗e2y erhalten wir

 m1 x¨1 cos ϑ + m1 x¨2 = −N1 sin ϑ + µN1 cos ϑ (7.33a)



 m1 x¨1 sin ϑ = −m1 g + N1 cos ϑ + µN1 sin ϑ (7.33b)

 m2 x¨2 = −µN1 cos ϑ + N1 sin ϑ (7.33c)



0 = N2 − m2 g − µN1 sin ϑ − N1 cos ϑ (7.33d)
oder 

 N1
 ẍ1 cos ϑ + ẍ2 = −
 (sin ϑ − µ cos ϑ) (7.34a)

 m 1



 ẍ1 sin ϑ = −g + N1 (cos ϑ + µ sin ϑ) (7.34b)
m1



 N 1
 ẍ2 = m2 (sin ϑ − µ cos ϑ)


(7.34c)



N2 = m2 g + N1 (µ sin ϑ + cos ϑ) . (7.34d)

Berechnung von N1 Gl. 7.34c eingesetzt in Gl. 7.34a gibt


( )
1 1
ẍ1 cos ϑ = −N1 (sin ϑ − µ cos ϑ) + . (7.35)
m1 m2
Division von Gl. 7.34b durch Gl. 7.35
−g + N1
(cos ϑ + µ sin ϑ)
tan ϑ = ( ) m1

− m11 + m12 N1 (sin ϑ − µ cos ϑ)


( )
1 1 N1
⇒− + N1 (sin ϑ − µ cos ϑ) tan ϑ = −g + (cos ϑ + µ sin ϑ) .
m1 m2 m1
Wir stellen weiter um
[( 1 1
)
cos ϑ ]
N1 + sin ϑ (sin ϑ − µ cos ϑ) + (cos ϑ + µ sin ϑ) = g cos ϑ
m1 m2 m1
[( 1 1
)( ) 1 ( 2 )]
⇒ N1 + sin2 ϑ − µ sin ϑ cos ϑ + cos ϑ + µ sin ϑ cos ϑ = g cos ϑ
m1 m2 m1
314 7 Lösung von linearen Gleichungssystemen

und vereinfachen den Ausdruck in den eckigen Klammern


( 2 )
sin ϑ + cos2 ϑ sin2 ϑ − µ sin ϑ cos ϑ
N1 + = g cos ϑ
m1 m2
( )
m1 ( 2 )
⇒ N1 1 + sin ϑ − µ sin ϑ cos ϑ = m1 g cos ϑ
m2
und erhalten schließlich
m1 g cos ϑ
N1 = . (7.36)
1− m1
m2 sin ϑ (µ cos ϑ − sin ϑ)

Die Beschleunigungen für m1 und m2 Wir setzen N1 in Gl. 7.34c ein:

m2 g cos ϑ (sin ϑ − µ cos ϑ)


m1
ẍ2 = . (7.37)
1− mm2 sin ϑ (µ cos ϑ − sin ϑ)
1

N1 und ẍ2 eingesetzt in Gl. 7.34a und Gl. 7.34b gibt:


( )
2 1 1 m1 g cos2 ϑ (sin ϑ − µ cos ϑ)
ẍ1 cos ϑ = − + (7.38a)
m1 m2 1 − m m2 sin ϑ (µ cos ϑ − sin ϑ)
1

g sin ϑ cos ϑ (cos ϑ + µ sin ϑ)


ẍ1 sin2 ϑ = −g sin ϑ + (7.38b)
1− m m2 sin ϑ (µ cos ϑ − sin ϑ)
1

und damit [ ]
m2 cos ϑ (µ cos ϑ − sin ϑ) +
m1
g cos ϑ µ
ẍ1 = −g sin ϑ + . (7.39)
1 −mm1
2
sin ϑ (µ cos ϑ − sin ϑ)

Das Computerprogramm

Kurzbeschreibung Wir identifizieren als charakteristische Eigenschaften des Systems


und damit auch der Modellklasse, die Massen der schiefen Ebene und des Holzblocks,
den Gleitreibungskoeffizienten und den Neigungswinkel der schiefen Ebene. Mithilfe von
zwei Rechenmodellen werden wir zwei Möglichkeiten vorstellen die Beschleunigungen und
die Normalkräfte zu berechnen. Das erste Rechenmodell wird die hergeleiteten Formeln
anwenden und das zweite das lineare Gleichungssystem Gl. 7.34 lösen. Beide Rechenmo-
delle werden von der Modellklasse abgeleitet.

Die Modellklasse Wir möchten vermeiden, unnötige Elementfunktionen für den Zugriff
auf die bekannten Attribute der Modellklasse zu programmieren und speichern diese als
konstante Variablen im public-Bereich ab (Listing 7.46). Die einzige Möglichkeit, dieses
Vorhaben umzusetzen, ist die Variablen über den Konstruktor zu initialisieren (Listing
7.47). Für den Zugriff auf die Ergebnisse, die in einem Feld gespeichert werden, führen
wir einen Aufzählungstypen ein (Listing 7.48). Statt eines Index kann dadurch bequem
mittels eines Bezeichners auf die Elemente des Feldes zugegriffen werden. Es ist aber
nicht nur das, sondern wir sichern uns auch gleichzeitig von falschen Zugriffen ab, denn
wir garantieren durch die Einführung des Aufzählungstypen, dass nur gültige Indizes
verwendet werden können.
7.5 Beispiele aus der Physik 315

/**
* @brief The X410 class Masse gleitet auf bewegliche schiefe Ebene
* ( Modellklasse )
* @param XModel Basisklasse fur alle Modellklassen
* @param CResult Klasse zur Speicherung der Rechenergebnisse
*/
class X410 : public XModel , public CResult <4, Idx >
{
public :
// Eingabeparameter
const double m1 , m2;
const double theta ;
const double mu;

Listing 7.46 cppsrc/apps-x410/hpp/x410–02

/**
* @brief X410 Konstruktor
* @param m1 Masse des Blocks
* @param m2 Masse der schiefen Ebene
* @param theta Neigung
* @param mu Reibungskoeffizient
*/
X410( double m1 , double m2 , double theta , double mu)
: XModel ( __func__ )
, m1{ m1 }
, m2{ m2 }
, theta { theta }
, mu{ mu } {
// sind alle Eingaben gültig ?
Check :: all(nmx_msg , { m1 > 0, m2 > 0, theta > 0, mu > 0 });
}
}; // X410

Listing 7.47 cppsrc/apps-x410/hpp/x410–03

/**
* @brief The Idx enum Zugriff auf die einzelnen Elemente
*/
enum class Idx { x1ddot , x2ddot , N1 , N2 };

Listing 7.48 cppsrc/apps-x410/hpp/x410–01

Das erste Rechenmodell Ziel dieses Rechenmodells ist, die gesuchten Größen direkt
über den Einsatz der hergeleiteten Formeln für ẍ1 (Gl. 7.39), ẍ2 (Gl. 7.37), N1 (Gl. 7.36)
und N2 (Gl. 7.34d) zu berechnen. Da die Ausdrücke kompliziert sind, wollen wir für jede
einzelne Formel eine Elementfunktion einführen. Die Klasse wird dadurch übersichtlicher.
Terme wie der Sinus und Kosinus des Neigungswinkels, die in allen Formeln vorkommen,
wollen wir nur einmal berechnen und innerhalb der Klasse speichern.
316 7 Lösung von linearen Gleichungssystemen

/**
* @brief The CModel1 class Rechenmodell
* Berechnet Daten durch Anwendung der hergeleiteten Formeln
*/
class CModel1 : public X410
{
private :
double _cTheta , _sTheta ;
double _m12 , _trm1 , _den;

Listing 7.49 cppsrc/apps-x410/hpp/x410–04

Es folgen die Implementierungen der Formeln. Diese Elementfunktionen können nur in-
nerhalb der Klasse aufgerufen werden.

/**
* @brief N1
* @return Flächennormale auf Block
*/
double N1() const { //
return m1 * Earth ::g * _cTheta / (1 - _m12 * _sTheta * _trm1);
}

Listing 7.50 cppsrc/apps-x410/hpp/x410–05

/**
* @brief N2
* @return Flächennormale auf schiefe Ebene
*/
double N2() const { //
return m2 * Earth ::g - N1 () * (mu * _sTheta + _cTheta );
}

Listing 7.51 cppsrc/apps-x410/hpp/x410–06

/**
* @brief x1_ddot
* @return Beschleunigung des Blocks im System der schiefen Ebene
*/
double x1_ddot () const {
double num = Earth ::g * _cTheta * (_m12 * _cTheta * _trm1 + mu);
double den = (1 - _m12 * _sTheta * _trm1);
return -Earth ::g * _sTheta + (num / den);
}

Listing 7.52 cppsrc/apps-x410/hpp/x410–07

/**
* @brief x2_ddot
* @return Beschleunigung der schiefen Ebene im festen
* Koordinatensystem
*/
7.5 Beispiele aus der Physik 317

double x2_ddot () const {


return -_m12 * Earth ::g * _cTheta * _trm1 / (1 - _m12 * _sTheta
* _trm1);
}

public :
// erbe Konstruktoren von der Basisklasse
using X410 :: X410;

Listing 7.53 cppsrc/apps-x410/hpp/x410–08

Bei jedem Aufruf der Elementfunktion Listing 7.54 werden zuerst, die in allen Formeln
vorkommenden Terme berechnet und zwischengespeichert. Diese werden in Kombinati-
on mit den Elementfunktionen der Klasse dazu benutzt, die Beschleunigungen und die
Normalkräfte zu berechnen.

/**
* @brief exec Berechnung und Speicherung der Normalkräfte und
* der Beschleunigungen
* @param xmodel Modellklasse
*/
inline void apply () {
_cTheta = cos( theta );
_sTheta = sin( theta );
_trm1 = mu * _cTheta - _sTheta ;
_den = 1 - _m12 * _sTheta * _trm1;
_m12 = m1 / m2;
// speichere Ergebnisse
set_result ({ x1_ddot () , x2_ddot (), N1 (), N2 () });
}
}; // CModel1

Listing 7.54 cppsrc/apps-x410/hpp/x410–09

Das zweite Rechenmodell Statt die hergeleiteten Formeln für die gesuchten Größen
zu übernehmen, löst dieses Rechenmodell das lineare Gleichungssystem (Gl. 7.34, hier in
Matrixform):
    
cos ϑ 1 sin ϑ−µ cos ϑ
0 ẍ1 0
 m 1    
 sin ϑ 0 − cos ϑ+µ sin ϑ 0  ẍ   −g 
 m1  2  
   =   (7.40)
 0 1 − sin ϑ−µ cos ϑ 0 N1   0 
 m2    
0 0 µ sin ϑ + cos ϑ 1 N2 m2 g
Mithilfe einer LU-Zerlegung erhalten wir eine Lösung, die identisch ist mit der des vorhe-
rigen Rechenmodells, ohne die komplizierten Formeln zu implementieren. Ein Blick auf
den Quelltext zeigt, dass dies wesentlich einfacher zu programmieren ist.

/**
* @brief The CModel2 struct Rechenmodell
* Berechnet Daten durch direktes Lösen des Gleichungssystems
*/
struct CModel2 : public X410 {
318 7 Lösung von linearen Gleichungssystemen

// erbe Konstruktoren von der Basisklasse


using X410 :: X410;

// Berechnung und Speicherung der Normalkräfte und


// der Beschleunigungen durch direktes Lösen des
// Gleichungssystems
inline void apply () {
using LU = gsl :: LU_Decomposition ;
const double cosTheta = cos( theta);
const double sinTheta = sin( theta);
LU:: Matrix _left ({
{ cosTheta , 1.0 , ( sinTheta - mu * cosTheta ) / (m1), 0 },
{ sinTheta , 0, -( cosTheta + mu * sinTheta ) / (m1), 0 },
{ 0, 1, -( sinTheta - mu * cosTheta ) / (m2), 0 },
{ 0, 0, ( cosTheta + mu * sinTheta ), 1 } //
});
LU:: Vector _right ({ 0.0 , -Earth ::g, 0, m2 * Earth ::g });
LU lusolver { _left , _right };
lusolver . solve ();
// speichere Ergebnisse
set_result ( lusolver . solution ());
}
}; // CModel2

Listing 7.55 cppsrc/apps-x410/hpp/x410–10

Daten Der Block mit Masse m1 = 1 kg befindet sich am Ort x0 = 10 m (gemessen im


Koordinatensystem K1 , Abb. 7.5a) und beginnt eine schiefe Ebene der Masse m2 = λm1
mit λ = 10, 100, 1000 und Neigungswinkel ϑ = 30◦ herabzugleiten. Der Gleitreibungs-
koeffizient ist µ = 0.1.

/**
* @brief run Beschleunigte schiefe Ebene Erstellung von
* Beispieldaten
*/
inline void run () {
// Masse gleitet auf schiefe Ebene
const double m1 = 1.0;
// Neigungswinkel der schiefen Ebene
const double theta = 30. _deg;
// Datenobjekt speichert berechnete Daten
Data <8> data;
// variiere lambda und Gleitreibungskoeffizienten
for ( double lambda : { 10, 100 , 1000 }) {
for ( double mu : { 0., 0.1 , 0.2 }) {
const double m2 = lambda * m1;
// Modellklasse und Rechenmodelle
CModel1 cobj1 { m1 , m2 , theta , mu };
CModel2 cobj2 { m1 , m2 , theta , mu };
cobj1 . apply ();
cobj2 . apply ();
data += { lambda ,
mu ,
cobj1 (Idx :: x1ddot ),
cobj1 (Idx :: x2ddot ),
cobj1 (Idx :: N1),
7.6 Übungsaufgaben 319

cobj1 (Idx :: N2),


cobj2 (Idx :: x1ddot ),
cobj2 (Idx :: x2ddot ) };
}
}
// Daten werden in Dateien geschrieben
auto ofs1 = X410 :: get_output_stream ( Output :: latex );
ofs1 << std :: setprecision (2);
data.save(ofs1 , Output :: latex );
}

Listing 7.56 cppsrc/apps-x410/hpp/x410–11

Tab. 7.4: Beschleunigte schiefe Ebene mit Neigungswinkel φ = 30◦ . Beschleunigungen


der Ebene und des Holzblocks und die Kontaktkräfte: Die beiden letzten Spalten enthal-
ten die Ergebnisse aus den Formeln.
λ µ ẍ1 [ sm2 ] ẍ2 [ sm2 ] N1 [N] N2 [N] ẍ1ex [ sm2 ] ẍ2ex [ sm2 ]
1.00e+01 0.00e+00 -5.26e+00 4.14e-01 8.29e+00 9.09e+01 -5.26e+00 4.14e-01
1.00e+01 1.00e-01 -4.37e+00 3.44e-01 8.32e+00 9.04e+01 -4.37e+00 3.44e-01
1.00e+01 2.00e-01 -3.47e+00 2.73e-01 8.36e+00 9.00e+01 -3.47e+00 2.73e-01
1.00e+02 0.00e+00 -4.94e+00 4.24e-02 8.47e+00 9.73e+02 -4.94e+00 4.24e-02
1.00e+02 1.00e-01 -4.09e+00 3.50e-02 8.48e+00 9.73e+02 -4.09e+00 3.50e-02
1.00e+02 2.00e-01 -3.23e+00 2.77e-02 8.48e+00 9.72e+02 -3.23e+00 2.77e-02
1.00e+03 0.00e+00 -4.91e+00 4.25e-03 8.49e+00 9.80e+03 -4.91e+00 4.25e-03
1.00e+03 1.00e-01 -4.06e+00 3.51e-03 8.49e+00 9.80e+03 -4.06e+00 3.51e-03
1.00e+03 2.00e-01 -3.21e+00 2.77e-03 8.49e+00 9.80e+03 -3.21e+00 2.77e-03

Wie erwartet nimmt die Beschleunigung der schiefen Ebene, bei wachsender Masse, ab.

7.6 Übungsaufgaben
1. Lösen Sie das lineare Gleichungssystem


 2x − 3y + 2z + 3w =1


 x − 2z + 2w =6

 3x + y − z + 3w =8



4x − z =1
2. Entwerfen Sie für die Aufgabe aus Abschnitt 7.5.1 ein Rechenmodell, welches das
Gleichungssystem Gl. 7.15 mit der Cramer’schen Regel löst.
3. Die Rolle in einer idealen Atwood’schen Fallmaschine (Abb. 7.6) wird mit einer kon-
stanten Beschleunigung ⃗a nach oben beschleunigt. Die Masse der Seile kann vernach-
lässigt werden.
320 7 Lösung von linearen Gleichungssystemen

⃗a

m1
m2

Abb. 7.6: Atwood’sche Fallmaschine beschleunigt senkrecht nach oben

a) Mit welchen Beschleunigungen bewegen sich die Massen m1 und m2 ? Wie groß ist
die Seilspannung?
b) Erstellen Sie eine Tabelle, in der für unterschiedliche Massenverhältnisse m1 /m2
die Beschleunigungen der Massen m1 und m2 und die Seilspannung in Abhängig-
keit von der Beschleunigung der Rolle dargestellt werden.
8 Nullstellenberechnung reeller
Funktionen

Übersicht
8.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
8.2 Zwei Beispielprogramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
8.3 Eine Klasse für das Bisektionsverfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
8.4 Eine Klasse für das Newton-Verfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
8.5 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
8.6 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340

8.1 Einleitung
Es existieren zwei Gruppen von Algorithmen zur Berechnung von Nullstellen reeller Funk-
tionen. Zur ersten Gruppe gehören Verfahren, die ohne Kenntnis der Ableitung einer
Funktion auskommen, und zur zweiten Gruppe solche, bei denen die Ableitung einer
Funktion bekannt sein muss. Alle Verfahren arbeiten iterativ.
Beim Bisektionsverfahren wird die Ableitung einer Funktion nicht vorausgesetzt. Ein
Intervall [x1 , x2 ] (Abb. 8.1a) wird so gewählt, dass f (x1 )f (x2 ) < 0 gilt. Dies garantiert,
dass die gesuchte Nullstelle xr innerhalb des Intervalls liegt. In einem nächsten Schritt
wird die Mitte des Intervalls x3 = x1 +x 2
2
gewählt und getestet, ob f (x1 )f (x3 ) < 0 oder
f (x3 )f (x2 ) < 0 ist und dadurch die Position der Nullstelle eingegrenzt. Dies wird so
lange fortgesetzt, bis das Intervall kleiner als ein vorgegebener Wert wird und damit ein
Näherungswert der Nullstelle mit einer gewünschten Genauigkeit feststeht.
Zur Anwendung des Newton-Verfahrens wird die Ableitung einer Funktion vorausge-
setzt. Von einem gegebenen Näherungswert xn (Abb. 8.1b) wird eine verbesserte Nähe-
rung für die Nullstelle bestimmt, indem die Tangente an der Stelle f (xn ) an die Funktion
gelegt und der Schnittpunkt mit der x-Achse xn+1 bestimmt wird. Dies wird gemäß der
Vorschrift xn+1 = xn − ff′(x n)
(xn ) so lange wiederholt, bis die Nullstelle gefunden wird, d.h
bis |xn+1 − xn | < ϵ, wobei das ϵ der Routine vorgegeben wird.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_8
322 8 Nullstellenberechnung reeller Funktionen

y y

f (xn )
f (x2 )
x1 +x2
x3 = 2
x1 xr x2 x xn+1 xn x
f (x1 )

(a) (b)
Abb. 8.1: (a) Bisektionsverfahren (b) Newton-Verfahren

8.2 Zwei Beispielprogramme


Wir beginnen mit zwei Beispielen, die auf der GNU Scientific Library Webseite zu finden
sind und die Ausgangsbasis für den Entwurf zweier Klassen sein werden. In beiden Bei-

spielprogrammen wird der Wert 5 ≈ 2.2360679775 berechnet. Dies geschieht zunächst
mit dem Bisektionsverfahren und dann mit dem Newton-Verfahren. Wir gehen von der
Gleichung x2 − 5 = 0 aus und suchen nach Nullstellen der Funktion f (x) = x2 − 5.
Den gsl-Algorithmen wird f (x) nicht direkt übergeben, sondern indirekt über folgende
Struktur:

struct gsl_function_struct
{
double (* function )( double x, void * params ); // Zeiger auf eigene Funktion
void * params ; // optionaler Parameter
};
typedef struct gsl_function_struct gsl_function ;

Listing 8.1

Diese beinhaltet neben einen Zeiger auf f (x) auch einen zusätzlichen optionalen Para-
meter, mit dem die Funktion f (x) näher spezifiziert werden kann oder ihr zusätzliche
Daten übergeben werden können. Der Grund ist, dass die gsl in C geschrieben ist und
innerhalb dieser Sprache nach Möglichkeiten gesucht wird, die Algorithmen so flexibel
wie möglich zu entwerfen. Für g(x) = 3x2 − 2x + 1 könnten wir z.B., ohne vom zweiten
Eingabeparameter Gebrauch zu machen, Folgendes schreiben:

double g( double x, void *p) {


(void) p; // Warnung vom Compiler unterdrücken
// liefert das Polynom , ohne von zusätzlichen
// Parametern Gebrauch zu machen
return (3 * x + -2) * x + 1;
}

gsl_function_struct F;
8.2 Zwei Beispielprogramme 323

// Übergabe an gsl - Struktur


F. function = &g;
F. params = nullptr ;

Listing 8.2

In unserem Beispielprogramm ist f (x) folgendermaßen definiert:

/**
* @brief quadratic quadratisches Polynom
* @param x Variable
* @param params zusätzliche Daten
* @return reelle Zahl
*/
double quadratic_fn ( double x, void * params ) {
// Konvertierung des void -Zeigers , die Koeffizienten des Polynoms
// werden über diesen Parameter festgelegt
struct quadratic_params *p = static_cast < quadratic_params
*>( params );
// Koeffizienten a x^2 + b x + c
double a = p->a;
double b = p->b;
double c = p->c;
return (a * x + b) * x + c;
}

Listing 8.3 cppsrc/apps-x034/hpp/x034–02

Das zweite Eingabeargument in Listing 8.3 ist ein Zeiger auf folgende Struktur:

/**
* @brief The quadratic_params struct
*/
struct quadratic_params {
double a, b, c;
};

Listing 8.4 cppsrc/apps-x034/hpp/x034–01

Für das Beispielprogramm müssen die Werte a = 1, b = 0 und c = −5 lauten. Es stellt


sich die Frage, warum diese Werte nicht direkt in der Funktionsdefinition eingesetzt
wurden. Eine mögliche Antwort ist, dass mit demselben Programm durch die Änderung
der Parameter a, b und c für jedes quadratische Polynom nach Nullstellen gesucht werden
kann.
Das folgende Beispiel kann in vier logische Einheiten aufgeteilt werden. Im ersten Teil
wird die Funktion definiert, für welche die Nullstelle gesucht wird (Listing 8.5).

/**
* @brief ex1 Bisektionsverfahren mit gsl - Routinen
*/
inline void ex1 () {
// Definition der Funktion , für welche die Nullstelle gesucht wird
// Polynomkoeffizienten
quadratic_params params = { 1.0 , 0.0, -5.0 };
324 8 Nullstellenberechnung reeller Funktionen

//gsl - Funktion
gsl_function F;
F. function = & quadratic_fn ;
F. params = & params ;

Listing 8.5 cppsrc/apps-x034/hpp/x034–05

Im zweiten Teil werden alle nötigen gsl-Strukturen initialisiert. Dazu gehören der
gsl_root_fsolver_type, der festlegt, welcher Algorithmus zur Nullstellensuche benutzt
werden soll. Für das Bisektionsverfahren muss dieser gsl_root_fsolver_bisection lauten.
Mit gsl_root_fsolver werden die einzelnen Iterationsschritte koordiniert. Anschließend
wird die Funktion, für welche eine Nullstelle gesucht wird, registriert (Listing 8.6).

// Initialisierung der gsl_Routinen


// Anfangsintervall
double x_lo = 0.0 , x_hi = 5.0;
// Bisektionsalgorithmus
const gsl_root_fsolver_type *T = gsl_root_fsolver_bisection ;
// Routine zur Nullstellensuche
gsl_root_fsolver *s;
s = gsl_root_fsolver_alloc (T);
// registriere Funktion
gsl_root_fsolver_set (s, &F, x_lo , x_hi);

Listing 8.6 cppsrc/apps-x034/hpp/x034–06

Im dritten Teil wird die Iteration durchgeführt. Hier werden die Intervallgrenzen nach je-
dem Schritt neu gesetzt und es wird geprüft, ob das Verfahren gestoppt werden kann, weil
eine Nullstelle gefunden wurde (Listing 8.7). Im vierten Teil werden die vom Programm
angeforderten Ressourcen wieder freigegeben (Listing 8.8).

// Iteration
printf ("using %s method \n", gsl_root_fsolver_name (s));
printf ("%5s [%9s, %9s] %9s %10s %9s\n", "iter", "lower", "upper",
"root", "err", "err(est)");

double r = 0, r_expected = sqrt (5.0) ;


int status ;
int iter = 0, max_iter = 100;
do {
// Zähler damit keine Endlosschleife entsteht
iter ++;
// Iterationschritt
status = gsl_root_fsolver_iterate (s);
r = gsl_root_fsolver_root (s);
// neue Grenzen für das Intervall
x_lo = gsl_root_fsolver_x_lower (s);
x_hi = gsl_root_fsolver_x_upper (s);
// ist das Intervall klein genug ?
status = gsl_root_test_interval (x_lo , x_hi , 0, 0.001) ;

if ( status == GSL_SUCCESS ) {
printf (" Converged :\n");
}
8.2 Zwei Beispielprogramme 325

printf ("%5d [%.7f, %.7f] %.7f %+.7f %.7f\n",


iter ,
x_lo ,
x_hi ,
r,
r - r_expected ,
x_hi - x_lo);
} while ( status == GSL_CONTINUE && iter < max_iter );

Listing 8.7 cppsrc/apps-x034/hpp/x034–07

// Speicherfreigabe
gsl_root_fsolver_free (s);
}

Listing 8.8 cppsrc/apps-x034/hpp/x034–08

Da wir vorhaben auch das Newton-Verfahren anzuwenden, implementieren wir die Ab-
leitung f ′ (x) = 2x.

/**
* @brief quadratic_deriv Ableitung des quadratischen Polynoms
* @param x variable
* @param params zusätzliche Daten
* @return reelle Zahl
*/
double quadratic_deriv ( double x, void * params ) {
// Konvertierung des void -Zeigers , die Koeffizienten des Polynoms
// werden über diesen Parameter festgelegt
struct quadratic_params *p = static_cast < quadratic_params
*>( params );
// Koeffizienten a x^2 + b x + c
double a = p->a;
double b = p->b;
return 2.0 * a * x + b;
}

Listing 8.9 cppsrc/apps-x034/hpp/x034–03

Mit einer Funktion können zur schnelleren Ausführung des Programms, sowohl f (x) als
auch f ′ (x) aufgerufen werden.

/**
* @brief quadratic_fdf quadratisches Polynom und Ableitung
* @param x Variable
* @param params zusätzliche Daten
* @param y Rückgabewert quadratisches Polynom
* @param dy Rückgabewert Ableitung des
* quadratischen Polynoms
*/
void quadratic_fdf ( double x, void *params , double *y, double *dy) {
// Konvertierung des void -Zeigers , die Koeffizienten des Polynoms
// werden über diesen Parameter festgelegt
326 8 Nullstellenberechnung reeller Funktionen

struct quadratic_params *p = static_cast < quadratic_params


*>( params );
// Koeffizienten a x^2 + b x + c
double a = p->a;
double b = p->b;
double c = p->c;
*y = (a * x + b) * x + c;
*dy = 2.0 * a * x + b;
}

Listing 8.10 cppsrc/apps-x034/hpp/x034–04

Auch das zweite Beispielprogramm besteht aus vier logischen Einheiten. Der erste Teil
initialisiert die Funktion und ihre Ableitung (Listing 8.11). Im zweiten Teil werden, analog
zum ersten Beispiel, die Strukturen der gsl initialisiert und es wird der Algorithmus
festgelegt, mit dem die Nullstelle gesucht werden soll (Listing 8.12).

/**
* @brief ex2 Nullstellensuche mit dem Newton - Verfahren und gsl - Routinen
*/
inline void ex2 () {
// Definition der Funktion , für welche die Nullstelle gesucht
// wird , Ableitung der Funktion , kombinierter Aufruf Funktion
// und Ableitung
quadratic_params params = { 1.0 , 0.0, -5.0 };
gsl_function_fdf FDF;
FDF.f = & quadratic_fn ;
FDF.df = & quadratic_deriv ;
FDF.fdf = & quadratic_fdf ;
FDF. params = & params ;

Listing 8.11 cppsrc/apps-x034/hpp/x034–09

// Initialisierung der gsl_Routinen Anfangsintervall


double x0 , x = 5.0 , r_expected = sqrt (5.0);
// Newton - Algorithmus
const gsl_root_fdfsolver_type *T = gsl_root_fdfsolver_newton ;
// Routine zur Nullstellensuche
gsl_root_fdfsolver *s;
s = gsl_root_fdfsolver_alloc (T);
// registriere Funktion und ihre Ableitung
gsl_root_fdfsolver_set (s, &FDF , x);

Listing 8.12 cppsrc/apps-x034/hpp/x034–10

Im dritten Teil wird die Iteration ausgeführt. Nach jedem Iterationsschritt wird mit gsl-
Funktionen getestet, ob die gewünschte Genauigkeit erreicht wurde und gegebenenfalls
die Iteration beendet (Listing 8.13). Im letzten Teil werden die angeforderten Ressourcen
wieder freigegeben (Listing 8.14).

// Iteration
int status ;
int iter = 0, max_iter = 100;
8.3 Eine Klasse für das Bisektionsverfahren 327

printf ("using %s method \n", gsl_root_fdfsolver_name (s));


printf ("%-5s %10s %10s %10s\n", "iter", "root", "err", "err(est)");
do {
// Zähler damit keine Endlosschleife entsteht
iter ++;
// Iterationschritt
status = gsl_root_fdfsolver_iterate (s);
x0 = x;
x = gsl_root_fdfsolver_root (s);
// ist Nullstelle gefunden ?
status = gsl_root_test_delta (x, x0 , 0, 1e -3);

if ( status == GSL_SUCCESS )
printf (" Converged :\n");

printf ("%5d %10.7 f %+10.7 f %10.7 f\n", iter , x, x - r_expected ,


x - x0);
} while ( status == GSL_CONTINUE && iter < max_iter );

Listing 8.13 cppsrc/apps-x034/hpp/x034–11

// Speicherfreigabe
gsl_root_fdfsolver_free (s);
}

Listing 8.14 cppsrc/apps-x034/hpp/x034–12

8.3 Eine Klasse für das Bisektionsverfahren


Wir beginnen mit dem Entwurf einer Klasse zur Suche von Nullstellen mittels Verfahren,
die keine Ableitung von Funktion benötigen. Alle im Listing 8.5 vorkommenden gsl-
Strukturen werden intern als Variablen gespeichert.

/**
* @brief The RootBracketing class gsl - Klasse für das
* Bisektionsverfahren
*/
class RootBracketing
{
private :
// gsl interne Struktur
const gsl_root_fsolver_type * _solver_type = nullptr ;
// legt Algorithmus fest
gsl_root_fsolver * _solver = nullptr ;

// Intervallgrenzen
double _xmin , _xmax ;

// mögliche Nullstelle nach jedem Iterationsschritt


double _currentRoot ;
328 8 Nullstellenberechnung reeller Funktionen

// Ergebnis hat noch keinen gültigen Wert


std :: optional <double > _result ;

// Status der Iteration


int _status = GSL_CONTINUE ;

// suche Nullstelle für diese Funktion (gsl - Funktion )


gsl_function F;
// obere Iterationsgrenze damit keine Endlosschleife entsteht
size_t MAX_STEPS = 100;

Listing 8.15 cppsrc/nmx-xroot/hpp/xroot–01

Mit dieser Elementfunktion wird ein Iterationsschritt durchgeführt. Es wird geprüft, ob


die Nullstelle gefunden wurde. Ist dies der Fall, erhält die interne Variable _status den
Wert GSL_SUCCESS sonst GSL_CONTINUE.

/**
* @brief next_step einzelner Iterationsschritt
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return GSL_SUCCESS wenn die Bedingung erfüllt ist
*/
inline int next_step ( double epsabs , double epsrel ) {
// nach jeder Iteration ...
if ( gsl_root_fsolver_iterate ( _solver ) != GSL_SUCCESS ) {
throw std :: runtime_error ( nmx_msgx ( _status ));
}
// ... ermittelte Nullstelle
_currentRoot = gsl_root_fsolver_root ( _solver );
// untere Grenze des Intervalls
_xmin = gsl_root_fsolver_x_lower ( _solver );
// obere Grenze des Intervalls
_xmax = gsl_root_fsolver_x_upper ( _solver );
// ist die Bedingung
//|xmin -xmax| < eposabs + epsrel min (| xmin |,| xmax |)
// erfüllt ?
_status = gsl_root_test_interval (_xmin , _xmax , epsabs , epsrel );
return _status ;
}

public :

Listing 8.16 cppsrc/nmx-xroot/hpp/xroot–02

Neben dem Algorithmus zur Nullstellensuche erwartet der Konstruktor die Funktion,
für die eine Nullstelle gesucht wird. Diese kann eine λ-Funktion oder eine Klasse mit
überladenem Klammeroperator sein.

/**
* @brief RootBracketing Konstruktor
* @param pobj Objekt mit überladenem () -operator oder lambda
* @param type Algorithmus z.B. das klassische Bisektionsverfahren
* gsl_root_fsolver_bisection ( default )
*/
template <class T>
8.3 Eine Klasse für das Bisektionsverfahren 329

RootBracketing (T &pobj , const gsl_root_fsolver_type *type =


gsl_root_fsolver_bisection )
: _solver_type { type } {
// gsl -Routine zur Lösung der Gleichung wird initialisiert
_solver = gsl_root_fsolver_alloc ( _solver_type );
// definiere eine gsl - konforme Funktion
// zusätzlicher Parameter zeigt auf pobj
F. params = &pobj;
// sie ruft intern pobj auf und übergibt x
F. function = []( double x, void * params ) -> double {
auto *myFn = static_cast <T *>( params );
return (* myFn)(x);
};
}

Listing 8.17 cppsrc/nmx-xroot/hpp/xroot–03

Der Destruktor gibt alle angeforderten Ressourcen wieder frei.

/**
* Ressourcen werden freigegeben
*/
~ RootBracketing () {
if ( _solver != nullptr ) {
gsl_root_fsolver_free ( _solver );
}
}

Listing 8.18 cppsrc/nmx-xroot/hpp/xroot–04

Es folgen drei Elementfunktionen zum Lesen von intern gespeicherten Parametern wie
Status der Iteration, Intervallgrenzen usw.

/**
* @brief converged Status der Nullstellensuche
* @return true wenn das Verfahren erfolgreich war
*/
inline bool converged () const { return _status == GSL_SUCCESS ; }

Listing 8.19 cppsrc/nmx-xroot/hpp/xroot–05

/**
* @brief xminmax obere und untere Intervallgrenze
* @return [xmin ,xmax]
*/
inline auto xminmax () const { return std :: make_pair (_xmin , _xmax); }

Listing 8.20 cppsrc/nmx-xroot/hpp/xroot–06

/**
* @brief method_used
* @return Name des Algorithmus
*/
330 8 Nullstellenberechnung reeller Funktionen

inline const char * method_used () const { //


return gsl_root_fsolver_name ( _solver );
}

Listing 8.21 cppsrc/nmx-xroot/hpp/xroot–07

Jede der beiden nächsten Funktionen führt eine Nullstellensuche komplett durch. Der
Rückgabewert vom Typ std::optional, ist nur dann gültig, wenn eine Nullstelle gefun-
den wurde. Die erste der beiden Methoden erlaubt es, über ein Funktionsobjekt die
Iterationsschritte aufzuzeichnen.

/**
* @brief apply startet Nullstellensuche
* @param xmin untere Intervallgrenze
* @param xmax obere Intervallgrenze
* @param fn Funktion zum Aufzeichnen der Iterationsschritte
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return gefundene Nullstelle
*/
template <class FN >
auto apply( double xmin , double xmax , FN fn , double epsabs , double
epsrel ) {
_xmin = xmin;
_xmax = xmax;
gsl_root_fsolver_set (_solver , &F, _xmin , _xmax);
for ( size_t i = 1; i <= MAX_STEPS && _status == GSL_CONTINUE ;
++i) {
next_step (epsabs , epsrel );
if ( _status == GSL_SUCCESS ) {
_result = _currentRoot ;
}
fn(i, _xmin , _xmax , _currentRoot );
}
return _result ;
}

Listing 8.22 cppsrc/nmx-xroot/hpp/xroot–08

/**
* @brief apply startet Nullstellensuche
* @param xmin untere Intervallgrenze
* @param xmax obere Intervallgrenze
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return gefundene Nullstelle
*/
inline auto apply ( double xmin , double xmax , double epsabs , double
epsrel ) {
_xmin = xmin;
_xmax = xmax;
gsl_root_fsolver_set (_solver , &F, _xmin , _xmax);
for ( size_t i = 1; i <= MAX_STEPS && _status == GSL_CONTINUE ;
++i) {
next_step (epsabs , epsrel );
8.4 Eine Klasse für das Newton-Verfahren 331

if ( _status == GSL_SUCCESS ) {
_result = _currentRoot ;
}
}
return _result ;
}

Listing 8.23 cppsrc/nmx-xroot/hpp/xroot–09

Die ermittelte Nullstelle kann über eine Elementfunktion abgefragt werden. Diese interne
Variable ist vom Typ std::optional. Dies bedeutet, dass falls keine Nullstelle gefunden
wurde, die Variable keinen gültigen Wert besitzt. Der Status der Variablen kann durch
eine einfache Abfrage getestet werden.

/**
* @brief result liefert die Nullstelle nur falls eine gefunden
* wurde , sonst wird eine Ausnahme ausgeworfen .
* @return gefundene Nullstelle
*/
inline double result () const {
// teste Status der Variablen (für die Nullstelle )
Check :: error_if (nmx_msg , ! _result );
return * _result ;
}
}; // RootBracketing

Listing 8.24 cppsrc/nmx-xroot/hpp/xroot–10

8.4 Eine Klasse für das Newton-Verfahren


Es folgt der Entwurf einer Klasse zur Nullstellensuche mit Verfahren, für welche die
Angabe der Funktionsableitung benötigt wird.

/**
* @brief The FDFSolver class Nullstellensuche Funktion und ihre
* Ableitung werden benötigt
*/
class FDFSolver
{
private :
// gsl interne Struktur
const gsl_root_fdfsolver_type * _solverType ;
// Algorithmus
gsl_root_fdfsolver * _solver = nullptr ;
// suche Nullstelle für diese Funktion (gsl - Funktion )
gsl_function_fdf FDF;
// x_(i+1) ,x_i
double _xnew , _xold ;
// Status der Iteration
int _status ;
// Anzahl der Iterationen
332 8 Nullstellenberechnung reeller Funktionen

size_t _iteration = 0;
// gefundene Nullstelle
std :: optional <double > _foundRoot ;

public :
size_t MAX_STEPS = 100;

private :

Listing 8.25 cppsrc/nmx-xroot/hpp/xroot–11

In Anlehnung an das gsl-Beispiel führen wir eine Elementfunktion zur Durchführung


eines Iterationsschritts ein.

/**
* @brief next_step einzelner Iterationsschritt
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return GSL_SUCCESS wenn die Bedingung erfüllt ist
*/
inline void next_step ( double epsabs , double epsrel ) {
_iteration ++;
_status = gsl_root_fdfsolver_iterate ( _solver );
// speichere zuletzt bekannte Nullstelle
_xold = _xnew ;
// berechne neue Nullstelle
_xnew = gsl_root_fdfsolver_root ( _solver );
// teste Konvergenz
_status = gsl_root_test_delta (_xnew , _xold , epsabs , epsrel );

if ( _status == GSL_SUCCESS ) {
_foundRoot = _xnew ;
}
}

public :

Listing 8.26 cppsrc/nmx-xroot/hpp/xroot–12

Dem Konstruktor wird ein Objekt übergeben, welches die Funktion und ihre Ableitung
definiert. Optional kann die Variante des Algorithmus zur Nullstellensuche angegeben
werden. Sein Name kann mittels einer Elementfunktion abgefragt werden (Listing 8.27).

/**
* @brief FDFSolver Konstruktor
* @param obj enthält Funktion und ihre Ableitung
*/
template < typename T>
FDFSolver (T &obj , const gsl_root_fdfsolver_type *type =
gsl_root_fdfsolver_newton ) {
_solverType = type;
_solver = gsl_root_fdfsolver_alloc ( _solverType );
// verbinde obj mit gsl
FDF. params = &obj;
// Funktion
FDF.f = []( double x, void * params ) { //
8.4 Eine Klasse für das Newton-Verfahren 333

return static_cast <T *>( params )->f(x);


};
// Ableitung
FDF.df = []( double x, void * params ) { //
return static_cast <T *>( params )->df(x);
};
// Funktion und Ableitung in einem Aufruf
FDF.fdf = []( double x, void *params , double *f, double *df) {
T *obj = static_cast <T *>( params );
*f = obj ->f(x);
*df = obj ->df(x);
};
}

Listing 8.27 cppsrc/nmx-xroot/hpp/xroot–13

/**
* @brief method_used
* @return Name des Algorithmus
*/
inline const char * method_used () const { //
return gsl_root_fdfsolver_name ( _solver );
}

Listing 8.28 cppsrc/nmx-xroot/hpp/xroot–15

Der Destruktor gibt die von der gsl benötigten Ressourcen wieder frei.

/**
* Destruktor
*/
inline ~ FDFSolver () {
if ( _solver != nullptr ) {
gsl_root_fdfsolver_free ( _solver );
}
}

Listing 8.29 cppsrc/nmx-xroot/hpp/xroot–14

Der Status und die gefundene Nullstelle können mit der beiden nächsten Elementfunk-
tionen abgefragt werden.

/**
* @brief converged Status der Nullstellensuche
* @return true wenn eine Nullstelle gefunden wurde
*/
inline bool converged () const { return _status == GSL_SUCCESS ; }

Listing 8.30 cppsrc/nmx-xroot/hpp/xroot–16

/**
* @brief getRoot Abfrage der gefundenen Nullstelle
* @return gesuchte Nullstelle
334 8 Nullstellenberechnung reeller Funktionen

*/
inline double result () const {
// teste Status der Variablen (für die Nullstelle )
Check :: error_if (nmx_msg , ! _foundRoot );
return * _foundRoot ;
}

Listing 8.31 cppsrc/nmx-xroot/hpp/xroot–17

Es folgen zwei Methoden, mit denen die Nullstellensuche durchgeführt wird. Mit der
zweiten Variante können die einzelnen Schritte aufgezeichnet werden (Listing 8.33).

/**
* @brief apply Nullstellensuche
* @param startval Startwert
* @param epsabs absoluter Fehler ( optional )
* @param epsrel relativer Fehler ( optional )
* @return gefundene Nullstelle
*/
inline auto apply ( double startval , double epsabs = 0, double epsrel
= 1e -3) {
_xnew = startval ;
gsl_root_fdfsolver_set (_solver , &FDF , _xnew);
do {
next_step (epsabs , epsrel );
} while ( _status == GSL_CONTINUE && _iteration < MAX_STEPS );

return _foundRoot ;
}

Listing 8.32 cppsrc/nmx-xroot/hpp/xroot–18

/**
* @brief apply Nullstellensuche
* @param startval Startwert
* @param epsabs absoluter Fehler ( optional )
* @param epsrel relativer Fehler ( optional )
* @return gefundene Nullstelle
*/
template <class FN >
auto apply( double startval , FN fn , double epsabs = 0, double epsrel
= 1e -3) {
_xnew = startval ;
gsl_root_fdfsolver_set (_solver , &FDF , _xnew);
do {
next_step (epsabs , epsrel );
fn( _iteration , _xold , _xnew );

} while ( _status == GSL_CONTINUE && _iteration < MAX_STEPS );

return _foundRoot ;
}

Listing 8.33 cppsrc/nmx-xroot/hpp/xroot–19


8.5 Beispiele 335

8.5 Beispiele
Alle Beispiele in diesem Abschnitt verwenden zwei Hilfsfunktionen zur Präsentation der
Ergebnisse. Die Funktion Listing 8.34 erzeugt einen Ausgabestrom, welcher alle Dateien
in einem Verzeichnis schreibt. Der erzeugte Datenstrom wird der Funktion Listing 8.35
übergeben, welche dann die Ergebnisse des Programms zur Nullstellensuche zusammen-
fasst.

/**
* @brief get_output_stream
* @param name Name der Datei
* @param fmt Formatierung der Daten
*/
inline auto get_output_stream ( const char *name , Format fmt =
Output :: terminal ) {
auto ofs = Output :: get_stream ("x034", fmt , name);
fmt. set_defaults (ofs);
return ofs;
}

Listing 8.34 cppsrc/apps-x034/hpp/x034–13

/**
* @brief show_results Präsentation der Ergebnisse
* @param ofs Ausgabestrom
* @param obj Klasse zur Nullstellensuche
* @param expected exakt berechneter Wert
*/
template <class T>
inline void show_results (std :: ofstream &ofs , const T &obj , double
expected ) {
ofs << " method used :" << obj. method_used () << std :: endl;
if (obj. converged ()) {
ofs << " success \n";
ofs << "t:" << obj. result () << std :: endl;
ofs << "t ( exact ):" << expected << std :: endl;
ofs << " error :" << abs(obj. result () - expected ) << std :: endl;
} else {
ofs << " failed " << std :: endl;
}
}

Listing 8.35 cppsrc/apps-x034/hpp/x034–14


8.5.1 Berechnung von 5 mit dem Bisektionsverfahren

Gesucht wird der Wert von 5. Wir definieren die Funktion f (x) = x2 − 5 und su-
chen ihre Nullstellen. Wir wenden das Bisektionsverfahren an und zeichnen die einzelnen
Iterationsschritte auf.
336 8 Nullstellenberechnung reeller Funktionen

/**
* @brief root1 Berechnung von sqrt {5} mit dem Bisektionsverfahren
*/
inline void root1 () {
// Suche Nullstelle dieser Funktion
auto function = []( double x) { return pow(x, 2) - 5; };

// öffne Ausgabestrom
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: fixed << std :: setprecision (7);

// Zeichne Iterationsschritte auf


auto monitor = [& ofs ]( size_t i,
double xmin , //
double xmax ,
double result ) {
ofs << i << "\t" << xmin << "\t" //
<< xmax << "\t" << result << std :: endl;
};

//gsl - Klasse zur Nullstellenberechnung


gsl :: RootBracketing rootFinder { function };
ofs << "iter\ txmin \ txmax \ terror \n";
rootFinder . apply (0, 5, monitor , 0, 0.001) ;

// Ausgabe
show_results (ofs , rootFinder , std :: sqrt (5));
}

Listing 8.36 cppsrc/apps-x034/hpp/x034–15

Folgende Ausgabe wird produziert.

iter xmin xmax error


1 0.0000000 2.5000000 1.2500000
2 1.2500000 2.5000000 1.8750000
3 1.8750000 2.5000000 2.1875000
4 2.1875000 2.5000000 2.3437500
5 2.1875000 2.3437500 2.2656250
6 2.1875000 2.2656250 2.2265625
7 2.2265625 2.2656250 2.2460938
8 2.2265625 2.2460938 2.2363281
9 2.2265625 2.2363281 2.2314453
10 2.2314453 2.2363281 2.2338867
11 2.2338867 2.2363281 2.2351074
12 2.2351074 2.2363281 2.2357178
method used : bisection
success
t :2.2357178
t (exact) :2.2360680
error :0.0003502

Listing 8.37 data/x034/terminal–root1.out


8.5 Beispiele 337

Tauschen wir die Zeile gsl::RootBracketing rootFinder{ function}; im Programm durch


gsl::RootBracketing rootFinder{ function, gsl_root_fsolver_brent}; finden wir die ge-
suchte Nullstelle mittels der Brent-Dekker-Methode mit folgendem Ergebnis:

iter xmin xmax error


1 1.0000000 5.0000000 1.0000000
2 1.0000000 3.0000000 3.0000000
3 2.0000000 3.0000000 2.0000000
4 2.2000000 3.0000000 2.2000000
5 2.2000000 2.2366300 2.2366300
6 2.2360634 2.2366300 2.2360634
method used : brent
method used :brent
success
t :2.2360634
t (exact) :2.2360680
error :0.0000046

Listing 8.38 data/x034/terminal–root2.out

Bei beiden Ausgaben werden die einzelnen Iterationsschritte und der Name des Verfah-
rens, dass angewandt wurde, ausgegeben.


8.5.2 Berechnung von 5 mit dem Newton-Verfahren

Wir greifen noch einmal das Beispiel aus Abschnitt 8.5.1 auf und berechnen 5 mit dem
Newton-Verfahren. Die Funktion f (x) = x2 − 5 und ihre Ableitung f ′ (x) = 2x werden
innerhalb eines Objekts definiert. Eine Instanz dieses Objekts und eine λ-Funktion zur
Aufzeichnung der Iterationsschritte werden der neuen Klasse übergeben.

/**
* @brief root4 Berechnung von sqrt (5) mit dem Newton - Verfahren
*/
inline void root4 () {
// Funktion und ihre Ableitung
struct MyFN {
inline double f( double x) const { return pow(x, 2) - 5; }
inline double df( double x) const { return 2 * x; }
} myFN;

// Ausgabestrom
std :: ofstream ofs = get_output_stream ( __func__ );
ofs << std :: setprecision (6) << std :: fixed ;

// zeichne Schritte auf


auto monitor = [& ofs ]( size_t itr , double x0 , double x) {
ofs << itr << "\t" << x << "\t" << x - x0 //
<< "\t" << x - sqrt (5) << std :: endl;
};

// löse die Gleichung


gsl :: FDFSolver rootFinder { myFN };
rootFinder . apply (5, monitor );
338 8 Nullstellenberechnung reeller Funktionen

// zeige Iterationsschritte und Ergebnis


show_results (ofs , rootFinder , std :: sqrt (5));
}

Listing 8.39 cppsrc/apps-x034/hpp/x034–18

Das Ergebnis der Berechnung lautet:

1 3.000000 -2.000000 0.763932


2 2.333333 -0.666667 0.097265
3 2.238095 -0.095238 0.002027
4 2.236069 -0.002026 0.000001
method used : newton
success
t :2.236069
t (exact) :2.236068
error :0.000001

Listing 8.40 data/x034/terminal–root4.out

8.5.3 Zwei sich aufeinander zubewegende Teilchen

Zwei Teilchen mit Massen m1 = 1 kg und m2 = 2 kg ruhen in einem Abstand von


d = 100 m auf horizontalem reibungsfreien Boden. Das erste Teilchen beginnt, sich unter
dem Einfluss einer konstanten horizontalen Kraft F1 = 10 N zu bewegen. Gleichzeitig
beginnt auf das zweite Teilchen eine der F1 entgegengesetzte konstante horizontale Kraft
F2 = 30 N zu wirken (Abb. 8.2).

m1 m2
⃗1
F ⃗2
F

x
x0 = 0 t1 =?, x1 =? x2 = d
Abb. 8.2: Zwei Teilchen bewegen sich unter dem Einfluss von konstanten Kräften auf-
einander zu.

1. Wann und wo werden sich die beiden Teilchen begegnen?


2. Berechnen Sie mittels eines numerischen Verfahrens den Zeitpunkt der Begegnung.
Vergleichen Sie das Ergebnis mit dem exakten Wert.
8.5 Beispiele 339

Lösung

Die Bewegungsgleichungen Wir legen die positive x-Achse entlang der Bewegungs-
richtung des Teilchens m1 . Die beiden Teilchen bewegen sich mit konstanten Beschleu-
nigungen
F1 F2
a1 = , a2 = . (8.1)
m1 m2
Die jeweiligen Ortskoordinaten werden durch

1 1
x1 = a 1 t2 , x 2 = d − a 2 t2 (8.2)
2 2
gegeben. Zum Zeitpunkt der Begegnung t1 gilt
1 1 1
x1 = x2 ⇒ a1 t21 = d − a2 t21 ⇒ d = (a1 + a2 ) t21
2 2 2
und damit √
2d
t1 = ± , (8.3)
a1 + a2
wobei wir nur das positive Vorzeichen beibehalten. Den Ort erhalten wir durch Einsetzen
von t1 in eine der beiden Gleichungen aus Gl. 8.2.

Das Computerprogramm

Kurzbeschreibung Wir werden mit einem kurzen Programm den Zeitpunkt der Begeg-
nung berechnen. Dazu definieren wir eine Funktion f (t) = x1 (t) − x2 (t) (siehe Gl. 8.2).
Der gesuchte Zeitpunkt ist die Nullstelle von f (t).

Quelltext und Daten Wir implementieren f (x) mithilfe einer λ-Funktion. Die Nullstelle
berechnen wir mit unserer gsl-Klasse.

/**
* @brief root3 zwei Teilchen bewegen sich aufeinader zu
*/
inline void root3 () {
// Bewegungsparameter
const double d = 100;
const double m1 = 1, m2 = 2;
const double f1 = 10, f2 = 15;
const double a1 = f1 / m1;
const double a2 = f2 / m2;

// Suche Nullstelle dieser Funktion


auto fn = [d, a1 , a2 ]( double t) {
const auto x1 = 0.5 * a1 * pow(t, 2);
const auto x2 = d - 0.5 * a2 * pow(t, 2);
return (x1 - x2);
};

// Ausgabestrom
std :: ofstream ofs = get_output_stream ( __func__ );
340 8 Nullstellenberechnung reeller Funktionen

ofs << std :: fixed << std :: setprecision (7);

//gsl - Klasse
gsl :: RootBracketing rootFinder { fn , gsl_root_fsolver_brent };
rootFinder . apply (0, 100 , 0, 1e -6);

// Ergebnisse
const auto expected = sqrt (2 * d / (a1 + a2));
show_results (ofs , rootFinder , expected );
}

Listing 8.41 cppsrc/apps-x034/hpp/x034–17

method used :brent


success
t :3.3806170
t (exact) :3.3806170
error :0.0000000

Listing 8.42 data/x034/terminal–root3.out

8.6 Übungsaufgaben
1. Implementieren Sie einen eigenen Algorithmus für das Bisektionsverfahren und be-

nutzen Sie es, um 5 zu berechnen.
2. Schreiben Sie den Quelltext aus Listing 8.39 so um, dass die aufgezeichneten Daten
im LATEX-Tabellenformat abgespeichert werden.
3. Bestimmen Sie die Nullstellen der Funktion f (x) = e3x − x − 6 mittels der beiden in
diesem Kapitel vorgestellter Verfahren.
4. Lösen Sie die Gleichung sin(x) = ex .
5. Ein Teilchen wird aus einer Höhe H mit einer Geschwindigkeit v0 unter einem Winkel
φ abgeworfen. Berechnen Sie den Winkel φ0 für eine Wurfweite xw = 17.55 m, H =
10 m und v0 = 10 m/s. (Hinweis: Stellen Sie die Gleichung für die Wurfparabel auf.)
9 Numerische Integration

Übersicht
9.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
9.2 Numerische Integration mit der GNU Scientific Library . . . . . . . . . . . . . . . . . . . 342
9.3 Klassen als Schnittstellen zur GNU Scientific Library . . . . . . . . . . . . . . . . . . . . . 346
9.4 Berechnung von Beispielintegralen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
9.5 Beispiele aus der Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
9.6 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375

9.1 Einleitung
Durch Anwendung der Ableitungsregeln lässt sich praktisch jede Funktion differenzieren.
Dies gilt nicht für die Integration. Nicht jedes Integral kann analytisch berechnet werden
und selbst wenn eine Lösung existiert, ist diese manchmal nur durch eine Reihe von
geschickten und oftmals komplizierten Substitutionen zu berechnen. Schließlich treten
auch Fälle auf, in denen der Integrand nur in tabellarischer Form vorliegt. In all diesen
Situationen bietet es sich an, numerisch zu integrieren.
Die numerische Integration basiert auf der Idee, ein Integral durch eine Summe zu
approximieren.
∫ b ∑N
f (x)dx ≈ wi f (xi ), (9.1)
a i=i

wobei die Stützstellen xi Werte innerhalb des Intervalls [a, b] sind. Zu jeder Stützstel-
le existiert ein Integrationsgewicht wi , welches eine reelle Zahl ist. Es existieren zwei
Strategien, Integrale numerisch auszuwerten. Die erste ist, die Stützstellen äquidistant
über das Integrationsintervall zu verteilen und die Gewichte zu berechnen, indem gefor-
dert wird, f (x) durch ein Interpolationspolynom zu ersetzen und dieses zu integrieren.
Dieser Ansatz führt zu den Newton-Cotes-Formeln. Die andere Strategie ist, nicht nur
die Integrationsgewichte, sondern auch die Stützstellen zu berechnen mit dem Ziel, die
Genauigkeit zu optimieren. Diese Methode heißt Gauß-Quadratur. In diesem Fall fordern

wir, dass N i=i wi f (xi ) dasselbe Ergebnis liefert wie der exakte Wert des Integrals eines
Polynoms von Grad 2N − 1. Aus dieser Forderung entsteht ein nichtlineares Gleichungs-
system, dessen Lösung die Integrationsgewichte und Stützstellen sind. Wenn zusätzlich
in Bereichen, in denen eine Funktion stark variiert, das Integrationsintervall in kleinere

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_9
342 9 Numerische Integration

y y

f (x2 ) p(x)

f (x1 )

f (x2 )
f (x1 )
f (x)

f (x3 )

x x
x1 x2 x3 x1 x2 a

(a) (b)
Abb. 9.1: (a) Newton-Codes Quadratur (b) Gauß-Quadratur

unterteilt wird, so spricht man von einem adaptiven Verfahren. Pro Teilintervall werden
zwei Sätze von Stützstellen berechnet, wobei der zweite Satz mehr Stützstellen enthält als
der erste und somit ein Ergebnis mit höherer Genauigkeit berechnet wird. Wenn die Dif-
ferenz der beiden Ergebnisse unter einer vorgegebenen Schranke liegt, wird das Ergebnis
akzeptiert, sonst wird das Intervall weiter unterteilt. Dieser Prozess wird im Normal-
fall von einem Algorithmus gesteuert. Der Preis dieser intelligenten Art zu integrieren
ist, dass pro Teilintervall ein doppelter Satz von Stützstellen und Integrationsgewich-
ten berechnet werden muss, denn bei einer Gauß-Quadratur sind n Stützstellen nicht
in n + 1 Stützstellen enthalten. Die gute Nachricht ist, dass Techniken wie die Gauß-
Kronrod-Formeln existieren, die diesen Aufwand reduzieren. Hier werden bestehende n
Stützstellen wiederverwendet, um zusätzliche n + 1 zu berechnen.

9.2 Numerische Integration mit der GNU Scientific


Library
Im Funktionsumfang der gsl finden wir eine Reihe von Routinen zur Berechnung von
eindimensionalen Integralen, die hauptsächlich nach der Gauß-Methode arbeiten. Den
meisten dieser Routinen wird eine Schranke vorgegeben, sodass die geschätzte Abwei-
chung des Ergebnisses vom wahrem Wert unter dieser Schranke liegt. Wie auch alle
anderen Funktionen der Bibliothek sind die Routinen in C geschrieben.

9.2.1 Vom C-Programm zu C++

Wir beginnen mit der Erstellung eines Programms zur numerischen Auswertung von
∫ 1
ln (ax)
√ dx = −4. (9.2)
0 x
9.2 Numerische Integration mit der GNU Scientific Library 343

Wir orientieren uns dabei an einem Beispiel, dass auf der Webseite der GNU Scien-
tific Library zu finden ist. Das Programm beginnt mit der Definition des Integranden
f (x) = ln√ax
x
, allerdings in einer ungewohnten Form, da die Funktion statt einem Ein-
gabeargument zwei erwartet. Über diesen zweiten Parameter können der Funktion zu-
sätzliche Daten wie hier der Wert von a übergeben werden. Erreicht wird dadurch, dass
nicht nur eine Funktion, sondern gleich eine ganze Funktionsfamilie implementiert wird.

/**
* @brief f Integrand (gsl - Funktion )
* @param x Variable
* @param params zusätzliche Parameter
* @return Funktionswert
*/
double f( double x, void * params ) {
double alpha = *( double *) params ;
double f = log( alpha * x) / sqrt(x);
return f;
}

Listing 9.1 cppsrc/apps-x039/hpp/x039–01

Wir fahren mit dem Quelltext zur numerischen Berechnung des Integrals (Gl. 9.2) fort.

/**
* @brief gslex numerische Integration Beispiel mit Kommentaren
* https :// www.gnu.org/ software /gsl/doc/html/ integration .html# examples
*/
void gslex () {
// gsl Struktur : Verwaltung von Daten
gsl_integration_workspace *w =
gsl_integration_workspace_alloc (1000) ;

// exaktes Ergebnis
double expected = -4.0;
// Parameter des Integranden
double alpha = 1.0;
// Ausgabewerte der Integrationsroutine
double result , error ;
//gsl - Funktion ( übergibt Integranden an die Integrationsroutine )
gsl_function F;
F. function = &f;
F. params = & alpha ;
// numerische Integration , Integrand hat Singularität
gsl_integration_qags (&F, 0, 1, 0, 1e-7, 1000 , w, &result , &error);
// Ausgabe der Daten
printf (" result = % .18f\n", result );
printf ("exact result = % .18f\n", expected );
printf (" estimated error = % .18f\n", error);
printf (" actual error = % .18f\n", result - expected );
printf (" intervals = %zu\n", w->size);

// Freigabe des workspaces


gsl_integration_workspace_free (w);
}

Listing 9.2 cppsrc/apps-x039/hpp/x039–02


344 9 Numerische Integration

Die Funktion beginnt mit der Definition eines Zeigers auf eine Datenstruktur vom
Typ gsl_integration _workspace. Diese Struktur, die wir der Einfachheit halber auch
workspace nennen werden, dient zur Verwaltung der Teilintervalle im Fall einer adapti-
ven Integration und den dazugehörigen Ergebnissen und Fehlerabschätzungen.
Nachdem der Wert von a und der exakte Wert des Integrals initialisiert wird, folgt die
Definition einer gsl_function. Es handelt sich hierbei nicht um eine Funktion, sondern
vielmehr um einer Datenstruktur (siehe Abschnitt 8.2), die dazu dient, den Integranden
an die Integrationsroutine zu übergeben .
Die numerische Integration erfolgt mittels der Routine gsl_integration_qags, die auf
Integranden mit Singularitäten innerhalb des Integrationsintervalls spezialisiert ist. Die-
ser Routine wird der Zeiger auf das workspace, die gsl_function, die Fehlerschranken
sowie eine obere Grenze für die Anzahl der Intervalle, in denen das Integrationsintervall
aufgeteilt werden soll, übergeben. Nach der Ausgabe der Ergebnisse wird der für das
workspace reservierte Speicher wieder freigegeben.
Die Logik hinter dem Aufbau des Programms ist nicht schwierig zu verstehen, sie ist
aber gewöhnungsbedürftig, denn sie folgt nicht dem von der Mathematik gewohnten Mus-
∫b
ter, eine Funktion f (x) zu definieren und dann das Integral a f (x)dx zu berechnen. Wir
denken hier an die zwei Eingabeparameter des Integranden oder seine indirekte Überga-
be an die Integrationsroutine. Die manuelle Speicherfreigabe ist ein weiterer Punkt, der
eine mögliche Fehlerquelle darstellt. Es sei an dieser Stelle betont, dass trotz Kritik diese
Vorgehensweise einem C-Programm einen hohen Grad an Flexibilität verleiht. Die Frage
ist, ob durch den Einsatz von C++ Verbesserungen möglich sind, die den Quelltext kürzer
machen, die Programmierung vereinfachen und die Speicherverwaltung automatisieren.
Die Antwort ist Ja und das C++-Programm könnte dann so aussehen:

/**
* @brief ex1
*/
inline void ex1 () {
// Integrand
auto fn = []( double x) { return log(x) / sqrt(x); };
//gsl - Klasse
gsl :: Integral integral (fn);
// adaptive Integration von fn (mit Singularitäten )
// im Intervall [0 ,1]
integral .qags (0, 1);
// Ausgabe
const double expected = -4;
std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );
integral . show_results (sout , expected );
}

Listing 9.3 cppsrc/apps-x039/hpp/x039–05

Der Quelltext ist deutlich kürzer geworden und die manuelle Speicherverwaltung ver-
schwunden. Der Integrand wird im gewohnten Stil als λ-Funktion mit einem Eingabepa-
rameter definiert und ohne Umwege integriert.
9.2 Numerische Integration mit der GNU Scientific Library 345

Bei CQUAD handelt es sich um eine allgemeine gsl-Routine, die auch für die Integration
singulärer Integranden geeignet ist. Das Integrationsintervall wird in kleinere unterteilt.
Jedes der Subintervalle wird mit 4 Clenshaw-Curtis-Punkte integriert und das Ergebnis
mit dem aus der Integration mit 5 Punkten verglichen. Liegt die Differenz nicht unter
einer vorgegebenen Fehlerschranke, wird die Prozedur mit 8 und 9 Punkten wiederholt.
Ist auch dieser Versuch nicht erfolgreich, folgt eine Integration mit 16 und 17 Punkten.
Wenn die Integration auch mit 32 und 33 Punkten nicht zum Erfolg führt, wird das Inter-
vall halbiert und die Prozedur für jedes Subintervall wiederholt. Der folgende Quelltext
zeigt, wie das Integral Gl. 9.2 mit dieser Methode berechnet wird.

/**
* @brief gslex_cquad numerische Integration mit CQUAD
* (gsl - Beispiel mit Kommentaren )
*/
void gslex_cquad () {
// gsl Struktur : Verwaltung von Daten
gsl_integration_cquad_workspace *w =
gsl_integration_cquad_workspace_alloc (100);
// analytisch berechnetes Ergebnis
double expected = -4;
// Parameter des Integranden
double alpha = 1.0;
// Ausgabewerte der Integrationsroutine
double result , error ;
// Anzahl der Funktionsauswertungen
size_t nevals ;
//gsl - Funktion ( übergibt Integranden an die Integrationsroutine )
gsl_function F;
F. function = &f;
F. params = & alpha ;
// numerische Integration , Integrand hat Singularität
gsl_integration_cquad (&F, 0, 1, 0, 1e-7, w, &result , &error ,
& nevals );
// Ausgabe der Daten
printf (" result = % .18f\n", result );
printf ("exact result = % .18f\n", expected );
printf (" estimated error = % .18f\n", error);
printf (" actual error = % .18f\n", result - expected );
printf (" intervals = %zu\n", w->size);

// Freigabe des workspace


gsl_integration_cquad_workspace_free (w);
}

Listing 9.4 cppsrc/apps-x039/hpp/x039–21

Wir stellen eine große Ähnlichkeit zum ersten Beispielprogramm (Listing 9.2) fest. Auch
hier wird ein workspace initialisiert und einer Routine übergeben, welche die Integration
durchführt und anschließend die angeforderten Ressourcen wieder freigibt.
346 9 Numerische Integration

9.3 Klassen als Schnittstellen zur GNU Scientific


Library
Der Weg vom C- zu einem C++-Programm setzt die Einführung von Klassen voraus,
die durch Kapselung von gsl-Elementen die Programmierarbeit vereinfachen. Ein Stu-
dium der Beispielprogramme ( Listing 9.2 und Listing 9.4) sowie der Online-Hilfe zeigt,
welche Gemeinsamkeiten zwischen den gsl-Routinen bestehen und wo die Unterschiede
liegen. Die gemeinsamen Elemente werden wir in einer Basisklasse unterbringen und die
Unterschiede werden die Bausteine für die abgeleiteten Klassen sein.
Wir stellen fest, dass die Funktionen zur numerischen Integration innerhalb der
gsl in Gruppen unterteilt sind und jeder Gruppe ein workspace-Typ zugeordnet wird,
der zu dem einer anderen Gruppe nicht identisch ist (gsl_integration_workspace und
gsl_integration_cquad_workspace). Die Art, mit der aber mit workspace-Instanzen gear-
beitet wird, bleibt immer dieselbe. Sie werden initialisiert und einer Integrationsroutine
übergeben, wie auch in Listing 9.1 und Listing 9.4 zu sehen ist. Wir denken einen Schritt
voraus und führen ein Klassentemplate ein, das mit einem generischen workspace, mit
Variablen zur Speicherung des Ergebnisses und der Fehlerabschätzung ausgestattet ist.
Es soll die Rolle der Basisklasse übernehmen, welches die gemeinsamen Elemente aller
abgeleiteten Klassen enthält.

/**
* @brief The BaseIntegral class Basisklasse zur numerischen
* Integration
* @param T workspace -Typ
*/
template <class T>
class BaseIntegral
{
protected :
T * _workspace = nullptr ;
gsl_function _gslFunction ;
double _result , _error ;
int _errorCode ;

Listing 9.5 cppsrc/nmx-xquad/hpp/xquad–01

Der Klasse wird der Integrand über ihren Konstruktor zugewiesen. Wir erinnern uns,
dass diese Zuweisung im C-Programm (Listing 9.1) indirekt geschieht. Diesen Vorgang
wollen wir innerhalb des Konstruktors so implementieren, dass er automatisch ausge-
führt wird. Wir platzieren diesen einzigen Konstruktor im protected-Bereich, sodass er
nur von abgeleiteten Klassen aufgerufen werden kann. Er erwartet als Eingabe die zu
integrierende Funktion.

/**
* @brief BaseIntegral Konstruktor
* @param fn Integrand ( Funktionsobjekt muss über einen
* überladenen Klammeroperator verfügen )
*/
9.3 Klassen als Schnittstellen zur GNU Scientific Library 347

template <class FN >


BaseIntegral (FN &fn)
: _gslFunction { nullptr , nullptr }
, _result (0)
, _error (0) {
// Definition der gsl - Funktion . Es werden das Funktionsargument
// und ein zusätzlicher Parameter übergeben
_gslFunction . function = []( double x, void * params ) {
// der zusätzliche Parameter enthält einen Zeiger auf
// die Funktion fn ...
FN *_obj = static_cast <FN *>( params );
// ihr wird hier x übergeben , um den Funktionswert zu
// berechnen
return (* _obj)(x);
};
// hier wird fn registriert
_gslFunction . params = static_cast <void *>(&fn);
}

public :

Listing 9.6 cppsrc/nmx-xquad/hpp/xquad–02

Zur einfachen Präsentation der Ergebnisse implementieren wir folgende Hilfsfunktion.

/**
* @brief show_results einfache Ausgabe des Ergebnisses
* mit Zusatzinformationen
* @param sout Ausgabestrom
* @param expected das exakte Ergebnis
*/
inline void show_results (std :: ofstream &sout , double expected ) {
sout << " result = " << result () << "\n";
sout << " exact result = " << expected << "\n";
sout << " estimated error = " << error () << "\n";
sout << " actual error = " << abs( result () - expected ) << "\n";
sout << " intervals = " << intervals () << "\n";
}

Listing 9.7 cppsrc/nmx-xquad/hpp/xquad–03

Der Rest der Klasse enthält Methoden zur Abfrage des Integrationsergebnisses, der Feh-
lerabschätzung und der Anzahl der verwendeten Teilintervalle.

/**
* @brief result
* @return Ergebnis der Integration
*/
inline double result () const { return _result ; }

Listing 9.8 cppsrc/nmx-xquad/hpp/xquad–04

/**
* @brief error
* @return Fehlerabschätzung
348 9 Numerische Integration

*/
inline double error () const { return _error ; }

Listing 9.9 cppsrc/nmx-xquad/hpp/xquad–05

/**
* @brief intervals
* @return Anzahl der eingesetzten Teilintervalle
*/
inline size_t intervals () const { return _workspace ->size; }
}; // BaseIntegral

Listing 9.10 cppsrc/nmx-xquad/hpp/xquad–06

Eine Gruppe der gsl-Integrationsroutinen integriert Funktionen nach der Gauß-Methode.


Sie umfasst Routinen zur Integration von einfachen Fällen bis hin zu Integranden mit
Singularitäten innerhalb des Integrationsintervalls sowie Integrale, mit denen über ganz R
oder nur über (−∞, a] bzw. über [a, +∞) integriert wird. Alle Mitglieder dieser Gruppe
(mit Ausnahme von einem) arbeiten mit dem gleichen workspace-Typ und sind adaptive
Verfahren. Es ist sinnvoll, diese Routinen innerhalb einer Klasse durch Elementfunktionen
abzubilden.

/**
* @brief The Integral class erste Varianten der gsl - Klasse
* zur numerischen Integration . Der Template - Parameter ist
* der spezielle workspace -Typ für diese Gruppe von Routinen
*/
class Integral : public BaseIntegral < gsl_integration_workspace >
{
public :

Listing 9.11 cppsrc/nmx-xquad/hpp/xquad–07

Der Konstruktor der ersten abgeleiteten Klasse erwartet als Eingabe den Integranden
und die maximale Anzahl der Teilintervalle, in die das Integrationsintervall aufgeteilt
werden darf, wenn eine Schrittweitenanpassung erfolgt.

/**
* @brief Integral Konstruktor
* @param fn der Integrand
* @param size [a,b] maximale Anzahl der Intervalle
* [x_i ,x_i +1]
*/
template <class FN >
inline Integral (FN &fn , size_t size = 1000)
: BaseIntegral (fn) {
_workspace = gsl_integration_workspace_alloc (size);
}

Listing 9.12 cppsrc/nmx-xquad/hpp/xquad–08


9.3 Klassen als Schnittstellen zur GNU Scientific Library 349

Ein Kopierkonstruktor und ein Zuweisungsoperator sind für diese Klasse nicht zugelassen,
denn dies würde zu unnötigem Programmieraufwand führen.

/**
* @brief Integral
* @param i
*/
Integral ( const Integral &i) = delete ;

Listing 9.13 cppsrc/nmx-xquad/hpp/xquad–09

/**
* @brief operator =
* @param i
* @return
*/
Integral & operator =( const Integral &i) = delete ;

Listing 9.14 cppsrc/nmx-xquad/hpp/xquad–10

Der Destruktor gibt den Speicher, der für den workspace reserviert wurde, wieder frei.
Dies geschieht automatisch und ist an die Lebensdauer der dazugehörigen Klassenin-
stanz gebunden. Der Programmierer braucht sich nicht um die Speicherverwaltung zu
kümmern.

/**
* gsl - Speicher wird freigegeben
**/
~ Integral () {
if ( _workspace != nullptr ) {
gsl_integration_workspace_free ( _workspace );
}
}

Listing 9.15 cppsrc/nmx-xquad/hpp/xquad–11

Die erste Elementfunktionen ruft eine gsl-Routine auf, die keinen workspace benötigt.
Wir nehmen dennoch diese Funktion in die Klasse mit auf, um von der automatischen
Zuweisung des Integranden zu profitieren. Obwohl intern nicht mit einem adaptiven Ver-
fahren gearbeitet wird, erfolgt die Integration in mindestens zwei Schritten. Es wird der
Reihe nach mit 10, 21, 43, 87 Gauss-Kronrod-Punkte über das ganze Integrationsinter-
vall integriert, bis die Differenz zweier nacheinander folgenden Ergebnisse unterhalb einer
vorgegebenen Schranke liegt.

/**
* @brief qng Integration von stetigen Funktionen keine
* automatische Schrittweitenanpassung
* @param xmin untere Intervallgrenze
* @param xmax obere Intervallgrenze
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
350 9 Numerische Integration

* @return Fehlercode und Anzahl der Funktionsauswertungen


*/
inline auto qng( double xmin , double xmax , double epsabs = 1e-9,
double epsrel = 1e -9) {
size_t nofFunEvals ;
// clang - format off
_errorCode = gsl_integration_qng (& _gslFunction , xmin , xmax ,
epsabs ,epsrel ,& _result ,
&_error ,& nofFunEvals );
// clang - format on
return std :: make_pair ( _errorCode , nofFunEvals );
}

Listing 9.16 cppsrc/nmx-xquad/hpp/xquad–12

Es folgt ein adaptives Verfahren mit vorgegebener Anzahl von Gauß-Punkten.

/**
* @brief qag numerische Integration mit automatischer
* Schrittweitenanpassung (Gauß - Kronrod )
* @param xmin untere IntervallGrenze
* @param xmax obere IntervallGrenze
* @param key Anzahl der Gauß - Punkte
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return Fehlercode
*/
inline int qag( double xmin , double xmax , int key , double epsabs =
1e-9, double epsrel = 1e -9) {
Check :: error_if_not (nmx_msg , key >= 1 && key <= 6);
// clang - format off
_errorCode = gsl_integration_qag (& _gslFunction , xmin , xmax ,
epsabs , epsrel , _workspace ->limit ,
key , _workspace , &_result , & _error );
// clang - format on
return _errorCode ;
}

Listing 9.17 cppsrc/nmx-xquad/hpp/xquad–13

Wenn innerhalb des Integrationsintervalls eine Singularität des Integraden vorliegt, so ist
die nächste Methode die richtige Wahl.

/**
* @brief qags numerische Integration mit automatischer
* Schrittweitenanpassung von singulären Funktionen
* @param xmin untere Intervallgrenze
* @param xmax obere Intervallgrenze
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return Fehlercode
*/
inline int qags( double xmin , double xmax , double epsabs = 1e-9,
double epsrel = 1e -9) {
// clang - format off
_errorCode = gsl_integration_qags (& _gslFunction , xmin , xmax ,
epsabs , epsrel ,_workspace ->limit ,
9.3 Klassen als Schnittstellen zur GNU Scientific Library 351

_workspace ,& _result , & _error );


// clang - format on
return _errorCode ;
}

Listing 9.18 cppsrc/nmx-xquad/hpp/xquad–14

Es folgen drei Elementfunktionen, welche für die Integration von Funktionen über ganz
R (Listing 9.19) und halbendliche Intervalle (Listing 9.20, Listing 9.21) zuständig sind.

/**
* @brief qagi numerische Integration mit automatischer
* Schrittweitenanpassung von - Unendlich bis + Unendlich
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return Fehlercode
*/
inline int qagi( double epsabs = 1e-9, double epsrel = 1e -9) {
// clang - format off
_errorCode = gsl_integration_qagi (& _gslFunction , epsabs ,
epsrel ,_workspace ->limit ,_workspace ,
&_result , & _error );
// clang - format on
return _errorCode ;
}

Listing 9.19 cppsrc/nmx-xquad/hpp/xquad–15

/**
* @brief qagiu numerische Integration mit automatischer
* Schrittweitenanpassung von xmin bis + Unendlich
* @param xmin untere Integrationsgrenze
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return Fehlercode
*/
inline int qagiu ( double xmin , double epsabs = 0, double epsrel =
1e -9) {
// clang - format off
_errorCode = gsl_integration_qagiu (& _gslFunction , xmin ,
epsabs , epsrel ,
_workspace ->limit , _workspace ,
&_result , & _error );
// clang - format on
return _errorCode ;
}

Listing 9.20 cppsrc/nmx-xquad/hpp/xquad–16

/**
* @brief qagil numerische Integration mit automatischer
* Schrittweitenanpassung von - Unendlich bis xmax
* @param xmax obere Integrationsgrenze
* @param epsabs absoluter Fehler
352 9 Numerische Integration

* @param epsrel relativer Fehler


* @return Fehlercode
*/
inline int qagil ( double xmax , double epsabs = 1e-9, double epsrel =
1e -9) {
// clang - format off
_errorCode = gsl_integration_qagil (& _gslFunction , xmax ,
epsabs , epsrel ,
_workspace ->limit ,_workspace ,
&_result ,& _error );
// clang - format on
return _errorCode ;
}
}; // Integral

Listing 9.21 cppsrc/nmx-xquad/hpp/xquad–17

Die zweite von der Basisklasse abgeleitete Klasse enthält nur eine Elementfunktion. Über
diese Methode werden alle Funktionen in einem endlichen Integrationsintervall integriert.
Dies gilt auch für singuläre Funktionen und solche, die an bestimmten Stellen NaN (Not a
Number) als Rückgabewert liefern z.B. sinx x an der Stelle x = 0. Die Initialisierung und
Freigabe des workspace sowie die Zuweisung des Integranden erfolgt wie gewohnt über
den Konstruktor und den Destruktor.

/**
* @brief The CQUADIntegral class adaptive numerische Integration
* geeignet für Integranden mit Singularitäten oder solchen die
* als Funktionswert an einigen Stellen Unendlich oder NaN zurückgeben
*/
class CQUADIntegral : public
BaseIntegral < gsl_integration_cquad_workspace >
{
private :
size_t _nFunctionEvals ; // Anzahl der Funktionsaufrufe
public :

Listing 9.22 cppsrc/nmx-xquad/hpp/xquad–18

/**
* @brief CQUADIntegral Konstruktor
* @param dim minimale Anzahl der Intervalle 3 in den meisten
* Fällen ist 100 ausreichend
*/
template <class FN >
CQUADIntegral (FN &fn , size_t dim = 100)
: BaseIntegral (fn) {
_workspace = gsl_integration_cquad_workspace_alloc (dim);
// Überprüfung der minimalen Anzahl der Intervalle
Check :: error_if_not (nmx_msg , dim >= 3);
}

Listing 9.23 cppsrc/nmx-xquad/hpp/xquad–19


9.4 Berechnung von Beispielintegralen 353

/**
* Destruktor , Freigabe des von der gsl reservierten Speichers
*/
~ CQUADIntegral () {
if ( _workspace != nullptr ) {
gsl_integration_cquad_workspace_free ( _workspace );
}
}

Listing 9.24 cppsrc/nmx-xquad/hpp/xquad–20

/**
* @brief apply Ausführung der numerischen Integration
* @param fn die zu integrierende Funktion
* @param xmin untere Intervallgrenze
* @param xmax obere Intervallgrenze
* @param epsabs absoluter Fehler
* @param epsrel relativer Fehler
* @return Fehlercode
*/
int apply( double xmin , double xmax , double epsabs = 1e-9, double
epsrel = 1e -9) {
// clang - format off
_errorCode = gsl_integration_cquad (& _gslFunction , xmin , xmax ,
epsabs , epsrel ,
_workspace , &_result ,
&_error , & _nFunctionEvals );
// clang - format on
return _errorCode ;
}

Listing 9.25 cppsrc/nmx-xquad/hpp/xquad–21

9.4 Berechnung von Beispielintegralen


Zum Schreiben der Daten in Dateien werden wir in diesem Abschnitt folgende Hilfsfunk-
tion einsetzen:

/**
* @brief get_output_stream generiere Ausgabestrom
* @param name Name der Datei
* @param fmt Formatierungsanweisung
*/
static inline auto get_output_stream ( const char *name , Format fmt) {
auto ofs = Output :: get_stream ("x039", fmt , name);
return ofs;
}

Listing 9.26 cppsrc/apps-x039/hpp/x039–03


354 9 Numerische Integration

Sind die Schnittstellen zur gsl einmal erstellt, kümmern wir uns nicht mehr um die Im-
plementierungsdetails, sondern nutzen sie, um Integrale zu berechnen. Als erstes Beispiel
werden wir mit einem Programm folgendes Integral auswerten:
∫ 1
4
dx = π (9.3)
0 1 + x2

/**
* @brief ex0 nicht adaptive Gauß - Kronrod Integration
*/
inline void ex0 () {
// Integrand
auto fn = []( double x) { return 4.0 / (1 + pow(x, 2)); };
//gsl - Klasse
gsl :: Integral integral (fn);
// nicht adaptive Integration von fn im Intervall [0 ,1]
integral .qng (0, 1);
// Ausgabe von Ergebnissen
const double expected = Math :: PI;
std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );
integral . show_results (sout , expected );
}

Listing 9.27 cppsrc/apps-x039/hpp/x039–04

Wir haben hier ein nichtadaptives Verfahren (Listing 9.16) eingesetzt. Folgende Ergeb-
nisse werden in eine Datei geschrieben:

result = 3.142 e+00


exact result = 3.142 e+00
estimated error = 3.488e -14
actual error = 4.441e -16
intervals = 0

Listing 9.28 data/x039/terminal–ex0.out

In einem weiteren Beispiel wollen wir das Integral


∫ 1
1
√ dx = 2.0348053 (9.4)
0 sin x
numerisch lösen. Dazu wählen wir ein adaptives Verfahren (Listing 9.18), dass auf Inte-
granden mit Singularitäten spezialisiert ist.

/**
* @brief ex1
*/
inline void ex15 () {
// Integrand
auto fn = []( double x) { return 1 / sqrt(sin(x)); };
//gsl - Klasse
gsl :: Integral integral (fn);
// adaptive integration (mit Singularitäten ) im Intervall [0 ,1]
9.4 Berechnung von Beispielintegralen 355

integral .qags (0, 1);


// Ausgabe
const double expected = 2.0348053;
std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );
integral . show_results (sout , expected );
}

Listing 9.29 cppsrc/apps-x039/hpp/x039–19

Der Programmaufbau folgt dem gewohntem Muster. Nach Ausführung des Programms
erhalten wir

result = 2.035 e+00


exact result = 2.035 e+00
estimated error = 9.121e -10
actual error = 1.921e -08
intervals = 6

Listing 9.30 data/x039/terminal–ex15.out

Als nächstes betrachten wir das Integral


∫ ∞
x2
e− 2 dx ≈ 2.5066 (9.5)
−∞

und lösen es mit einem Programm, welches ebenfalls eine Elementfunktion (Listing 9.19)
der Klasse nutzt und sehr einfach zu implementieren ist.

/**
* @brief ex2
*/
inline void ex2 () {
// Integrand
auto fn = []( double x) { return exp (-.5 * pow(x, 2)); };
//gsl - Klasse
gsl :: Integral integral (fn);
// adaptive Integration von fn von -Unendlich bis + Unendlich
integral .qagi ();
// Ausgabe
const double expected = 2.5066282746310002;
std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );
integral . show_results (sout , expected );
}

Listing 9.31 cppsrc/apps-x039/hpp/x039–06

Das Ergebnis nach Ausführung des Programms lautet:

result = 2.507 e+00


exact result = 2.507 e+00
estimated error = 6.229e -11
actual error = 0.000 e+00
intervals = 6

Listing 9.32 data/x039/terminal–ex2.out


356 9 Numerische Integration

Für eine Integration im Intervall [a, +∞) wie für


∫ ∞
x2
e− 2 dx ≈ 1.25331 (9.6)
0

verwenden wir die dafür spezialisierte Elementfunktion (Listing 9.20).

/**
* @brief ex3
*/
inline void ex3 () {
// Integrand
auto fn = []( double x) { return exp (-.5 * pow(x, 2)); };
//gsl - Klasse
gsl :: Integral integral (fn);
// adaptive Integration von fn von 0 bis + Unendlich
integral .qagiu (0);
// Ausgabe
const double expected = 1.2533141373155001;
std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );
integral . show_results (sout , expected );
}

Listing 9.33 cppsrc/apps-x039/hpp/x039–07

Wir erhalten nach Ausführung des Programms folgende Ausgabe:

result = 1.253 e+00


exact result = 1.253 e+00
estimated error = 3.115e -11
actual error = 0.000 e+00
intervals = 6

Listing 9.34 data/x039/terminal–ex3.out

Anhand des nächsten Beispiels soll demonstriert werden, wie Integrale im Bereich
(−∞, a) numerisch berechnet werden.
∫ 0 x2
e− 2 dx ≈ 1.25331 (9.7)
−∞

Mithilfe von Listing 9.21 und mit folgendem Programm

/**
* @brief ex4
*/
inline void ex4 () {
// Integrand
auto fn = []( double x) { return exp (-.5 * pow(x, 2)); };
//gsl - Klasse
gsl :: Integral integral (fn);
// adaptive Integration von fn von -Unendlich bis 0
integral .qagil (0);
// Ausgabe
const double expected = 1.2533141373155001;
9.4 Berechnung von Beispielintegralen 357

std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );


integral . show_results (sout , expected );
}

Listing 9.35 cppsrc/apps-x039/hpp/x039–08

erhalten wir das Ergebnis:

result = 1.253 e+00


exact result = 1.253 e+00
estimated error = 3.115e -11
actual error = 0.000 e+00
intervals = 6

Listing 9.36 data/x039/terminal–ex4.out

Es folgt die bequeme Art numerisch zu integrieren. Mithilfe der Klasse CQUADIntegral
(Listing 9.22) werden wir folgendes Integral berechnen:
∫ 5 x2
e− 2 dx ≈ 5.70254 × 10−2 (9.8)
2

Das Programm ist schnell erstellt.

/**
* @brief ex5
*/
inline void ex5 () {
// Integrand
auto fn = []( double x) { return exp (-.5 * pow(x, 2)); };

//gsl - Klasse
gsl :: CQUADIntegral integral (fn);
integral .apply (2, 5);

// Ausgabe
const double expected = 5.7025405463957006e -2;
std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );
integral . show_results (sout , expected );
}

Listing 9.37 cppsrc/apps-x039/hpp/x039–09

Es erzeugt folgende Ausgabe:

result = 5.703e -02


exact result = 5.703e -02
estimated error = 5.494e -11
actual error = 0.000 e+00
intervals = 100

Listing 9.38 data/x039/terminal–ex5.out


358 9 Numerische Integration

9.5 Beispiele aus der Physik


9.5.1 Teilchen bremst auf geradliniger Strecke

Ein Teilchen erhält nach einem kurzen Stoß eine Geschwindigkeit v0 und bewegt sich
geradlinig auf horizontalen Boden, bis es nach einer Strecke s zum Stillstand kommt.
Der Gleitreibungskoeffizient zwischen Teilchen und Boden sei µ:

1. Berechnen Sie durch Anwendung des Arbeitssatzes die Geschwindigkeit v0 .


2. Entwerfen Sie ein Computerprogramm, mit dem Sie für jeweils drei Werte des Rei-
bungskoeffizienten µ, v0 berechnen.

Lösung

Wir legen die x-Achse entlang der Bewegungsrichtung und wenden den Arbeitssatz an
∫ s
Kf − Ki = ⃗ x
Rd⃗ (9.9)
0

oder ∫
1 s √
0 − mv02 = ⃗ x ⇒ − 1 mv02 = −µmgs ⇒ v0 =
Rd⃗ 2µgs, (9.10)
2 0 2
⃗ die Reibungskraft ist.
wobei R

Das Computerprogramm

Quelltext und Daten Wir lösen das Integral aus Gl. 9.9 numerisch und berechnen
anschließend die Anfangsgeschwindigkeit über die Formel

∫s
2 Rd⃗⃗ x
v0 = − 0 . (9.11)
m
Für feste Werte m = 10 kg und s = 50 m berechnen wir v0 für µ = 0.1, µ = 0.4 und
µ = 0.8. Die Ergebnisse schreiben wir in eine Datei im LATEX-Tabellen Format.

/**
* @brief ex6 Teilchen bremst auf geradliniger Strecke
*/
inline void ex6 () {
using Data = Data <5 >;
// Masse
const auto mass = 10.0;
// ... kommt nach einer Strecke zum Stillstand
const auto s = 50.0;
const auto g = gravitation :: Earth ::g;
// Datenobjekt zur Speicherung der berechneten Daten
Data data;
// Schleife über Werte des Reibungskoeffizienten
for (auto mu : { 0.1 , 0.4 , 0.8 }) {
9.5 Beispiele aus der Physik 359

auto fn = [mu , mass ]( double x) {


(void) x;
return -mu * mass * gravitation :: Earth ::g;
};
// berechne das Integral
gsl :: Integral integral (fn);
integral .qng (0.0 , s);
const auto v0Exact = sqrt (2 * mu * g * s);
const auto v0Approx = sqrt ( -2.0 / mass * integral . result ());
data += { mass , s, mu , v0Approx , v0Exact };
}
// Ausgabe der Daten
std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);
data.save(ofs , Output :: latex );
}

Listing 9.39 cppsrc/apps-x039/hpp/x039–10

Tab. 9.1: Bremsweg eines Teilchen für drei verschieden Gleitreibungskoeffizienten. In


den beiden letzten Spalten werden die numerisch berechneten und die exakten Werte für
v0 aufgelistet.
m [kg] s [m] µ v0 [m/s] v0,exact [m/s]
1.000e+01 5.000e+01 1.000e-01 9.903e+00 9.903e+00
1.000e+01 5.000e+01 4.000e-01 1.981e+01 1.981e+01
1.000e+01 5.000e+01 8.000e-01 2.801e+01 2.801e+01

9.5.2 Zwei Blöcke und eine Feder

Ein Block der Masse m1 ist an einer horizontalen Feder mit der Federkonstanten k
befestigt (Abb. 9.2b). Die Feder befindet sich im entspannten Zustand. Ein zweiter Block
der Masse m2 wird gegen die Masse m1 gedrückt, sodass die Feder um die Länge x0
gestaucht wird. Das System wird losgelassen und bewegt sich reibungsfrei.

x=0 ⃗1
N
⃗ex m1 ⃗2

x0 N
m1 ⃗k m2

N F
m2
k ⃗
N
w
⃗1 w
⃗2
⃗ex

(a) (b)
Abb. 9.2: System aus zwei Blöcken und eine Feder. (a) Die Feder wird um x0 gestaucht.
(b) Auf die Masse m1 wirken die Federkraft, die Kontaktkraft N⃗ , die von m2 ausgeübt
⃗ 1 und das Gewicht w
wird, die Normalkraft N ⃗ 1 . Auf m2 wirken die Kontaktkraft N⃗ , die

Normalkraft N2 und das Gewicht w ⃗ 2.
360 9 Numerische Integration

1. Wann werden sich die zwei Blöcke trennen? Wie groß wird zu diesem Zeitpunkt ihre
Geschwindigkeit sein?
2. Erstellen Sie ein Computerprogramm, mit dem Sie durch Anwendung eines numeri-
schen Verfahrens die gesuchte Geschwindigkeit berechnen.

Lösung

Wir legen die positive x-Achse parallel zur Federachse und in Richtung der Anfangsaus-
lenkung. Da die zwei Blöcke sich berühren, übt jeweils der eine Block auf den anderen
eine Kontaktkraft aus. Die Kräfte sind nach dem dritten Newton’schen Gesetz vom glei-
chen Betrag und entgegengesetzter Richtung. Solange die Blöcke sich berühren, haben sie
eine gemeinsame Beschleunigung. Wir wenden das zweite Newton’sche Gesetz auf jeden
Block an.


 m ẍ = −kx + N (9.12a)
 1 1
m2 ẍ2 = −N (9.12b)


 ẍ = ẍ = ẍ, (9.12c)
1 2

wobei wir für die gemeinsame Beschleunigung ẍ schreiben werden. Wir addieren Gl. 9.12a
und Gl. 9.12b
(m1 + m2 ) ẍ = −kx
k
⇒ ẍ = − x. (9.13)
m1 + m2
Aus (Gl. 9.12b) folgt für die Kontaktkraft

k m2
N= x. (9.14)
m1 + m2
Wenn die Blöcke sich trennen, ist N = 0 und damit x = 0. Wir schreiben für Gl. 9.13
dv dv dx dv k
= =v =− x.
dt dx dt dx m1 + m2
Nach Trennung der Variablen integrieren wir beide Seiten:
∫ v ∫ 0
kx′
v ′ dv ′ = − dx′
0 x 0 m1 + m2
[ ]v [ ]
2 x0
v ′2 k x′
⇒ =
2 m1 + m2 2
0 0
v2 kx20
⇒ =
2 2 (m1 + m2 )

Wir erhalten: √
k
v= x0 (9.15)
m1 + m2
9.5 Beispiele aus der Physik 361

Quelltext und Daten Wir berechnen die Geschwindigkeit


√ ∫ exakt durch Anwendung von
x0 kx′
Gl. 9.15 sowie numerisch durch die Formel v = 2 0 m1 +m2 dx′ .

/**
* @brief ex14 Zwei Blöcke und eine Feder
*/
inline void ex14 () {
// Federkonstante
const double k = 100;
// Massen der Blöcke
const double m1 = 10, m2 = 5;
// Feder wird um diese Länge gestaucht
const double l = 20. _cm;
// exakte Formel für die Geschwindigkeit
auto vex = [k, m1 , m2 ]( double x) { //
return sqrt(k / (m1 + m2)) * x;
};
// Integrand
auto fn = [k, m1 , m2 ]( double x) { //
return k / (m1 + m2) * x;
};
// numerische Berechnung des Integrals ohne den Faktor 2
gsl :: Integral integral (fn);
integral .qng (0, l);
// Ausgabe in Datei
std :: ofstream ofs = get_output_stream (__func__ , Output :: terminal );
ofs << "exact :" << vex(l) << std :: endl;
ofs << " numerical :" << sqrt (2 * integral . result ()) << std :: endl;
}

Listing 9.40 cppsrc/apps-x039/hpp/x039–18

Nach Ausführung des Programms erhalten wir die Ausgabe:

exact :5.164e -01


numerical :5.164e -01

Listing 9.41 data/x039/terminal–ex14.out

9.5.3 Geschwindigkeitsänderung durch Kraftstoß

Ein Block der Masse m bewegt sich geradlinig und reibungsfrei auf horizontalem Boden.
Eine zeitabhängige horizontale Kraft der Form F (t) = F0 cos ωt wirkt in Richtung der
Geschwindigkeit des Teilchens für ein Zeitintervall ∆t.

1. Berechnen Sie die Geschwindigkeit des Teilchens als Funktion der Zeitdauer des Kraft-
stoßes.
2. Berechnen Sie die gesuchte Geschwindigkeit durch Einsatz eines numerischen Algo-
rithmus.
362 9 Numerische Integration

Lösung

Wir legen die positive x-Achse parallel zur Anfangsgeschwindigkeit des Teilchens und
wenden den Impulssatz an. Die Zeitmessung soll beginnen, wenn die Kraft auf den Block
wirkt, und enden, wenn die Kraft nicht mehr angewandt wird:
∫ t ∫
F0 t
mv = mv0 + F (t′ )dt′ ⇒ v = v0 + cos ωt′ dt′
0 m 0
[ ]t
F0 ′
⇒ v = v0 + sin ωt

0

oder
F0
v = v0 + sin ωt (9.16)

Quelltext und Daten Wir implementieren Gl. 9.16 mittels einer λ-Funktion und berech-
∫t
nen das Integral 0 cos ωt′ dt′ mit einer Elementfunktion der gsl-Klasse. Die Berechnung
der Geschwindigkeit erfolgt für drei Werte für ∆t und die Daten werden im LATEX-Format
in eine Datei geschrieben.

/**
* @brief ex12 Geschwindigkeitsänderung durch Kraftstoß
*/
inline void ex12 () {
nmx ::Data <5> data;
const double v0 = -6;
const double m = 2;
const double F0 = 25, omega = Math :: PI / 10;

auto force = [F0 , omega ]( double t) { // zeitabhängige Kraft


return F0 * cos( omega * t);
};

// exakte Formel für die Geschwindigkeit


auto vex = [v0 , F0 , m, omega ]( double t) { //
return v0 + F0 / (m * omega ) * sin(omega * t);
};

for ( double t : { 2., 10. , 15. }) {


gsl :: Integral integral ( force ); // numerische Berechnung
integral .qag (0, t, 6);
const double vnum = v0 + 1 / m * integral . result ();
data += { t, v0 , force (t), vnum , vex(t) };
}

// Ausgabe in Datei
std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);
data.save(ofs , Output :: latex );
}

Listing 9.42 cppsrc/apps-x039/hpp/x039–16

Es folgt die Ausgabe des Programms als LATEX-Tabelle. Die beiden letzten Spalten ent-
halten den numerisch berechneten und den exakten Wert für die Geschwindigkeit.
9.5 Beispiele aus der Physik 363

Tab. 9.2: Geschwindigkeitsänderung durch Kraftstoß


t [s] v0 [m/s] F [N] v [m/s] vex [m/s]
2.000e+00 -6.000e+00 2.023e+01 1.739e+01 1.739e+01
1.000e+01 -6.000e+00 -2.500e+01 -6.000e+00 -6.000e+00
1.500e+01 -6.000e+00 -4.592e-15 -4.579e+01 -4.579e+01

9.5.4 Teilchen bewegt sich geradlinig mit zeitabhängiger


Beschleunigung

Ein Teilchen bewegt sich auf gerader Strecke mit einer Beschleunigung der Form a =

(c1 t + c2 t) m/s2 , wobei t die Zeit gemessen in Sekunden ist. Für das Teilchen gelten
die Anfangsbedingungen x(0) = x0 und v(0) = v0 .

1. Wie lauten der Ort und die Geschwindigkeit als Funktion der Zeit?
2. Berechnen Sie durch numerische Integration den Ort und die Geschwindigkeiten für
drei unterschiedliche Zeitpunkte und für c2 > 0. Wiederholen Sie die Rechnung für
c2 < 0. Vergleichen Sie jedes mal die Daten mit den exakten Ergebnissen.

Lösung

Wir legen die positive x-Achse entlang der Bewegungsrichtung des Teilchens. Die Ge-
schwindigkeit erhalten wir durch Integration nach der Zeit:
∫ ∫ (
√ t
dv ′ t √)
ẍ = c1 t + c2 t ⇒ dt = c1 t + c2 t dt′
0 dt′ 0
[ ]t
c1 ′2 2 ′3/2
⇒ v(t) − v(0) = t + c2 t
2 3
0
c1 2
⇒ v(t) = t2 + c2 t3/2 + v0 . (9.17)
2 3
Eine erneute Integration nach der Zeit berechnet den Ort als Funktion der Zeit:
∫ t ∫ t( )
dx ′ c1 ′2 2 ′3/2

dt = t + c2 t + v0 dt′
0 dt 0 2 3
[ ]t
c1 ′3 4 ′5/2 ′
⇒ x(t) − x(0) = t + c2 t + v0 t
6 15
0
c1 4
⇒ x(t) = t3 + c2 t5/2 + v0 t + x0 (9.18)
6 15

Das Computerprogramm

Quelltext und Daten Für die Anfangsbedingungen x(0) = 0 und v(0) = 0 und einen
festen Wert c1 = 10 rechnen wir mit c2 = 1 und dann mit c2 = −3. Zur exakten Be-
364 9 Numerische Integration

rechnung der Ergebnisse implementieren wir Gl. 9.17 und Gl. 9.18 als λ-Funktionen. Die
numerische Berechnung der Geschwindigkeit als Integral über die Beschleunigung ist kein
Problem, im Gegensatz zur Berechnung des Ortes. Hier muss über die Geschwindigkeit
numerisch integriert werden. Wenn wir aber nicht die exakte Formel Gl. 9.17 einsetzen
wollen, liegen nur diskrete Daten für v(t) vor. Die numerische Routine wird aber nicht
unbedingt Stützstellen verwenden, für die v(t) bekannt ist. Wir definieren deshalb eine
λ-Funktion, die für jedes t eine numerische Integration über die Beschleunigung ausführt
und somit die Werte der Geschwindigkeit an den gewünschten Stützstellen zurückgibt.
Dies ist nicht die performanteste Lösung. Solange aber keine Berechnung mit sehr großen
Datenmengen erforderlich ist, bleibt der Aufwand vertretbar.

/**
* @brief ex10 Teilchen bewegt sich geradlinig mit zeitabhängiger
* Beschleunigung
*/
inline void ex10 () {
nmx ::Data <7> data;
// Anfangsbedingungen
const double x0 = 0, v0 = 0;
const double c1 = 10;
for (auto c2 : { 1., -3. }) {
// Beschleunigung
auto afn = [c1 , c2 ]( double t) { //
return c1 * t + c2 * sqrt(t);
};
// Funktionen zur exakten Berechnung von
// Geschwindigkeit ...
auto vex = [c1 , c2 , v0 ]( double t) {
const auto static f1 = 2. / 3.;
const auto static f2 = 3. / 2.;
return 0.5 * c1 * pow(t, 2) + f1 * c2 * pow(t, f2) + v0;
};
// ... und Ort
auto xex = [c1 , c2 , v0 , x0 ]( double t) {
const auto static f1 = 1. / 6.;
const auto static f2 = 4. / 15.;
const auto static f3 = 5. / 2.;
return f1 * c1 * pow(t, 3) //
+ f2 * c2 * pow(t, f3) + v0 * t + x0;
};

// Integrand zur numerischen Berechnung des Ortes


// berechnet für jedes t intern über numerische Integration
// die Geschwindigkeit und wird dann zur numerischen
// Berechnung des Ortes eingesetzt .
auto vfn = [& afn ]( double t) {
gsl :: CQUADIntegral integral (afn);
integral . apply (0, t);
return integral . result ();
};
// berechne Daten für t
for ( double tmax : { 10, 20, 30 }) {
gsl :: Integral integral1 (afn);
integral1 .qng(v0 , tmax , 0, 1e -6);
gsl :: Integral integral2 (vfn);
9.5 Beispiele aus der Physik 365

integral2 .qng(x0 , tmax , 0, 1e -6);


data += { c1 ,
c2 ,
tmax ,
integral1 . result (), //
integral2 . result (),
vex(tmax),
xex(tmax) };
}
}

// speichere Daten im LaTeX - Format


std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);
data.save(ofs , Output :: latex );
}

Listing 9.43 cppsrc/apps-x039/hpp/x039–14

Es folgen die berechneten Daten. Die beiden letzten Spalten enthalten die exakt berech-
neten Werte für Geschwindigkeit und Ort.

Tab. 9.3: Teilchen bewegt sich geradlinig mit zeitabhängiger Beschleunigung


c2 [ms− 2 ]
5
c1 [ms−3 ] t [s] v [m/s] x [m] vex [m/s] xex [m]
1.000e+01 1.000e+00 1.000e+01 5.211e+02 1.751e+03 5.211e+02 1.751e+03
1.000e+01 1.000e+00 2.000e+01 2.060e+03 1.381e+04 2.060e+03 1.381e+04
1.000e+01 1.000e+00 3.000e+01 4.610e+03 4.631e+04 4.610e+03 4.631e+04
1.000e+01 -3.000e+00 1.000e+01 4.368e+02 1.414e+03 4.368e+02 1.414e+03
1.000e+01 -3.000e+00 2.000e+01 1.821e+03 1.190e+04 1.821e+03 1.190e+04
1.000e+01 -3.000e+00 3.000e+01 4.171e+03 4.106e+04 4.171e+03 4.106e+04

9.5.5 Teilchen bewegt sich geradlinig mit einer


geschwindigkeitsabhängigen Beschleunigung

Ein ruhendes Teilchen wird entlang einer geraden Strecke mit einer geschwindigkeits-
abhängigen Beschleunigung der Form a = (c1 − c2 v) m/s2 beschleunigt, wobei v die
Geschwindigkeit in m/s gemessen wird.

1. Leiten Sie eine Formel her, welche den Zeitpunkt t berechnet, zu dem die Geschwin-
digkeit einen Wert v erreicht.
2. Schreiben Sie ein Computerprogramm, welches durch den Einsatz eines numerischen
Verfahrens die Zeit berechnet. Variieren Sie die Konstanten c1 und c2 sowie die An-
fangsgeschwindigkeit und stellen die Ergebnisse in eine LATEX-Tabelle dar. Fügen Sie
zum Vergleich die exakten Ergebnisse hinzu.
366 9 Numerische Integration

Lösung

Wir legen die positive x-Achse entlang der Bewegungsrichtung des Teilchens. Wir schrei-
ben den Ausdruck für die Beschleunigung des Teilchens und integrieren durch Trennung
der Variablen: ∫ v ∫ t
dv dv ′
= c1 − c2 v ⇒ ′
= dt′
dt 0 c1 − c2 v 0
[ 1 ( ) ]v
⇒ − ln c1 − c2 v ′ =t
c2 0
1 [ ]
⇒t= ln c1 − ln (c1 − c2 v)
c2
1 c1
⇒t= ln (9.19)
c2 c1 − c2 v

Das Computerprogramm

Quelltext und Daten Das Programm ist schnell erstellt. In geschachtelten Schleifen
durchlaufen wir die Werte für c1 und c2 sowie die Werte für die Geschwindigkeiten.
Innerhalb der Schleifen werden die Funktionen zur exakten Berechnung der Zeit (Gl.
∫ v dv′
9.19) und eine λ-Funktion zur numerischen Berechnung des Integrals 0 c1 −c 2v
′ definiert.

Die Ergebnisse werden in einer Zahlentabelle gespeichert und anschließend in eine Datei
im LATEX-Tabellen-Format geschrieben.

/**
* @brief ex11 Teilchen bewegt sich geradlinig mit einer
* geschwindigkeitsabhängigen Beschleunigung
*/
inline void ex11 () {
nmx ::Data <5> data;

for ( double c1 : { 10, 20 }) {


for ( double c2 : { 0.1 , 0.2 }) {
// Formel zur Berechnung des exakten Werts
auto fnex = [c1 , c2 ]( double v) { //
return 1 / c2 * log(c1 / (c1 - c2 * v));
};

// Integrand des numerischen Integrals


auto fn = [c1 , c2 ]( double v) { //
return 1 / (c1 - c2 * v);
};

// Iteration über die Geschwindigkeiten


for ( double v : { 10, 30 }) {
gsl :: Integral integral (fn);
integral .qag (0, v, 6, 0, 1e -5);
data += { c1 , c2 , v, integral . result (), fnex(v) };
}
}
}

// Ausgabe in Datei
9.5 Beispiele aus der Physik 367

std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);


data.save(ofs , Output :: latex );
}

Listing 9.44 cppsrc/apps-x039/hpp/x039–15

Es folgen die berechneten Daten als LATEX-Tabelle. In den beiden letzten Spalten sind
die numerisch berechneten und die exakten Werte für die Zeit dargestellt.

Tab. 9.4: Teilchen bewegt sich geradlinig mit zeitabhängiger Beschleunigung.


c1 [ms−2 ] c2 [s−1 ] v [m/s] t [s] tex [s]
1.000e+01 1.000e-01 1.000e+01 1.054e+00 1.054e+00
1.000e+01 1.000e-01 3.000e+01 3.567e+00 3.567e+00
1.000e+01 2.000e-01 1.000e+01 1.116e+00 1.116e+00
1.000e+01 2.000e-01 3.000e+01 4.581e+00 4.581e+00
2.000e+01 1.000e-01 1.000e+01 5.129e-01 5.129e-01
2.000e+01 1.000e-01 3.000e+01 1.625e+00 1.625e+00
2.000e+01 2.000e-01 1.000e+01 5.268e-01 5.268e-01
2.000e+01 2.000e-01 3.000e+01 1.783e+00 1.783e+00

9.5.6 Perle bewegt sich reibungsfrei entlang einer Schiene unter


dem Einfluss einer Feder

Eine Perle der Masse m ist an eine Feder mit der Federkonstanten k befestigt und kann
sich horizontal entlang einer Schiene reibungsfrei bewegen (Abb. 9.3). Zum Zeitpunkt
t0 = 0 hat die Feder eine vertikale Ausrichtung. Sie befindet sich im entspannten Zustand
und hat die Länge l0 . Die Perle erhält durch einen Stoß eine horizontale Geschwindigkeit
v0 .
x x

N
v0 v0
⃗x
F

Fk ⃗y
F
l l
l0 l0
ϑ

⃗ey
⃗ex
0 0
(a) (b)
Abb. 9.3: Perle bewegt sich reibungsfrei entlang einer Schiene unter den Einfluss einer
Feder

1. Wir groß ist die Geschwindigkeit der Perle, wenn diese eine Strecke x entlang der
Schiene zurückgelegt hat?
368 9 Numerische Integration

2. Berechnen Sie die gesuchte Geschwindigkeit für eine Reihe von Beispielwerten für
x mittels eines numerischen Verfahrens und vergleichen Sie das Ergebnis mit den
exakten Werten.

Lösung

Wir legen den Ursprung des Koordinatensystems an den unteren Befestigungsort der
Feder (Abb. 9.3b). Aus der Geometrie des Systems folgt:

l0 x
∆l = |l − l0 | = l02 + x2 − l0 , cos ϑ = √ 2 , sin ϑ = √ 2 (9.20)
l 0 + x2 l 0 + x2

Wir wenden das zweite Newton’sche Gesetz an:

m⃗r¨ = F ⃗ ⇒ mẍ⃗ex = −k∆l (sin ϑ⃗ex + cos ϑ⃗ey ) + N⃗ey


⃗k + N (9.21)

Wir multiplizieren die Gleichung einmal skalar mit ⃗ex und dann mit ⃗ey :
 ( )

 k l0 x

 ẍ = − x− √ 2 (9.22a)

 m l0 + x2
( )



 0 = N − kl0 1 − √ l 0

 (9.22b)
l02 + x2

Wir arbeiten mit Gl. 9.22a weiter:


( ) ( )
dv k l0 x dv dx k l0 x
=− x− √ 2 ⇒ =− x− √ 2
dt m l 0 + x2 dx dt m l0 + x2
( )
dv k l0 x
⇒v =− x− √ 2
dx m l0 + x2

Es folgt die Integration durch Trennung der Variablen:


∫ v ∫ ( )
′ ′ k x ′ l 0 x′
v dv = − x −√2 dx′
m l + x ′2
v0 0 0
[ ]v [ √ ]x
′2 ′2
v k x ′2
⇒ =− − l0 l0 + x
2
2 m 2
v0 0
( √ )
v2 v02 k x2 2
⇒ − =− − l0 l0 + x + l0
2 2
2 2 m 2

oder √ ( √ )
2k x2
v= v02 − − l0 l02 + x2 + l02 (9.23)
m 2
9.5 Beispiele aus der Physik 369

Das Computerprogramm

Quelltext und Daten Wir implementieren Gl.


( 9.23 als λ-Funktion
) zur exakten Berech-
∫x ′ ′

nung der Geschwindigkeit. Das Integral 0 x − √ 2 ′2 dx wird numerisch ausge-
l 0 x
l0 +x
wertet und in die Formel
v
u
u ∫ x( ′
)
2k l x
v = tv02 −
0
x′ − √ 2 dx′ (9.24)
m 0 l0 + x′2

eingesetzt, um die Geschwindigkeit zu berechnen.

/**
* @brief ex13 Perle bewegt sich reibungsfrei entlang einer Schiene
* unter dem Einfluss einer Feder
*/
inline void ex13 () {
nmx ::Data <5> data;
const double m = 2;
const double l0 = 1, v0 = 15;
const double k = 4;

auto force = [l0 ]( double x) { //


return x - l0 * x / sqrt(pow(l0 , 2) + pow(x, 2));
};

auto vex = [l0 , v0 , k, m]( double x) {


const static double trm1 = pow(v0 , 2);
const static double trm2 = 2 * k / m;
const double trm3 = sqrt(pow(l0 , 2) + pow(x, 2));
return sqrt(trm1 - trm2 * (0.5 * pow(x, 2) - l0 * trm3 +
pow(l0 , 2)));
};

auto vnum = [v0 , k, m]( const auto & intresult ) { //


return sqrt(pow(v0 , 2) - 2 * k / m * intresult );
};

for ( double x : { 1., 2.5 , 4. }) {


gsl :: Integral integral ( force );
integral .qng (0, x, 0, 1e -6);
data += { v0 ,
x,
force (x), //
vnum( integral . result ()),
vex(x) };
}

// Ausgabe in Datei
std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);
ofs << std :: setprecision (4);
data.save(ofs , Output :: latex );
}

Listing 9.45 cppsrc/apps-x039/hpp/x039–17


370 9 Numerische Integration

Nach Ausführung des Programms erhalten wir folgende Daten, wobei die beiden letzten
Spalten die numerisch berechneten und die exakten Werte für die Geschwindigkeiten
enthalten.

Tab. 9.5: Perle bewegt sich reibungsfrei entlang einer Schiene unter dem Einfluss einer
Feder
v0 [m/s] x [m] F [N] v [m/s] vex [m/s]
1.5000e+01 1.0000e+00 2.9289e-01 1.4989e+01 1.4989e+01
1.5000e+01 2.5000e+00 1.5715e+00 1.4808e+01 1.4808e+01
1.5000e+01 4.0000e+00 3.0299e+00 1.4335e+01 1.4335e+01

9.5.7 Massenschwerpunkt bei kontinuierlichen Massenverteilungen

Berechnen Sie den Massenschwerpunkt

1. eines homogenen dünnen zylindrischen Stabs mit Dichte ρS , Länge l und einen Durch-
messer, der im Vergleich zur Länge vernachlässigbar ist,
2. eines homogenen Halbkreisrings mit Dichte ρK und Ri ≈ Ra ≈ R, wobei Ri der
innere und Ra der äußere Radius ist.
3. Wenn ρS = ρK = 7.7 kg/dm2 und l = 1 m, berechnen Sie mit einem Computerpro-
gramm den Schwerpunkt und das Gewicht für einen dünnen Draht, der einmal die
Form einer Geraden und die eines dünnen Halbkreisrings hat.

dl
l dϕ
dx
ey R dm = ρK dl
ex dm = ρS dx
ex
(a)
(b)
Abb. 9.4: (a) Ein dünner homogener Stab und (b) ein homogener dünner Kreisring. In
beiden Fällen ist die infinitesimale Masse vergrößert dargestellt.

Lösung

Der Massenschwerpunkt eines dünnen Stabes Der Ortsvektor des Schwerpunkts wird
über die Formel ∫l ∫l
xdm xρS dx⃗ex
⃗rcm = ∫0 l = 0∫ l (9.25)
0
dm 0 S
ρ dx
9.5 Beispiele aus der Physik 371

berechnet. Wir integrieren den Nenner und Zähler.


∫ [ ]l ∫ [ ]l
l l
x2 l2
xρS dx = ρS = ρS , ρS dx = ρS x = ρS l (9.26)
0 2 2 0
0 0

und erhalten
l
⃗rcm = ⃗ex . (9.27)
2
Quelltext und Daten Die Integranden in Gl. 9.26 werden im Programm als λ-
Funktionen implementiert. Die gsl-Klasse berechnet die Integrale über diese beiden
Funktionen. Anschließend werden die Masse und der Ortsvektor berechnet und in ei-
ne Datei ausgegeben.

/**
* @brief ex7 Massenmittelpunkt eines homogenen dünnen
* zylindrischen Stabs
*/
inline void ex7 () {
std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );
const double l = 1.0;
const double density = 7.7 / 1e -3;

// Zähler und Nenner


auto fnnum = [ density ]( double x) { return x * density ; };
auto fnden = [ density ]( double x) {
(void) x; // avoid warning
return density ;
};

// integriere Zähler und Nenner von 0 bis l


gsl :: Integral integralnum ( fnnum ), integralden (fnden);
integralnum .qng (0, l);
integralden .qng (0, l);

// berechne Ortsvektor und Masse


const double xcm = integralnum . result () / integralden . result ();
const double m = density * l;

// Ausgabe
sout << "mass =" << m << std :: endl;
sout << "xcm=" << xcm << std :: endl;
sout << "xcm=" << 0.5 * l << " ( exact)" << std :: endl;
}

Listing 9.46 cppsrc/apps-x039/hpp/x039–11

Das Programm produziert folgende Daten.

mass =7.700 e+03


xcm =5.000e -01
xcm =5.000e -01 ( exact )

Listing 9.47 data/x039/terminal–ex7.out


372 9 Numerische Integration

Der Massenschwerpunkt eines dünnen Kreisrings Wenn x = R cos φ, y = R sin φ die


Koordinaten des infinitesimal kleinen Massenelements dm = ρK dlφ sind (Abb. 9.4b),
gilt für die Ortskoordinate des Schwerpunkts
∫l ∫l
0
xdm ydm
xcm = ∫ l , ycm = ∫0 l . (9.28)
0
dm 0
dm

Wir berechnen diese Integrale


∫π
ρK R2 0 cos φdφ
xcm = ∫π =0 (9.29)
ρK R 0 dφ
[ ]π
2
∫π cos φ
ρK R 0 sin φdφ 2R
ycm = ∫π = −R [ ]π 0 = (9.30)
ρK R 0 dφ φ π
0

und erhalten für die Koordinaten des Schwerpunkts (xcm , ycm ) = (0, 2R
π ).

Quelltext und Daten Das Programm implementiert die Integranden in Gl. 9.29 und
Gl. 9.30 mit drei λ-Funktionen. Diese werden von der gsl-Klasse integriert. Anschließend
werden die Ortskoordinaten des Schwerpunkts berechnet und die Ergebnisse zusammen
mit den exakten Werten in einer Datei ausgegeben.

/**
* @brief ex8 Massenmittelpunkt eines homogenen Halbkreisrings
*/
inline void ex8 () {
std :: ofstream sout = get_output_stream (__func__ , Output :: terminal );

// Daten des Halbkreisrings


const double l = 1;
const double density = 7.7 / pow (0.1 , 3);
const double radius = l / Math :: PI;

// Zähler der x- Koordinate


auto fnnumx = [density , radius ]( double phi) {
return cos(phi) * density * pow(radius , 2); //
};

// Zähler der y- Koordinate


auto fnnumy = [density , radius ]( double phi) {
return sin(phi) * density * pow(radius , 2); //
};

// der Nenner
auto fnden = [density , radius ]( double x) {
(void) x; // avoid warning
return density * radius ;
};

// Berechne die Integrale


gsl :: Integral integralnumx ( fnnumx ), integralden (fnden),
integralnumy ( fnnumy );
// benötigt epsabs um einen Wert auszugeben .
9.5 Beispiele aus der Physik 373

integralnumx .qag (0.0 , Math ::PI , 6, 1e -9);


integralnumy .qag (0, Math ::PI , 6);
integralden .qag (0, Math ::PI , 6);

//x- und y- Koordinate des Schwerpunktes und Masse


const double xcm = integralnumx . result () / integralden . result ();
const double ycm = integralnumy . result () / integralden . result ();
const double w = density * l;

// Ausgabe in Datei
sout << "w =" << w << std :: endl;
sout << "xcm=" << xcm << std :: endl;
sout << "ycm=" << ycm << std :: endl;
sout << "xcm=" << 0 << " ( exact )" << std :: endl;
sout << "ycm=" << 2 * radius / Math ::PI << " (exact)" << std :: endl;
}

Listing 9.48 cppsrc/apps-x039/hpp/x039–12

Es folgen die vom Programm berechneten Werte.

w =7.700 e+03
xcm =7.375e -18
ycm =2.026e -01
xcm =0 (exact)
ycm =2.026e -01 ( exact )

Listing 9.49 data/x039/terminal–ex8.out

9.5.8 Potenzielle Energie einer Masse im Gravitationsfeld der Erde

Ein Teilchen mit der Masse m = 1000 kg befindet sich in einem Abstand z0 vom Erdmit-
telpunkt. Berechnen Sie mit einem Computerprogramm die potenzielle Energie von m als
Arbeit der Gravitationskraft entlang einer Strecke von z0 bis ins Unendliche. Vergleichen
Sie die Ergebnisse mit den exakten Werten.

Lösung

Die potenzielle Energie eines Teilchens wird durch

Mm
U = −G (9.31)
z0
gegeben. Die Gravitationskraft ist konservativ, deswegen ist die von ihr verrichtete Arbeit
vom Weg unabhängig. Wir berechnen die potenzielle Energie des Teilchens als die Arbeit
der Gravitationskraft F entlang einer geraden Strecke von z0 bis +∞.
∫ ∞ ∫ ∞
dz
W = F dz = −GM m 2
(9.32)
z0 z0 z
374 9 Numerische Integration

Wir wollen für die Abstände in Einheiten von R (Erdradius) rechnen und für die Energien
in Einheiten von λ = GM m
R , was dem Betrag der potenziellen Energie eines Teilchens
mit der Masse m auf der Erdoberfläche entspricht. Wenn z = uR, folgt für Gl. 9.31

Mm U 1
U = −G ⇒ =− . (9.33)
uR λ u
Für das Integral (Gl. 9.32) erhalten wir mit dz = Rdu:
∫ ∫ ∞
M m ∞ du W du
W = −G ⇒ = − (9.34)
R z0 u2 λ z0 u2
R R

Das Computerprogramm

Quelltext und Daten Wir werden das Integral in Gl. 9.34 für vier Werte für z0 nume-
risch berechnen und dann W /λ mit U /λ (Gl. 9.33) vergleichen.

/**
* @brief ex9 Potenzielle Energie einer Masse im Gravitationsfeld
* der Erde
*/
inline void ex9 () {
using Data = nmx :: Data <4 >;
const double mass = 1e3;

// Funktion über die integriert werden soll


auto workFn = []( double u) { return 1 / pow(u, 2); };

// Berechnung des exakten Wertes für das Potential


auto exactFn = []( double z) { return 1 / z; };

// Datenobjekt und Ausgabestrom


Data data;
std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);

// Berechne exakten Wert für die potenzielle Energie


// und das Arbeitsintegral in Einheiten von GM/R
for (auto u : { 1e1 , 1e2 , 1e3 , 1e4 }) {
gsl :: Integral integral ( workFn );
integral . qagiu (u, 1e -8);
const auto approxValue = -mass * integral . result ();
const auto exactValue = -mass * exactFn (u);
const auto error = abs( exactValue - approxValue );
data += { u, approxValue , exactValue , error };
}

// Ausgabe in Datei
data.save(ofs , Output :: latex );
}

Listing 9.50 cppsrc/apps-x039/hpp/x039–13

Wir führen das Programm aus und erhalten folgende Resultate zusammengefasst in einer
LATEX-Tabelle:
9.6 Übungsaufgaben 375

Tab. 9.6: Arbeitsintegral (numerisch berechnet), potenzielle Energie (exakte Werte) ei-
nes Teilchens im Gravitationsfeld der Erde in Einheiten von λ = G MR
z0 /R W /λ U /λ Fehler
1.000e+01 -1.000e+02 -1.000e+02 0.000e+00
1.000e+02 -1.000e+01 -1.000e+01 5.329e-15
1.000e+03 -1.000e+00 -1.000e+00 1.332e-14
1.000e+04 -1.000e-01 -1.000e-01 3.068e-14

9.6 Übungsaufgaben
∫ 4 6x
1. Erstellen Sie Programme zur numerischen Berechnung folgender Integrale: 0 16−x 2 dx,
∫ 10 √ ∫ 2 sin x
0
xdx, 0 x dx.
2. Ein Teilchen bewegt sich mit einer konstanten Geschwindigkeit und einer geschwin-
digkeitsabhängigen Beschleunigung a = vc m/s2 geradlinig, wobei v die momentane
Geschwindigkeit und c eine reelle Konstante ist. Zum Zeitpunkt t0 = 0 gilt v(0) = v0
und x(0) = x0 .
a) Berechnen Sie den Ort und die Geschwindigkeit als Funktion der Zeit.
b) Erstellen Sie ein Computerprogramm, welches mittels numerischer Integration die
Geschwindigkeit für drei bestimmte Zeitpunkte Ihrer Wahl berechnet.
3. Ein Massenpunkt bewegt sich mit einer konstanten Geschwindigkeit v0 in einem Me-
dium hinein und wird mit einer zeitabhängigen Beschleunigung a = −ct m/s2 abge-
bremst, wobei t die Zeit und c eine reelle Konstante ist.
a) Wie lange braucht das Teilchen, bis es zum Stillstand kommt?
b) Berechnen Sie, für ein c Ihrer Wahl, die gesuchte Zeit mittels eines Computerpro-
gramms.
10 Anfangswertprobleme für
gewöhnliche
Differenzialgleichungen

Übersicht
10.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
10.2 Ein gsl-Programm zur numerischen Lösung einer Differenzialgleichung . . . . . . 379
10.3 Die Schnittstelle zur GNU Scientific Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
10.4 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
10.5 Beispiele aus der Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
10.6 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411

10.1 Einleitung
Viele Probleme aus den Naturwissenschaften, wie z.B. Wachstumsmodelle, chemische
Reaktionen oder die Bewegung von Objekten werden mithilfe von Differenzialgleichungen
beschrieben. Wenn keine analytische Lösung für diese Gleichungen gefunden werden kann
oder diese zu kompliziert ist, können numerische Methoden eingesetzt werden.
Liegt eine Differenzialgleichung der Form

y ′ (x) = f (x, y(x)) (10.1)

mit y(x0 ) = y0 vor, handelt es sich um ein Anfangswertproblem. Eine Gruppe von Algo-
rithmen zur Lösung solcher Probleme basiert auf dem Euler-Verfahren, welches wir hier
kurz vorstellen wollen. Ausgehend von Gl. 10.1 versuchen wir über geometrische Über-
legungen y(x) zu berechnen (Abb. 10.1a). Bekannt sind dabei ein bestimmter Wert der
Kurve y(x0 ) = y0 (Anfangswert) und die Steigung für jedes x, welche aus der Differenzi-
algleichung hervorgeht. Wir wählen eine Anzahl von äquidistanten Punkten xi innerhalb
eines Intervalls mit xi+1 = xi + h und zeichnen die Tangente an der Stelle (xi , yi ). Der
Punkt yi+1 der Tangente soll ein Näherungswert der Kurve y(x) an der Stelle xi+1 sein.
Mithilfe der Trigonometrie schreiben wir folgende Relation für den Winkel φ:

yi+1 − yi
tan φ = (10.2)
xi+1 − xi

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_10
378 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

Dieser Ausdruck ist gleichzeitig auch die Ableitung von y an der Stelle xi . Es gilt dem-
zufolge
yi+1 − yi
y ′ (xi ) = f (xi , yi ) = (10.3)
xi+1 − xi
⇒ yi+1 = yi + hf (xi , yi ), (10.4)
was eine Iterationsvorschrift zur Berechnung der yi ist.

y
y

(xi+1 , yi+1 )

yi+1 − yi
(xi , yi ) φ

0 xi xi+1 x x

h
(b)
(a)
Abb. 10.1: (a) Geometrische Deutung des Euler-Verfahrens. (b) Das Verhalten der Kur-
ve wird an Stellen, an denen die Funktion stärker variiert durch Schrittweitenanpassung
berücksichtigt.

Je kleiner die Schrittweite h gewählt wird, um so mehr nähert sich yi dem gesuchten
exakten Wert an. Da die Funktion entlang des Definitionsbereichs unterschiedlich vari-
ieren kann, ist es sinnvoll mit einer automatischen Schrittweitenanpassung zu arbeiten.
Dies bedeutet, dass der Algorithmus selbst die Schrittweise steuert. Ein sehr einfaches
Verfahren in diesem Fall wäre ein Ergebnis für h zu berechnen und dann dasselbe über
zwei Schritte, d.h mit h/2 zu tun. Liegen die beiden Resultate nahe genug beieinander,
kann mit der Berechnung des nächsten Punktes fortgefahren werden.
Eine Weiterentwicklung des Euler-Verfahrens ist die Familie der Runge-Kutta-
Verfahren. Statt die Steigung am Punkt xi zu nehmen, wird ein (gewichteter) Mittelwert
von Steigungen innerhalb des Intervalls [xi , xi+1 ] ermittelt und damit eine bessere Appro-
ximation für yi berechnet. Nehmen wir als Beispiel das Runge-Kutta-Verfahren zweiter
Ordnung. Hier lautet die Iterationsvorschrift
1
yi+1 = yi + h (f (xi , yi ) + f (xi + h, yi+1 )) , (10.5)
2
wobei in f (xi + h, yi+1 ), yi+1 = yi + hf (xi , yi ) ist. Statt die Steigung der Tangente
an der Stelle xi für die Iterationsvorschrift zu wählen, berechnen wir den Mittelwert der
Steigungen an den Stellen xi und xi+1 . Auch für diesen Fall gilt, dass die Anpassung
der Schrittweite h zu besseren Ergebnissen führt. Die Halbierung der Schrittweite erfor-
dert jedoch zusätzliche Rechenschritte, die wir durch einen anderen Ansatz versuchen
zu reduzieren und mithilfe der beiden oben beschriebenen Verfahren erklären wollen.
10.2 Ein gsl-Programm zur numerischen Lösung einer Differenzialgleichung 379

Ein genauere Betrachtung der Formeln Gl. 10.4 und Gl. 10.5 zeigt, dass zur Berech-
nung von Gl. 10.5 die Berechnung von Gl. 10.4 erforderlich ist. Wir berechnen für eine
bestimmte Schrittweite h mit beiden Formeln zwei Näherungswerte. Wenn die Differenz
der Ergebnisse innerhalb einer vorgegeben Schranke liegt, so wird das Ergebnis akzeptiert
und weitergerechnet. Dies ist die Idee des Runge-Kutta-Fehlberg-Verfahrens, welches mit
Formeln höherer Ordnung realisiert wird.
Wenn y(x) in bestimmten Bereichen stärker variiert als in anderen, liegt unter Um-
ständen eine steife Differenzialgleichung vor. In diesem Zusammenhang wird eine Jacobi-
Matrix berechnet, welche das lokale Verhalten der gesuchten Funktion zu bestimmen
hilft. Wir werden im Rahmen dieses Buchs nicht mit steifen Differenzialgleichungen ar-
beiten.

10.2 Ein gsl-Programm zur numerischen Lösung


einer Differenzialgleichung
Wir beginnen mit einem Programm, dass ausschließlich mit gsl-Routinen arbeitet und
zeigt, wie eine Differenzialgleichung numerisch gelöst wird. Es soll die Differenzialglei-
chung
2x
y ′ (x) = y − (10.6)
y
mit der Anfangsbedingung y(0) = 1 gelöst werden. Die exakte Lösung lautet y(x) =

2x + 1. Die numerische Lösung der Differenzialgleichung erfordert die Angabe der rech-
ten Seite von Gl. 10.6
2xi
yi+1 = yi − . (10.7)
yi
Diese Formel wird mithilfe folgender Funktion der Lösungsroutine übergeben und bei
jedem Iterationschritt aufgerufen.

/**
* @brief func rechte Seite der Differenzialgleichung
* @param x unabhängige Variable
* @param yin Eingabe
* @param yout Ausgabe
* @param params zusätzlicher Parameter
* @return GSL_SUCCESS
*/
int func( double x, const double yin [], double yout [], void * params ) {
(void) params ;
yout [0] = yin [0] - 2 * x / yin [0];
return GSL_SUCCESS ;
}

Listing 10.1 cppsrc/apps-x036/hpp/x036–01

Wir bemerken an dieser Stelle, dass der Funktion nicht Zahlen, sondern Felder übergeben
werden. Dies liegt daran, dass die Routinen der gsl auch zur Lösung von Differenzial-
380 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

gleichungssystemen gedacht sind und jede Differenzialgleichung höherer Ordnung sich als
ein System von Differenzialgleichungen erster Ordnung formulieren lässt:



 y1′ = f1 (x1 , y1 , . . . , yn )



 y2′ = f2 (x1 , y1 , . . . , yn )
 ..

 .



 ′
yn = fn (x1 , y1 , . . . , yn )

In unserem Beispiel handelt es sich um den Spezialfall eines Systems, das aus nur einer
Gleichung besteht. Wir implementieren zusätzlich die exakte Lösung der Differenzialglei-
chung (Gl. 10.6).

/**
* @brief fexact exakte Lösung
* @param x Variable
* @return Funktionswert
*/
double fexact ( double x) {
return std :: sqrt (2 * x + 1);
}

Listing 10.2 cppsrc/apps-x036/hpp/x036–02

Das Programm zur Lösung von Gl. 10.6 besteht aus vier logischen Teilen.

/**
* @brief ex1 Beispiel ( Vorlage )
*/
inline void ex1 () {
// Teil 1: Definition des Dgl - Systems
gsl_odeiv2_system sys = {
func , // rechte Seite des Dgl - Systems
nullptr , // Jacobi - Matrix
1, // Anzahl der Gleichungen des Dgl - Systems
nullptr // zusätzliche Parameter
};
// Teil2 : Name des Lösungsalgorithmus
const gsl_odeiv2_step_type *type = gsl_odeiv2_step_rk4 ;
// Initialisierung des Lösungsalgorithmus
gsl_odeiv2_driver *d = //
gsl_odeiv2_driver_alloc_y_new (&sys , // Dgl - System
type , // Lösungsalgorithmus
1e-6, // Schrittweite ( Start )
1e-6, // absoluter Fehler
0.0); // relativer Fehler
// Teil 3: Berechnung der Lösung
double x = 0;
double y[] = { 1.0 };
for ( double xi = 0; xi < 2; xi += 0.25) {
int status = gsl_odeiv2_driver_apply (d, &x, xi , y);
if ( status != GSL_SUCCESS ) {
printf ("error , return value =%d\n", status );
break;
10.2 Ein gsl-Programm zur numerischen Lösung einer Differenzialgleichung 381

printf ("%.5e %.5e %.5e\n", x, y[0], fexact (x));


}
// Teil 4: Ressourcen werden freigegeben
gsl_odeiv2_driver_free (d);
}

Listing 10.3 cppsrc/apps-x036/hpp/x036–03

Im ersten Teil wird die Differenzialgleichung (Gl. 10.6, Listing 10.1) als ein System von
Differenzialgleichungen erster Ordnung definiert. Die nötigen Informationen werden der
gsl über eine Struktur (Listing 10.4) mitgeteilt. Neben dem Differenzialgleichungssystem
muss zusätzlich die Anzahl der Gleichungen des Systems übergeben werden. Die Angabe
einer Jacobi-Matrix sowie des letzten Parameters ist optional.

typedef struct {
// rechte Seite des Differenzialgleichungssystems
int (* function ) ( double t, const double y[], double dydt [], void
* params );
// Jacobi - Matrix
int (* jacobian ) ( double t, const double y[], double *dfdy ,
double dfdt [], void * params );
// Anzahl der Gleichungen
size_t dimension ;
// zusätzliche Parameter
void * params ;
} gsl_odeiv2_system ;

Listing 10.4

Im zweiten Teil werden die Parameter für die Lösungsroutine festgelegt. Hierzu gehören
die Wahl des Lösungsalgorithmus, in diesem Fall ist es das Runge-Kutta-Verfahren vierter
Ordnung, und die Angabe des Differenzialgleichungssystems.
Es folgt der dritte Teil mit der Festlegung der Anfangsbedingungen und dem Beginn
der Iteration. Wir geben der Lösungsroutine die Anweisung, die Lösungen an den Stellen
x = 0, . . . 2 mit ∆x = 0.25 zu berechnen. Die Ergebnisse werden auf dem Bildschirm
ausgegeben und im letzten Teil werden die angeforderten Ressourcen wieder freigegeben.
Nach Ausführung der Funktion erhalten wir folgende Daten:

0.00000 e+00 1.00000 e+00 1.00000 e+00


2.50000e -01 1.22474 e+00 1.22474 e+00
5.00000e -01 1.41421 e+00 1.41421 e+00
7.50000e -01 1.58114 e+00 1.58114 e+00
1.00000 e+00 1.73205 e+00 1.73205 e+00
1.25000 e+00 1.87083 e+00 1.87083 e+00
1.50000 e+00 2.00000 e+00 2.00000 e+00
1.75000 e+00 2.12132 e+00 2.12132 e+00

Listing 10.5
382 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

10.3 Die Schnittstelle zur GNU Scientific Library


Wir werden auf Basis des einführenden Beispiels ein Klassentemplate implementieren,
das mithilfe von gsl-Routinen numerisch Anfangswertprobleme löst. Mit drei Template-
Parametern sollen Eigenschaften der zu generierenden Klassen gesteuert werden. Die An-
zahl der Gleichungen des Systems wird über den ersten Template-Parameter angegeben.
Über den dritten Template-Parameter wird festgelegt, ob für die Lösung eine Jacobi-
Matrix benötigt wird. In diesem Buch werden wir von dieser Option keinen Gebrauch
machen. Die Bedeutung des zweiten Template-Parameters wird im nächsten Quelltext-
abschnitt geklärt (Listing 10.7).

/**
* @brief The Odeiv2 class numerische Lösung eines Systems von N
* Differenzialgleichungen Schnittstelle zur gsl
* @param N Anzahl der Differenzialgleichungen
* @param HASCALLOP Funktion wird über (...) aufgerufen
* @param Jacobi - Matrix wird benötigt
*/
template < size_t N, bool HASCALLOP = false , bool JACOBIAN = false >
class Odeiv2
{
public :
// Synonym für den Lösungsvektor
using Array = std :: array <double , N >;
// Synonym für Anzahl der Differenzialgleichungen
static constexpr size_t DIM = N;

private :
int _status ; // Status gsl - Meldung

//gsl - Strukturen zur Definition des DGL - Systems


gsl_odeiv2_system odeSystem ;
// zur Wahl und Anpassung der Schrittweite
gsl_odeiv2_driver * odeDriver ;
// unabhängige Variable z.B. die Zeit
double currentTime ;
// Lösungsvektor
Array currentState ;

Listing 10.6 cppsrc/nmx-xode/hpp/xode–01

Wir möchten flexibel sein und das Differenzialgleichungssystem mit eigenen Objekten
festlegen können und führen den Konstruktor als Template ein. Damit die gsl-Routinen
eingesetzt werden können, muss das Objekt über eine gsl_odeiv2_system-Struktur (Lis-
ting 10.4) registriert werden. Dies geschieht, indem die Struktur einen Zeiger auf das
Objekt speichert. Statt der gsl_odeiv2_system das Differenzialgleichungssystem direkt
anzugeben, wird eine Funktion registriert, die jeden Aufruf an das Objekt weiterleitet.
Ist das Objekt eine λ-Funktion , so verfügt es über einen überladenen Klammeropera-
tor und der zweite Template-Parameter der Klasse HASCALLOP muss gleich true gesetzt
werden; sonst muss das Objekt eine Schnittstelle odefn implementieren.
10.3 Die Schnittstelle zur GNU Scientific Library 383

/**
* @brief Odeiv2
* @param p enthält das DGL - System
*/
template <class T>
Odeiv2 (T &p)
: odeSystem { nullptr , nullptr , N, static_cast <void *>(&p) }
, odeDriver { nullptr } {
if constexpr (! HASCALLOP ) {
//DGL - System wird über eine Schnittstelle odefn aufgerufen
// gsl ruft diese Funktion auf
odeSystem . function = []( double t, //
const double y[],
double f[],
void * params ) -> int {
T * myObj = static_cast <T *>( params );
// Objekt muss diese Funktion definieren
return myObj -> odefn (t, y, f);
};
} else {
//DGL - System wird über () -Operator aufgerufen
// gsl ruft diese Funktion auf
odeSystem . function = []( double t, //
const double y[],
double f[],
void * params ) -> int {
T * myObj = static_cast <T *>( params );
// Objekt muss diesen () -Operator definieren
return (* myObj )(t, y, f);
};
}

// Lösung benötigt JACOBI - Matrix


if constexpr ( JACOBIAN == true) {
odeSystem . jacobian = []( double t, //
const double y[],
double *dfdy ,
double dfdt [],
void * params ) {
T * myObj = static_cast <T *>( params );
return myObj -> getJacobian (t, y, *dfdy , dfdt);
};
}
}

Listing 10.7 cppsrc/nmx-xode/hpp/xode–02

Wir werden innerhalb dieser Klasse nur Algorithmen einsetzen, die mit Schrittweitenan-
passung arbeiten.

/**
* @brief init Initialisierung der gsl - Schnittstelle
* @param initstepsize Anfangsschrittweite
* @param abserr absoluter Fehler
* @param relerr relativer Fehler
* @param steptype Lösungsalgorithmus ( optional )
* standardmäßig Runge -Kutta - Fehlberg
384 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

*/
inline void init( double initstepsize ,
double abserr ,
double relerr ,
const gsl_odeiv2_step_type * steptype =
gsl_odeiv2_step_rkf45 ) {
odeDriver = gsl_odeiv2_driver_alloc_y_new (& odeSystem , steptype ,
initstepsize , abserr , relerr );
}

public :

Listing 10.8 cppsrc/nmx-xode/hpp/xode–03

Eine Instanz dieser Klasse kann nur über folgenden Konstruktor erzeugt werden.

/**
* @brief Odeiv2 Konstruktor
* @param obj Implementierung des DGL - Systems
* @param istepsize anfägliche Schrittweite
* @param abserror absoluter Fehler
* @param relerror relativer Fehler
* @param steptype Algorithmus (Runge -Kutta Familie )
*/
template <class T>
Odeiv2 (T &obj ,
double istepsize ,
double abserror ,
double relerror ,
const gsl_odeiv2_step_type * steptype = gsl_odeiv2_step_rkf45 )
: Odeiv2 (obj) {
init(istepsize , abserror , relerror , steptype );
}

Listing 10.9 cppsrc/nmx-xode/hpp/xode–04

Der Destruktor gibt alle gsl-Ressourcen frei.

/**
* Destructor
*/
inline ~ Odeiv2 () {
if ( odeDriver != nullptr ) {
gsl_odeiv2_driver_free ( odeDriver );
}
}

Listing 10.10 cppsrc/nmx-xode/hpp/xode–05

Die Anfangsbedingungen werden festgelegt.

/**
* @brief set_init_conditions lege Anfangsbedingungen fest
* @param t unabhängige Variable
* @param y Lösungsvektor
*/
10.3 Die Schnittstelle zur GNU Scientific Library 385

inline void set_init_conditions ( double t, const Array &y) {


currentTime = t;
currentState = y;
}

Listing 10.11 cppsrc/nmx-xode/hpp/xode–06

Ein einzelner Iterationsschritt wird ausgeführt.

/**
* @brief applyStep berechnet Lösung
* @param t für einen Wert der unabhängigen Variablen
*/
inline void solve ( double t) {
_status = gsl_odeiv2_driver_apply (odeDriver , & currentTime , t,
& currentState [0]);

if ( _status != GSL_SUCCESS ) {
throw std :: runtime_error ( nmx_msgx (std :: to_string ( _status )));
}
}

Listing 10.12 cppsrc/nmx-xode/hpp/xode–07

Die Anzahl der Gleichungen des Gleichungssystems sowie die Elemente des Lösungsvek-
tors können über die nächsten beiden Elementfunktionen gelesen werden.

/**
* @brief operator [] Zugriff auf einzelnen Elemente des
* Lösungsvektors mit Index
* @param idx Index
* @return Element des Lösungsvektors
*/
inline double operator []( size_t idx) const {
if (idx < 0 || idx >= N) {
throw std :: range_error ( nmx_msgx (std :: to_string (idx)));
}
return currentState [idx ];
}

Listing 10.13 cppsrc/nmx-xode/hpp/xode–08

/**
* @brief size
* @return Anzahl der Gleichungen im Gleichungssystem
*/
inline constexpr size_t size () const { return N; }

Listing 10.14 cppsrc/nmx-xode/hpp/xode–09


386 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

10.4 Beispiele
Beispiel Es soll die Differenzialgleichung dx dt = x − x ,
2t
x(0) = 1 numerisch gelöst

werden. Ihre analytische Lösung ist bekannt und lautet x(t) = 2t + 1. Im folgen-
den Quelltext wird über den zweiten Parameter des Klassentemplates festgelegt, wel-
che Schnittstelle die Implementierung der Differenzialgleichung haben muss, die wenige
Zeilen weiter definiert ist. Es folgt die analytisch hergeleitete Formel und anschließend
die numerische Lösung. Hier werden innerhalb einer Schleife Lösungen an den Stellen
ti+1 = ti + dt berechnet.

/**
* @brief ode Lösung einer DGL
*/
inline void ode1 () {
// eindimensionale Gleichung , kein () -Operator
// zweiter Template -Parameter false
using ODESolver = gsl :: Odeiv2 <1, false >;
using Data = Data <4 >;
// Datenobjekt
Data data;

// die DGL mit Schnittstelle zur gsl


struct MyODE {
int odefn ( double t, const double fin [], double fout []) {
fout [0] = fin [0] - 2 * t / fin [0];
return GSL_SUCCESS ;
}
} myObj ;

// analytisch hergeleitete Lösung


auto exactSolutionFn = []( double t) { return sqrt (2 * t + 1); };

// numerische Lösung der DGL


ODESolver ode{ myObj , 1e-2, 1e-9, 0 };
ode. set_init_conditions (0, { 1 });
double t = 0, dt = 0.25;
while (t < 2.5) {
const double numSolution = ode [0];
const double exactSolution = exactSolutionFn (t);
const double absError = abs( numSolution - exactSolution );
data += { t, numSolution , exactSolution , absError };
t += dt;
// nächste Lösung
ode.solve (t);
}

std :: ofstream mystable = get_output_stream (__func__ , Output :: latex);


data.save(mystable , Output :: latex );
}

Listing 10.15 cppsrc/apps-x036/hpp/x036–05


10.4 Beispiele 387

Tab. 10.1: Numerische Lösung der Differenzialgleichung dx


dt =x− 2t
x, x(0) = 1
t xapprox xexact |xapprox − xexact |
0.000e+00 1.000e+00 1.000e+00 0.000e+00
2.500e-01 1.225e+00 1.225e+00 3.140e-10
5.000e-01 1.414e+00 1.414e+00 6.455e-10
7.500e-01 1.581e+00 1.581e+00 1.100e-09
1.000e+00 1.732e+00 1.732e+00 1.798e-09
1.250e+00 1.871e+00 1.871e+00 2.889e-09
1.500e+00 2.000e+00 2.000e+00 4.606e-09
1.750e+00 2.121e+00 2.121e+00 7.318e-09
2.000e+00 2.236e+00 2.236e+00 1.161e-08
2.250e+00 2.345e+00 2.345e+00 1.842e-08

Beispiel Wir wollen als nächstes eine lineare Differenzialgleichung zweiten Grades nu-
merisch lösen. Dazu ist es erforderlich, die Gleichung in ein System von linearen Diffe-
renzialgleichungen ersten Grades umzuwandeln. Wir betrachten folgendes Beispiel:

d2 x dx
= 2, x(0) = 0, = 0. (10.9)
dt2 dt t=0

Diese Gleichung kann als System von linearen Differenzialgleichungen ersten Grades for-
muliert werden: 

 dx
 =u
dt

 du
 =2
dt
Für die Implementierung des Differenzialgleichungssystems soll diesmal ein Klammer-
operator erforderlich sein. Dieser wird durch die Wahl des zweiten Template-Parameters
der Odeiv2-Klasse festgelegt. Der weitere Ablauf des Programms erfolgt analog zum vor-
herigen Beispiel.

/**
* @brief ex2 DGL 2. Grades
*/
inline void ode2 () {
// zweiter Template - Parameter true
// die Implementierung des DGL - Systems erfordert
// einen () -Operator
using ODESolver = gsl :: Odeiv2 <2, true >;
using Data = nmx :: Data <7 >;

const double acceleration = 2;


const double x0 = 0, v0 = 0;

Data data;
// Implementierung des DGL - Systems
auto obj = []( double t, const double fin [], double fout []) {
(void) t;
fout [0] = fin [1];
fout [1] = 2;
388 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

return GSL_SUCCESS ;
};

// exakte Lösung
auto v = [& acceleration ]( double t) { //
return acceleration * t;
};
auto x = [& acceleration ]( double t) { //
return 0.5 * acceleration * pow(t, 2);
};

// numerische Lösung
ODESolver myOde { obj , 1e-2, 1e-9, 0 };
myOde. set_init_conditions (0, { x0 , v0 });
double t = 0, dt = 0.5;
while (t < 5) {
const double xnum = myOde [0];
const double xexact = x(t);
const double vnum = myOde [1];
const double vexact = v(t);
const double verror = abs(vnum - vexact );
const double xerror = abs(xnum - xexact );

// registriere Daten
data += { t, xnum , xexact , xerror , vnum , vexact , verror };
t += dt;
// berechne Lösung für den nächsten Schritt
myOde.solve (t);
}
// Ausgabe
std :: ofstream mystable = get_output_stream (__func__ , Output :: latex);
data.save(mystable , Output :: latex );
}

Listing 10.16 cppsrc/apps-x036/hpp/x036–06

Tab. 10.2: Numerische Lösung einer linearen Differenzialgleichung zweiter Ordnung


t x xexact |x − xexact | v vexact |v − vexact |
0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00
5.000e-01 2.500e-01 2.500e-01 2.776e-17 1.000e+00 1.000e+00 0.000e+00
1.000e+00 1.000e+00 1.000e+00 0.000e+00 2.000e+00 2.000e+00 0.000e+00
1.500e+00 2.250e+00 2.250e+00 0.000e+00 3.000e+00 3.000e+00 0.000e+00
2.000e+00 4.000e+00 4.000e+00 0.000e+00 4.000e+00 4.000e+00 0.000e+00
2.500e+00 6.250e+00 6.250e+00 0.000e+00 5.000e+00 5.000e+00 0.000e+00
3.000e+00 9.000e+00 9.000e+00 0.000e+00 6.000e+00 6.000e+00 0.000e+00
3.500e+00 1.225e+01 1.225e+01 0.000e+00 7.000e+00 7.000e+00 0.000e+00
4.000e+00 1.600e+01 1.600e+01 0.000e+00 8.000e+00 8.000e+00 0.000e+00
4.500e+00 2.025e+01 2.025e+01 0.000e+00 9.000e+00 9.000e+00 0.000e+00
10.5 Beispiele aus der Physik 389

10.5 Beispiele aus der Physik


10.5.1 Konstante Kraft wirkt auf Holzblock
⃗.
Auf einen ruhenden Holzblock der Masse m wirkt eine konstante horizontale Kraft F
2
Der Reibungskoeffizient zwischen Holzblock und Boden ist µ (g = 9.81 m/s ).

1. Wie viel Zeit braucht der Holzblock, bis er eine Strecke s zurückgelegt hat? Welche
Geschwindigkeit hat er zu diesem Zeitpunkt?
2. Erstellen Sie mit einem Computerprogramm Bewegungsdaten für m = 40 kg, µ = 0.4,
|F
⃗ | = 200 N, s = 5 m. Lösen Sie die Aufgabe durch Anwendung des Arbeitssatzes
und erstellen Sie die Bewegungsdaten durch die numerische Lösung der Bewegungs-
gleichung. Vergleichen Sie die Ergebnisse.

Lösung

Lösung über den Arbeitssatz Auf den Holzblock wirken neben F ⃗ die Gewichtskraft w,

die Normalkraft N⃗ und die Gleitreibung R.
⃗ Wir legen fest, dass sich der Holzblock zu
Beginn der Bewegung an der Stelle x = 0 befindet. Es gelten somit folgende Anfangsbe-
dingungen: v(0) = 0 und x(0) = 0. Wir wenden den Arbeitssatz an:

Ks − K 0 = W g + W R + W N + W F , (10.10)

wobei Ks die kinetische Energie des Holzblocks am Ende und K0 jene am Anfang der
Strecke s ist. Es gilt Wg = 0 und WN = 0 für die Arbeit der Gewichtskraft und der
Normalkraft, da beide senkrecht zur Bewegungsrichtung stehen.
∫ s ∫ s [ ]s
WF = ⃗
F d⃗
x= F dx = F x = F s (10.11)
0 0 0
∫ s ∫ s [ ]s
WR = ⃗ x=−
Rd⃗ µN dx = −µN x = −µN s. (10.12)
0 0 0

Aus der Gleichgewichtsbedingung in der vertikalen Richtung folgt

N ⃗ = 0 ⇒ N = mg.
⃗ +w (10.13)

Daraus folgt für Gl. 10.12


WR = −µmgs. (10.14)

Aus Gl. 10.10 zusammen mit K0 = 0 wird


1
mvs2 = F s − µmgs
2 √
F − µmg
⇒ vs = ± 2 s. (10.15)
m
390 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

Der Holzblock bewegt sich mit konstanter Beschleunigung a = F


m − µg. Die Geschwin-
digkeit lautet demzufolge
vs
vs = at ⇒ t = F , (10.16)
m − µg

wobei t die Zeit ist, die der Holzblock braucht, bis er die Strecke s zurückgelegt hat.

Lösung über die Bewegungsgleichung Die Aufgabe kann auch direkt über die Integra-
tion der Bewegungsgleichung, welche aus dem zweiten Newton’schen Gesetz hervorgeht,
gelöst werden:
{
mẍ = F − µN, entlang der Bewegungsrichtung (10.17a)
0 = N − mg, senkrecht zur Bewegungsrichtung. (10.17b)

Daraus folgt für die Beschleunigung:


F
ẍ = − µg (10.18)
m

Das Computerprogramm

Kurzbeschreibung Wir wollen zeigen, dass mit C++ kurze Programme möglich sind
und programmieren alles innerhalb einer einzigen Funktion. Dies ist praktisch, wenn
schnell ein Prototyp erstellt werden muss. Zur Lösung der Differenzialgleichung sowie
zur Speicherung und Ausgabe der Daten in Dateien werden wir die von uns erstellten
Klassen einsetzen.

Quelltext Die Funktion beginnt mit der Initialisierung der Daten, die durch die Aufgabe
vorgegeben werden.

/**
* @brief ode3 Konstante Kraft wirkt auf Holzblock
*/
void ode3 () {
// Daten der Aufgabe
const double mass = 40.0;
const double mu = 0.4;
const double force = 200.0;
const double s = 5.0;
const double g = gravitation :: Earth ::g;
// Anfangsbedingungen
const double x0 = 0, v0 = 0;

Listing 10.17 cppsrc/apps-x036/hpp/x036–07

Es folgt die Implementierung der Formeln Gl. 10.15 und Gl. 10.16.

// Lösungen über den Arbeitssatz zum Vergleich


const double a = force / mass - mu * g;
const double vs = std :: sqrt (2 * a * s);
const double tval = vs / a;
10.5 Beispiele aus der Physik 391

std :: cout << std :: setw (3) << tval << "," //
<< std :: setw (3) << vs << std :: endl;

Listing 10.18 cppsrc/apps-x036/hpp/x036–08

Das Differenzialgleichungssystem wird als λ-Funktion formuliert. Dies wird der Klasse
zur numerischen Lösung der Differenzialgleichung über den zweiten Template-Parameter,
der auf true gesetzt wird, mitgeteilt. Als System von Differenzialgleichungen formuliert
lautet Gl. 10.18 
 χ̇ = ψ
 ψ̇ = F − µg,
m
wobei χ = x und ψ = v ist.

// das Differenzialgleichungssystem , durch das =


// in der Erfassungsliste , können alle Aufgabendaten
// innerhalb der lambda - Funktion genutzt werden .
auto acceleration = [=]( double t, //
const double fin [],
double fout []) {
(void) t;
fout [0] = fin [1];
fout [1] = a;
return GSL_SUCCESS ;
};

Listing 10.19 cppsrc/apps-x036/hpp/x036–09

Wir berechnen solange Lösungen, bis die Strecke s erreicht ist. Die Ergebnisse werden in
eine Zahlentabelle geschrieben.

// lege Dgl fest , der zweite Template - Parameter muss true sein ,
// da das System als lambda implementiert ist.
using Ode = gsl :: Odeiv2 <2, true >;
using Data = nmx :: Data <4 >;
Data data;
Ode ode{ acceleration , 1e-2, 1e-9, 0 };
ode. set_init_conditions (0, { x0 , v0 });
double t = 0, dt = 0.01;
// berechne solange Lösungen bis die Strecke s ist
while (ode [0] < s) {
data += { t, ode [0] , ode [1] , 0.5 * mass * pow(ode [1], 2) };
t += dt;
ode.solve (t);
}

Listing 10.20 cppsrc/apps-x036/hpp/x036–10

Es folgt die Ausgabe im CSV- und im LATEX-Format.

// Ausgabe
std :: ofstream mystable = get_output_stream (__func__ , Output :: latex);
data. select_total_rows (5).save(mystable , Output :: latex);
392 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

std :: ofstream mysplot = get_output_stream (__func__ , Output :: plot);


data.save(mysplot , Output :: plot);
}

Listing 10.21 cppsrc/apps-x036/hpp/x036–11

Daten Die Werte für Zeit und Geschwindigkeit lauten, nachdem der Holzblock eine
Strecke von s = 5 m zurückgelegt hat, t = 3.04666 s und v = 3.28229 m/s. Wir erzeugen
mithilfe der abgespeicherten Daten eine Tabelle (Tab. 10.3) und Diagramme (Abb. 10.2).

Tab. 10.3: Bewegungsdaten für den Holzblock, der sich unter dem Einfluss einer hori-
zontalen konstanten Kraft und der Gleitreibung bewegt
t [s] x [m] v [m/s] K [J]
0.000e+00 0.000e+00 0.000e+00 0.000e+00
6.000e-01 1.939e-01 6.464e-01 8.357e+00
1.210e+00 7.887e-01 1.304e+00 3.399e+01
1.820e+00 1.784e+00 1.961e+00 7.689e+01
2.430e+00 3.181e+00 2.618e+00 1.371e+02
3.040e+00 4.978e+00 3.275e+00 2.145e+02

5
3
4
v[m/s]

3 2
x[m]

2
1
1

0 0
0 0.5 1 1.5 2 2.5 3 0 0.5 1 1.5 2 2.5 3
t[s] t[s]

(a) (b)
Abb. 10.2: Ein Holzblock bewegt sich unter dem Einfluss einer horizontalen konstanten
Kraft und der Gleitreibung. (a) Orts-Zeit-Diagramm (b) Geschwindigkeit-Zeit-Diagramm

10.5.2 Der schiefe Wurf ohne Luftwiderstand

Ein Teilchen mit der Masse m wird mit einer Anfangsgeschwindigkeit v0 unter einem
Winkel ϑ0 von einer Höhe H schräg nach oben geworfen (Abb. 10.3).

1. Wie lauten der Ort und die Geschwindigkeit des Teilchens als Funktion der Zeit?
2. Welche maximale Höhe erreicht das Teilchen und wo schlägt es auf den Boden auf?
Für welchen Winkel wird die horizontale Reichweite maximal?
10.5 Beispiele aus der Physik 393

y
ymax
⃗v0
⃗g w
⃗ vx
H ϑ0
vy
y ⃗ey

⃗ex
⃗ey ⃗
r
(b)
0 ⃗ex x xmax x

(a)
Abb. 10.3: (a) Schiefer Wurf eines Teilchens ohne Luftwiderstand aus einer Höhe H
unter einem Winkel ϑ0 (b) Geschwindigkeiten und Kräfte auf das Teilchen

3. Lösen Sie die Bewegungsgleichung numerisch. Tragen Sie in einer Tabelle zusätzlich
zu den Bewegungsdaten auch die potenzielle, die kinetische und die Gesamtenergie
ein.

Lösung

Die Bewegungsgleichungen Das zweite Newton’sche Gesetz lautet:

m⃗r¨ = −mg⃗ey (10.19)

oder in Komponenten {
mẍ⃗ex = 0
(10.20)
mÿ⃗ey = −mg⃗ey .
Nach elementarer Integration beider Gleichungen erhalten wir
{ 
ẋ = c1 x = c1 t + c3

ẏ = c2 − gt y = c4 + c2 t − 1 gt2 .
2
Es ist leicht, mithilfe der Anfangsbedingungen x(0) = 0, y(0) = H, vx (0) = v0 cos ϑ0 ,
vy (0) = v0 sin ϑ0 die Integrationskonstanten c1 , c2 , c3 , c4 zu bestimmen. Ist dies gesche-
hen, folgt für die Orts- und die Geschwindigkeitskoordinaten als Funktion der Zeit:

 x = v0 cos ϑ0 t (10.21a)
 y = H + v0 sin ϑ0 t − 1 gt2 (10.21b)
2
und
{
vx = v0 cos ϑ0 (10.22a)
vy = v0 sin ϑ0 − gt (10.22b)
394 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

Berechnung der maximalen Steighöhe Hat das Teilchen seine maximale Steighöhe
erreicht, ist die vertikale Komponente der Geschwindigkeit vy (Gl. 10.22b) 0.

v0 sin ϑ0
v0 sin ϑ0 − gt = 0 ⇒ t = . (10.23)
g

Mit Gl. 10.21a erhalten wir die x-Komponente:

v0 sin ϑ0 v 2 sin 2ϑ0


x = v0 cos ϑ0 = 0 (10.24)
g 2g

und mit Gl. 10.21b die y-Komponente:

v0 sin ϑ0 1 v 2 sin2 ϑ0
y = H + v0 sin ϑ0 − g 0 2
g 2 g
oder
v02 sin2 ϑ0
y=H+ . (10.25)
2g
Die Wurfweite Wir setzen in Gl. 10.21b y = 0 ein und berechnen die gesamte Bewe-
gungszeit:
1
H + v0 sin ϑ0 t − gt2 = 0
2
2 2v0 sin ϑ0 2H
⇒t − t− =0 (10.26)
g g
Die Diskriminante dieser quadratischen Gleichung lautet:

4v02 sin2 ϑ0 8H
∆= + (10.27)
g2 g

Die zwei Lösungen der Gleichungen sind:



v0 sin ϑ0 ± v02 sin2 ϑ0 + 2gH
t1,2 = .
g

Das Minuszeichen führt zu negativen Zeiten, deswegen ist



v0 sin ϑ0 + v02 sin2 ϑ0 + 2gH
t= . (10.28)
g

Dieses Ergebnis, kombiniert mit Gl. 10.21a, gibt die Wurfweite:


( √ )
v02 2 2gH
xmax = cos ϑ0 sin ϑ0 + sin ϑ0 + 2 (10.29)
g v0
10.5 Beispiele aus der Physik 395

d
Der optimale Wurfwinkel ϑ0 Wir suchen ein ϑ0 , für das dϑ 0
xmax = 0 wird. Damit
die Formeln überschaubar bleiben, führen wir folgende Bezeichnungen ein:
√ 2gH
sin ϑ0 = u, cos ϑ0 = 1 − u2 , λ = 2 (10.30)
v0
mit u > 0 weil 0 ≤ ϑ0 < π
2. Wir schreiben für (Gl. 10.29)

v02 √ ( √ )
xmax = 1 − u2 u + u2 + λ .
g
dxmax
=0
dϑ0
( )
−u ( √ ) √ u
⇒ √ u + u2 + λ + 1 − u2 1 + √ =0
1 − u2 u2 + λ
( ( ))
√ −u ( √ )
2 +λ + 1+ √
u
⇒ 1 − u2 u + u =0
1 − u2 2
u +λ
( )
√ −u ( √ )
2 +λ + 1+ √
u
⇒ 1 − u2 = 0 ∨ u + u =0 (10.31)
1 − u2 u2 + λ

Wenn 1 − u2 = 0, haben wir einen senkrechten Wurf nach oben und damit die minimale
Reichweite. Wir rechnen mit
−u ( √ ) ( u
)
u+ u +λ + 1+ √
2 =0
1 − u2 u2 + λ
weiter.
( √ ) ( −u 1
)
u+ +λ u2 +√ =0
} 1−u
2
| {z u2 + λ
>0

1 u2
⇒ =
u2 +λ (1 − u2 )2
⇒ 1 − 2u2 + u4 = u4 + λu2 ⇒ u2 (λ + 2) = 1

1
⇒u=± . (10.32)
λ+2
Da u > 0, folgt
1
u= √ . (10.33)
λ+2
Durch Rücksubstitution erhalten wir den gesuchten Wert für ϑ0 :
( )
v0
ϑ0 = arcsin √ 2 (10.34)
2v0 + 2gH

Das Computerprogramm

Kurzbeschreibung Wir führen eine Modellklasse ein und leiten von ihr ein Rechenmo-
dell ab. Die Daten für die Flugbahn werden mit dem Rechenmodell berechnet und in
diesem gespeichert. Es folgt eine Liste mit den wichtigsten Eigenschaften beider Klassen:
396 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

Modellklasse
– Eingabewerte: Masse des Teilchens m, Anfangshöhe H, Betrag der Anfangsge-
schwindigkeit v0 und der Wurfwinkel ϑ0
– Implementierung theoretisch hergeleiteter Formeln
Rechenmodell abgeleitet von der Modellklasse
– Numerische Lösung der Bewegungsgleichungen
– Speicherung der Daten in einer Zahlentabelle
– Ausgabe der Zahlentabelle im Grafik- und Tabellenformat

Quelltext Eine Instanz der Modellklasse wird durch die Angabe der Masse des Teilchens
und der Anfangsbedingungen Anfangshöhe, Geschwindigkeitsbetrag und Winkel über den
Konstruktor erzeugt.

/**
* @brief The X710 class schiefer Wurf ohne Luftwiderstand
* ( Modellklasse )
*/
class X710 : public XModel
{
protected :
// Komponenten der Anfangsgeschwindigkeit
double _v0y = 0, _v0x = 0;

public :
// Eingabeparameter : Masse
const double mass;
// Anfangsbedingungen : Anfangshöhe ,
// Geschwindigkeit , Winkel
const double H, v0 , theta0 ;

Listing 10.22 cppsrc/apps-x710/hpp/x710–01

/**
* @brief Konstruktor
* @param obj Zeiger auf Modellklasse
*/
inline X710( double m, double yinit , double vinit , double angle)
: XModel { __func__ }
, mass{ m }
, H{ yinit }
, v0{ vinit }
, theta0 { angle } {
// teste Eingabeparameter
Check :: all(nmx_msg , { mass > 0, H > 0, v0 > 0, theta0 > 0 });
// berechne und speichere Anfangsbedingungen
_v0x = v0 * std :: cos( theta0 );
_v0y = v0 * std :: sin( theta0 );
}

Listing 10.23 cppsrc/apps-x710/hpp/x710–02


10.5 Beispiele aus der Physik 397

Es folgen die Formeln (Gl. 10.29, Gl. 10.25) für die Wurfweite und die maximale Höhe
sowie Gl. 10.28, Gl. 10.23 für die dazugehörigen Zeiten.

/**
* @brief xmax
* @return Wurfweite
*/
inline double xmax () const {
double term0 = pow(v0 , 2) / Earth ::g * cos( theta0 );
double term1 = pow(sin( theta0 ), 2) + (2 * Earth ::g * H) /
pow(v0 , 2);
return term0 * (sin( theta0 ) + sqrt(term1 ));
}

Listing 10.24 cppsrc/apps-x710/hpp/x710–03

/**
* @brief ymax
* @return Steighöhe
*/
inline double ymax () const { //
return H + 0.5 * pow(_v0y , 2) / Earth ::g;
}

Listing 10.25 cppsrc/apps-x710/hpp/x710–04

/**
* @brief t4xmax
* @return Zeit für maximale Wurfweite
*/
inline double t4xmax () const {
const double _tmp = _v0y + sqrt(pow(_v0y , 2) + 2 * Earth ::g *
H);
return (_tmp) / Earth ::g;
}

Listing 10.26 cppsrc/apps-x710/hpp/x710–05

/**
* @brief t4ymax
* @return Zeit für Steighöhe
*/
inline double t4ymax () const { return _v0y / Earth ::g; }

Listing 10.27 cppsrc/apps-x710/hpp/x710–06

Für gegebene Anfangsbedingungen wird der optimale Winkel für die maximale Reichweite
berechnet (Gl. 10.34).

/**
* @brief xmax_angle
* @return Winkel für maximale Wurfweite
398 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

*/
inline double xmax_angle () const { //
return asin(v0 / sqrt (2 * pow(v0 , 2) + 2 * Earth ::g * H));
}

Listing 10.28 cppsrc/apps-x710/hpp/x710–07

Es folgen die Geschwindigkeits- und Ortsfunktionen.

/**
* @brief vx x- Komponente der Geschwindigkeit als Funktion der Zeit
* @param t Zeit
* @return x- Komponente der Geschwindigkeit
*/
inline double vx( double t) const {
(void) t;
return _v0x;
}

Listing 10.29 cppsrc/apps-x710/hpp/x710–08

/**
* @brief vy y- Komponente der Geschwindigkeit als Funktion der Zeit
* @param t Zeit
* @return y- Komponente der Geschwindigkeit
*/
inline double vy( double t) const { //
return v0 * sin( theta0 ) - Earth ::g * t;
}

Listing 10.30 cppsrc/apps-x710/hpp/x710–09

/**
* @brief x -Koordinate als Funktion der Zeit
* @param t Zeit
* @return x zum Zeitpunkt t
*/
inline double x( double t) const { return _v0x * t; }

Listing 10.31 cppsrc/apps-x710/hpp/x710–10

/**
* @brief y -Koordinate als Funktion der Zeit
* @param t Zeit
* @return y zum Zeitpunkt t
*/
inline double y( double t) const { //
return H + vy(t) * t - 0.5 * Earth ::g * pow(t, 2);
}
}; // X710

Listing 10.32 cppsrc/apps-x710/hpp/x710–11


10.5 Beispiele aus der Physik 399

Das Rechenmodell implementiert die Bewegungsgleichungen (Gl. 10.20) als ein System
von linearen Differenzialgleichungen erster Ordnung


 χ̇1 = ψ1 (10.35a)



 χ̇2 = ψ2 (10.35b)

 ψ̇1 = 0 (10.35c)



ψ̇2 = −g, (10.35d)

löst es numerisch und speichert die berechneten Daten in Dateien.

/**
* @brief The C710 class Rechenmodell schiefer Wurf ohne Luftwiderstand
*/
class C710 : public X710
{
public :
using Ode = gsl :: Odeiv2 <4 >;
friend Ode;
using Data = Data <9 >;

private :
// Speicher für die berechneten Daten
Data _data;

Listing 10.33 cppsrc/apps-x710/hpp/x710–12

Das Gleichungssystem Gl. 10.35 wird implementiert. Damit muss dieses Rechenmodell
der numerischen Routine (Instanz der Klasse gsl::Odeiv2) übergeben werden.

/**
* @brief odefn Schnittstelle zur gsl - Klasse
* @param t Zeit
* @param fin , Ort Geschwindigkeit
* @param fout Geschwindigkeit , Beschleunigung
* @return GSL_SUCCESS
*/
inline int odefn ( double t, const double fin [], double fout []) {
(void) t;
fout [0] = fin [2];
fout [1] = fin [3];
fout [2] = 0;
fout [3] = -Earth ::g;
return GSL_SUCCESS ;
}

public :
// Konstruktoren der Modellklasse werden geerbt
using X710 :: X710;

Listing 10.34 cppsrc/apps-x710/hpp/x710–13

Der Ort und die Geschwindigkeit werden als Funktion der Zeit berechnet. Wenn sich das
Teilchen dem Boden nähert, werden die Zeitschritte kleiner gemacht. Wir kommen somit
400 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

näher an das theoretisch berechnete Ergebnis für Zeit und Geschwindigkeit. Es werden
Daten berechnet, bis die Bedingung y ≥ 0 nicht mehr gültig ist.

/**
* @brief solve_ode numerische Lösung der DGL
*/
inline void exec () {
// Anfangsbedingungen Ort
double x0 = 0.0 , y0 = H;
// Anfangsbedingungen Geschwindigkeit
double vx0 = _v0x , vy0 = _v0y;

// numerische Lösung der Bewegungsgleichungen , die Instanz des


// Rechenmodells wird der Lösungsroutine übergeben .
Ode myOde { *this , 1e-2, 1e-9, 0 };
myOde. set_init_conditions (0, { x0 , y0 , vx0 , vy0 });
double t = 0, dt = 0.1;
do {
_data += { t, myOde [0] , myOde [1], myOde [2], myOde [3] };
t += dt;
myOde . solve (t);
if ( myOde [1] < 2) {
// wenn sich das Teilchen dem Boden nähert
dt = 0.001;
}
} while ( myOde [1] >= 0);

// Einfügen von Energien und Geschwindigkeitsbetrag


for (auto &crow : _data .data ()) {
const double _velocity = sqrt(pow(crow [3] , 2) +
pow(crow [4] , 2));
crow [5] = _velocity ;
crow [6] = Mechanics :: kinetic_energy (mass , _velocity );
crow [7] = Mechanics :: potential_energy (mass , crow [2]);
crow [8] = crow [6] + crow [7];
}
}

Listing 10.35 cppsrc/apps-x710/hpp/x710–14

/**
* @brief save_data speichert Daten im LaTeX - und im CSV - Format
*/
inline void save_data () {
const auto id = Math :: to_degrees ( theta0 );
save(_data , Output :: plot , id);

auto view = _data . select_total_rows (5);


std :: stringstream sstr1 ;
sstr1 << id << "-v";
auto ofslatex1 = get_output_stream ( Output :: latex , sstr1.str ());
view.save(ofslatex1 , Output :: latex , []( const auto &row) {
return std :: array { row [0] , row [1], row [2], row [3], row [4],
row [5] };
});

std :: stringstream sstr2 ;


10.5 Beispiele aus der Physik 401

sstr2 << id << "-e";


auto ofslatex2 = get_output_stream ( Output :: latex , sstr2.str ());
view.save(ofslatex2 , Output :: latex , []( const auto &row) {
return std :: array { row [0] , row [1], row [2], row [6], row [7],
row [8] };
});
}
}; // C710

Listing 10.36 cppsrc/apps-x710/hpp/x710–15

Daten Ein Teilchen der Masse m1 wird unter verschiedenen Abwurfwinkeln von einer
Höhe H = 10 m mit einer Geschwindigkeit v0 = 10 m/s geworfen. Für jeden Abwurfwin-
kel werden Bewegungsdaten berechnet (Tab. 10.4, Abb. 10.4).

/**
* @brief run Berechnung von Beispieldaten
*/
inline void run () {
const double H = 10.0;
const double mass = 1.0;
const double v0 = 10.0;
for ( double angle : { 10.0 , 30.0 , 45.0 , 60.0 }) {
const double theta0 = Math :: to_radians (angle);
C710 cobj{ mass , H, v0 , theta0 };
cobj.exec ();
cobj. save_data ();
}

Listing 10.37 cppsrc/apps-x710/hpp/x710–16

Tab. 10.4: Schiefer Wurf für ϑ0 = 30◦ , v0 = 10 m/s für ein Teilchen mit der Masse
m = 1 kg
t [s] x [m] y [m] vx [m/s] vy [m/s] v [m/s]
0.000e+00 0.000e+00 1.000e+01 8.660e+00 5.000e+00 1.000e+01
1.917e+00 1.660e+01 1.566e+00 8.660e+00 -1.380e+01 1.629e+01
1.953e+00 1.691e+01 1.063e+00 8.660e+00 -1.415e+01 1.659e+01
1.989e+00 1.723e+01 5.469e-01 8.660e+00 -1.451e+01 1.689e+01
2.025e+00 1.754e+01 1.830e-02 8.660e+00 -1.486e+01 1.720e+01
2.026e+00 1.755e+01 3.440e-03 8.660e+00 -1.487e+01 1.721e+01
402 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

Tab. 10.5: Schiefer Wurf für ϑ0 = 30◦ , v0 = 10 m/s für ein Teilchen mit der Masse
m = 1 kg
t [s] x [m] y [m] K [J] U [J] E [J]
0.000e+00 0.000e+00 1.000e+01 5.000e+01 9.807e+01 1.481e+02
1.917e+00 1.660e+01 1.566e+00 1.327e+02 1.536e+01 1.481e+02
1.953e+00 1.691e+01 1.063e+00 1.376e+02 1.042e+01 1.481e+02
1.989e+00 1.723e+01 5.469e-01 1.427e+02 5.363e+00 1.481e+02
2.025e+00 1.754e+01 1.830e-02 1.479e+02 1.795e-01 1.481e+02
2.026e+00 1.755e+01 3.440e-03 1.480e+02 3.373e-02 1.481e+02

12 15
ϑ0 = 10◦ ϑ0 = 45◦
10 ϑ0 = 30◦ ϑ0 = 60◦

8 10
y[m]

y[m]

4 5

0 0
0 5 10 15 0 5 10 15
x[m] x[m]

(a) (b)
Abb. 10.4: Flugbahnen eines Teilchens mit der Anfangsgeschwindigkeit v0 = 10 m/s für
unterschiedliche Winkel

10.5.3 Bewegung einer Rakete in einem konstanten


Gravitationsfeld

Eine Rakete wird senkrecht nach oben geschossen. Sie stößt Gase mit einer konstanten
Geschwindigkeit vR aus. Die Brennrate (Gasmenge, die pro Zeiteinheit ausgestoßen wird)
sei λ. Die Rakete hat zusammen mit dem Treibstoff eine Anfangsmasse m0 . Gehen Sie
von einem konstanten Gravitationsfeld aus und vernachlässigen Sie den Luftwiderstand.

1. Wie lautet die Bewegungsgleichung für die Rakete bis zum Zeitpunkt, zu dem der
gesamte Treibstoff verbraucht ist (Brennschluss)? Berechnen Sie die Geschwindigkeit
und die Höhe der Rakete zum Zeitpunkt des Brennschlusses.
2. Lösen Sie die Bewegungsgleichungen numerisch und vergleichen Sie die Daten mit den
exakten Ergebnissen.
10.5 Beispiele aus der Physik 403

Lösung

Das Koordinatensystem Wir legen das Koordinatensystem so, dass die Flugrichtung
mit der positiven z-Achse übereinstimmt.

Das zweite Newton’sche Gesetz Die Bewegungsgleichung für die Bewegung der Rakete
lautet
⃗ext + dm ⃗vR = m d⃗v ,
F (10.36)
dt dt
⃗ext = m⃗g und vR die Geschwindigkeit der
wobei die Masse m eine Funktion der Zeit, F
ausgestoßenen Gase relativ zur Rakete ist. Da die Brennrate konstant ist, folgt für die
Masse als Funktion der Zeit
m = m0 − λt (10.37)
dm
⇒ = −λ. (10.38)
dt
Wir setzen diesen Ausdruck in (Gl. 10.36) und erhalten

dv
m ⃗ez = −mg⃗ez − λ(−vR⃗ez )
dt
dv λ λvR
⇒ = −g + vR = − g. (10.39)
dt m m0 − λt
Die Geschwindigkeit als Funktion der Zeit Wir integrieren Gl. 10.39 nach der Zeit
und erhalten: ∫ v ∫ t( )
λvR
dv ′ = − g dt′
0 0 m0 − λt
[ ]t
⇒v= − vR ln(m0 − λt′ ) − gt′
( ) 0
m0
⇒ v = vR ln − gt. (10.40)
m0 − λt
Die Höhe als Funktion der Zeit Integration von Gl. 10.40 nach der Zeit liefert die
Höhe der Rakete als Funktion der Zeit:
∫ z ′ ∫ t ( ) ∫ t
dz ′ m0 ′

dt = v R ln dt − g t′ dt′
0 dt 0 m0 − λt′ 0
∫ t ( )
m0 1
⇒ z = vR ln ′
dt′ − gt2 (10.41)
0 m0 − λt 2

Für das Integral der linken Seite gilt:


∫ t ( ) ∫ t ( )
m0 ′ λ ′
ln dt = − ln 1 − t dt′ (10.42)
0 m0 − λt′ 0 m0

Mit der Substitution


λ du λ m0
u=1− t⇒ =− ⇒ dt = − du (10.43)
m0 dt m0 λ
404 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

wird aus dem Integral in Gl. 10.42


∫ ∫ [ ]u
u u
′ m0 m0 m0 ′
− ln u (− )du′ = ′
ln u du = ′ ′
u ln u − u ′
1 λ λ 1 λ
1
m0
= (1 − u + u ln u) . (10.44)
λ
Durch Rücksubstitution erhalten wir
∫ t ( ) ( ) ( )
m0 ′ m0 λ λ
ln dt = t + 1 − t ln 1 − t , (10.45)
0 m0 − λt′ λ m0 m0

und mit Gl. 10.41 die Höhe als Funktion der Zeit:
( ) ( )
m0 λ λ 1
z = vR t + 1− t ln 1 − t vR − gt2 (10.46)
λ m0 m0 2

Höhe und Geschwindigkeit der Rakete, wenn der gesamte Treibstoff verbraucht ist
Wenn zum Zeitpunkt tB der gesamte Treibstoff verbraucht und die Masse der Rakete zu
diesem Zeitpunkt M ist, gilt
M = m0 − λtB
m0 − M
⇒ tB = . (10.47)
λ
Die gesuchte Geschwindigkeit zum Zeitpunkt tB (Gl. 10.40) ist somit:
(m ) g
0
vB = vR ln − (m0 − M ) (10.48)
M λ
Entsprechend folgt aus (Gl. 10.46) für die Höhe:
( ) ( )2
vR M 1 m0 − M
zB = m0 − M + M ln − g (10.49)
λ m0 2 λ

Das Computerprogramm

Kurzbeschreibung Wir führen eine Modellklasse und ein von ihr abgeleitetes Rechen-
modell ein mit folgenden Eigenschaften:

Modellklasse
– Speicherung von Eingabewerten m0 , M , λ, vR und Anfangsbedingungen z(0), v(0)
– Implementierung der hergeleiteten Formeln
Rechenmodell (abgeleitet von der Modellklasse)
– Numerische Lösung der Bewegungsgleichung
– Speicherung der berechneten Daten in einer Zahlentabelle
– Ausgabe der Daten in Dateien
10.5 Beispiele aus der Physik 405

Quelltext Die Modellklasse enthält alle für die Beschreibung der Bewegung relevan-
ten Parameter. Sie werden mit dem Konstruktor initialisiert und dürfen nicht geändert
werden. Der Zeitpunkt, zu dem der gesamte Treibstoff verbraucht ist, die Geschwindig-
keit und die Höhe für diesen Zeitpunkt werden im Konstruktor berechnet und intern
gespeichert (Gl. 10.47 bis Gl. 10.49).

/**
* @brief The X1200 class Bewegung einer Rakete in einem konstanten
* Gravitationsfeld ( Modellklasse )
*/
class X1200 : public XModel
{
private :
// Brennschluss : Zeit , Geschwindigkeit , Höhe
double _zB , _vB , _tB;

public :
// Anfangswerte
using IValues = std :: array <double , 2>;

// Eingabewerte
const double mass0 ; // Masse der Rakete plus Treibstoff t=0
const double massRocket ; // Masse der Rakete ohne Treibstoff
const double lambda ; // Brennrate
const double vRelative ; // Relativgeschwindigkeit
const double z0 , v0; // Anfangsbedingungen

public :

Listing 10.38 cppsrc/apps-x1200/hpp/x1200–01

/**
* @brief X1200 Konstruktor mit Liste aller Eingabeparameter .
* @param minit Masse der Rakete plus Treibstoff t=0
* @param mrocket Masse der Rakete ohne Treibstoff
* @param l Brennrate
* @param vrocket Relativgeschwindigkeit
* @param zinit Anfangsbedingungen (Höhe)
* @param vinit Anfangsbedingungen ( Geschwindigkeit )
*/

inline X1200 ( double minit ,


double mrocket , //
double l,
double vrocket ,
IValues ivals )
: XModel ( __func__ )
, mass0 { minit }
, massRocket { mrocket }
, lambda { l }
, vRelative { vrocket }
, z0{ ivals [0] }
, v0{ ivals [1] } {
Check :: all(nmx_msg ,
{ mass0 > 0,
406 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

massRocket > 0, //
lambda > 0,
vRelative > 0,
z0 >= 0,
v0 >= 0 });
_tB = ( mass0 - massRocket ) / lambda ;
_vB = vRelative * log( mass0 / massRocket ) - Earth ::g * _tB;
_zB = vRelative / lambda * ( mass0 - massRocket + massRocket *
log( massRocket / mass0 ))
- 0.5 * Earth ::g * pow(_tB , 2);
}

Listing 10.39 cppsrc/apps-x1200/hpp/x1200–02

Es kann getestet werden, ob Treibstoff vorhanden ist.

/**
* @brief is_propellant Treibstoff als Funktion der Zeit
* @param t Zeit
* @return false wenn Treibstoff verbraucht ist
*/
inline bool is_propellant ( double t) const { return t <= _tB; }

Listing 10.40 cppsrc/apps-x1200/hpp/x1200–03

Es folgen die Formeln Gl. 10.38 und Gl. 10.39 für die Masse und die Beschleunigung als
Funktion der Zeit.

/**
* @brief mass Masse als Funktion der Zeit
* @param t Zeit
* @return Momentanwert der Masse
*/
inline double mass( double t) const { //
if ( is_propellant (t)) {
return mass0 - lambda * t;
}
return massRocket ;
}

Listing 10.41 cppsrc/apps-x1200/hpp/x1200–04

/**
* @brief acceleration Beschleunigung als Funktion der Zeit
* @param t Zeit
* @return momentaner Wert der Beschleunigung
*/
inline double acceleration ( double t) const { //
return lambda / mass(t) * vRelative - Earth ::g;
}

Listing 10.42 cppsrc/apps-x1200/hpp/x1200–05


10.5 Beispiele aus der Physik 407

Die Höhe und Geschwindigkeit als Funktion der Zeit (Gl. 10.46, Gl. 10.40) werden im-
plementiert:

/**
* @brief height Höhe als Funktion der Zeit
* @param t Zeit
* @return Höhe zum Zeitpunkt t
*/
inline double height ( double t) const {
const double term0 = mass(t) / mass0 ;
const double term1 = term0 * log(term0) * vRelative ;
const double term2 = 0.5 * Earth ::g * pow(t, 2);
return vRelative * t + ( mass0 / lambda ) * term1 - term2;
}

Listing 10.43 cppsrc/apps-x1200/hpp/x1200–06

/**
* @brief velocity Geschwindigkeit als Funktion der Zeit
* @param t Zeit
* @return Geschwindigkeit zum Zeitpunkt t
*/
inline double velocity ( double t) const {
const double term = mass0 / mass(t);
return vRelative * log(term) - Earth ::g * t;
}

Listing 10.44 cppsrc/apps-x1200/hpp/x1200–07

Den Zeitpunkt, zu dem der gesamte Treibstoff verbraucht ist, die Höhe und die Geschwin-
digkeit geben folgende Funktionen zurück.

/**
* @brief time Zeitpunkt zu dem der gesamte Treibstoff
* verbraucht ist
* @return Zeit
*/
inline double time () const { return _tB; }

Listing 10.45 cppsrc/apps-x1200/hpp/x1200–08

/**
* @brief velocity Geschwindigkeit zum Zeitpunkt zu dem der gesamte
* Treibstoff verbraucht ist
* @return Geschwindigkeit
*/
inline double velocity () const { return _vB; }

Listing 10.46 cppsrc/apps-x1200/hpp/x1200–09

/**
* @brief height Höhe zum Zeitpunkt zum dem der gesamte Treibstoff
408 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

* verbraucht ist
* @return Höhe
*/
inline double height () const { return _zB; }
}; // X1200

Listing 10.47 cppsrc/apps-x1200/hpp/x1200–10

Das Rechenmodell übernimmt die numerische Lösung der Bewegungsgleichungen. Gl.


10.38 und Gl. 10.39 werden zu einem System von linearen Differenzialgleichungen erster
Ordnung zusammengefasst:


 ω̇ = −λ (10.50a)


χ̇ = ψ (10.50b)



 ψ̇ = λvR − g, (10.50c)
ω
wobei ω = m, χ = z und ψ = v.

/**
* @brief The C1200 class Bewegung einer Rakete in einem
* konstanten Gravitationsfeld ( Rechenmodell )
*/
class C1200 : public X1200
{
public :
using Ode = gsl :: Odeiv2 <3 >;
using Data = Data <7 >;
friend Ode;

protected :
Data _data; // speichere numerisch berechnete und exakte Werte

public :
using X1200 :: X1200 ; // erbe Konstruktoren der Modellklasse

Listing 10.48 cppsrc/apps-x1200/hpp/x1200–11

Das Rechenmodell implementiert das Gleichungssystem Gl. 10.50.

/**
* @brief odefn Schnittstelle zur gsl
* @param t Zeit
* @param fin Eingabe Feld der Länge 3
* @param fout Ausgabe Feld der Länge 3
* @return GSL_SUCCESS
*/
inline int odefn ( double t, const double fin [], double fout []) {
(void) t;
fout [0] = -lambda ;
fout [1] = fin [2];
fout [2] = lambda / fin [0] * vRelative - Earth ::g;
return GSL_SUCCESS ;
}

Listing 10.49 cppsrc/apps-x1200/hpp/x1200–12


10.5 Beispiele aus der Physik 409

Es werden solange Daten berechnet, bis der Treibstoff verbraucht ist. Die numerisch
berechneten Lösungen werden um die exakt berechneten Werten ergänzt.

/**
* @brief exec Berechnung der numerischen Lösung
* @param t0 Zeit
* @param tstep Zeitschritte
*/
void exec( double t0 = 0, double tstep = 1) {
// Instanz zur Lösung der Differenzialgleichung
Ode myOde { *this , 1e-2, 1e-6, 0 };
myOde. set_init_conditions (0, { mass0 , z0 , v0 });
double t = t0 , dt = tstep ;

// rechne bis der Treibstoff verbraucht ist


while (myOde [0] > massRocket ) {
_data += {
t,
myOde [0] , // Masse
myOde [1] , // Höhe
myOde [2] // Geschwindigkeit
};
t += dt;
myOde . solve (t);
}

// exakte Werte
for (auto &crow : _data .data ()) {
const double t = crow [0];
crow [4] = mass(t);
crow [5] = height (t);
crow [6] = velocity (t);
}
}

Listing 10.50 cppsrc/apps-x1200/hpp/x1200–13

Das Schreiben der Daten in Dateien erfolgt über die Elementfunktion Listing 10.51.
Für die Darstellung der Daten in Diagrammen erzeugen wir eine CSV-Datei mit allen
berechneten Daten. Wir treffen eine Auswahl dieser Daten und speichern diese im LATEX-
Tabellenformat ab, damit die Tabelle übersichtlich ist.

/**
* @brief save_data schreibe berechnete Daten
*/
inline void save_data () {
save(_data , Output :: plot);
save(_data . select_total_rows (8) , Output :: latex);
}
}; // C1200

Listing 10.51 cppsrc/apps-x1200/hpp/x1200–14


410 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

Daten Ein Satz von Eingabeparametern wird einer Instanz eines Rechenmodells über-
geben. Es werden Bewegungsdaten berechnet und im Tabellen- und Grafik-Format in
Dateien gespeichert (Tab. 10.6, Abb. 10.5).

/**
* @brief run Bewegung einer Rakete in einem konstanten Gravitationsfeld
* ( Berechnung von Beispielwerten )
*/
inline void run () {
// Programmparameter
const double m0 = 2.85 e6;
const double M = 0.27 * m0;
const double lambda = 13.84 e3;

// Umrechnung in m/s ( benutzerdefinierte Literale )


const double vR = 2.46 _km; // km/s
const double z0 = 0.0;
const double v0 = 0.0;

// Rechenmodell
C1200 cobj{ m0 , M, lambda , vR , { z0 , v0 } };
cobj.exec ();
cobj. save_data ();
}

} // namespace nmx :: apps :: x1200

Listing 10.52 cppsrc/apps-x1200/hpp/x1200–15

Tab. 10.6: Bewegung einer Rakete im konstanten Gravitationsfeld der Erde. Die drei
letzten Spalten beinhalten die exakten Ergebnisse.
t [s] m [kg] z [m] v [m] mex [kg] zex [m] vex [m/s]
0.000e+00 2.850e+06 0.000e+00 0.000e+00 2.850e+06 0.000e+00 0.000e+00
2.100e+01 2.559e+06 5.662e+02 5.866e+01 2.559e+06 5.662e+02 5.866e+01
4.200e+01 2.269e+06 2.687e+03 1.493e+02 2.269e+06 2.687e+03 1.493e+02
6.300e+01 1.978e+06 7.119e+03 2.806e+02 1.978e+06 7.119e+03 2.806e+02
8.400e+01 1.687e+06 1.484e+04 4.655e+02 1.687e+06 1.484e+04 4.655e+02
1.050e+02 1.397e+06 2.719e+04 7.246e+02 1.397e+06 2.719e+04 7.246e+02
1.260e+02 1.106e+06 4.603e+04 1.093e+03 1.106e+06 4.603e+04 1.093e+03
1.470e+02 8.155e+05 7.429e+04 1.636e+03 8.155e+05 7.429e+04 1.636e+03
1.500e+02 7.740e+05 7.935e+04 1.736e+03 7.740e+05 7.935e+04 1.736e+03
10.6 Übungsaufgaben 411

·106 ·104
3
8

2.5
6

a[m/s2 ]
2
m[kg]

1.5
2

1
0

0 50 100 150 0 50 100 150


t[s] t[s]

(a) (b)
·104

8
1,500

6
v[m/s] 1,000
z[m]

500
2

0
0
0 50 100 150
0 50 100 150
t[s]
t[s]

(c) (d)

Abb. 10.5: Bewegung einer Rakete im konstanten Gravitationsfeld der Erde. (a) m − t-
Diagramm, (b) a − t-Diagramm (c) z − t-Diagramm, (d) v − t-Diagramm

10.6 Übungsaufgaben
1. Implementieren Sie das Euler- und das Runge-Kutta-Verfahren zweiter Ordnung ohne
Schrittweitenanpassung. Lösen Sie mit beiden Methoden
a) die Differenzialgleichung (Gl. 10.6) an den Stellen x = 0, . . . 2 mit ∆x = 0.25,
b) die Differenzialgleichung (Gl. 10.9) an den Stellen x = 0, . . . 5 mit ∆x = 0.5.
Stellen Sie die Ergebnisse in Tabellen zusammen mit den Abweichungen von der
exakten Lösung dar.
2. Erstellen Sie mit den Daten aus Abschnitt 10.5.2 Energie-Zeit-Diagramme.
3. Entwerfen Sie ein Rechenmodell für die Aufgabe aus Abschnitt 10.5.2, welches Da-
ten für die Flugbahn eines Teilchens durch Anwendung der analytisch hergeleiteten
Formeln berechnet.
4. Eine Perle ist an einem Ende einer Feder befestigt und kann entlang einer Schiene
reibungsfrei gleiten (Abb. 10.6). Die Perle wird aus ihrer Ruhelage ausgelenkt und
losgelassen.
a) Stellen Sie die Bewegungsgleichung für die Perle auf und lösen Sie diese numerisch.
412 10 Anfangswertprobleme für gewöhnliche Differenzialgleichungen

b) Stellen Sie den Ort, die Geschwindigkeit und die Beschleunigung als Funktion der
Zeit in einer Tabelle und in Diagrammen dar.
Implementieren Sie alles innerhalb einer Funktion in Form eines Prototyps und über-
legen Sie, wie Sie daraus ein Modell erzeugen können.

l ⃗k
l0 F

0
Abb. 10.6: Perle bewegt sich reibungsfrei entlang einer Schiene nur unter dem Einfluss
der Federkraft. l0 ist die Länge der entspannten Feder.
11 Interpolation

Übersicht
11.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
11.2 Interpolation mit der GNU Scientific Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
11.3 Die Schnittstelle zur GNU Scientific Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
11.4 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
11.5 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423

11.1 Einleitung
Bei einer eindimensionalen Interpolation handelt es sich um die Aufgabe, Werte einer
Funktion f (x) zu ermitteln, von der nur N Wertepaare (x0 , y0 ), . . . (xN , yN ) bekannt
sind. Dies können z.B. Messdaten eines Experiments oder Ergebnisse einer numerischen
Berechnung sein. Die xi heißen Stützstellen, die yi Stützwerte und die Punkte (xi , yi )
Stützpunkte. In der Praxis werden die Daten gruppenweise bzw. paarweise durch ein
Polynom approximiert. Wenn das Polynom zwischen zwei benachbarten Stützpunkten
ersten Grades ist, so spricht man von einer linearen Interpolation (Abb. 11.1a). Eine
derartige Approximation ist zwar stetig, ihre erste und zweite Ableitung an den Stütz-
punkten aber nicht. Bei kubischen Splines handelt es sich um Polynome dritten Gerades,
die zwei benachbarte Stützpunkte verbinden. Zusätzlich sind die erste und zweite Ablei-
tung der zwei Polynome an den Verbindungspunkten gleich (Abb. 11.1b).

y y
(x1 , y1 ) s1 (x)
s0 (x) (x1 , y1 )
y (x2 , y2 )
(x2 , y2 )
(x3 , y3 )
(x0 , y0 )
(x0 , y0 )
0 x
x x
0
(a) (b)
Abb. 11.1: (a) Lineare Interpolation und (b) Spline-Interpolation. s0 (x) und s1 (x) sind
kubische Polynome, welche die Punkte (x0 , y0 ) und (x1 , y1 ) verbinden.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_11
414 11 Interpolation

Die Berechnung der Spline-Koeffizienten basiert auf einem Verfahren, das wir hier kurz
skizzieren möchten. Sei s0 ein Polynom dritten Grades mit s0 (x) = a0 +b0 x+c0 x2 +d0 x3 ,
welches die Punkte (x0 , y0 ) und (x1 , y1 ) verbindet, und s1 mit s1 (x) = a1 + b1 x + c1 x2 +
d1 x3 , dass die Punkte (x1 , y1 ) und (x2 , y2 ) miteinander verbindet. Wir fordern, dass
folgende Bedingungen erfüllt werden:

s0 (x0 ) = y0 (11.1)
s0 (x1 ) = y1 (11.2)
s′0 (x1 ) = s′1 (x1 ) (11.3)
s′′
0 (x1 ) = s′′
1 (x1 ), (11.4)

wobei s′i und s′′


i die erste und die zweite Ableitung des Polynoms si (x) sind. Diese Bedin-
gungen müssen für jede si (x) und si+1 (x) erfüllt werden, was somit zu vier Gleichungen
für jedes Polynom si (x) führt und damit seine Koeffizienten bestimmt.

11.2 Interpolation mit der GNU Scientific Library


Wir erzeugen einen Beispieldatensatz aus zehn Datenpaaren (xi , yi ) = (i + 0.5 sin(i), i +
cos(i2 )) mit i = 0, . . . 9, für die wir Zwischenwerte mittels Interpolation mit einem
Programm berechnen wollen. Wir beginnen mit einer Variante, in der direkt mit gsl-
Strukturen und Funktionen gearbeitet wird. Als erstes wird der Datensatz erzeugt. Es
folgt ein Block, in dem die gsl-Objekte zur Verwaltung der Daten initialisiert werden.
Über eine Schleife werden dann die zu berechnenden Wertepaare ermittelt. Zum Schluss
werden alle Ressourcen wieder freigegeben.

/**
* @brief ex1 Interpolation mit gsl - Routinen
*/
inline void ex1 () {
// Anzahl der Daten
constexpr size_t N = 10;
// Felder mit diskreten Daten
double xi , yi , x[N], y[N];

for ( size_t i = 0; i < N; i++) {


x[i] = i + 0.5 * sin(i);
y[i] = i + cos(i * i);
}

// gsl Objekte Initialisierung


gsl_interp_accel *acc = gsl_interp_accel_alloc ();
const gsl_interp_type *t = gsl_interp_cspline ;
gsl_spline * spline = gsl_spline_alloc (t, N);

// Verbindung gsl - Objekte mit diskreten Daten


gsl_spline_init (spline , x, y, N);

// berechne Werte
11.2 Interpolation mit der GNU Scientific Library 415

for (xi = x[0]; xi < x[9]; xi += 0.01) {


yi = gsl_spline_eval (spline , xi , acc);
std :: cout << xi << "," << yi << std :: endl;
}

// Speicherfreigabe
gsl_spline_free ( spline );
gsl_interp_accel_free (acc);
}

Listing 11.1 cppsrc/apps-x033/hpp/x033–01

Die Struktur gsl_spline ist das Objekt, mit dem die zu interpolierenden Daten verwaltet
werden. Diesem Objekt wird der erzeugte Datensatz zugeordnet und über diesem Ob-
jekt wird mittels einer Funktion jeder beliebige Zwischenwert berechnet. Die Struktur
gsl_interp_accel hilft die Berechnung der Werte zu beschleunigen. Das Programm muss
dafür sorgen, dass die angeforderten Ressourcen wieder freigegeben werden.
Bevor wir darüber nachdenken, wie mithilfe dieses Programms eine Klasse entwor-
fen werden kann, betrachten wir ein weiteres Beispiel (Listing 11.2). In diesem Beispiel
werden periodische Daten interpoliert.

/**
* @brief ex2 Interpolation periodischer Daten
*/
inline void ex2 () {
// Anzahl der Daten
size_t N = 4;

// es muss y[0]=y[3] gelten damit


// gsl_interp_cspline_periodic eingesetzt werden kann
double x[4] = { 0.00 , 0.10 , 0.27 , 0.30 };
double y[4] = { 0.15 , 0.70 , -0.10 , 0.15 };

// Initialisierung von gsl Objekten


gsl_interp_accel *acc = gsl_interp_accel_alloc ();
const gsl_interp_type *t = gsl_interp_cspline_periodic ;
gsl_spline * spline = gsl_spline_alloc (t, N);

// Verbindung gsl - Objekte mit diskreten Daten


gsl_spline_init (spline , x, y, N);

// berechne Werte
for ( size_t i = 0; i <= 100; i++) {
double xi = (1 - i / 100.0) * x[0] + (i / 100.0) * x[N - 1];
double yi = gsl_spline_eval (spline , xi , acc);
std :: cout << xi << "," << yi << std :: endl;
}

// Speicherfreigabe
gsl_spline_free ( spline );
gsl_interp_accel_free (acc);
}

Listing 11.2 cppsrc/apps-x033/hpp/x033–02


416 11 Interpolation

Wir stellen fest, dass die Berechnung der Daten nach demselben Muster erfolgt. Was sich
geändert hat, ist das Verfahren, mit dem interpoliert wird, welches über den Parameter
gsl_interp_type festgelegt wird. Die gsl-Funktionen sind so konstruiert, dass es jeder-
zeit möglich ist, das Interpolationsverfahren zu ändern. Wir bemerken an dieser Stelle
zusätzlich, dass im Fall von periodischen Daten der erste und letzte Stützwert identisch
sein müssen.

11.3 Die Schnittstelle zur GNU Scientific Library


Für die eindimensionale Interpolation existieren in der GNU Scientific Library zwei Grup-
pen von Funktionen, welche genau dieselbe Arbeit tun. Sie unterscheiden sich dadurch,
dass die erste Gruppe (alle Funktionen diese Gruppe beginnen mit gsl_interp) bei je-
der Funktionsauswertung die Angabe des kompletten Datensatzes erfordert, während die
Funktionen der zweiten Gruppe dies nicht benötigen (die Funktionen dieser Gruppe be-
ginnen mit gsl_spline). Wir werden mit folgender Klasse die Funktionen der zweiten
Gruppe abbilden. Den zwei Beispielprogrammen entnehmen wir, dass unsere Klasse ei-
nen Zeiger auf eine gsl_spline-Struktur speichern muss. Hinzu kommt ein Zeiger auf eine
gsl_interp_accel-Struktur.

/**
* @brief The Spline class Interpolation von diskreten Daten
*/
class Interpolation
{
private :
gsl_spline * _workspace = nullptr ; //gsl - interne Datenstruktur
gsl_interp_accel * _accel = nullptr ; // effiziente Suche im Datensatz

public :

Listing 11.3 cppsrc/nmx-xinterpolation/hpp/xinterpolation–01

Die Klasse kann ohne gültigen gsl_spline-Zeiger nicht korrekt arbeiten, deswegen soll
es nicht erlaubt sein, Instanzen mittels des Standardkonstruktors anzulegen, da hier
notwendige Informationen zum Anlegen eines gsl_spline fehlen.

/**
* @brief InterpolationX Einsatz des
* Standardkonstruktors nicht erlaubt
*/
Interpolation () = delete ;

Listing 11.4 cppsrc/nmx-xinterpolation/hpp/xinterpolation–02

Durch Angabe des Interpolationsalgorithmus und der Daten wird eine funktionsfähige
Instanz der Klasse erzeugt. Der Typ des Daten-Containers wird an dieser Stelle offen
11.3 Die Schnittstelle zur GNU Scientific Library 417

gelassen. Infrage kommen alle Container-Typen, die Daten intern in ein eindimensionales
C-Feld speichern.

/**
* @brief Interpolation
* @param type Interpolationsalgorithmus
* @param x Daten z.B. C-Feld oder std :: vector ( monoton wachsend )
* @param y Daten z.B. C-Feld oder std :: vector
*/
template <class X, class Y>
inline Interpolation ( const gsl_interp_type *type , const X &x, const
Y &y) {
size_t _dim = x.size ();
_workspace = gsl_spline_alloc (type , _dim);
_accel = gsl_interp_accel_alloc ();
gsl_spline_init ( _workspace , &x[0], &y[0], _dim);
}

Listing 11.5 cppsrc/nmx-xinterpolation/hpp/xinterpolation–03

Es folgt ein weiterer Konstruktor für den Fall, dass die Daten in gsl::Vector-Containern
gespeichert sind.

/**
* @brief Interpolation
* @param type Interpolationsalgorithmus
* @param x gsl :: Vector
* @param y gsl :: Vector
*/
inline Interpolation ( const gsl_interp_type *type , const gsl :: Vector
&x, const gsl :: Vector &y) {
size_t _dim = x.size ();
_workspace = gsl_spline_alloc (type , _dim);
_accel = gsl_interp_accel_alloc ();
gsl_spline_init ( _workspace , x. begin (), y.begin (), _dim);
}

Listing 11.6 cppsrc/nmx-xinterpolation/hpp/xinterpolation–04

Die Klasse besitzt keinen Kopierkonstruktor, dadurch wird ihre Implementierung einfa-
cher.

/**
* @brief InterpolationX Einsatz des Kopierkonstruktors
* nicht erlaubt
* @param obj
*/
Interpolation ( const Interpolation &obj) = delete ;

Listing 11.7 cppsrc/nmx-xinterpolation/hpp/xinterpolation–05

Der Destruktor sorgt dafür, dass die Klasse, den von der gsl reservierten Speicher wieder
freigibt.
418 11 Interpolation

/**
* @brief ~ Interpolation Destruktor gsl - Strukturen werden
* freigegeben
*/
inline ~ Interpolation () {
if ( _accel != nullptr ) {
gsl_interp_accel_free ( _accel );
}

if ( _workspace != nullptr ) {
gsl_spline_free ( _workspace );
}
}

Listing 11.8 cppsrc/nmx-xinterpolation/hpp/xinterpolation–06

Wenn das Kopieren von Instanzen dieser Klasse verboten ist, so muss dies auch für die
Anwendung des Zuweisungsoperators gelten.

/**
* @brief operator = Zuweisungsoperator nicht erlaubt
* @param obj
* @return
*/
Interpolation & operator =( const Interpolation &obj) = delete ;

Listing 11.9 cppsrc/nmx-xinterpolation/hpp/xinterpolation–07

Die Berechnung eines y-Wertes für ein gegebenes x erfolgt über diese Funktion. Sollten die
x-Werte nicht monoton wachsend sein, wird seitens der gsl eine Fehlermeldung erzeugt.

/**
* @brief eval Funktionswert ...
* @param val ... an der Stelle val
* @return Funktionswert
*/
inline double eval( double val) {
double result ; //y-Wert wird hier gespeichert
// Rückgabewert enthält Fehlermeldung
const int errorcode = gsl_spline_eval_e ( _workspace ,
val , // x-Wert
_accel ,
& result );
// wenn der Wert val sich außerhalb der vorgegebenen Werte
// befindet meldet die gsl einen Fehler
Check :: error_if (nmx_msg , errorcode == GSL_EDOM );
return result ;
}

Listing 11.10 cppsrc/nmx-xinterpolation/hpp/xinterpolation–08

Es können Näherungswerte für die erste und zweite Ableitung berechnet werden (Listing
11.11, Listing 11.12).
11.3 Die Schnittstelle zur GNU Scientific Library 419

/**
* @brief deriv erste Ableitung der Funktion ...
* @param val ... an der Stelle val
* @return Rückgabewert erste Ableitung
*/
inline double deriv ( double val) {
double result ; //y-Wert
const int errorcode = gsl_spline_eval_deriv_e (_workspace , //
val ,
_accel ,
& result );
Check :: error_if (nmx_msg , errorcode == GSL_EDOM );
return result ;
}

Listing 11.11 cppsrc/nmx-xinterpolation/hpp/xinterpolation–09

/**
* @brief deriv2 zweite Ableitung der Funktion ...
* @param val ... an der Stelle val
* @return Rückgabewert zweite Ableitung
*/
inline double deriv2 ( double val) {
double result ; //y-Wert
const int errorcode = gsl_spline_eval_deriv2_e (_workspace , //
val ,
_accel ,
& result );
Check :: error_if (nmx_msg , errorcode == GSL_EDOM );
return result ;
}

Listing 11.12 cppsrc/nmx-xinterpolation/hpp/xinterpolation–10

Diese Funktion berechnet das Integral über den diskreten Datensatz, wobei die x-Werte
die Stützstellen und die y-Werte die Funktionswerte sind.

/**
* @brief integral numerische Integration
* @param xmin untere Grenze
* @param xmax obere Grenze
* @return Ergebnis der Integration
*/
inline double integral ( double xmin , double xmax) {
double result ;
const int errorcode = gsl_spline_eval_integ_e (_workspace , //
xmin ,
xmax ,
_accel ,
& result );
Check :: error_if (nmx_msg , errorcode == GSL_EDOM );
return result ;
}

Listing 11.13 cppsrc/nmx-xinterpolation/hpp/xinterpolation–11


420 11 Interpolation

Die Anzahl der gespeicherten Datenpaare kann über folgende Elementfunktion gelesen
werden.

/**
* @brief size
* @return Anzahl der Datenpaare im Datensatz
*/
inline size_t size () const { return _workspace ->size; }

Listing 11.14 cppsrc/nmx-xinterpolation/hpp/xinterpolation–12

11.4 Beispiele
Für die Ausgabe der Daten in Dateien werden wir in diesem Abschnitt folgende Hilfs-
funktion einsetzen.

/**
* @brief get_output_stream
* @param name Name der Datei ohne Suffix
* @param fmt Formatierungsobjekt
*/
inline auto get_output_stream ( const char *name , Format fmt =
Output :: csv) {
auto ofs = Output :: get_stream ("x033", fmt , name);
ofs << std :: fixed << std :: setprecision (2);
return ofs;
}

Listing 11.15 cppsrc/apps-x033/hpp/x033–03

11.4.1 Interpolation diskreter Daten

Für vorgegebene Werte von f (x) = sin(x) mit 0 < x < 2π wird eine interpolieren-
de Funktion berechnet. Mittels einer Schleife wird ein Datensatz erzeugt und in zwei
std::vector-Containern gespeichert. Zwei Instanzen der Klasse berechnen mittels linea-
rer und Spline-Interpolation y-Werte für beliebige x im Intervall 0 < x < 2π. Die Ausgabe
erfolgt im Tabellen- und Grafikformat (Tab. 11.1, Abb. 11.2).

/**
* @brief interp1 lineare und spline Interpolation
* eines diskreten Datensatzes
*/
inline void interp1 () {
using Data = Data <4 >;
// Datenobjekt
Data data;
11.4 Beispiele 421

// Datenreihe
std :: vector <double > xvalues , yvalues ;

// erstelle diskrete Daten


auto fn = []( double x) { return sin(x); };

// berechne diskrete Werte


double dx = 0.4 * Math :: PI;
for ( double x = 0; x <= 2 * Math :: PI; x += dx) {
xvalues . push_back (x);
yvalues . push_back (fn(x));
std :: cout << x << "," << fn(x) << std :: endl;
}

// lineare Interpolation
gsl :: Interpolation linear ( gsl_interp_linear , xvalues , yvalues );

// spline Interpolation
gsl :: Interpolation spline ( gsl_interp_cspline , xvalues , yvalues );

// berechne interpolierte Werte


dx = 0.3 * Math :: PI;
for ( double x = dx; x < 2 * Math :: PI; x += dx) {
data += { x, linear .eval(x), spline .eval(x), fn(x) };
}

// Ausgabe
std :: ofstream ofsplot = get_output_stream (__func__ , Output :: plot);
data.save(ofsplot , Output :: plot);
std :: ofstream ofstable = get_output_stream (__func__ , Output :: latex);
ofstable << std :: setprecision (4);

// LaTeX - Tabelle enthält eine Auswahl der berechneten Werte


const auto view = data. select_total_rows (4);
view.save(ofstable , Output :: latex );
}

Listing 11.16 cppsrc/apps-x033/hpp/x033–04

1 1

0.5 0.5
fs (x)x
fl (x)

0 0

−0.5 −0.5

−1 −1

1 2 3 4 5 6 1 2 3 4 5 6
x x

(a) (b)
Abb. 11.2: (a) Lineare Interpolation und (b) Spline-Interpolation der Daten. Die Punkte
stellen die mit std::sin berechneten Werte dar.
422 11 Interpolation

Tab. 11.1: Lineare Interpolation fl (x) und Spline-Interpolation fs (x). In der letzten
Spalte stehen die mit std::sin berechneten Werte.
x fl (x) fs (x) sin(x)
0.9425 0.7133 0.8067 0.8090
2.8274 0.2939 0.3104 0.3090
4.7124 -0.8602 -0.9949 -1.0000
5.6549 -0.4755 -0.5823 -0.5878

11.4.2 Beschreibung einer geradlinigen Bewegung durch


Interpolation

Der Ort eines sich geradlinig bewegenden Massenpunktes wird durch die Funktion ⃗x(t) =
(−0.75t2 + 3t + 1)⃗ex (alle Angaben im SI) beschrieben.

1. Wie lauten die Formeln für die Geschwindigkeit v(t) und die Beschleunigung a(t)?
2. Erstellen Sie mit den hergeleiteten Formeln Daten für x(t) für Zeiten 0 ≤ t ≤ 4 mit
∆t = 0.1. Berechnen Sie auf Basis dieses Datensatzes Werte für x, v und a mittels
Interpolation für die Zeiten t = 0.3, 1.7, 2.2, 3.8. Vergleichen Sie die Ergebnisse mit
den exakten Werten.

Formeln für Geschwindigkeit und Beschleunigung Es lässt sich schnell feststellen, dass
es sich um eine geradlinige Bewegung mit konstanter Beschleunigung handelt. Die For-
meln für Geschwindigkeit und Beschleunigung lassen sich durch einmaliges bzw. zweima-
liges Differenzieren der Ortsfunktion gewinnen.

⃗x˙ = (−1.5t + 3)⃗ex , ¨ = −1.5⃗ex


⃗x (11.5)

Quelltext und Daten Wir werden die Funktionalität der in diesem Abschnitt imple-
mentierten Klasse nutzen, nicht nur um Werte für den Ort, sondern auch für die erste
und zweite Ableitung und damit die Geschwindigkeit und Beschleunigung zu berechnen
(Tab. 11.2).

/**
* @brief interp2 Beschreibung einer geradlinigen Bewegung
* durch Interpolation
*/
inline void interp2 () {
using Data = Data <7 >;
Data data; // Datenobjekt
// Ort
auto x = []( double t) { return -0.75 * pow(t, 2) + 3 * t + 1; };
// Geschwindigkeit
auto xdot = []( double t) { return -1.5 * t + 3; };
// Beschleunigung
auto xddot = []( double t) {
(void) t;
return -1.5;
};
11.5 Übungsaufgaben 423

// diskrete Werte werden erzeugt


std :: vector <double > tvalues , xvalues ;
for ( double t = 0; t < 4; t += 0.1) {
tvalues . push_back (t);
xvalues . push_back (x(t));
}

gsl :: Interpolation interpfn { gsl_interp_cspline , tvalues , xvalues };


// fülle Datenobjekt mit interpolierten und exakten Daten
for (auto t : { 0.32 , 1.75 , 2.22 , 3.18 }) {
data += { t,
x(t),
xdot(t),
xddot (t),
interpfn .eval(t),
interpfn . deriv (t),
interpfn . deriv2 (t) };
}
// Ausgabe
std :: ofstream ofs = get_output_stream (__func__ , Output :: latex);
ofs << std :: setprecision (4);
data.save(ofs , Output :: latex );
}

Listing 11.17 cppsrc/apps-x033/hpp/x033–05

Tab. 11.2: Ort, Geschwindigkeit und Beschleunigung exakte (Spalten 2, 3, 4) und in-
terpolierte Werte (Spalten 5, 6, 7)
t [s] x [m] v [m/s] a [m/s2 ] xapp [m] vapp [m/s] aapp [m/s2 ]
0.3200 1.8832 2.5200 -1.5000 1.8832 2.5203 -1.5215
1.7500 3.9531 0.3750 -1.5000 3.9531 0.3750 -1.5000
2.2200 3.9637 -0.3300 -1.5000 3.9637 -0.3300 -1.5000
3.1800 2.9557 -1.7700 -1.5000 2.9557 -1.7700 -1.5001

11.5 Übungsaufgaben
1. Der diskrete Datensatz aus (Tab. 11.3) wurde mit der Funktion f (x) = ecos x erzeugt.
Finden Sie die Werte y1 , y2 an den Stellen x1 = 0.34 und x2 = 0.62 mittels linearer
und Spline-Interpolation. Vergleichen Sie die berechneten Daten mit den exakten
Ergebnissen.
2. Ein Teilchen bewegt sich geradlinig mit einer Geschwindigkeit, welche in Abb. 11.3
dargestellt wird.
a) Stellen Sie für 0 ≤ t ≤ 10 s mit ∆t = 0.25 s die Geschwindigkeit und die Beschleu-
nigung als Funktion der Zeit in einer Tabelle dar.
b) Berechnen Sie die Strecke, welche das Teilchen in den ersten 10 s zurückgelegt hat.
424 11 Interpolation

3. Benutzen Sie das Modell für die Bewegung einer Rakete im homogenen Schwerefeld
(Abschnitt 10.5.3) um Tab. 10.6 zu generieren. Berechnen Sie mittels Interpolation
t −t
für Zeiten t = i+12 i Werte für die Masse m und die Höhe z der Rakete. Nutzen Sie
die Funktionen der Klasse, um die Geschwindigkeit der Rakete zu berechnen.

Tab. 11.3: Datensatz, erzeugt mit f (x) = ecos x


x 0 0.25 0.5 0.75 1
y 2.7183 2.6351 2.4051 2.0786 1.7165

30

20
v[m/s]

10

−10

0 2 4 6 8 10 12
t[s]
Abb. 11.3: Geschwindigkeit-Zeit-Diagramm
Teil III

Klassische Mechanik mit C++


12 Eindimensionale Bewegungen

Übersicht
12.1 Geradlinige Bewegung mit konstanter äußerer Kraft und Gleitreibung . . . . . . 427
12.2 Teilchen bewegt sich unter dem Einfluss einer ortsabhängigen Kraft . . . . . . . . 434
12.3 Teilchen bewegt sich unter dem Einfluss einer zeitabhängigen Kraft . . . . . . . . . 440
12.4 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446

12.1 Geradlinige Bewegung mit konstanter äußerer


Kraft und Gleitreibung
Ein auf horizontaler Ebene ruhendes Objekt der Masse m wird unter dem Einfluss einer
konstanten, nach oben gerichteten äußeren Kraft, die einen Winkel φ mit der Horizontalen
bildet, in Bewegung gesetzt. Die Kraft wirkt für t1 Sekunden und das Objekt kommt nach
insgesamt t2 Sekunden zum Stillstand. Der Gleitreibungskoeffizient zwischen Körper und
Ebene sei µ.


N ⃗′
N

F
⃗ey

R φ ⃗′
R
⃗ex

(t0 , x0 , v0 ) = (0, 0, 0) w
⃗ (t1 , x1 , v1 ) w
⃗ (t2 , x2 , v2 = 0)

Abb. 12.1: Bewegung eines Objekts auf horizontaler Ebene. Ab den Zeitpunkt t1 wirkt
⃗ nicht mehr.
die äußere Kraft F

1. Stellen Sie die Bewegungsgleichungen für das Objekt auf. Leiten Sie eine Formel für
die Zeit, die das Objekt braucht, bis es zum Stillstand kommt sowie eine Formel für
die insgesamt zurückgelegte Strecke her.
2. Beschreiben Sie die Bewegung mithilfe eines Computerprogramms und vergleichen
Sie die Daten mit den exakt ermittelten Ergebnissen.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_12
428 12 Eindimensionale Bewegungen

Lösung

Das Koordinatensystem Wir legen die positive x-Achse entlang der Bewegungsrichtung
des Objekts (Abb. 12.1). Die Anfangsbedingungen sind x(0) = 0, v(0) = 0.

Die Bewegungsgleichungen Wir schreiben das zweite Newton’sche Gesetz:

m ⃗r¨ = F
⃗ +R
⃗ +N
⃗ + w,
⃗ (12.1)

wobei N⃗ die Normalkraft, F⃗ die äußere Kraft, w ⃗ die Gleitreibung


⃗ die Gewichtskraft und R
ist. Für die äußere Kraft gilt: 
F, t ≤ t
1
|F
⃗| = (12.2)
0, t > t1 .

Wir multiplizieren Gl. 12.1 einmal skalar mit ⃗ex dann mit ⃗ey und erhalten


 mẍ = F cos φ − µN ẋ (12.3a)
|⃗x˙ |


mÿ = F sin φ + N − mg, (12.3b)
⃗ = −µN ẋ ⃗ex gesetzt haben. Das Objekt wird sich nur in die positive
wobei wir R x˙ |
|⃗

Richtung bewegen, deswegen ist x˙ |
|⃗
= 1.
{
mẍ = F cos φ − µN (12.4a)
mÿ = F sin φ + N − mg. (12.4b)
Entlang der y-Achse findet keine Bewegung statt. Wir erhalten deswegen aus Gl. 12.4b

N = mg − F sin φ. (12.5)
Daraus folgt für die Bewegung entlang der x-Richtung
F µ
ẍ = cos φ − (mg − F sin φ)
m m
F
⇒ ẍ = (cos φ + µ sin φ) − µg (12.6)
m
Der erste Bewegungsabschnitt t ≤ t1 Wir erhalten die Geschwindigkeit, indem wir
Gl. 12.6 nach der Zeit integrieren:
∫ t[ ]
F
v(t) = (cos φ + µ sin φ) − µg dt′ (12.7)
0 m
und damit [ ]
F
v(t) = (cos φ + µ sin φ) − µg t. (12.8)
m
Die Ortskoordinate erhalten wir durch erneute Integration nach der Zeit:
∫ t[ ]
F
x(t) = (cos φ + µ sin φ) − µg t′ dt′
m
[ 0 ]
1 F
= (cos φ + µ sin φ) − µg t2 . (12.9)
2 m
12.1 Geradlinige Bewegung mit konstanter äußerer Kraft und Gleitreibung 429

Der zweite Bewegungsabschnitt t > t1 In diesem Fall ist F = 0 und aus Gl. 12.6 wird

ẍ = −µg. (12.10)

Wir berechnen die Geschwindigkeit durch Integration nach der Zeit.


∫ t
v(t) − v(t1 ) = (−µg) dt′ = −µg (t − t1 )
t1

oder
v(t) = −µg (t − t1 ) + v1 , (12.11)

wobei v1 = v(t1 ) (Gl. 12.8). Wir integrieren noch einmal und erhalten die Ortskoordinate:
∫ t
( )
x(t) − x(t1 ) = −µg(t′ − t1 ) + v1 dt′
t1

oder
1
x(t) = x1 + v1 (t − t1 ) − µg (t − t1 )2 . (12.12)
2
Die Bewegungszeit und die gesamte zurückgelegte Strecke Wir setzen in Gl. 12.11
v = 0 ein und erhalten die Zeit t2 vom Anfang der Bewegung bis das Objekt zum
Stillstand kommt:
v1
v1 − µg(t2 − t1 ) = 0 ⇒ t2 = + t1 . (12.13)
µg
Setzen wir dieses Ergebnis in Gl. 12.12, erhalten wir für die Ortskoordinate und damit
für die gesamte zurückgelegte Strecke

v12
x2 = x1 + . (12.14)
2µg

Das Computerprogramm

Kurzbeschreibung Zur Beschreibung der Bewegung des Objekts führen wir eine Mo-
dellklasse und ein Rechenmodell ein. Es folgen die Eigenschaften der beiden Modelle.

Modellklasse
– Eingabe und Speicherung der Bewegungsparameter µ, m, F, φ, t1
– Implementierung der theoretisch hergeleiteten Formeln
Rechenmodell (abgeleitet von der Modellklasse)
– Numerische Lösung der Bewegungsgleichungen
– Ergänzung der numerisch berechneten Daten (optional) mit exakten Werten
– Speicherung der Daten in Dateien
430 12 Eindimensionale Bewegungen

Quelltext Die Beschleunigung des Objekts verändert sich nach dem Wegfall der äuße-
ren Kraft. Innerhalb der Zeitintervalle [0, t1 ] und (t1 , t2 ] bewegt sich das Objekt aber mit
konstanten Beschleunigungen (Gl. 12.6 und Gl. 12.10). Wir werden diese im Konstruktor
berechnen und intern speichern. Dies vereinfacht die Implementierung der Elementfunk-
tionen der Modellklasse.

/**
* @brief The X200 class Geradlinige Bewegung eines Objekts mit
* konstanter äußerer Kraft und Gleitreibung ( Modellklasse )
*/
class X200 : public XModel
{
private :
// konstante Beschleunigungen
double _accel1 = 0, _accel2 = 0;
// Anfangsort und Geschwindigkeit für den zweiten Teil der Bewegung
double _v1final = 0, _x1final = 0;

public :
// Eingabeparameter
const double mass , mu;
const double force , phi;
const double t1;

Listing 12.1 cppsrc/apps-x200/hpp/x200–01

/**
* @brief X200 Konstruktor
* @param inmu Gleitreibungskoeffizient
* @param inforce Betrag der Kraft
* @param inphi Winkel
*/
inline X200( double inmass , double inmu , double inforce , double
inphi , double t1)
: XModel ( __func__ )
, mass( inmass )
, mu{ inmu }
, force { inforce }
, phi{ inphi }
, t1{ t1 } {
Check :: all(nmx_msg ,
{ inmass > 0, //
inmu > 0,
inforce > 0,
inphi > 0,
t1 > 0 });
_accel1 = ( force ) / (mass) * (cos(phi) + (mu) *sin(phi)) - (mu)
*Earth ::g;
_accel2 = -(mu) * Earth ::g;
_v1final = v(t1);
_x1final = x(t1);
}

Listing 12.2 cppsrc/apps-x200/hpp/x200–02


12.1 Geradlinige Bewegung mit konstanter äußerer Kraft und Gleitreibung 431

Es folgen die Elementfunktionen zur Berechnung des Betrags der äußeren Kraft und der
Beschleunigung des Objekts (Gl. 12.2).

/**
* @brief get_force die zeitabhängige Kraft
* @param t Zeit
* @return Momentanwert der Kraft
*/
inline double get_force ( double t) const { //
return t <= (t1) ? force : 0;
}

Listing 12.3 cppsrc/apps-x200/hpp/x200–03

/**
* @brief acceleration die zeitabhängige Kraft Beschleunigung
* @param t Zeit
* @return Momentanwert der Beschleunigung
*/
inline double acceleration ( double t) const { //
return t <= t1 ? _accel1 : _accel2 ;
}

Listing 12.4 cppsrc/apps-x200/hpp/x200–04

Der Ort als Funktion der Zeit wird implementiert (Gl. 12.9 und Gl. 12.12).

/**
* @brief v Ortsvektor als Funktion der ...
* @param t .. Zeit
* @return Ortsvektor zum Zeitpunkt t
*/
inline double x( double t) const {
if (t <= t1) {
return 0.5 * _accel1 * pow(t, 2);
}
const double deltat = (t - t1);
return _x1final + _v1final * deltat + 0.5 * _accel2 *
pow(deltat , 2);
}

Listing 12.5 cppsrc/apps-x200/hpp/x200–05

Schließlich berechnet eine Elementfunktion mit Gl. 12.8 und Gl. 12.11 die Geschwindig-
keit.

/**
* @brief v Geschwindigkeit als Funktion der ...
* @param t .. Zeit
* @return Geschwindigkeit zum Zeitpunkt t
*/
inline double v( double t) const {
if (t <= t1) {
return _accel1 * t;
432 12 Eindimensionale Bewegungen

}
return _v1final + _accel2 * (t - t1);
}

Listing 12.6 cppsrc/apps-x200/hpp/x200–06

Das Rechenmodell löst die Bewegungsgleichung numerisch. Die Bewegungsdaten werden


intern in einer Zahlentabelle gespeichert. Über einen Template-Parameter kann festgelegt
werden, ob die exakten Ergebnisse zur Zahlentabelle hinzugefügt werden.

/**
* @brief The C200 class Rechenmodell ( numerisch berechnete Werte )
*/
template <bool EXACTDATA = true >
class C200 : public X200
{
public :
using Data = Data <5 >;
using Ode = gsl :: Odeiv2 <2 >;
friend Ode;

private :
// Speicher für Bewegungsdaten
Data _data;

Listing 12.7 cppsrc/apps-x200/hpp/x200–07

Die Schnittstelle zur gsl-Klasse implementiert Gl. 12.6 als ein System von linearen Dif-
ferenzialgleichungen erster Ordnung.

 χ̇ = ψ
 ψ̇ = F (cos φ + µ sin φ) − µg
m
Als abgeleitete Klasse verfügt das Rechenmodell über alle Elementfunktionen der Mo-
dellklasse. Zur Berechnung der Beschleunigung wird die Elementfunktion Listing 12.4
aufgerufen.

/**
* @brief odefn gsl - Schnittstelle
* @param t Zeit
* @param yin Eingabe
* @param yout Ausgabe
* @return GSL_SUCCESS
*/
inline int odefn ( double t, const double yin [], double yout []) {
yout [0] = yin [1];
yout [1] = acceleration (t);
return GSL_SUCCESS ;
}

public :
using X200 :: X200; // erbe Konstruktoren der Basisklasse

Listing 12.8 cppsrc/apps-x200/hpp/x200–08


12.1 Geradlinige Bewegung mit konstanter äußerer Kraft und Gleitreibung 433

Die Bewegungsgleichungen werden numerisch gelöst. Es werden Lösungen berechnet, bis


die Geschwindigkeit 0 wird.

/**
* @brief exec numerische Lösung der Bewegungsgleichungen
* @param dt Zeitschritte ( optional )
* @param tmax Zeitpunkt bis zu dem maximal gerechnet wird
*/
inline void exec( double dt = 0.1 , double tmax = 500) {
Ode ode{ *this , 1e-2, 1e-9, 0 };
ode. set_init_conditions (0, { 0, 0 });
double t = 0;
// rechne , bis die Geschwindigkeit 0 wird. Abbruch spätestens
// bei tmax
while (ode [1] >= 0 && t < tmax) {
if constexpr ( EXACTDATA ) {
_data += { t,
ode [0] , // numerisch berechnete ...
ode [1] , // ... Werte
x(t), // exakte Werte Ort und
v(t) }; // Geschwindigkeit
} else {
_data += { t, ode [0] , ode [1], 0, 0 };
}

t += dt;
ode. solve (t);
}
}

Listing 12.9 cppsrc/apps-x200/hpp/x200–09

Es folgt die Speicherung der Daten in Dateien.

/**
* @brief save_data speichere Daten in Dateien
*/
inline void save_data () {
//csv -Datei
save(_data , Output :: plot);
// LaTeX - Tabelle mit Auswahl an Daten inklusive erster
// und letzter Reihe
auto view = _data . select_total_rows (5);
save(view , Output :: latex );
}
}; // C200

Listing 12.10 cppsrc/apps-x200/hpp/x200–10

Daten Eine Instanz des Rechenmodells wird initialisiert. Die Bewegungsdaten werden
numerisch berechnet und um die exakt berechneten Werte ergänzt. Die Daten werden
im LATEX-Tabellenformat und im CSV-Format gespeichert (Tab. 12.1, Abb. 12.2).

/**
* @brief run Berechnung von Beispieldaten Geradlinige Bewegung eines
434 12 Eindimensionale Bewegungen

* Objekts mit konstanter äußerer Kraft und Gleitreibung


*/
inline void run () {
// Rechenmodell
C200 <true > cobj{ 1.0 , 0.25 , 10, 30.0 _deg , 10 };
cobj.exec ();
cobj. save_data ();
}

Listing 12.11 cppsrc/apps-x200/hpp/x200–11

Tab. 12.1: Bewegung eines Objekts mit Gleitreibung und äußerer Kraft. Die Werte in
den Spalten 2 und 3 sind numerisch berechnete Daten.
t [s] x [m] v [m/s] xex [m] vex [m/s]
0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00
8.000e+00 2.387e+02 5.967e+01 2.387e+02 5.967e+01
1.610e+01 7.823e+02 5.963e+01 7.823e+02 5.963e+01
2.420e+01 1.185e+03 3.977e+01 1.185e+03 3.977e+01
3.230e+01 1.427e+03 1.991e+01 1.427e+03 1.991e+01
4.040e+01 1.507e+03 5.538e-02 1.507e+03 5.538e-02

80
1,500

60
1,000
v[m/s]
x[m]

40

500
20

0 0
0 10 20 30 40 0 10 20 30 40
t[s] t[s]

(a) (b)
Abb. 12.2: Bewegung eines Objekts mit Gleitreibung und äußerer Kraft. (a) Ort-Zeit-
Diagramm (b) Geschwindigkeit-Zeit-Diagramm

12.2 Teilchen bewegt sich unter dem Einfluss einer


ortsabhängigen Kraft
Ein Teilchen bewegt sich geradlinig auf reibungsfreiem Boden unter dem Einfluss einer
ortsabhängigen horizontalen Kraft der Form F = bx, wobei b eine positive reelle Kon-
stante ist.

1. Wie lauten der Orts- und der Geschwindigkeitsvektor als Funktion der Zeit?
12.2 Teilchen bewegt sich unter dem Einfluss einer ortsabhängigen Kraft 435

2. Lösen Sie die Newton’sche Bewegungsgleichung numerisch und erstellen Sie Diagram-
me für den Ort, die Geschwindigkeit, die Beschleunigung und die kinetische Energie
als Funktion der Zeit. Vergleichen Sie die Daten mit den exakt berechneten Werten.

Lösung

Die Bewegungsgleichung Wir legen die positive x-Achse entlang der Bewegungsrich-
tung. Das zweite Newton’sche Gesetz lautet:

¨ = b⃗ b
m⃗x x ⇒ ẍ⃗ex = x⃗ex (12.15)
m
oder √
2 b
ẍ = µ x, µ= . (12.16)
m
Dass der Ansatz
x(t) = c1 sinh (µt) + c2 cosh (µt) , c1 , c2 ∈ R (12.17)

eine Lösung der Differenzialgleichung ist, lässt sich schnell durch einmaliges bzw. zwei-
maliges Differenzieren des Ortes nach der Zeit

ẋ = µ (c1 cosh (µt) + c2 sinh (µt)) (12.18)

ẍ = µ2 (c1 sinh (µt) + c2 cosh (µt)) (12.19)


und anschließendes Einsetzen in Gl. 12.16 feststellen. Ausgehend von den Anfangsbedin-
gungen x(0) = x0 , v(0) = v0 berechnen wir die reellen Konstanten c1 und c2 :

v0
c 2 = x0 , c 1 = (12.20)
µ
Damit erhalten wir für die Lösung der Bewegungsgleichung
 v0

 x(t) = sinh (µt) + x0 cosh (µt) (12.21a)

 µ
 ẋ(t) = v0 cosh µt + x0 µ sinh µt (12.21b)



ẍ(t) = bx(t). (12.21c)

Das Computerprogramm

Kurzbeschreibung Wir führen eine Modellklasse und ein von ihr abgeleitetes Rechen-
modell ein. Es folgt eine Liste mit den wichtigsten Eigenschaften beider Modelle.

Modellklasse:
– Eingabe und Speicherung der Bewegungsparameter b, m und der Anfangsbedin-
gungen x0 und v0
– Implementierung der theoretisch hergeleiteten Formeln für x(t) und v(t)
436 12 Eindimensionale Bewegungen

Rechenmodell (abgeleitet von der Modellklasse):


– Numerische Lösung der Bewegungsgleichung
– Ausgabe der Daten ergänzt durch die exakt berechneten Werte

Quelltext Die Modellklasse liest die Programmparameter über den Konstruktor ein.
Der Parameter µ (Gl. 12.16) wird berechnet und gespeichert.

/**
* @brief The X9000 class Teilchen bewegt sich unter dem Einfluss einer
* ortsabhängigen Kraft ( Modellklasse )
*/
class X9000 : public XModel
{
public :
// Eingabeparameter
const double mass , b;
const double mu; // sqrt(b/m)
const double x0 , v0;

public :

Listing 12.12 cppsrc/apps-x9000/hpp/x9000–01

/**
* @brief X9000 Konstruktor
* @param b Konstante
* @param m Masse
* @param x0 Ort zur Zeit t=0
* @param v0 Geschwindigkeit zur Zeit t=0
*/
X9000( double m, double b, double x0 , double v0)
: XModel ( __func__ )
, mass{ m }
, b{ b }
, mu{ sqrt ((b) / (mass)) }
, x0{ x0 }
, v0{ v0 } {
Check :: all(nmx_msg , { b > 0, mass > 0 });
}

Listing 12.13 cppsrc/apps-x9000/hpp/x9000–02

Es folgen die Formeln für die Beschleunigung und die Kraft als Funktion des Ortes und
für die Geschwindigkeit und den Ort als Funktion der Zeit.

/**
* @brief acceleration Beschleunigung als Funktion von x
* @param x Koordinate
* @return Beschleunigung an der Stelle x
*/
inline double a( double x) const { return pow(mu , 2) * x; }

Listing 12.14 cppsrc/apps-x9000/hpp/x9000–03


12.2 Teilchen bewegt sich unter dem Einfluss einer ortsabhängigen Kraft 437

/**
* @brief force Kraft als Funktion von x
* @param x Koordinate
* @return Kraft an der Stelle x
*/
inline double force ( double x) const { return b * x; }

Listing 12.15 cppsrc/apps-x9000/hpp/x9000–04

/**
* @brief v Geschwindigkeit als Funktion von t
* @param t Zeit
* @return Geschwindigkeit zum Zeitpunkt t
*/
inline double v( double t) const { //
return v0 * cosh(mu * t) + mu * (x0) *sinh(mu * t);
}

Listing 12.16 cppsrc/apps-x9000/hpp/x9000–05

/**
* @brief x Ort als Funktion von t
* @param t Zeit
* @return Ort zum Zeitpunkt t
*/
inline double x( double t) const { //
return (x0) *cosh(mu * t) + (v0) / mu * sinh(mu * t);
}
}; // X9000

Listing 12.17 cppsrc/apps-x9000/hpp/x9000–06

Das Rechenmodell löst die Bewegungsgleichung numerisch. Es reserviert eine Zahlen-


tabelle mit sieben Spalten zur Speicherung der numerisch und der exakt berechneten
Werte.

/**
* @brief The CModel class Teilchen bewegt sich unter dem Einfluss
* einer ortsabhängigen Kraft ( Rechenmodell )
*/
class C9000 : public X9000
{
public :
using Data = Data <7 >;
using Ode = gsl :: Odeiv2 <2 >;
friend Ode;

private :
// Datenobjekt
Data _data;

Listing 12.18 cppsrc/apps-x9000/hpp/x9000–07


438 12 Eindimensionale Bewegungen

Die Differenzialgleichung Gl. 12.19 wird als System von linearen Differenzialgleichungen
erster Ordnung implementiert.
{
χ̇ = ψ (12.22a)
ψ̇ = µ2 χ, (12.22b)
wobei χ = x und ψ = ẋ ist.

/**
* @brief odefn Schnittstelle zur gsl -Klasse
* @param t Zeit
* @param fin Eingabe
* @param fout Ausgabe
* @return GSL_SUCCESS
*/
inline int odefn ( double t, const double fin [], double fout []) {
(void) t;
fout [0] = fin [1];
fout [1] = a(fin [0]);
return GSL_SUCCESS ;
}

public :
// benutze Konstruktoren der Basisklasse
using X9000 :: X9000 ;

Listing 12.19 cppsrc/apps-x9000/hpp/x9000–08

Die Bewegungsgleichung wird numerisch gelöst. Der numerischen Routine wird die auf-
rufende Instanz des Rechenmodells übergeben, da diese Gl. 12.22 implementiert.

/**
* @brief solve numerische Lösung der Bewegungsgleichung
* @param tmax Zeitintervall [0, tmax]
*/
void exec( double tmax) {
Ode ode{ *this , 1e-2, 1e-6, 0 };
ode. set_init_conditions (0, { x0 , v0 });
double t = 0, dt = 0.1;
// berechne Daten für die tmax s
while (t <= tmax) {
// speichere nur numerisch berechnete Werte
_data += { t,
ode [0] ,
ode [1] ,
a(ode [0]) , //
Mechanics :: kinetic_energy (mass , ode [1]) ,
x(t),
v(t) };
t += dt;
ode. solve (t);
}
}

Listing 12.20 cppsrc/apps-x9000/hpp/x9000–09


12.2 Teilchen bewegt sich unter dem Einfluss einer ortsabhängigen Kraft 439

Die Daten werden über diese Elementfunktion im CSV- und LATEX-Tabellenformat gespei-
chert.

/**
* @brief save_data Daten werden im CSV - und
* LaTeX - Format gespeichert .
*/
inline void save_data () {
// eine Auswahl der Daten wird zur Darstellung
// als LaTeX - Tabelle gespeichert
auto view = _data . select_total_rows (5);
save(view , Output :: latex );
// alle berechneten Daten werden im CSV - Format gespeichert
save(_data , Output :: plot);
}

Listing 12.21 cppsrc/apps-x9000/hpp/x9000–10

Daten Für m = 1 kg, b = 2 s−2 und die Anfangsbedingungen x0 = 10 m und v0 =


10 m/s erzeugen wir eine Instanz eines Rechenmodells und berechnen Bewegungsdaten
für die ersten zwei Sekunden (Tab. 12.2,Abb. 12.3).

/**
* @brief run Teilchen bewegt sich unter dem Einfluss einer
* ortsabhängigen Kraft ( Berechnung von Beispieldaten )
*/
inline void run () {
// Rechenmodell
C9000 cobj (1, 2, 10, 10);
// berechne Lösungen von t=0 s bis t = 2 s
cobj.exec (2);
cobj. save_data ();

Listing 12.22 cppsrc/apps-x9000/hpp/x9000–11

Tab. 12.2: Teilchen bewegt sich unter dem Einfluss einer ortsabhängigen Kraft mit
b = 2 s−2 . Die beiden letzten Spalten sind die exakten Werte
t [s] x [m] v [m/s] a [m/s2 ] K [J] xex [m] vex [m/s]
0.000e+00 1.000e+01 1.000e+01 2.000e+01 5.000e+01 1.000e+01 1.000e+01
3.000e-01 1.400e+01 1.710e+01 2.801e+01 1.461e+02 1.400e+01 1.710e+01
7.000e-01 2.351e+01 3.171e+01 4.703e+01 5.029e+02 2.351e+01 3.171e+01
1.100e+00 4.075e+01 5.676e+01 8.150e+01 1.611e+03 4.075e+01 5.676e+01
1.500e+00 7.138e+01 1.005e+02 1.428e+02 5.045e+03 7.138e+01 1.005e+02
1.900e+00 1.255e+02 1.772e+02 2.509e+02 1.569e+04 1.255e+02 1.772e+02
440 12 Eindimensionale Bewegungen

120
150
100

80

v[m/s]
x[m]

100
60

40 50

20

0 0
0 0.5 1 1.5 2 0 0.5 1 1.5 2
t[s] t[s]

(a) (b)
·104
250
1.5
200
a[m/s2 ]

150 1
K[J]
100
0.5
50

0 0
0 0.5 1 1.5 2
0 0.5 1 1.5 2
t[s]
t[s]

(c) (d)
Abb. 12.3: Teilchen bewegt sich unter dem Einfluss einer ortsabhängigen Kraft. (a) Der
Ort, (b) die Geschwindigkeit, (c) die Beschleunigung und (d) die kinetische Energie als
Funktionen der Zeit

12.3 Teilchen bewegt sich unter dem Einfluss einer


zeitabhängigen Kraft
Ein Teilchen bewegt sich auf horizontalem reibungsfreien Boden mit einer konstanten
Geschwindigkeit v0 . Eine horizontale zeitabhängige Kraft beginnt auf das Teilchen zu
wirken. Die Kraft wird durch F ⃗ = F0 sin ωt⃗ex gegeben, wobei ω eine positive reelle
Konstante ist.

1. Geben Sie den Ort, die Geschwindigkeit und die Beschleunigung als Funktion der Zeit
an.
2. Lösen Sie die Newton’sche Bewegungsgleichung numerisch und erstellen Sie x − t,
v − t, a − t und K − t-Diagramme, wobei K die kinetische Energie des Teilchens ist.
Vergleichen Sie die Daten mit den exakten Werten.
12.3 Teilchen bewegt sich unter dem Einfluss einer zeitabhängigen Kraft 441

Lösung

Die Bewegungsgleichung Wir legen die positive x-Achse entlang der Bewegungsrich-
tung des Teilchens. Die Newton’sche Bewegungsgleichung lautet:

¨ = F0 sin ωt⃗ex
m⃗x (12.23)

oder
F0
ẍ = f0 sin ωt, f0 = (12.24)
m
Die Geschwindigkeit als Funktion der Zeit Wir integrieren Gl. 12.24 nach der Zeit
∫ v ∫ t
dv
= f0 sin ωt ⇒ dv ′ = f0 sin ωt′ dt′
dt v0 0
[ ]t
f0 ′
⇒ v − v0 = − cos ωt
ω
0

und damit ( )
f0 f0
v= v0 + − cos ωt, (12.25)
ω ω
wobei v(0) = v0 ist.

Der Ort als Funktion der Zeit Wir integrieren Gl. 12.25 nach der Zeit
∫ x ∫ t [( ) ]
′ f0 f0
dx = v0 + − cos ωt dt′

x0 0 ω ω
[( ) ]t
f0 ′ f0 ′
⇒ x − x0 = v0 + t − 2 sin ωt
ω ω
0

oder ( )
f0 f0
x = x 0 + v0 + t − 2 sin ωt, (12.26)
ω ω
wobei x(0) = x0 ist.

Das Computerprogramm

Kurzbeschreibung Wie auch in allen anderen Fällen erstellen wir eine Modellklasse und
leiten von ihr ein Rechenmodell ab. Diese wird die Bewegungsgleichung numerisch lösen.
(Zur Erinnerung: Die Trennung ist nicht zwingend notwendig. Wir können alles innerhalb
einer Modellklasse implementieren. Der Nachteil ist, dass die Klasse zu umfangreich und
dadurch fehleranfälliger und schwieriger zu pflegen wird.) Es folgen die Eigenschaften der
Modellklasse und des Rechenmodells.

Modellklasse
– Eingabeparameter: m, F0 , ω und die Anfangsbedingungen x(0), v(0)
442 12 Eindimensionale Bewegungen

– Implementierung der theoretisch hergeleiteten Formeln


Rechenmodell (abgeleitet von der Modellklasse)
– Numerische Lösung der Bewegungsgleichung
– Ausgabe der Daten in Dateien im Tabellen- und Grafikformat

Quelltext Wir beginnen mit der Implementierung der Modellklasse.

/**
* @brief The X9100 class Teilchen bewegt sich unter dem Einfluss
* einer zeitabhängigen Kraft ( Modellklasse )
*/
class X9100 : public XModel
{
private :
double _f0 , _f0overomega ;

public :
// Eingabeparameter
const double mass;
const double Force0 , omega ;
// Anfangsbedingungen
const double x0 , v0;

public :

Listing 12.23 cppsrc/apps-x9100/hpp/x9100–01

Der Konstruktor erzeugt eine Instanz mit gültigen Bewegungsparametern.

/**
* @brief X9100 Konstruktor
* @param m Masse
* @param F0 Kraft
* @param omg Kreisfrequenz
* @param x0 Ort zur Zeit t=0
* @param v0 Geschwindigkeit zur Zeit t=0
*/
inline X9100 ( double m, double F0 , double omg , double x0 , double v0)
: XModel { __func__ }
, mass{ m }
, Force0 { F0 }
, omega { omg }
, x0{ x0 }
, v0{ v0 } {
Check :: all(nmx_msg , { mass > 0, Force0 > 0, omega > 0 });
_f0 = Force0 / (mass);
_f0overomega = _f0 / ( omega );
}

Listing 12.24 cppsrc/apps-x9100/hpp/x9100–02

Es folgen der Ort und die Geschwindigkeit sowie die Beschleunigung als Funktion der
Zeit (Gl. 12.26, Gl. 12.25 und Gl. 12.24).
12.3 Teilchen bewegt sich unter dem Einfluss einer zeitabhängigen Kraft 443

/**
* @brief x Ortsfunktion ( analytische Formel )
* @param t Zeit
* @return Ort zum Zeitpunkt t
*/
inline double x( double t) const {
return x0 + (v0 + _f0overomega ) * t - _f0 / pow(omega , 2) *
sin(omega * t);
}

Listing 12.25 cppsrc/apps-x9100/hpp/x9100–03

/**
* @brief v Geschwindigkeit als Funktion der Zeit
* ( analytische Formel )
* @param t Zeit
* @return Geschwindigkeit zur Zeit t
*/
inline double v( double t) const { //
return (v0 + _f0overomega ) - _f0overomega * cos(omega * t);
}

Listing 12.26 cppsrc/apps-x9100/hpp/x9100–04

/**
* @brief a Beschleunigung als Funktion der Zeit
* ( analytische Formel )
* @param t Zeit
* @return Beschleunigung zur Zeit t
*/
inline double a( double t) const { return _f0 * sin(omega * t); }
}; // X9100

Listing 12.27 cppsrc/apps-x9100/hpp/x9100–05

Das Rechenmodell löst die Bewegungsgleichungen numerisch und speichert intern die
Bewegungsdaten in einer Zahlentabelle.

/**
* @brief The CModel class Teilchen bewegt sich unter
* dem Einfluss einer zeitabhängigen Kraft , numerische Lösung
* der Bewegungsgleichungen . ( Rechenmodell )
*/
class C9100 : public X9100
{
public :
using Data = nmx :: Data <7 >;
using Ode = gsl :: Odeiv2 <2 >;
friend Ode;

private :
// Zahlentabelle für die Bewegungsdaten
Data _data;
444 12 Eindimensionale Bewegungen

public :
using X9100 :: X9100 ;

Listing 12.28 cppsrc/apps-x9100/hpp/x9100–06

Die Schnittstelle zur gsl-Klasse (numerische Lösung der Bewegungsgleichung) implemen-


tiert Gl. 12.24 als System eindimensionaler linearer Differenzialgleichungen:
{
χ̇ = ψ
ψ̇ = f0 sin ωt

mit χ = x und ψ = v.

/**
* @brief odefn Schnittstelle zur gsl
* @param t Zeit
* @param fin Eingabe
* @param fout Ausgabe
* @return GSL_SUCCESS
*/
inline int odefn ( double t, const double fin [], double fout []) {
fout [0] = fin [1];
fout [1] = a(t);
return GSL_SUCCESS ;
}

Listing 12.29 cppsrc/apps-x9100/hpp/x9100–07

Es folgt die numerische Lösung der Bewegungsgleichung. Den Daten werden die exakten
Ergebnisse hinzugefügt.

/**
* @brief solve_ode numerische Lösung der Bewegungsgleichung
* @param tmax Zeitintervall [0, tmax]
*/
void exec( double tmax) {
Ode ode{ *this , 1e-2, 1e-9, 0 };
ode. set_init_conditions (0, { x0 , v0 });
double t = 0, dt = 0.1;
while (t < tmax) {
_data += { t,
ode [0] ,
ode [1] ,
a(t), //
Mechanics :: kinetic_energy (mass , ode [1]) ,
x(t),
v(t) };
t += dt;
ode. solve (t);
}
}

Listing 12.30 cppsrc/apps-x9100/hpp/x9100–08

Die Daten werden in Dateien einmal im CSV- und LATEX-Format gespeichert.


12.3 Teilchen bewegt sich unter dem Einfluss einer zeitabhängigen Kraft 445

/**
* @brief save_data speichere Daten in Dateien
*/
inline void save_data () {
save(_data , Output :: plot);
save(_data . select_rows (4) , Output :: latex );
}

Listing 12.31 cppsrc/apps-x9100/hpp/x9100–09

Daten Für m = 2 kg, F0 = 10 N, ω = 2 rad/s und den Anfangsbedingungen x0 = 0


und v0 = 10 m/s, werden Bewegungsdaten für die ersten vier Sekunden berechnet. Die
Daten werden im CSV- und im LATEX-Tabellenformat gespeichert (Tab. 12.3, Abb. 12.4).

/**
* @brief run Teilchen bewegt sich unter dem Einfluss
* einer zeitabhängigen Kraft ( Berechnung von Beispieldaten )
*/
inline void run () {
// Bewegungsparameter
const double mass = 2.0 , F0 = 10.0 , omega = 2;
// Anfangsbedingungen
const double x0 = 0.0 , v0 = 10.0;
// Rechenmodell
C9100 cobj{ mass , F0 , omega , x0 , v0 };
cobj.exec (4);
cobj. save_data ();
}

Listing 12.32 cppsrc/apps-x9100/hpp/x9100–10

Tab. 12.3: Bewegungsdaten für eine geradlinige Bewegung mit einer zeitabhängigen
Beschleunigung. Die beiden letzten Spalten sind die exakten Werte.
t [s] x [m] v [m/s] a [m/s2 ] K [J] xex [m] vex [m/s]
0.000e+00 0.000e+00 1.000e+01 0.000e+00 1.000e+02 0.000e+00 1.000e+01
3.000e-01 3.044e+00 1.044e+01 2.823e+00 1.089e+02 3.044e+00 1.044e+01
7.000e-01 7.518e+00 1.208e+01 4.927e+00 1.458e+02 7.518e+00 1.208e+01
1.100e+00 1.274e+01 1.397e+01 4.042e+00 1.952e+02 1.274e+01 1.397e+01
1.500e+00 1.857e+01 1.497e+01 7.056e-01 2.243e+02 1.857e+01 1.497e+01
1.900e+00 2.451e+01 1.448e+01 -3.059e+00 2.096e+02 2.451e+01 1.448e+01
2.300e+00 2.999e+01 1.278e+01 -4.968e+00 1.633e+02 2.999e+01 1.278e+01
2.700e+00 3.472e+01 1.091e+01 -3.864e+00 1.191e+02 3.472e+01 1.091e+01
3.100e+00 3.885e+01 1.001e+01 -4.154e-01 1.002e+02 3.885e+01 1.001e+01
3.500e+00 4.293e+01 1.062e+01 3.285e+00 1.127e+02 4.293e+01 1.062e+01
3.900e+00 4.750e+01 1.237e+01 4.993e+00 1.529e+02 4.750e+01 1.237e+01
446 12 Eindimensionale Bewegungen

50

40
14

30

v[m/s]
x[m]

20 12

10

0 10

0 1 2 3 4 0 1 2 3 4
t[s] t[s]

(a) (b)

4
200
2
a[m/s2 ]

K[J]
0
150
−2

−4
100

0 1 2 3 4 0 1 2 3 4
t[s] t[s]

(c) (d)
Abb. 12.4: Bewegung eines Teilchens unter dem Einfluss einer zeitabhängigen Kraft.
(a) x − t-Diagramm, (b) v − t-Diagramm, (c) a − t-Diagramm (d) K − t-Diagramm.

12.4 Übungsaufgaben
1. Zwei Teilchen mit Massen m1 und m2 ruhen auf horizontalem Boden in einem Ab-
stand d voneinander. Zwei konstante Kräfte F⃗1 und F ⃗2 , die jeweils einen Winkel φ1
und φ2 mit der Horizontalen bilden, beginnen auf die zwei Teilchen zu wirken, sodass
diese sich aufeinander zu bewegen. µ ist der Gleitreibungskoeffizient zwischen den
Teilchen und Boden.
a) Stellen Sie die Bewegungsgleichungen für die Teilchen auf. Wann und wo treffen
sich diese und welche Geschwindigkeit haben sie zu diesem Zeitpunkt?
b) Entwerfen Sie eine Modellklasse für das System der beiden Teilchen und ein Re-
chenmodell, welches Bewegungsdaten erstellt. Stellen Sie diese Daten in gemein-
samen Ort-Zeit- und Geschwindigkeit-Zeit-Diagrammen dar. Benutzen sie für die
Rechnungen Werte ihrer Wahl für die Massen, die Kräfte, die Winkel und den
Anfangsabstand d. (Die Modellklasse soll eine Ausnahme auswerfen, falls durch
die Parameterwahl die oben beschriebene Bewegung nicht stattfinden kann).
c) Berechnen Sie auf Basis der erstellten Bewegungsdaten einen Näherungswert für
den Zeitpunkt der Begegnung. Vergleichen Sie das Ergebnis mit dem exakten Wert.
12.4 Übungsaufgaben 447

d) Erstellen Sie zusätzliche Rechenmodelle, die statt mit konstanten Kräften mit
ortsabhängigen bzw. mit zeitabhängigen Kräften arbeiten. Erstellen Sie für diese
Fälle Bewegungsdaten.
13 Bewegung in einem Medium mit
Reibung

Übersicht
13.1 Bewegung mit Reibung nach Stokes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449
13.2 Bewegung mit Reibung nach Newton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
13.3 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464

13.1 Bewegung mit Reibung nach Stokes


Eine Kugel mit der Masse m wird mit einer Anfangsgeschwindigkeit v0 senkrecht nach
oben geschossen. Die Kugel bewegt sich in einem Medium, das auf sie eine geschwindig-
⃗ = −β⃗v ausübt. Auf die Kugel wirkt neben der
keitsabhängige Reibungskraft der Form R
Reibungskraft nur noch die Schwerkraft (Abb. 13.1).

y
⃗v1 ⃗2
R

⃗1
R m⃗g m⃗g
⃗ey ⃗ey ⃗v2
⃗ey

(b) (c)
(a)
Abb. 13.1: Kugel bewegt sich im Medium. (a) Ortsvektor (b) Zeitpunkt während der
Aufwärtsbewegung (c) Zeitpunkt während der Abwärtsbewegung

1. Wie lauten die Geschwindigkeit und der Ort als Funktion der Zeit. Wie viel Zeit
benötigt das Teilchen, bis es den höchsten Punkt erreicht?
2. Erstellen Sie Daten für die Bewegung des Teilchens für β = 3 kg/s, β = 6 kg/s und
β = 9 kg/s. Benutzen Sie dafür folgende Werte für Masse, Anfangsgeschwindigkeit
und Anfangshöhe: m = 1 kg, v0 = 10 m/s, y0 = 0 m. Vergleichen Sie die numerisch
berechneten Werte mit den exakten Resultaten.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_13
450 13 Bewegung in einem Medium mit Reibung

Lösung

Die Bewegungsgleichung Das zweite Newton’sche Gesetz für die Bewegung der Kugel
lautet
m⃗ y¨ = −m⃗g − β⃗v (13.1)
oder
mÿ⃗ey = −mg⃗ey − βv⃗ey .

Mit ÿ = v̇ erhalten wir


β
v̇ = −g − v (13.2)
m
Die Geschwindigkeit als Funktion der Zeit Wir integrieren Gl. 13.2 nach der Zeit
∫ v ∫ t ( )
dv ′ ′ m[ β ′ ]v
β ′
= − dt ⇒ −t = ln g + v
v0 g + m v 0 β m v0
( ) ( )
β β
m g + m v0 βt g + m v0
⇒t= ln β
⇒ = ln β
β g+ m v m g+ m v
β
βt g+ m v0
⇒ em = β
g +m v
( )
β βt β
⇒ g + v e m = g + v0
m m

oder ( )
mg mg βt
v=− + + v0 e − m . (13.3)
β β
Nach ausreichend langer Zeit fällt die Kugel mit einer konstanten Geschwindigkeit (End-
geschwindigkeit) ( ( ) )
mg mg βt mg
lim − + + v0 e − m = − (13.4)
t→∞ β β β
die wir mit
mg
vT = − (13.5)
β
bezeichnen. Damit wird aus Gl. 13.3:
−β
v = vT + (v0 − vT ) e m t (13.6)

Wann wird die Geschwindigkeit 0? Wir setzen in Gl. 13.6 v = 0 und lösen nach t auf
βt βt
vT + (v0 − vT ) e− m = 0 ⇒ (v0 − vT ) e− m = −vT
( )
βt vT m vT
⇒ e− m = ⇒ t = − ln
vT − v0 β vT − v0

oder ( )
m vT − v0
t= ln . (13.7)
β vT
13.1 Bewegung mit Reibung nach Stokes 451

Der Ort als Funktion der Zeit Eine erneute Integration, diesmal die von Gl. 13.6 nach
der Zeit, liefert den Ort als Funktion der Zeit:
∫ y ∫ t( )
βt′
dy ′ = vT + (v0 − vT ) e− m dt′
0 0
[ m ]
βt′ t
⇒ y = v T t′ − (v0 − vT ) e− m
β 0
m − βt m
⇒ y = vT t − (v0 − vT ) e m + (v0 − vT )
β β
oder ( )
m βt
y = vT t + (v0 − vT ) 1 − e− m (13.8)
β

Das Computerprogramm

Kurzbeschreibung Es folgt eine Auflistung der Eigenschaften der Modellklasse und des
von ihr abgeleiteten Rechenmodells.

Modellklasse:
– Eingabewerte: Masse des Teilchens m, Reibungskoeffizient β und Anfangsbedin-
gungen v0 , y0
– Implementierung der theoretisch hergeleiteten Formeln
Rechenmodell (abgeleitet vom Rechenmodell)
– Numerische Lösung der Bewegungsgleichung
– Ergänzung der Daten mit den exakten Werten für Höhe und Geschwindigkeit
– Speichern der Daten im Diagramm- und Tabellen-Format

Quelltext Wir beginnen mit der Implementierung der Modellklasse.

/**
* @brief The X2000 class Bewegung im konstanten Gravitationsfeld
* mit Reibung nach Stokes ( Modellklasse )
*/
class X2000 : public XModel
{
private :
double _mOverBeta ; // m/b
double _vTerminal ; // Endgeschwindigkeit

public :
// Eingabeparameter
// Masse , Koeffizient ( Reibung )
const double mass , beta;
// Anfangsbedingungen
const double y0 , v0;

public :

Listing 13.1 cppsrc/apps-x2000/hpp/x2000–01


452 13 Bewegung in einem Medium mit Reibung

β
Der Konstruktor erzeugt eine Instanz mit gültigen Bewegungsparametern. Es werden m
und die Endgeschwindigkeit vT (Gl. 13.5) berechnet und gespeichert.

/**
* @brief X2000 Konstruktor
* @param m Masse
* @param b Reibungskoeffizient
* @param y0 Höhe zur Zeit t=0
* @param v0 Geschwindigkeit zur Zeit t=0
*/
X2000( double m, double b, double y0 , double v0)
: XModel ( __func__ )
, mass{ m }
, beta{ b }
, y0{ y0 }
, v0{ v0 } {
Check :: all(nmx_msg , { mass > 0, beta >= 0 });
_mOverBeta = (mass) / (beta);
_vTerminal = -_mOverBeta * gravitation :: Earth ::g;
}

Listing 13.2 cppsrc/apps-x2000/hpp/x2000–02

Es folgt eine Elementfunktion zum Lesen der gespeicherten Endgeschwindigkeit (Gl.


13.5).

/**
* @brief terminalVelocity
* @return Endgeschwindigkeit
*/
inline double terminal_velocity () const { return _vTerminal ; }

Listing 13.3 cppsrc/apps-x2000/hpp/x2000–03

Die Geschwindigkeit als Funktion der Zeit (Gl. 13.6):

/**
* @brief v Geschwindigkeit als Funktion der Zeit
* @param t Zeit
* @return Geschwindigkeit
*/
inline double v( double t) const {
return _vTerminal + (v0 - _vTerminal ) * exp(-t / _mOverBeta );
}

Listing 13.4 cppsrc/apps-x2000/hpp/x2000–04

Der Ort als Funktion der Zeit (Gl. 13.8):

/**
* @brief y Ort als Funktion der Zeit
* @param t Zeit
* @return Ort zum Zeitpunkt t
*/
inline double y( double t) const {
13.1 Bewegung mit Reibung nach Stokes 453

const double term1 = (v0 - _vTerminal );


const double term2 = (1 - exp(-t / _mOverBeta ));
return _vTerminal * t + _mOverBeta * term1 * term2;
}

Listing 13.5 cppsrc/apps-x2000/hpp/x2000–05

Die Zeit zum Erreichen der maximalen Steighöhe (Gl. 13.7):

/**
* @brief time4ymax
* @return Zeit für maximale Höhe
*/
inline double time4ymax () const { //
return _mOverBeta * log (( _vTerminal - v0) / _vTerminal );
}

Listing 13.6 cppsrc/apps-x2000/hpp/x2000–06

Die maximale Steighöhe (Gl. 13.8):

/**
* @brief ymax
* @return maximale Höhe
*/
inline double ymax () const { return y( time4ymax ()); }
}; // X2000

Listing 13.7 cppsrc/apps-x2000/hpp/x2000–07

Es folgt die numerische Lösung der Bewegungsgleichung durch das Rechenmodell. Eine
Tabelle mit acht Spalten wird zur Speicherung der Ergebnisse angelegt.

/**
* @brief The C2000 class Bewegung im konstanten Gravitationsfeld mit
* Reibung nach Stokes ( numerisch berechnete Werte ) ( Rechenmodell )
*/
class C2000 : public X2000
{
public :
using Ode = gsl :: Odeiv2 <2 >;
friend Ode;
using Data = Data <8 >;

private :
// Zahlentabelle zur Speicherung der Ergebnisse
Data _data;

public :
// benutze Konstruktoren der Basisklasse
using X2000 :: X2000 ;

Listing 13.8 cppsrc/apps-x2000/hpp/x2000–08


454 13 Bewegung in einem Medium mit Reibung

Für die numerische Lösung der Bewegungsgleichung Gl. 13.2 muss diese als System von
linearen Differenzialgleichungen erster Ordnung geschrieben werden,

 χ̇ = ψ
 ψ̇ = −g − β ψ,
m
wobei χ = x und ψ = v.

/**
* @brief odefn Schnittstelle zur gsl
* @param t Zeit
* @param fin Ort Geschwindigkeit
* @param fout Geschwindigkeit Beschleunigung
* @return Status
*/
inline int odefn ( double t, const double fin [], double fout []) {
(void) t;
fout [0] = fin [1];
fout [1] = -Earth ::g - (beta / (mass)) * fin [1];
return GSL_SUCCESS ;
}

Listing 13.9 cppsrc/apps-x2000/hpp/x2000–09

Solange y ≥ 0, werden numerisch Lösungen berechnet. Wir ergänzen jeden Datensatz


mit der potenziellen, der kinetischen und der Gesamtenergie sowie den exakten Werten
für Höhe und Geschwindigkeit.

/**
* @brief solve_ode numerische Lösung der DGL
* numerische Lösung der Bewegungsgleichung
*/
inline void exec () {
Ode myOde { *this , 1e-2, 1e-9, 0 };
myOde. set_init_conditions (0, { y0 , v0 });
double t = 0, dt = 0.01;
do {
_data += { t, myOde [0] , myOde [1] };
t += dt;
myOde . solve (t);
} while ( myOde [0] >= 0); // stop wenn Kugel den Boden erreicht

// Energien und exakte Werte werden hinzugefügt


for (auto &crow : _data .data ()) {
const double t = crow [0];
const double yval = crow [1];
const double vval = crow [2];
crow [3] = Mechanics :: kinetic_energy (mass , vval);
crow [4] = Mechanics :: potential_energy (mass , yval);
crow [5] = crow [3] + crow [4]; // Gesamtenergie
crow [6] = y(t);
crow [7] = v(t);
}
}

Listing 13.10 cppsrc/apps-x2000/hpp/x2000–10


13.1 Bewegung mit Reibung nach Stokes 455

Die Daten werden in den beiden üblichen Formaten gespeichert.

/**
* @brief save_data speichere Daten im Diagramm -
* und Tabellenformat .
*/
void save_data () {
save(_data , Output :: plot , beta);
std :: ofstream ofs = get_output_stream ( Output :: latex , beta);

// ändere Formatierung der Zahlen für die LaTeX - Tabelle


ofs << std :: fixed << std :: setprecision (4);

// speichere insgesamt 7 Reihen (5 + erste + letzte )


_data. select_total_rows (4).save(ofs , Output :: latex);
}

Listing 13.11 cppsrc/apps-x2000/hpp/x2000–11

Daten Für m = 1 kg und die Anfangsbedingungen y0 = 0 und v0 = 10 m/s variieren


wir die Werte von β (Die Werte von β sind zur Demonstration des Konzepts gedacht).
Mit einer Instanz des Rechenmodells werden Beispieldaten berechnet (Tab. 13.1, Abb.
13.2).

/**
* @brief run Bewegung im konstanten Gravitationsfeld mit Reibung
* nach Stokes ( Beispieldaten )
*/
inline void run () {
const double y0 = 0.0 , v0 = 10.0; // Anfangsbedingungen
const double mass = 1.0;

for (const auto beta : { 3.0 , 6.0 , 9.0 }) {


C2000 cobj{ mass , beta , y0 , v0 }; // Rechenmodell
cobj.exec ();
cobj. save_data ();
}
}

Listing 13.12 cppsrc/apps-x2000/hpp/x2000–12

Tab. 13.1: Senkrechte Auf- und Abwärtsbewegung eines Teilchens in einem Medium mit
Reibung und Reibungskoeffizienten β = 3 kg/s. Die beiden letzten Spalten sind die exakt
berechneten Werte.
t [s] y [m] v [m/s] K [J] U [J] E [J] yt [m] vt [m/s]
0.0000 0.0000 10.0000 50.0000 0.0000 50.0000 0.0000 10.0000
0.4400 1.8031 0.2757 0.0380 17.6826 17.7206 1.8031 0.2757
0.8800 1.2307 -2.3220 2.6958 12.0692 14.7650 1.2307 -2.3220
1.3200 0.0237 -3.0159 4.5479 0.2326 4.7805 0.0237 -3.0159
456 13 Bewegung in einem Medium mit Reibung

β=3 10 β=3
β=6 β=6
1.5 β=9 β=9

v[m/s]
1
y[m]

0.5
0

0 0.2 0.4 0.6 0.8 1 1.2 1.4 0 0.2 0.4 0.6 0.8 1 1.2 1.4
t[s] t[s]

(a) (b)
Abb. 13.2: Senkrechte Auf- und Abwärtsbewegung eines Teilchens in einem Medium
mit Reibung. (a) Höhe und (b) Geschwindigkeit als Funktion der Zeit für verschiedene
Reibungskoeffizienten

13.2 Bewegung mit Reibung nach Newton


Ein Teilchen wird mit einer Anfangsgeschwindigkeit v0 nach oben geschossen. Auf das
Teilchen wirkt eine Reibungskraft, welche proportional zum Quadrat der Geschwindigkeit
ist und die Schwerkraft.

1. Stellen Sie die Bewegungsgleichungen des Teilchens auf und lösen Sie die Gleichungen.
2. Lösen Sie die Gleichungen numerisch und stellen Sie die Ergebnisse in Diagrammen
und Tabellen dar. Vergleichen Sie die Daten mit den exakten Ergebnissen.

Lösung

Das Koordinatensystem Wie legen den Einheitsvektor ⃗ey entlang der Bewegungsrich-
tung. Die positive Richtung sei die Aufwärtsrichtung.

Die Bewegungsgleichung Die Anwendung des zweiten Newton’schen Gesetzes gibt


⃗v
m⃗r¨ = −m⃗g − γv 2 (13.9)
|⃗v |
oder
mv̇⃗ey = −mg⃗ey − γv|v|⃗ey

und damit ( )
γ
v̇ = −g 1 + v|v| . (13.10)
mg
Mit √
mg
vT = (13.11)
γ
13.2 Bewegung mit Reibung nach Newton 457

und Integration nach Trennung der Variablen folgt


∫ ∫ ∫
v
dv ′ t v
dv ′
= −g dt′ ⇒ vT 2 = −gt. (13.12)
v0 1+ v ′ |v ′ |
0 v0 vT 2 + v ′ |v ′ |
vT 2

Wir stellen fest, dass das Ergebnis der Integration vom Vorzeichen der Geschwindigkeit
abhängt.

Die Aufwärtsbewegung Wenn v > 0, ist:


∫ [ ( ′ )]
2 dv ′ v
v v
vT 2 + v ′2
= −gt ⇒ vT arctan = −gt
v
v0 T v T v0
( ) ( )
v v0
⇒ vT arctan − vT arctan = −gt (13.13)
vT vT

Hat das Teilchen die maximale Höhe erreicht, ist seine Geschwindigkeit 0. Wir bezeichnen
diesen Zeitpunkt tmax . Mit v = 0 in Gl. 13.13 erhalten wir:
( )
v0
vT arctan = g tmax (13.14)
vT

Mit diesem Ergebnis und Gl. 13.13 erhalten wir für die Geschwindigkeit als Funktion der
Zeit: ( )
v g
arctan =− (t − tmax )
vT vT
( )
g
⇒ v = vT tan (tmax − t) , t ≤ tmax (13.15)
vT
Wir integrieren (Gl. 13.15) nach der Zeit:
∫ y ∫ t ( )
g ( )
dy ′ = vT tan tmax − t′ dt′
0 0 vT
[ ( ) ]t
vT2
g ( ′)
⇒y= ln cos tmax − t
g vT
0
( ( ) ( ))
vT2 g g
⇒y= ln cos (tmax − t) − ln cos tmax
g vT vT

oder ( )
vT2 cos vgT (tmax − t)
y= ln ( ) . (13.16)
g cos vgT tmax

Das Teilchen erreicht somit die maximale Steighöhe:

vT2 1
ymax = ln ( ) . (13.17)
g cos arctan vvT0
458 13 Bewegung in einem Medium mit Reibung

Wir wollen das Ergebnis vereinfachen und schreiben:


√  (
( )2 ( )2 )
vT2  v 0  vT2 v0
ymax = ln 1+ = ln 1 + (13.18)
g vT 2g vT

Wir haben dabei folgende Relation benutzt:

1
cos (arctan x) = √ , (13.19)
1 + x2
die sich mit Hilfe eines rechteckigen Dreiecks, in welchem die zwei senkrechten Seiten die
Längen 1 und x haben, beweisen lässt.

Die Abwärtsbewegung Ist der höchste Punkt erreicht, beginnt die Bewegung mit v < 0.
Aus Gl. 13.12 wird ∫ v ∫ t
dv ′
vT 2 ′2
= −g t′ dt′ .
0 vT − v
2
tmax

Da v < vT ist, folgt:


[ ( )
v ′ ]v
vT artanh = −g (t − tmax )
vT 0
( )
v g
⇒ artanh =− (t − tmax )
vT vT
( )
g
⇒ v = −vT tanh (t − tmax ) , t ≥ tmax (13.20)
vT

Integration von (Gl. 13.20) ergibt


∫ ∫ ( )
t
dy ′ t
g (′ )
dt = −vT tanh t − tmax dt′
tmax dt′ tmax vT
[ ( ) ]t
vT2 g (′ )
⇒ y − ymax =− ln cosh t − tmax
g vT
tmax
2 ( ( ))
v g
⇒ y − ymax = − T ln cosh (t − tmax ) . (13.21)
g vT

Setzen wir noch ymax aus Gl. 13.18 ein, erhalten wir:
 √ ( )2 
v0
2  1 + vT 
v
y = T ln  ( ) , t ≥ tmax (13.22)
g cosh vT (t − tmax )
g

Das Computerprogramm

Kurzbeschreibung Wir werden mit zwei Klassen, eine Modellklasse und ein von ihr
abgeleitetes Rechenmodell, die Bewegung beschreiben.
13.2 Bewegung mit Reibung nach Newton 459

Modellklasse
– Eingabe: Masse des Teilchens, Reibungskoeffizient und Anfangsbedingungen
– Implementierung der theoretisch hergeleiteten Formeln
Rechenmodell (abgeleitet von der Modellklasse)
– Numerische Lösung der Bewegungsgleichung
– Ergänzung der numerisch berechneten Daten mit exakten Werten
– Ausgabe der Daten in Dateien

Quelltext Die Modellklasse speichert neben den Bewegungsparametern m, γ, die An-


fangsbedingungen sowie intern charakteristische Werte der Bewegung wie die Steighöhe
(Gl. 13.18) und die Steigzeit (Gl. 13.14). Sie werden so nur einmal berechnet und im-
mer wieder verwendet. Sowohl die Eingabeparameter als auch die Anfangsbedingungen
können außerhalb der Klasse nicht verändert werden.

/**
* @brief The X2010 class Bewegung mit Newtonscher Reibung
* ( Modellklasse )
*/
class X2010 : public XModel
{
private :
// Endgeschwindigkeit
double vTerminal = 0;
// Steigzeit und Steighöhe
double tMax = 0, yMax = 0;
double gvt = 0, vt2g = 0;

public :
// Eingabeparameter
// Masse , Reibungskoeffizient
const double mass , gamma ;
// Anfangsbedingungen
const double y0 , v0;

public :

Listing 13.13 cppsrc/apps-x2010/hpp/x2010–01

Unter Verwendung von Gl. 13.11, Gl. 13.14 und Gl. 13.18 werden vT , tmax und ymax
berechnet und gespeichert.

/**
* @brief X2010 Konstruktor
* @param m Masse
* @param gm gamma
* @param y0in Anfangshöhe
* @param v0in Anfangsgeschwindigkeit
*/
inline X2010 ( double m, double gm , double y0in , double v0in)
: XModel ( __func__ )
, mass{ m }
, gamma { gm }
460 13 Bewegung in einem Medium mit Reibung

, y0{ y0in }
, v0{ v0in } {
// teste Eingabewerte
Check :: all(nmx_msg , { m > 0, gamma > 0, y0 >= 0, v0 > 0 });

// Endgeschwindigkeit
vTerminal = sqrt (( mass) * Earth ::g / (gamma));

// Werte werden wiederverwendet


gvt = Earth ::g / vTerminal ;
vt2g = pow(vTerminal , 2) / Earth ::g;
tMax = vTerminal / Earth ::g * atan ((v0) / vTerminal );
const double term0 = 0.5 * pow(vTerminal , 2) / Earth ::g;
yMax = term0 * log (1 + pow (( v0) / vTerminal , 2));
}

Listing 13.14 cppsrc/apps-x2010/hpp/x2010–02

Die intern gespeicherten Werte für tmax , ymax und vT können mit den folgenden drei
Elementfunktionen gelesen werden.

/**
* @brief time4ymax
* @return Steigzeit
*/
inline double time4ymax () const { return tMax; }

Listing 13.15 cppsrc/apps-x2010/hpp/x2010–03

/**
* @brief ymax
* @return Steighöhe
*/
inline double ymax () const { return yMax; }

Listing 13.16 cppsrc/apps-x2010/hpp/x2010–04

/**
* @brief terminalVelocity
* @return Endgeschwindigkeit
*/
inline double terminal_velocity () const { return vTerminal ; }

Listing 13.17 cppsrc/apps-x2010/hpp/x2010–05

Es folgen die Ausdrücke für Beschleunigung, Geschwindigkeit und Höhe. In den Element-
funktionen zur Berechnung von Höhe und Geschwindigkeit sind sowohl die Aufwärts- als
auch die Abwärtsbewegung enthalten.

/**
* @brief a Beschleunigung
* @param t Zeit
13.2 Bewegung mit Reibung nach Newton 461

* @return Beschleunigung zum Zeitpunkt t


*/
inline double a( double t) const {
return -Earth ::g * (1 + ( gamma ) / (( mass) *Earth ::g) * v(t) *
abs(v(t)));
}

Listing 13.18 cppsrc/apps-x2010/hpp/x2010–06

/**
* @brief v Geschwindigkeit
* @param t Zeit
* @return Geschwindigkeit zum Zeitpunkt t
*/
inline double v( double t) const {
if (t <= tMax) {
return vTerminal * tan(gvt * (tMax - t));
}
return -vTerminal * tanh(gvt * (t - tMax));
}

Listing 13.19 cppsrc/apps-x2010/hpp/x2010–07

/**
* @brief y Höhe
* @param t Zeit
* @return Höhe zum Zeitpunkt t
*/
inline double y( double t) const {
if (t <= tMax) {
return vt2g * log(cos(gvt * (tMax - t)) / cos(gvt * tMax));
}
return yMax - vt2g * log(cosh(gvt * (t - tMax)));
}
}; // X2010

Listing 13.20 cppsrc/apps-x2010/hpp/x2010–08

Das Rechenmodell implementiert die Bewegungsgleichung Gl. 13.9 als System von linea-
ren Differenzialgleichungen erster Ordnung und löst diese numerisch:


 χ̇ = ψ (13.23a)
( )
γ

 ψ̇ = −g 1 + mg ψ|ψ| (13.23b)

mit χ = x und ψ = v. Sie wird in Listing 13.22 implementiert.

/**
* @brief The CModel class Bewegung mit Newtonscher Reibung numerische
* Lösung der Bewegungsgleichung ( Rechenmodell )
*/
class C2010 : public X2010
{
462 13 Bewegung in einem Medium mit Reibung

public :
using Ode = gsl :: Odeiv2 <2 >;
friend Ode;
using Data = Data <8 >;

using X2010 :: X2010 ; // erbe Konstruktoren von der Basisklasse

private :
Data _data; // Zahlentabelle zur Speicherung der Ergebnisse

Listing 13.21 cppsrc/apps-x2010/hpp/x2010–09

/**
* @brief odefn Bewegungsgleichung als System von DGL
* @param t Zeit
* @param fin Eingabe
* @param fout Ausgabe
* @return GSL_SUCCESS
*/
inline int odefn ( double t, const double fin [], double fout []) {
(void) t;
fout [0] = fin [1];
fout [1] = -Earth ::g * (1 + gamma / (mass * Earth ::g) * fin [1] *
abs(fin [1]));
return GSL_SUCCESS ;
}

public :

Listing 13.22 cppsrc/apps-x2010/hpp/x2010–10

Zu den numerisch berechneten Daten, werden die kinetische, die potenzielle und die
Gesamtenergie hinzugefügt. Hinzugefügt werden auch die exakten Werte für Ort und
Geschwindigkeit.

/**
* @brief solve_ode numerische Lösung der Bewegungsgleichung
*/
inline void exec () {
Ode myOde { *this , 1e-2, 1e-9, 0 };
myOde. set_init_conditions (0, { y0 , v0 });

// löse Bewegungsgleichung
double t = 0, dt = 0.01;
do {
_data += { t, myOde [0] , myOde [1] };
t += dt;
myOde . solve (t);
} while ( myOde [0] > 0);

// füge Gesamtenergie und exakt berechnete Daten hinzu


for (auto &crow : _data .data ()) {
const double t = crow [0];
crow [3] = Mechanics :: kinetic_energy (mass , crow [2]);
crow [4] = Mechanics :: potential_energy (mass , crow [1]);
crow [5] = crow [3] + crow [4]; // Gesamtenergie
13.2 Bewegung mit Reibung nach Newton 463

crow [6] = y(t); // exakter Wert für Höhe


crow [7] = v(t); // exakter Wert für Geschwindigkeit
}
}

Listing 13.23 cppsrc/apps-x2010/hpp/x2010–11

Die Daten werden mit dieser Funktion in Dateien geschrieben.

/**
* @brief save_data speichert Daten in Dateien
*/
inline void save_data () {
save(_data , Output :: plot , gamma );
// ändere Formatierung der Daten für die LaTeX - Tabelle
std :: ofstream ofs = get_output_stream ( Output :: latex , gamma);
ofs << std :: fixed << std :: setprecision (4);
_data. select_total_rows (5).save(ofs , Output :: latex);
}
}; // C2010

Listing 13.24 cppsrc/apps-x2010/hpp/x2010–12

Daten Wir erstellen Daten, indem wir als einzigen Eingabeparameter des Rechenmo-
dells die Reibungskonstante variieren. Die Werte von γ sind zur Demonstration des Kon-
zepts gedacht (Tab. 13.2, Abb. 13.3).

/**
* @brief run Berechnung von Beispieldaten
*/
inline void run () {
const double mass = 1.0;
const double y0 = 0.0;
const double v0 = 30.0;

for (const auto gamma : { 1.3 , 1.6 , 2.0 }) {


// Rechenmodell
C2010 cobj{ mass , gamma , y0 , v0 };
cobj.exec ();
cobj. save_data ();
}
}

} // namespace nmx :: apps :: x2010

Listing 13.25 cppsrc/apps-x2010/hpp/x2010–13

Von den berechneten Werten stellen wir nur den Fall γ = 1.3 kg/s als Tabelle dar. In den
Diagrammen sind alle Fälle dargestellt.
464 13 Bewegung in einem Medium mit Reibung

Tab. 13.2: Bewegung einer Kugel in einem Medium mit Reibung nach Newton und
Reibungskoeffizienten γ = 1.3 kg/s
t [s] y [m] v [m/s] K [J] U [J] E [J] yex [m] vex [m/s]
0.0000 0.0000 30.0000 450.0000 0.0000 450.0000 0.0000 30.0000
0.2400 1.6825 1.9715 1.9434 16.4996 18.4430 1.6825 1.9715
0.4900 1.8146 -0.7242 0.2622 17.7952 18.0575 1.8146 -0.7242
0.7400 1.4094 -2.2574 2.5480 13.8215 16.3695 1.4094 -2.2574
0.9900 0.7820 -2.6579 3.5323 7.6687 11.2010 0.7820 -2.6579
1.2400 0.1057 -2.7315 3.7305 1.0370 4.7675 0.1057 -2.7315
1.2700 0.0238 -2.7344 3.7384 0.2330 3.9714 0.0238 -2.7344

2
γ = 1.3 30 γ = 1.3
γ = 1.6 γ = 1.6
γ=2 γ=2
1.5
20
v[m/s]
y[m]

1
10
0.5

0
0

0 0.2 0.4 0.6 0.8 1 1.2 0 0.2 0.4 0.6 0.8 1 1.2
t[s] t[s]

(a) (b)
Abb. 13.3: Bewegung einer Kugel in einem Medium mit Reibung nach Newton. (a) Höhe
und (b) Geschwindigkeit als Funktion der Zeit für unterschiedliche Reibungskoeffizienten.

13.3 Übungsaufgaben
1. Wie viel Zeit benötigt das Teilchen aus Abschnitt 13.1, um zum Ausgangspunkt zu-
rückzukehren? Lösen Sie Gl. 13.8 numerisch.
2. Ein Massenpunkt wird mit einer Anfangsgeschwindigkeit v0 unter einem Winkel ϑ0
schräg nach oben geworfen. Es soll in einem horizontalem Abstand d auf den Boden
auftreffen. Erstellen Sie für die Modellklasse aus Abschnitt 10.5.2 ein Rechenmodell,
welche für eine vorgegebene Anfangsgeschwindigkeit und Höhe H den Abwurfwinkel
berechnet. Die Software soll eine Ausnahme auswerfen, falls das Ziel nicht erreicht
werden kann.
3. Ein Massenpunkt wird mit einer Geschwindigkeit v0 unter einem Winkel ϑ0 schräg
nach oben geworfen. Auf den Massenpunkt wirkt außer der Gravitation auch eine
geschwindigkeitsabhängige Reibungskraft der Form F ⃗ = −β⃗v . Wählen Sie Werte für
v0 , ϑ0 , β und
13.3 Übungsaufgaben 465

a) erstellen Sie eine Modellklasse und ein Rechenmodell zur Berechnung der Bahn
des Massenpunktes.
b) Variieren Sie den Wert von β und zeichnen Sie die Bahnkurven in einem gemein-
samen Diagramm.
⃗ = −γv 2 ⃗v .
4. Wiederholen Sie den Vorgang für eine Reibungskraft der Form F |⃗v|
14 Schwingungen

Übersicht
14.1 Harmonische Schwingung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
14.2 Schwingung in vertikaler Richtung eines Systems aus zwei Massen . . . . . . . . . . 475
14.3 Harmonische Schwingungen mit Dämpfung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
14.4 Übungsaufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496

14.1 Harmonische Schwingung


Ein Klotz mit der Masse m wird an einer horizontalen Feder mit der Federkonstanten k
befestigt, um eine Distanz x0 von der Ruhelage ausgelenkt und anschließend losgelassen
(Abb. 14.1). Der Klotz bewegt sich ohne Reibung und die Masse der Feder ist vernach-
lässigbar klein. Stellen Sie die Bewegungsgleichungen auf. Lösen Sie diese numerisch und
vergleichen Sie die Daten mit den exakten Ergebnissen.

x=0 x=0

x


F

(a) (b)
Abb. 14.1: Das schwingende Feder-Masse System

Lösung

Die Bewegungsgleichung Wir legen den Einheitsvektor ⃗ex parallel zur Federachse.
Die Feder übt eine Kraft auf die Masse aus, welche proportional und entgegengesetzt zur
Auslenkung ist.
¨ = −k⃗x ⇒ mẍ⃗ex = −kx⃗ex
m⃗x
oder nach Division mit der Masse m
k
ẍ + ω02 x = 0, ω02 = . (14.1)
m

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7_14
468 14 Schwingungen

Lösung der Differenzialgleichung Wir setzen eλt in Gl. 14.1 ein und erhalten folgendes
charakteristische Polynom:
λ2 + ω02 = 0 (14.2)
−iω0 t
Für λ = ±iω0 erhalten wir die beiden Lösungen e iω0 t
,e für die Differenzialglei-
chung. Nicht nur diese, sondern auch jede beliebige Linearkombination dieser Funktionen
ist ebenfalls Lösung und somit auch
1 ( iω0 t ) 1 ( iω0 t )
e + e−iω0 t = cos ω0 t und e − e−iω0 t = sin ω0 t.
2 2i
Dies sind zwei linear unabhängige reelle Funktionen. Damit folgt für die reelle allgemeine
Lösung für Gl. 14.1
x(t) = A1 cos ω0 t + A2 sin ω0 t, (14.3)
wobei A1 , A1 reelle Konstanten sind, die durch die Anfangsbedingungen festgelegt wer-
den. Wir können x(t) auch etwas kompakter schreiben:
√ ( )
A1 A2
x(t) = A1 + A2 √ 2
2 2 cos ω0 t + √ 2 sin ω0 t (14.4)
A1 + A22 A1 + A22
Mit √
A1 A2
sin φ0 = √ 2 , cos φ0 = √ 2 ,A = A21 + A22
A1 + A22 A1 + A22
erhalten wir
x(t) = A sin (ω0 t + φ0 ) . (14.5)

Berechnung der Konstanten A und φ0 Für die Anfangsbedingungen x0 (0) ̸= 0 und


v0 (0) ̸= 0 erhalten wir aus Gl. 14.5
{
x(0) = x0 ⇒ A sin φ0 = x0
ẋ(0) = v0 ⇒ Aω0 cos φ0 = v0
und damit  √
 (
)2

 v0
 A= x20 + (14.6a)
ω0

 ω x

 φ0 = arctan 0 0 , v0 ̸= 0 (14.6b)
v0
wobei Gl. 14.6b so ausgewertet werden muss, dass φ0 im richtigen Quadranten liegt. Ist
für x0 (0) ̸= 0 und v0 (0) = 0, folgt:


 A = x0 (14.7a)
( )
ω0 x0 π

 φ0 = vlim arctan = (14.7b)
0 →0 v0 2


 x(t) = x0 cos ω0 t (14.8a)

ẋ(t) = −ω0 x0 sin ω0 t (14.8b)


 ẍ(t) = −ω 2 x cos ω t (14.8c)
0 0 0
14.1 Harmonische Schwingung 469

Das Computerprogramm

Kurzbeschreibung Wir werden die Schwingung der Masse mithilfe einer Modellklasse
und einem von ihr abgeleitetes Rechenmodell beschreiben.

Modellklasse
– Eingabewerte: Masse m, Federkonstante k und die Anfangsbedingungen x0 und
v0
– Implementierung der theoretisch hergeleiteten Formeln
Rechenmodell
– Numerische Lösung der Bewegungsgleichung
– Ausgabe der berechneten Daten in Dateien

Quelltext Der Konstruktor (Listing 14.2) der Modellklasse initialisiert alle Eingabepa-
rameter und berechnet die Amplitude, Phasenkonstante und Kreisfrequenz. Diese Werte
werden intern gespeichert.

/**
* @brief The X900 class Harmonische Schwingung eines
* Feder -Massen - Systems ( Modellklasse )
*/
class X900 : public XModel
{
private :
double _omega0 , _phi0 , _amplitude ;

public :
// Eingabeparameter
const double mass;
const double k;
// Anfangsbedingungen
const double x0 , v0;

Listing 14.1 cppsrc/apps-x900/hpp/x900–01

/**
* @brief X900
* @param m Masse
* @param k Federkonstante
* @param x0 Anfangsposition
* @param v0 Anfangsgeschwindigkeit
*/
inline X900( double m, double k, double x0 , double v0)
: XModel ( __func__ )
, mass{ m }
, k{ k }
, x0{ x0 }
, v0{ v0 } {
Check :: all(nmx_msg , { m > 0, k > 0 });

_omega0 = sqrt(k / mass);


_amplitude = sqrt(pow(x0 , 2) + pow(v0 / _omega0 , 2));
470 14 Schwingungen

_phi0 = atan2 ( _omega0 * x0 , v0);


}

Listing 14.2 cppsrc/apps-x900/hpp/x900–02

Kreisfrequenz, Phasenkonstante und Amplitude können über die nächsten drei Element-
funktionen gelesen werden.

/**
* @brief omega0
* @return Kreisfrequenz
*/
inline double omega0 () const { return _omega0 ; }

Listing 14.3 cppsrc/apps-x900/hpp/x900–03

/**
* @brief phi0
* @return Phasenkonstante
*/
inline double phi0 () const { return _phi0; }

Listing 14.4 cppsrc/apps-x900/hpp/x900–04

/**
* @brief amplitude
* @return Amplitude
*/
inline double amplitude () const { return _amplitude ; }

Listing 14.5 cppsrc/apps-x900/hpp/x900–05

Es folgen die Phase, die Auslenkung, die Geschwindigkeit und die Beschleunigung als
Funktionen der Zeit.

/**
* @brief phase
* @param t Zeit
* @return Phase zum Zeitpunkt t
*/
inline double phase ( double t) const { return omega0 () * t + _phi0; }

Listing 14.6 cppsrc/apps-x900/hpp/x900–06

/**
* @brief x Auslenkung
* @param t Zeit
* @return Auslenkung zum Zeitpunkt t
*/
inline double x( double t) const { return _amplitude *
sin(phase(t)); }
14.1 Harmonische Schwingung 471

Listing 14.7 cppsrc/apps-x900/hpp/x900–07

/**
* @brief v Geschwindigkeit
* @param t Zeit
* @return Geschwindigkeit zum Zeitpunkt t
*/
inline double v( double t) const { //
return _amplitude * omega0 () * cos( phase(t));
}

Listing 14.8 cppsrc/apps-x900/hpp/x900–08

/**
* @brief a Beschleunigung
* @param t Zeit
* @return Beschleunigung zum Zeitpunkt t
*/
inline double a( double t) const { //
return -_amplitude * pow( omega0 () , 2) * sin( phase(t));
}

Listing 14.9 cppsrc/apps-x900/hpp/x900–09

Die letzte Elementfunktion implementiert die Kraft als Funktion der Auslenkung.

/**
* @brief force Kraft
* @param x Auslenkung
* @return Kraftgesetz
*/
inline double force ( double x) const { return -k * x; }
}; // X900

Listing 14.10 cppsrc/apps-x900/hpp/x900–10

Das Rechenmodell löst die Bewegungsgleichung numerisch und speichert die Ergebnisse
in eine Zahlentabelle. Es transformiert Gl. 14.1 in ein System von linearen Differenzial-
gleichungen erster Ordnung (Listing 14.12).
{
χ̇ = ψ (14.9a)
ψ̇ = −kχ (14.9b)

mit x = χ und ψ = ẋ

/**
* @brief The X900 class Harmonische Schwingung eines
* Feder -Massen - Systems ( Rechenmodell )
*/
472 14 Schwingungen

class C900 : public X900


{
public :
using Ode = gsl :: Odeiv2 <2 >;
friend Ode;
using Data = Data <7 >;

using X900 :: X900; // erbe Konstruktor von der Basisklasse

private :
Data _data;

public :

Listing 14.11 cppsrc/apps-x900/hpp/x900–11

/**
* @brief odefn Bewegungsgleichung als System von
* Differenzialgleichungen erster Ordnung
* @param t Zeit
* @param yin Eingabe
* @param yout Ausgabe
* @return GSL_SUCCESS
*/
int odefn( double t, const double yin [], double yout []) {
(void) t; // compiler warning vermeiden
yout [0] = yin [1];
yout [1] = force (yin [0]) / mass;
return GSL_SUCCESS ;
}

Listing 14.12 cppsrc/apps-x900/hpp/x900–12

Die nächste Elementfunktion fügt zu den numerischen Lösungen die potenzielle, kineti-
sche und Gesamtenergie des Systems hinzu.

/**
* @brief solve_ode numerische Lösung der Bewegungsgleichungen
*/
inline void exec () {
Ode ode{ *this , 1e-2, 1e-9, 0 };
ode. set_init_conditions (0, { x0 , v0 });
const double _period = 2 * Math ::PI / omega0 ();

// löse Bewegungsgleichung
double t = 0.0 , dt = 0.01;
while (t <= 1.5 * _period ) {
_data += { t, ode [0] , ode [1] , force(ode [0]) };
t += dt;
ode. solve (t);
}

// füge Energien hinzu


for (auto &crow : _data .data ()) {
const auto x = crow [1];
const auto v = crow [2];
14.1 Harmonische Schwingung 473

crow [4] = Mechanics :: kinetic_energy (mass , v);


crow [5] = 0.5 * k * pow(x, 2);
crow [6] = crow [4] + crow [5];
}
}

Listing 14.13 cppsrc/apps-x900/hpp/x900–13

Die Daten werden im LATEX- und CSV-Format zur Ausgabe in Tabellen und Diagrammen
gespeichert.

/**
* @brief save_data schreibe Daten in Dateien (LaTeX und CSV)
*/
inline void save_data () {
// erzeuge id damit jede Datei einen eindeutigen Namen hat
std :: stringstream id;
id << std :: setprecision (3) << omega0 () << "-" << phi0 ();

// speichere Daten
save(_data , Output :: plot , id.str ());
save(_data . select_total_rows (10) , Output :: latex , id.str ());
}

Listing 14.14 cppsrc/apps-x900/hpp/x900–14

Daten Für ein Masse-Feder-System mit vorgegebener Masse m = 1 kg, Federkonstante


k = 100 N/m und den Anfangsbedingungen x0 = 1 m und v0 = 0 erstellen wir Daten
mithilfe eines Rechenmodells (Tab. 14.1, Abb. 14.2).

/**
* @brief run Harmonische Schwingung eines Feder -Massen - Systems
* Berechnung von Beispieldaten
*/
inline void run () {
// Modellparameter
const double mass = 1.0 , k = 100.0;
// Anfangsbedingungen
const double x0 = 1.0 , v0 = 0.0;
// Rechenmodell
C900 cobj{ mass , k, x0 , v0 };
cobj.exec ();
cobj. save_data ();
}

Listing 14.15 cppsrc/apps-x900/hpp/x900–15


474 14 Schwingungen

1 10

0.5 5

v[m/s]
x[m]

0 0

−0.5 −5

−1 −10

0 0.2 0.4 0.6 0.8 1 0 0.2 0.4 0.6 0.8 1


t[s] t[s]

(a) (b)
100

40
50

K[J], U [J], E[J]


a[m/s2 ]

0
20

−50

−100 0

0 0.2 0.4 0.6 0.8 1 0 0.2 0.4 0.6 0.8 1


t[s] t[s]

(c) (d)
Abb. 14.2: Harmonische Schwingung eines Masse-Feder-Systems m = 1 kg, k =
100 N/m. (a) x − t-Diagramm, (b) v − t-Diagramm (c) a − t-Diagramm (d) Energien

Tab. 14.1: Harmonische Schwingung eines Masse-Feder-Systems mit m = 1kg, k =


100 N/m
t [s] x [m] v [m/s] a [m/s2 ] K [J] U [J] E [J]
0.000e+00 1.000e+00 0.000e+00 -1.000e+02 0.000e+00 5.000e+01 5.000e+01
1.000e-01 5.403e-01 -8.415e+00 -5.403e+01 3.540e+01 1.460e+01 5.000e+01
2.000e-01 -4.161e-01 -9.093e+00 4.161e+01 4.134e+01 8.659e+00 5.000e+01
3.000e-01 -9.900e-01 -1.411e+00 9.900e+01 9.957e-01 4.900e+01 5.000e+01
4.000e-01 -6.536e-01 7.568e+00 6.536e+01 2.864e+01 2.136e+01 5.000e+01
5.000e-01 2.837e-01 9.589e+00 -2.837e+01 4.598e+01 4.023e+00 5.000e+01
6.000e-01 9.602e-01 2.794e+00 -9.602e+01 3.904e+00 4.610e+01 5.000e+01
7.000e-01 7.539e-01 -6.570e+00 -7.539e+01 2.158e+01 2.842e+01 5.000e+01
8.000e-01 -1.455e-01 -9.894e+00 1.455e+01 4.894e+01 1.059e+00 5.000e+01
9.000e-01 -9.111e-01 -4.121e+00 9.111e+01 8.492e+00 4.151e+01 5.000e+01
9.400e-01 -9.997e-01 -2.478e-01 9.997e+01 3.069e-02 4.997e+01 5.000e+01
14.2 Schwingung in vertikaler Richtung eines Systems aus zwei Massen 475

14.2 Schwingung in vertikaler Richtung eines


Systems aus zwei Massen
Ein Klotz mit der Masse m1 wird am oberen Ende einer senkrechten Feder mit der Feder-
konstante k befestigt, die wiederum am unteren Ende fest mit dem Boden verbunden ist.
Auf den Klotz wird eine kleinere Masse m2 gelegt. Das System befindet sich anfänglich
im Gleichgewicht und wird anschließend nach unten gedrückt.

d⃗ ⃗
N
y=0 m2
⃗ey w
⃗2

y
⃗k
F
m1

⃗′
N w
⃗1

(b)
(a)
Abb. 14.3: Zwei Massen schwingen vertikal am oberen Ende einer Feder. (a) Die Feder
ohne die Massen, mit Massen im Gleichgewicht und zu einem Zeitpunkt während der
Schwingung (b) Kräfte, die auf jede Masse während der Bewegung wirken

1. Zeigen Sie, dass das System der beiden Massen eine harmonische Schwingung ausführt.
Berechnen Sie die Normalkraft, die auf die Masse m2 wirkt. Unter welchen Umständen
verliert m2 den Kontakt zu m1 ?
2. Beschreiben Sie die Bewegung der beiden Massen mit einem Computerprogramm.

Lösung

Die Bewegungsgleichung Wir legen die y-Achse parallel zur Federachse (Abb. 14.3a).
Im Gleichgewicht ist die Summe aller Kräfte, die auf das System wirken 0:

⃗ = 0 ⇒ (m1 + m2 ) g⃗ey − kd⃗ey = 0
F
⇒ (m1 + m2 ) g = kd. (14.10)
Nach dem zweiten Newton’schen Gesetzes gilt für m1 (Abb. 14.3b):

m1 ÿ = m1 g − k (y + d) + N ′ . (14.11)

Entsprechend gilt für m2 (Abb. 14.3b):

m2 ÿ = m2 g − N, (14.12)
476 14 Schwingungen

wobei N ⃗ ′ immer in die positive Richtung zeigt und |N


⃗ stets in die negative, N ⃗ ′|
⃗ | = |N
ist (Abb. 14.3b). Wir addieren Gl. 14.11 und Gl. 14.12:

(m1 + m2 ) ÿ = − (m1 + m2 ) g − k (y + d)
⇒ (m1 + m2 ) ÿ = (m1 + m2 ) g − ky − kd. (14.13)
Dieses Ergebnis zusammen mit Gl. 14.10 gibt

(m1 + m2 ) ÿ = −ky (14.14)

oder √
k
ÿ + ω02 y = 0, . ω0 = (14.15)
m1 + m2
Das System der beiden Massen führt eine harmonische Schwingung aus. Die Lösung
dieser Gleichung ist bekannt:

y = y0 sin (ω0 t + φ0 ) . (14.16)

Für t0 = 0 und y(0) = y0 ⃗ey , v(0) = 0 folgt


π
y0 = y0 sin φ0 ⇒ φ0 = arcsin (1) ⇒ φ0 = .
2
Damit ist ( π)
y = y0 sin ω0 t + ⇒ y = y0 cos ω0 t. (14.17)
2
Die erste und zweite Ableitung lautet

ẏ = −y0 ω0 sin ω0 t, ÿ = −y0 ω02 cos ω0 t. (14.18)


Wir setzen Gl. 14.18 in Gl. 14.12 ein, um die Normalkraft auf m2 als Funktion der Zeit
zu erhalten
− m2 y0 ω02 cos ω0 t = m2 g − N
( )
y0 ω02
⇒ N = m2 g 1 + cos ω0 t . (14.19)
g
Da | cos ω0 t| ≤ 1, gilt für den Wertebereich von N
( ) ( )
y0 ω02 y0 ω02
m2 g 1 − ≤ N ≤ m2 g 1 + . (14.20)
g g
Wann und wo ist N = 0?
y0 ω02 g
N =0⇒1+ cos ω0 t = 0 ⇒ cos ω0 t = − ,
g y0 ω02
wobei g
y0 ω02
≤ 1 ist und
( )
1 g
acos −
t= (14.21)
ω0 y0 ω02
Die Ortskoordinate erhalten wir durch Einsetzen der berechneten Zeit in Gl. 14.17. Wenn
y0 = ωg2 ist, folgt t = ωπ0 . Dies bedeutet, dass die Feder ihre maximale Auslenkung
0
erreicht hat. Wenn y0 > ωg2 ist, verliert m2 den Kontakt zu m1 bevor die maximale
0
Auslenkung erreicht wird.
14.2 Schwingung in vertikaler Richtung eines Systems aus zwei Massen 477

Das Computerprogramm

Kurzbeschreibung Wir implementieren die charakteristischen Eigenschaften des Sys-


tems sowie die hergeleiteten Formeln innerhalb einer Modellklasse und leiten von ihr ein
Rechenmodell ab, welches die numerischen Berechnungen durchführen wird.

Modellklasse
– Attribute: Massen m1 , m2 , die Federkonstante k und Anfangsbedingungen
– Elementfunktionen zur Berechnung der Beschleunigung als Funktion der Ortsko-
ordinate und der Normalkraft auf m2 als Funktion der Zeit
Rechenmodell
– Numerische Lösung der Bewegungsgleichung
– Berechnung der Normalkraft auf m2
– Ausgabe der Daten in Dateien

Quelltext Alle Bewegungsparameter werden dem Konstruktor der Modellklasse über-


geben (Listing 14.17). Die Kreisfrequenz und die Schwingungsdauer werden berechnet
und gespeichert.

/**
* @brief The X910 class Schwingung in vertikaler Richtung eines
* Systems aus zwei Massen ( Modellklasse )
*/
class X910 : public XModel
{
public :
// Eingabeparameter
const double m1 , m2;
const double k;
const double y0 , v0;
const double omega0 ;
const double period ;

public :

Listing 14.16 cppsrc/apps-x910/hpp/x910–01

/**
* @brief X910 Konstruktor
* @param m1 Masse 1
* @param m2 Masse 2
* @param k Federkonstante
* @param y0 maximale Auslenkung
* @param v0 Anfangsgeschwindigkeit
*/
inline X910( double m1 , double m2 , double k, double y0 , double v0)
: XModel ( __func__ )
, m1{ m1 }
, m2{ m2 }
, k{ k }
, y0{ y0 }
478 14 Schwingungen

, v0{ v0 }
, omega0 { sqrt(k / (m1 + m2)) }
, period { 2 * Math :: PI / omega0 } {}

Listing 14.17 cppsrc/apps-x910/hpp/x910–02

Es folgen die Elementfunktionen zur Berechnung der Beschleunigung und der Normal-
kraft (Gl. 14.14 und Gl. 14.19).

/**
* @brief acceleration Beschleunigung
* @param x Auslenkung
* @return Beschleunigung an der Stelle x
*/
inline double acceleration ( double x) const { //
return -pow(omega0 , 2) * x;
}

Listing 14.18 cppsrc/apps-x910/hpp/x910–03

/**
* @brief normal_force2 Normalkraft
* @param t Zeit
* @return Normalkraft zum Zeitpunkt t
*/
inline double normal_force2 ( double t) const {
return m2 * Earth ::g * (1 + y0 * pow(omega0 , 2) / Earth ::g *
cos( omega0 * t));
}
}; // X910

Listing 14.19 cppsrc/apps-x910/hpp/x910–04

Das Rechenmodell implementiert alle Routinen zur numerischen Lösung der Bewegungs-
gleichung Gl. 14.15. Diese wird als ein System von Differenzialgleichungen erster Ordnung
formuliert. {
χ̇ = ψ
ψ̇ = −ω02 χ
mit χ = y und ψ = ẏ

/**
* @brief The C910 class Schwingung in vertikaler Richtung eines Systems
* aus zwei Massen ( Rechenmodell )
*/
class C910 : public X910
{
public :
using Ode = gsl :: Odeiv2 <2 >;
friend Ode;
using Data = Data <6 >;

// benutze Konstruktoren der Basisklasse


using X910 :: X910;
14.2 Schwingung in vertikaler Richtung eines Systems aus zwei Massen 479

private :
double _normalForce2 ;
// Zahlentabelle zum Speichern der Ergebnisse
Data _data;

Listing 14.20 cppsrc/apps-x910/hpp/x910–05

/**
* @brief odefn Bewegungsgleichung der beiden Massen
* als System von DGL
* @param t Zeit
* @param yin Eingabe
* @param yout Ausgabe
* @return GSL_SUCCESS
*/
int odefn( double t, const double yin [], double yout []) {
(void) t;
yout [0] = yin [1];
yout [1] = acceleration (yin [0]);
return GSL_SUCCESS ;
}

public :

Listing 14.21 cppsrc/apps-x910/hpp/x910–06

Es folgt die numerische Lösung der Bewegungsgleichungen, wobei neben der Ortskoor-
dinate und der Geschwindigkeit auch die Werte der Normalkraft aufgezeichnet werden.
Wir berechnen so lange Daten, bis m2 sich von m1 löst. Dies muss aber je nach Wahl
der Anfangsbedingungen nicht immer eintreten, was dann eine Endlosschleife zur Folge
hätte. Wir wissen, dass die Normalkraft auf m2 spätestens dann 0 wird, wenn die maxi-
male Auslenkung erreicht wird, was ein Vorzeichenwechsel der Geschwindigkeit bedeutet.
Diese Tatsache benutzen wir als zusätzliches Abbruchkriterium.

/**
* @brief exec Lösung der Bewegungsgleichung und
* Berechnung der Normalkraft auf m2
*/
void exec () {
// Objekt zur numerischen Lösung der Differenzialgleichung
Ode ode{ *this , 1e-2, 1e-9, 0 };
ode. set_init_conditions (0, { y0 , v0 });
double t = 0, dt = 0.001;

// rechne solange die Geschwindigkeit negativ ist.


while (ode [1] <= 0) {
_normalForce2 = m2 * (- acceleration (ode [0]) + Earth ::g);

// Abbruchkriterium
if ( _normalForce2 < 0) {
break ; // m2 hat sich gelöst
}
480 14 Schwingungen

// schreibe Daten in Zahlentabelle


_data += {
t,
t / period ,
ode [0] ,
ode [1] ,
_normalForce2 , // numerisch
normal_force2 (t) // exakt
};

t += dt;
ode. solve (t);
}
}

Listing 14.22 cppsrc/apps-x910/hpp/x910–07

Die Daten werden im CSV-Format und eine Auswahl der Daten wird im LATEX-
Tabellenformat gespeichert. Wir werden das Programm für unterschiedliche Anfangs-
bedingungen ausführen. Damit die Dateien nicht überschrieben werden, erhält jeder Da-
teiname einen Namenszusatz.

/**
* @brief save_data Speicherung der Daten in Dateien im CSV -
* und LaTeX - Format
*/
inline void save_data () const { //
// erzeuge id (für jede maximale Auslenkung wird eine sepparate
// Datei erzeugt )
std :: stringstream sid;
sid << std :: setprecision (2) << abs(y0);
const auto id = sid.str ();

save(_data , Output :: plot , id);


save(_data . select_total_rows (5) , Output :: latex , id);
}
}; // C910

Listing 14.23 cppsrc/apps-x910/hpp/x910–08

Daten Für m1 = 1.5 kg, m2 = 0.5 kg, k = 200 N/m und eine Anfangsgeschwindigkeit
v0 = 0 wählen wir zwei Werte für y0 und berechnen Daten für zwei Szenarien: wenn
sich m2 von m1 beim Erreichen der maximalen Auslenkung löst und wenn dies vorher
passiert (Tab. 14.2, Tab. 14.3, Abb. 14.4).

/**
* @brief run Schwingung in vertikaler Richtung eines Systems aus zwei
* Massen ( Berechnung von Beispieldaten )
*/
inline void run () {
// Bewegungsparameter
const double m1 = 1.5 , m2 = .5, k = 200.0;

// Anfangsbedingungen
14.2 Schwingung in vertikaler Richtung eines Systems aus zwei Massen 481

const double y0max = Earth ::g / (k / (m1 + m2)), v0 = 0.0;

// Berechnung der Daten


for (const auto y0 : { 1.5 * y0max , y0max }) {
// Rechenmodell
C910 cobj{ m1 , m2 , k, y0 , v0 };
cobj.exec ();
cobj. save_data ();
}
}

Listing 14.24 cppsrc/apps-x910/hpp/x910–09

3g 0.15 3g
y0 = 2ω 2 y0 = 2ω 2
0 0
y0 = ωg2 y0 = ωg2
0 0
10 0.1

5 · 10−2
N [N ]

x[m]

5 0

−5 · 10−2

0 −0.1

0 0.1 0.2 0.3 0.4 0.5 0 0.1 0.2 0.3 0.4 0.5
t/T t/T

(a) (b)
Abb. 14.4: Schwingung in vertikaler Richtung eines Systems aus zwei Massen. (a) Nor-
malkraft auf Masse m2 (b) Gemeinsame Auslenkung von m1 und m2 als Funktion des
Quotienten von Zeit und Schwingungsdauer

Tab. 14.2: Schwingung in vertikaler Richtung eines Systems aus zwei Massen. Die Nor-
3g
malkraft wurde sowohl numerisch als auch exakt berechnet (y0 = 2ω 2 ).
0

t [s] t/T y [m] v [m/s] N [N] Nex [N]


0.000e+00 0.000e+00 1.471e-01 0.000e+00 1.226e+01 1.226e+01
4.500e-02 7.162e-02 1.325e-01 -6.398e-01 1.153e+01 1.153e+01
9.100e-02 1.448e-01 9.028e-02 -1.161e+00 9.417e+00 9.417e+00
1.370e-01 2.180e-01 2.934e-02 -1.441e+00 6.370e+00 6.370e+00
1.830e-01 2.913e-01 -3.770e-02 -1.422e+00 3.018e+00 3.018e+00
2.290e-01 3.645e-01 -9.691e-02 -1.107e+00 5.796e-02 5.796e-02
2.300e-01 3.661e-01 -9.801e-02 -1.097e+00 2.873e-03 2.873e-03
482 14 Schwingungen

Tab. 14.3: Schwingung in vertikaler Richtung eines Systems aus zwei Massen. Die Nor-
malkraft wurde sowohl numerisch als auch exakt berechnet (y0 = ωg2 ).
0

t [s] t/T y [m] v [m/s] N [N] Nex [N]


0.000e+00 0.000e+00 9.807e-02 0.000e+00 9.807e+00 9.807e+00
6.200e-02 9.868e-02 7.981e-02 -5.698e-01 8.894e+00 8.894e+00
1.250e-01 1.989e-01 3.092e-02 -9.306e-01 6.449e+00 6.449e+00
1.880e-01 2.992e-01 -2.984e-02 -9.342e-01 3.411e+00 3.411e+00
2.510e-01 3.995e-01 -7.915e-02 -5.790e-01 9.459e-01 9.459e-01
3.140e-01 4.997e-01 -9.807e-02 -1.562e-03 6.219e-06 6.219e-06

14.3 Harmonische Schwingungen mit Dämpfung


Ein Körper mit Masse m ist an einer horizontalen Feder mit vernachlässigbarer Masse
und der Federkonstante k befestigt (Abb. 14.5). Der Körper bewegt sich in einem Medi-
um, dass eine geschwindigkeitsproportionale Reibungskraft der Form F⃗b = −β⃗v auf den
Körper ausübt. Die Reibung zwischen Körper und Boden soll vernachlässigbar sein.

x=0 x=0

x ⃗
x

⃗v
β⃗v

F

(a) (b)
Abb. 14.5: Schwingung eines Masse-Feder-Systems mit geschwindigkeitsproportionaler
Dämpfung

1. Stellen Sie die Bewegungsgleichung für den Körper auf und geben Sie eine Lösung für
die Anfangsbedingungen x(0) = x0 und ẋ(0) = 0 an.
2. Lösen Sie die Bewegungsgleichung numerisch. Stellen Sie in einer gemeinsamen Tabelle
die analytisch und die numerisch berechneten Werten dar.
3. Zeichnen Sie die Ort-Zeit- und die Geschwindigkeit-Zeit-Diagramme.

Lösung

Die Bewegungsgleichung Die Bewegungsgleichung lässt sich aus dem zweiten New-
ton’schen Gesetz herleiten. Die Kräftebilanz lautet in diesem Fall

¨ = −k⃗x − β ⃗x˙
m⃗x (14.22)
14.3 Harmonische Schwingungen mit Dämpfung 483

oder
mẍ⃗ex = −kx⃗ex − β ẋ⃗ex
β k
⇒ ẍ + ẋ + x = 0.
m m
β
Mit 2γ = m und ω02 = k
m folgt

ẍ + 2γ ẋ + ω02 x = 0. (14.23)

Es handelt sich hier um eine lineare Differenzialgleichung zweiter Ordnung mit konstanten
Koeffizienten, die wir mit folgendem Ansatz lösen wollen:

x(t) = eλt (14.24)

Durch Einsatz in Gl. 14.22 erhalten wir die quadratische Bestimmungsgleichung

λ2 + 2γλ + ω02 = 0 (14.25)

mit der Diskriminante


∆ = 4γ 2 − 4ω02 . (14.26)
Daraus folgt für die Lösung
√ √
−2γ ± 4γ 2 − 4ω02
λ1,2 = = −γ ± γ 2 − ω02 (14.27)
2
oder
λ1,2 = −γ ± ω̃ (14.28)

mit √
ω̃ = γ 2 − ω02 . (14.29)
Der Ausdruck unter der Wurzel in Gl. 14.29 legt die Art der Lösung für die Bewegungs-
gleichung fest.

Starke Dämpfung (γ 2 > ω02 ) ω̃ ist reell und die Lösung von Gl. 14.23 lautet
( )
x(t) = e−γt C1 eω̃t + C2 e−ω̃t , (14.30)

wobei die Konstanten C1 und C2 durch die Anfangsbedingungen festgelegt werden. In


jedem Fall gilt
lim x(t) = 0, (14.31)
t→∞

was bedeutet, dass ein exponentielles Abklingen vorliegt. Wird die Masse ausgelenkt und
dann losgelassen, d.h. wenn die Anfangsbedingungen x(0) = x0 ̸= 0 und ẋ(0) = 0 gelten,
ist
x0 = C1 + C2 (14.32)
und aus [ ]
ẋ(t) = e−γt (ω̃ − γ) C1 eω̃t − (ω̃ + γ) C2 e−ω̃t (14.33)
484 14 Schwingungen

folgt mit ẋ(0) = 0


(ω̃ − γ) C1 − (γ + ω̃) C2 = 0. (14.34)

Aus Gl. 14.32 und Gl. 14.34 entsteht folgendes Gleichungssystem:


 
C + C = x C = ω̃+γ x
1 2 0 1 0
⇒ 2ω̃
(14.35)
C1 = ω̃+γ C2 C2 = ω̃−γ x0
ω̃−γ 2ω̃

Daraus folgt für Gl. 14.30


x0 [ ]
x(t) = e−γt (ω̃ + γ) eω̃t + (ω̃ − γ) e−ω̃t
[2ω̃( ) ( )]
−γt x0
=e ω̃ eω̃t + e−ω̃t + γ eω̃t − e−ω̃t
2ω̃
und damit
x0
x(t) = e−γt (ω̃ cosh ω̃t + γ sinh ω̃t) . (14.36)
ω̃
Aperiodischer Grenzfall (∆ = 0) Die Bestimmungsgleichung Gl. 14.24 hat eine dop-
pelte Lösung mit λ = −γ. Wir benötigen allerdings zwei linear unabhängige Lösungen
und nehmen neben e−γt noch te−γt hinzu. Dass dieser Ausdruck eine Lösung ist, lässt
sich durch Einsatz in Gl. 14.23 schnell zeigen. Wir haben somit eine allgemeine Lösung
der Form
x(t) = e−γt (C1 + C2 t) . (14.37)
Es liegt genau wie im Fall der starken Dämpfung ein exponentielles Abklingen vor.

lim x(t) = 0 (14.38)


t→∞

Die Anfangsbedingungen x(0) = x0 und ẋ(0) = 0 legen auch hier die Konstanten C1
und C2 fest.
x(0) = x0 ⇒ C1 = x0 (14.39)

und
ẋ(t) = −γe−γt (C1 + C2 t) + e−γt C2
⇒ ẋ(0) = −γC1 + C2 = 0 (14.40)
Wir lösen das Gleichungssystem
 
C = x C = x
1 0 1 0
⇒ (14.41)
C2 = γC1 C2 = γx0

und erhalten die allgemeine Lösung:

x(t) = x0 e−γt (1 + γt) (14.42)


14.3 Harmonische Schwingungen mit Dämpfung 485

Schwache Dämpfung (∆ < 0) Diesmal haben wir für Gl. 14.24 ein Paar von konjugiert

komplexen Lösungen λ1,2 = −γ ± i ω02 − γ 2 . Mit

ω = ω02 − γ 2 (14.43)

folgt für die Lösung von Gl. 14.22

x(t) = e−γt (C1 cos ωt + C2 sin ωt) . (14.44)

Auch hier gilt, dass für große Zeiten limt→∞ x(t) = 0. Gl. 14.44 kann auch folgenderma-
ßen geschrieben werden:
x(t) = Ce−γt sin (ωt + φ0 ) (14.45)
Für die Anfangsbedingungen x(0) = x0 , v(0) = 0 folgt eine erste Gleichung

x(0) = x0 ⇒ C sin φ0 = x0 (14.46)

und mit der Ableitung

ẋ(t) = −γe−γt C sin (ωt + φ0 ) + e−γt ωC cos (ωt + φ0 ) (14.47)

erhalten wir eine zweite:


ω
ẋ(0) = −γC sin φ0 + ωC cos φ0 = 0 ⇒ tan φ0 = (14.48)
γ
Wir schreiben diese Gleichung mithilfe von Gl. 14.43 folgendermaßen:
√ √
√ ω 1 − γ2
1− ω γ2
ω0 − γ
2 2 0 2
ω0 2
0
tan φ0 = = = γ (14.49)
γ γ ω0

Hieraus folgt  √

 γ2

 sin φ0 = 1 − 2 (14.50)
ω0

 γ

 cos φ0 = (14.51)
ω0
und somit ist
x0 ω0 x0
C= = . (14.52)
sin φ0 ω

Das Computerprogramm

Kurzbeschreibung Spätestens an dieser Stelle zeigt der modulare Ansatz seine Vorteile.
Wir werden die gemeinsamen Attribute in einer Modellklasse unterbringen und für jeden
Typ von Schwingung ein spezielles Rechenmodell ableiten. Die numerische Behandlung
des Problems wird eine weitere abgeleitete Klasse übernehmen.

Modellklasse
486 14 Schwingungen

– Eingabe: Masse, Federkonstante, Reibungskoeffizient, Anfangsbedingungen


– Berechnung aller Konstanten, die für alle drei Fälle gemeinsam sind.
Vier Rechenmodelle
– Ein Rechenmodell zur numerischen Lösung der Bewegungsgleichung
– Jeweils ein Rechenmodell zur Berechnung von x(t) und v(t) für die starke, die
schwache Dämpfung und den aperiodischen Grenzfall mithilfe der hergeleiteten
Formeln.

Quelltext Wir ordnen jedem Schwingungstyp eine Konstante zu.

/**
* @brief The Damping enum Mögliche Schwingungstypen
*/
enum class Damping {
over = 0, // starke Dämpfung (0)
critical = 1, // aperiodischer Grenzfall (1)
under = 2 // schwache Dämpfung (2)
};

Listing 14.25 cppsrc/apps-x1500/hpp/x1500–01

Die Modellklasse legt alle Bewegungsparameter über den Konstruktor fest. Diese können
dann nicht mehr verändert werden.

/**
* @brief The X1500 class Harmonische Schwingungen mit Dämpfung
* ( Modellklasse )
*/
class X1500 : public XModel
{
public :
using IValues = std :: array <double , 2>;

const double mass , k;


const double omega0 , period0 , frequency0 ;
const double beta , gamma ;
const double x0 , v0;

public :

Listing 14.26 cppsrc/apps-x1500/hpp/x1500–02

/**
* @brief X1500 Konstruktor
* @param mass Masse
* @param k Federkonstante
* @param beta Reibungskoeffizient
* @param initc Feld der Länge 2 mit Anfangsbedingungen (x0 ,v0)
*/
inline X1500 ( double mass , double k, double beta , IValues initc)
: XModel { __func__ }
, mass{ mass }
14.3 Harmonische Schwingungen mit Dämpfung 487

, k{ k }
, omega0 { sqrt(k / mass) }
, period0 (2 * Math :: PI / omega0 )
, frequency0 (1 / period0 )
, beta{ beta }
, gamma { 0.5 * beta / mass }
, x0{ initc [0] }
, v0{ initc [1] } {}

Listing 14.27 cppsrc/apps-x1500/hpp/x1500–03

Der Faktor e−γt wird benötigt, um die Formeln für x(t) und v(t) zu programmieren. Er
wird über eine Elementfunktion (Listing 14.28) der Klasse zur Verfügung gestellt.

/**
* @brief expfactor Hilfsfunktion
* @param t Zeit
* @return e^(- gamma t)
*/
inline double expfactor ( double t) const { return exp(-gamma * t); }

Listing 14.28 cppsrc/apps-x1500/hpp/x1500–04

Die Beschleunigung des Systems (Gl. 14.23) wird als Funktion der Auslenkung und Ge-
schwindigkeit mit dieser Elementfunktion berechnet.

/**
* @brief acceleration
* @param x Ort
* @param v Geschwindigkeit
* @return momentane Beschleunigung
*/
inline double acceleration ( double x, double v) const {
return -2 * gamma * v - pow(omega0 , 2) * x;
}

Listing 14.29 cppsrc/apps-x1500/hpp/x1500–05

Zur Berechnung des Schwingungstyps führen wir eine statische Hilfsfunktion ein.

/**
* @brief type Hilfsfunktion zur Ermittlung des Dämpfungstyps
* @param gm gamma
* @param omg0 omega0
* @return Dämpfungstyp
*/
inline static Damping type( double gm , double omg0) {
double gamma2 = pow(gm , 2);
double omega02 = pow(omg0 , 2);
Damping result ;
// entscheide über den Typ der gedämpften Schwingung
if ( gamma2 > omega02 ) {
result = Damping :: over;
} else if (std :: abs( gamma2 - omega02 ) < 1e -6) {
result = Damping :: critical ;
488 14 Schwingungen

} else {
result = Damping :: under ;
}

return result ;
}
}; // X1500

Listing 14.30 cppsrc/apps-x1500/hpp/x1500–07

Der Dämpfungstyp einer Instanz der Modellklasse kann mittels dieser Funktion abgefragt
werden. Sie ruft intern die Hilfsfunktion Listing 14.30 auf.

/**
* @brief type Berechnung des Dämpfungstyps
* @return Dämpfungstyp der Instanz
*/
inline Damping type () const { return type(gamma , omega0 ); }

Listing 14.31 cppsrc/apps-x1500/hpp/x1500–06

Liegt eine starke Dämpfung vor, so können die Auslenkung und die Geschwindigkeit über
das nächste Rechenmodell als Funktion der Zeit berechnet werden. Es werden Gl. 14.36
x ω2
und ẋ(t) = − 0ω̃ 0 e−γt sinh ω̃t implementiert.

/**
* @brief The Overdamped class starke Dämpfung ( Rechenmodell )
*/

class Overdamped : public X1500


{
private :
double _omega = 0;

public :

Listing 14.32 cppsrc/apps-x1500/hpp/x1500–08

/**
* @brief Overdamped Konstruktor
* @param mass Masse
* @param k Federkonstante
* @param beta Dämpfungskonstante
* @param initc Anfangsbedingungen
*/
inline Overdamped ( double mass , double k, double beta , IValues initc)
: X1500 { mass , k, beta , initc } {
Check :: error_if_not (nmx_msg , type () == Damping :: over);
_omega = sqrt(pow(gamma , 2) - pow(omega0 , 2));
}

Listing 14.33 cppsrc/apps-x1500/hpp/x1500–09


14.3 Harmonische Schwingungen mit Dämpfung 489

/**
* @brief x Ort als Funktion der Zeit
* @param t Zeit
* @return Momentanwert des Ortes
*/
inline double x( double t) const {
const double phs = omega () * t;
const double fac = (x0 / omega ());
const double trm0 = ( omega () * cosh(phs) + gamma * sinh(phs));
const double term = fac * trm0;
return expfactor (t) * term;
}

Listing 14.34 cppsrc/apps-x1500/hpp/x1500–10

/**
* @brief v Geschwindigkeit als Funktion der Zeit
* @param t Zeit
* @return Momentanwert der Geschwindigkeit
*/
inline double v( double t) const {
const double phase = omega () * t;
const double trm0 = (x0 / omega ()) * sinh(phase);
return -pow(omega0 , 2) * expfactor (t) * trm0;
}

Listing 14.35 cppsrc/apps-x1500/hpp/x1500–11

/**
* @brief omega Kreisfrequenz
* @return die interngespeicherte Variable
*/
inline double omega () const { return _omega ; }

Listing 14.36 cppsrc/apps-x1500/hpp/x1500–12

Es folgt das Rechenmodell für den aperiodischen Grenzfall. Wir implementieren hier Gl.
14.42 und ẋ(t) = −x0 γ 2 te−γt .

/**
* @brief The Critical class Aperiodischer Grenzfall ( Rechenmodell )
*/
class Critical : public X1500
{
public :

Listing 14.37 cppsrc/apps-x1500/hpp/x1500–13

/**
* @brief Critical Konstruktor
* @param mass Masse
* @param k Federkonstante
490 14 Schwingungen

* @param beta Dämpfungskonstante


* @param initc Anfangsbedingungen
*/
inline Critical ( double mass , double k, double beta , IValues initc)
: X1500 { mass , k, beta , initc } {
Check :: error_if_not (nmx_msg , type () == Damping :: critical );
}

Listing 14.38 cppsrc/apps-x1500/hpp/x1500–14

/**
* @brief x Ort als Funktion der Zeit
* @param t Zeit
* @return Momentanwert des Ortes
*/
inline double x( double t) const { //
return x0 * expfactor (t) * (1 + gamma * t);
}

Listing 14.39 cppsrc/apps-x1500/hpp/x1500–15

/**
* @brief v Geschwindigkeit als Funktion der Zeit
* @param t Zeit
* @return Momentanwert der Geschwindigkeit
*/
inline double v( double t) const { //
return -x0 * t * expfactor (t) * pow(gamma , 2);
}

Listing 14.40 cppsrc/apps-x1500/hpp/x1500–16

Das nächste Rechenmodell ist für die Berechnung von Gl. 14.45, Gl. 14.49 und Gl. 14.52
und der Geschwindigkeit ẋ(t) = Ce−γt (ω cos (ωt + φ0 ) − γ sin (ωt + φ0 )) für den Fall
der schwachen Dämpfung zuständig.

/**
* @brief The Underdamped class Schwache Dämpfung ( Rechenmodell )
*/
class Underdamped : public X1500
{
private :
double _phi0 , _C , _omega ;

public :

Listing 14.41 cppsrc/apps-x1500/hpp/x1500–17

/**
* @brief Underdamped Konstruktor
* @param mass Masse
* @param k Federkonstante
14.3 Harmonische Schwingungen mit Dämpfung 491

* @param beta Dämpfungskonstante


* @param initc Anfangsbedingungen
*/
inline Underdamped ( double mass , double k, double beta , IValues
initc)
: X1500 { mass , k, beta , initc } {
Check :: error_if_not (nmx_msg , type () == Damping :: under);
_omega = sqrt(pow(omega0 , 2) - pow(gamma , 2));
_phi0 = atan2 ( omega () , gamma );
_C = omega0 * x0 / _omega ;
}

Listing 14.42 cppsrc/apps-x1500/hpp/x1500–18

/**
* @brief x Ort als Funktion der Zeit
* @param t Zeit
* @return Momentanwert des Ortes
*/
inline double x( double t) const { //
return _C * expfactor (t) * sin( omega () * t + _phi0);
}

Listing 14.43 cppsrc/apps-x1500/hpp/x1500–19

/**
* @brief v Geschwindigkeit als Funktion der Zeit
* @param t Zeit
* @return Momentanwert der Geschwindigkeit
*/
inline double v( double t) const {
const double phase = omega () * t + _phi0 ;
const double part1 = -gamma * x(t);
const double part2 = _C * expfactor (t) * omega () * cos(phase);
return part1 + part2 ;
}

Listing 14.44 cppsrc/apps-x1500/hpp/x1500–20

/**
* @brief omega
* @return
*/
inline double omega () const { return _omega ; }

Listing 14.45 cppsrc/apps-x1500/hpp/x1500–21

Das letzte Rechenmodell löst die Bewegungsgleichung numerisch. Sie wird in ein System
von Differenzialgleichungen erster Ordnung transformiert.
{
χ̇ = ψ
ψ̇ = −2γψ − ω02 χ, (14.53)
492 14 Schwingungen

wobei χ = x und ψ = ẋ ist. Hier ist eine Fallunterscheidung nicht notwendig.

/**
* @brief The X1500 class Harmonische Schwingungen mit Dämpfung
*/

class C1500 : public X1500


{
public :
using Ode = gsl :: Odeiv2 <2 >;
friend Ode;
using Data = Data <6 >;

using X1500 :: X1500 ;

private :
Data _data;

public :

Listing 14.46 cppsrc/apps-x1500/hpp/x1500–22

/**
* @brief odefn Transformation in ein System von linearen Dgl
* @param t Zeit
* @param fin Eingangsvektor
* @param fout Ausgangsvektor
* @return GSL_SUCCESS
*/
inline int odefn ( double t, const double fin [], double fout []) {
(void) t;
fout [0] = fin [1];
fout [1] = acceleration (fin [0] , fin [1]);
return GSL_SUCCESS ;
}

Listing 14.47 cppsrc/apps-x1500/hpp/x1500–23

/**
* @brief solve_ode numerische Lösung der Bewegungsgleichung
*/
void solve_ode () {
Ode ode{ *this , 1e-2, 1e-9, 0 };
ode. set_init_conditions (0, { x0 , v0 });
double t = 0, tmax = 2 * period0 , dt = tmax / 100;
// Schrittweite
while (t < tmax) {
_data += { t, ode [0] , ode [1] };
t += dt;
ode. solve (t);
}
}

Listing 14.48 cppsrc/apps-x1500/hpp/x1500–24


14.3 Harmonische Schwingungen mit Dämpfung 493

/**
* @brief exec numerische Berechnung der Daten
*/
inline void exec () {
// Lösung der Bewegungsgleichung
solve_ode ();
// Hinzufügen der Gesamtenergie
for (auto &crow : _data .data ()) {
const double ekin = Mechanics :: kinetic_energy (mass ,
crow [2]);
const double epot = 0.5 * k * pow(crow [1], 2);
crow [3] = ekin + epot;
}
// exakte Werte von x und v
auto exactFn = [this ]( auto cobj) {
for (auto &crow : _data .data ()) {
const double t = crow [0];
crow [4] = cobj.x(t);
crow [5] = cobj.v(t);
}
};
// rufe das zuständige Rechenmodell auf
if (type () == Damping :: over) {
exactFn ( Overdamped (mass , k, beta , { x0 , v0 }));
} else if (type () == Damping :: under ) {
exactFn ( Underdamped (mass , k, beta , { x0 , v0 }));
} else if (type () == Damping :: critical ) {
exactFn ( Critical (mass , k, beta , { x0 , v0 }));
}
}

Listing 14.49 cppsrc/apps-x1500/hpp/x1500–25

/**
* @brief save_data Ausgabe der Daten im Tabellen und Grafikformat
*/
void save_data () {
save(_data , Output :: plot , static_cast <int >( type ()));
save(_data . select_total_rows (6) , //
Output :: latex ,
static_cast <int >( type ()));
}
}; // C1500

Listing 14.50 cppsrc/apps-x1500/hpp/x1500–26

Daten Wir berechnen Beispielwerte für alle drei Dämpfungstypen, indem wir β und k
verändern. Die Anfangsbedingungen und die Masse sind in allen drei Fällen gleich (Tab.
14.4, Tab. 14.4, Abb. 14.4, Abb. 14.5, Abb. 14.6).

/**
* @brief run Berechnung von Beispieldaten
*/
inline void run () {
494 14 Schwingungen

// gemeinsame Programmparameter
const double mass = 1.0;
const double x0 = 30.0 _mm;
const double v0 = 0.0;

// starke Dämpfung
double beta = 15;
double k = 9.0;
C1500 cobj0 { mass , k, beta , { x0 , v0 } };
cobj0.exec ();
cobj0. save_data ();

// schwache Dämpfung
beta = 1;
k = 36.0;
C1500 cobj1 { mass , k, beta , { x0 , v0 } };
cobj1.exec ();
cobj1. save_data ();

// aperiodischer Grenzfall
beta = 6;
k = 9.0;
C1500 cobj2 { mass , k, beta , { x0 , v0 } };
cobj2.exec ();
cobj2. save_data ();
}

} // namespace nmx :: apps :: x1500

Listing 14.51 cppsrc/apps-x1500/hpp/x1500–27

Tab. 14.4: Schwingung mit geschwindigkeitsproportionaler Dämpfung (schwache Dämp-


fung)
t [s] x [m] v [m/s] E [J] xex [m] vex [m/s]
0.000e+00 3.000e-02 0.000e+00 1.620e-02 3.000e-02 1.561e-17
3.142e-01 -5.719e-03 -1.471e-01 1.141e-02 -5.719e-03 -1.471e-01
6.493e-01 -1.723e-02 8.807e-02 9.222e-03 -1.723e-02 8.807e-02
9.844e-01 1.631e-02 4.275e-02 5.705e-03 1.631e-02 4.275e-02
1.319e+00 7.489e-04 -9.332e-02 4.365e-03 7.489e-04 -9.332e-02
1.655e+00 -1.220e-02 3.564e-02 3.314e-03 -1.220e-02 3.564e-02
1.990e+00 8.120e-03 4.147e-02 2.047e-03 8.120e-03 4.147e-02
2.073e+00 1.034e-02 1.077e-02 1.982e-03 1.034e-02 1.077e-02
14.3 Harmonische Schwingungen mit Dämpfung 495

·10−2

0.1

v[m/s]
0
x[m]

−0.1

−2

0 0.5 1 1.5 2
0 0.5 1 1.5 2
t[s]
t[s]

(a) (b)

Abb. 14.6: Ort-Zeit- und Geschwindigkeit-Zeit-Diagramm, schwache Dämpfung

Tab. 14.5: Schwingung mit geschwindigkeitsproportionaler Dämpfung (starke Dämp-


fung).
t [s] x [m] v [m/s] E [J] xex [m] vex [m/s]
0.000e+00 3.000e-02 0.000e+00 4.050e-03 3.000e-02 -0.000e+00
6.283e-01 2.116e-02 -1.325e-02 2.103e-03 2.116e-02 -1.325e-02
1.299e+00 1.391e-02 -8.710e-03 9.088e-04 1.391e-02 -8.710e-03
1.969e+00 9.143e-03 -5.725e-03 3.926e-04 9.143e-03 -5.725e-03
2.639e+00 6.010e-03 -3.763e-03 1.696e-04 6.010e-03 -3.763e-03
3.309e+00 3.950e-03 -2.473e-03 7.328e-05 3.950e-03 -2.473e-03
3.979e+00 2.596e-03 -1.626e-03 3.166e-05 2.596e-03 -1.626e-03
4.147e+00 2.338e-03 -1.464e-03 2.567e-05 2.338e-03 -1.464e-03

·10−2
·10−2
0
3

−0.5
2
v[m/s]
x[m]

−1
1

−1.5
0
0 1 2 3 4
0 1 2 3 4
t[s]
t[s]

(a)
(b)
Abb. 14.7: Ort-Zeit- und Geschwindigkeit-Zeit-Diagramm, starke Dämpfung
496 14 Schwingungen

Tab. 14.6: Schwingung mit geschwindigkeitsproportionaler Dämpfung (aperiodischer


Grenzfall).
t [s] x [m] v [m/s] E [J] xex [m] vex [m/s]
0.000e+00 3.000e-02 0.000e+00 4.050e-03 3.000e-02 -0.000e+00
6.283e-01 1.314e-02 -2.576e-02 1.109e-03 1.314e-02 -2.576e-02
1.299e+00 2.986e-03 -7.128e-03 6.553e-05 2.986e-03 -7.128e-03
1.969e+00 5.641e-04 -1.447e-03 2.479e-06 5.641e-04 -1.447e-03
2.639e+00 9.752e-05 -2.598e-04 7.653e-08 9.752e-05 -2.598e-04
3.309e+00 1.600e-05 -4.362e-05 2.104e-09 1.600e-05 -4.362e-05
3.979e+00 2.537e-06 -7.023e-06 5.363e-11 2.537e-06 -7.023e-06
4.147e+00 1.594e-06 -4.427e-06 2.124e-11 1.594e-06 -4.427e-06

·10−2 ·10−2

3 0

2 −1
v[m/s]
x[m]

−2
1

−3
0

0 1 2 3 4 0 1 2 3 4
t[s] t[s]

(a) (b)
Abb. 14.8: Ort-Zeit- und Geschwindigkeit-Zeit-Diagramm, aperiodischer Grenzfall

14.4 Übungsaufgaben
1. Eine Masse m wird an einem Ende einer idealen horizontalen Feder befestigt, die am
anderen Ende an eine Wand befestigt ist. Die Masse wird aus ihrer Ruhelage aus-
gelenkt und losgelassen. Der Gleitreibungskoeffizient zwischen Masse und Boden ist
µ. Erstellen Sie ein Modell für das System, mit dem Sie Bewegungsdaten berechnen
können. Erstellen Sie eine repräsentative Wertetabelle im LATEX-Format sowie Dia-
gramme für den Ort, die Geschwindigkeit und die Beschleunigung als Funktion der
Zeit.
2. Ein Block mit einer Masse m2 wird auf einer schiefen Ebene mit dem Neigungs-
winkel φ am Ende einer Feder befestigt (Abb. 14.9). Eine Kugel mit der Masse m1
bewegt sich mit einer Geschwindigkeit ⃗v parallel zur schiefen Ebene, dringt in den
Block ein und bleibt in ihm stecken. Beschreiben Sie mit einem Computermodell die
Bewegung des Systems aus Klotz und Kugel in Abhängigkeit von der anfänglichen
Kugelgeschwindigkeit.
14.4 Übungsaufgaben 497

k
m2
v
m1
ϕ

Abb. 14.9: Kugel trifft auf Block, der an einer Feder befestigt ist
Literaturverzeichnis
K. Burg, H. Haf, and F. Wille. Höhere Mathematik für Ingenieure: Band I Analysis. Teubner-
Ingenieurmathematik. Vieweg+Teubner Verlag, 2013a. ISBN 9783322940803.

P.D.F. Wille, P.D.H. Haf, and P.D.K. Burg. Höhere Mathematik für Ingenieure: Band II Lineare
Algebra. Vieweg+Teubner Verlag, 2013. ISBN 9783322918888.

K. Burg, H. Haf, and F. Wille. Höhere Mathematik für Ingenieure: Bd. III: Gewöhnliche Diffe-
rentialgleichungen, Distributionen, Integraltransformationen. Teubner-Ingenieurmathematik.
Vieweg+Teubner Verlag, 2013b. ISBN 9783322941268.

U. Breymann. Der C++-Programmierer : C++ lernen - professionell anwenden - Lösungen


nutzen. Hanser, 2015. ISBN 9783446443464.

B. Stroustrup. A Tour of C++. C++ in-depth series. Addison-Wesley, 2014. ISBN


9780321958310.

A. Gilat. Numerical Methods for Engineers and Scientists, 3rd Edition: Third Edition. Wiley
Global Education, 2013. ISBN 9781118803042.

David Halliday, Robert Resnick, Jearl Walker, and Stephan W. Koch. Halliday Physik -. Wiley,
New York, 1. auflage edition, 2008. ISBN 978-3-527-40920-4.

M. Basler, P.A. Tipler, R. Dohmen, G. Mosca, C. Heinisch, A. Schleitzer, M. Zillgitt, J. Wagner,


and C. Kommer. Physik: für Wissenschaftler und Ingenieure. Springer Berlin Heidelberg, 2014.
ISBN 9783642541667.

A. Knochel, M. Basler, D. Mills, and M. Zillgitt. Arbeitsbuch zu Tipler/Mosca Physik: Alle


Aufgaben und Fragen mit Lösungen zur 7.Auflage. Springer Berlin Heidelberg, 2016. ISBN
9783662515044.

R.C. Hibbeler. Technische Mechanik: Dynamik / Übers. aus dem Amerikan.: Georgia Mais ;
Frank Langenau. Fachl. Betr. und Erw.:Jörg Wauer ; Wolfgang Seemann. Number Bd. 3 in
Always learning. Pearson, 2012. ISBN 9783868941272.

L. Papula. Mathematik für Ingenieure und Naturwissenschaftler 1: Ein Lehr- und Arbeitsbuch
für das Grundstudium. Mit zahlreichen Beispielen aus Naturwissenschaft und Technik. Mit
307 Übungsaufgaben mit ausführlichen Lösungen. Mathematik für Ingenieure und Naturwis-
senschaftler / Lothar Papula. Vieweg + Teubner, 2009. ISBN 9783834805454.

M. Galassi et al,. Gnu scientific library. URL https://www.gnu.org/software/gsl/doc/html.

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7
Index
A Diamond-Problem, 202
auto, 95, 97 Differenzialgleichung, 377
std::accumulate, 14 Drehimpuls, 231
std::adjacent_find, 167 Drehmatrix, 279
std::vector::at, 148 Drehmoment, 230
std::array, 25 Dreiecksmatrix, 286
Algorithmen, 10 Dämpfung, 482
Anfangswertproblem, 377
aperiodischer Grenzfall, 484
E
Arbeitssatz, 358
enum class, 51
Aufzählungen, 51
explicit, 72
Aufzählunstyp, 111
std::enable_if, 98
Ausnahmebehandlung, 7, 129
std::end, 11
std::exp, 127
B std::exception, 8
std::back_inserter, 20 Elementfunktionen, 61
std::begin, 11 Erddaten, 40
ballistisches Pendel, 188 Erdmittelpunkt, 373
benutzerdefinierte Literale, 32 Erfassungsliste, 19
Benutzereingaben, 6 Euler-Verfahren, 377
Benutzerschnittstelle, 83, 111 exception handling, 7
beschleunigte Schiefe Ebene, 312 exponentielles Abklingen, 483
Beschleunigung, 363
Bestimmungsgleichung, 483
F
Bisektionsverfahren, 321
__FILE__, 45, 132
BLAS, 214, 258
__PRETTY_FUNCTION__, 45, 132
Bogenmaß, 40
__func__, 45
Brennrate, 402
find_first_not_of, 53
Brennschluss, 402
find_first_of, 53
gsl_fcmp, 40
C std::find_if, 20
C++17, 3 std::for_each, 20
catch, 7 std::function, 92
chrono, 95 std::string::find, 55
cin::fail, 9 friend, 214
constexpr, 40 fstream, 47
static_cast, 123 gsl_function, 344
std::copy_if, 20 Faktorisierung, 287
std::copy, 218 Feder, 359
std::cos, 127 Federkonstante, 65, 359, 367, 467, 475
std::cin, 4 Fehlerbehandlung, 8
std::cout, 4 Fehlerbehebung, 129
Computermodelle, 178 Fehlererkennung, 8
Cramer’sche Regel, 298, 302 Fehlermeldung, 129
CRTP, 119 Fehlverhalten, 129
CSV, 134 Feldstärke, 235
Filter, 157
Funktionen, 14
D
#define, 44
std::distance, 28, 158 G
std::domain_error, 8 gsl_vector, 217
Datenstrukturen, 10 gsl_vector_view, 216
Determinante, 294 GSL_CONST_MKSA_HOUR, 33

© Springer-Verlag GmbH Deutschland, ein Teil von Springer Nature 2020


E. Posoukidis, Klassische Mechanik mit C++,
https://doi.org/10.1007/978-3-662-60905-7
502 Index

GSL_CONST_MKSA_MINUTE, 33 ideale Feder, 65


GSL_IMAG, 91 Impulssatz, 362
GSL_REAL, 91 Instanzen, 61
gsl_complex_poly_complex_eval, 89 Integral, 341
gsl_complex, 91 Interpolation, 413
gsl_poly_complex_eval, 89 Iteratoren, 11
gsl_poly_complex_solve_cubic, 143
gsl_poly_complex_solve_quadratic, 109
K
gsl_poly_eval, 89
kinetische Energie, 389
gsl_poly_solve_cubic, 143
Klasse, 63, 65, 75
gsl_poly_solve_quadratic, 109
Klassentemplates, 87, 202
gsl_root_fsolver_bisection, 324
Koeffizientenmatrix, 286
gsl_root_fsolver_type, 324
komplexe Zahlen, 30
gsl_root_fsolver, 324
Komponenten, 111
gsl_sf_polar_to_rect, 234
Konfigurationsparameter, 37
gsl_sf_rect_to_polar, 234
Kraft, 338, 389
gsl_spline, 416
Kraftstoß, 361
gsl_vector_view, 199
kubische Splines, 413
gsl_vector, 198
std::gcd, 71
std::generate_n, 20, 23, 35 L
std::generate, 28 λ-Funktion, 19, 344
std::getline, 52 LATEX, 134
std::get, 115, 116 __LINE__, 45, 132
std::transform, 20 lineare Gleichungssysteme, 285
gsl_interp_accel, 416 Literal, 32
Gauß-Algorithmus, 286 LU-Zerlegung, 287
Gauß-Kronrod-Formeln, 342 Luftwiderstand, 392
Gauß-Quadratur, 341
Gesamtkraft, 232, 233
M
Gleichgewicht, 233, 300, 475
gsl_matrix_view, 245, 263
Gleichgewichtsbedingung, 184
gsl_matrix, 245, 263
Gleitreibungskoeffizient, 358, 427
std::make_tuple, 114
Gradmaß, 40
std::map, 92
Gravitationsfeld, 235, 373
std::move, 223, 271
Gravitationskonstante, 40
std::map, 57
Gravitationskraft, 373
Makrofunktionen, 44
Makros, 44
H mathematisches Pendel, 63
std::hypot, 10, 76 Matrix, 243
Haftreibungskoeffizient, 306 Modellklassen, 178
homogener Stab, 183
Hypotenuse, 5
N
nullptr, 130
I steady_clock::now, 95
gsl_interp_accel, 415 Newton-Verfahren, 321
if constexpr, 208 Nullstellen, 321
std::initializer_list, 181 numerische Integration, 341
std::inner_product, 14
std::iota, 14
O
std::is_arithmetic_v, 72
std::optional, 330
std::is_integral_v, 72
std::ostream_iterator, 14
std::is_same_v, 72
ofstream, 47
std::is_same, 89
One Definition Rule, 4
ifstream, 47
ortsabhängige Kraft, 434
std::initializer_list, 43
iostream, 4
std::invalid_argument, 8 P
Index 503

push_back, 12 schwache Dämpfung, 485


std::make_pair, 28 Seilspannung, 300
private, 62 SFINAE, 73
protected, 62 SI-Einheiten, 32
public, 62 Sichten, 145
std::pair, 253 Standardausgabe, 4
Permutationsmatrix, 286 Standardbibliothek, 10
Polynom, 87 statische
potenzielle Energie, 373 Schnittstellen, 202
Potenzreihen, 102 Funktionen, 37
Probemasse, 235 Variablen, 37
Präprozessor, 44, 132 Stirlingformel, 94
Stoß, 358, 367
R Structured Bindings, 275
std::default_random_engine, 113
std::random_device, 113 T
std::replace_if, 163 std::transform, 23, 28, 178
std::reverse, 14 std::tuple, 116
std::string::replace, 55 throw, 7
std::runtime_error, 8
tolower, 178
Rakete, 402
try, 7
Reibung
type_traits, 72
nach Newton, 456
std::tuple, 114
nach Stokes, 449
Reibungskoeffizient, 312, 389 Taylorpolynome, 103
Reibungskraft, 482 Testgenerator, 114
Runge-Kutta-Fehlberg-Verfahren, 379 Trennzeichen, 52
Runge-Kutta-Verfahren, 378
U
S #undef, 46
GSL_SIGN, 40 std::unordered_map, 182
gsl_spline, 415 std::upper_bound, 28
static_cast, 51 using, 304
std::setw, 4
std::set, 153 V
std::shuffle, 113 std::vector, 12
std::sin, 127 variadische Funktion, 23
std::stoll, 117 Vektoren, 197
std::stringstream, 52, 129 Vererbung, 102
std::swap, 154 Views, 145
steady_clock, 95 virtuelle Funktionen, 120
substr, 53
static, 34
std::string, 51, 53 Z
harmonische Schwingung, 467 Zahlentabelle, 145
schiefe Ebene, 312 Zeichenketten, 51
schiefer Wurf, 392 zeitabhängige Kraft, 440
springer.com

Willkommen zu den
Springer Alerts
Jetzt
anmelden!
• Unser Neuerscheinungs-Service für Sie:
aktuell *** kostenlos *** passgenau *** flexibel

Springer veröffentlicht mehr als 5.500 wissenschaftliche Bücher jährlich in


gedruckter Form. Mehr als 2.200 englischsprachige Zeitschriften und mehr
als 120.000 eBooks und Referenzwerke sind auf unserer Online Plattform
SpringerLink verfügbar. Seit seiner Gründung 1842 arbeitet Springer
weltweit mit den hervorragendsten und anerkanntesten Wissenschaftlern
zusammen, eine Partnerschaft, die auf Offenheit und gegenseitigem
Vertrauen beruht.
Die SpringerAlerts sind der beste Weg, um über Neuentwicklungen im
eigenen Fachgebiet auf dem Laufenden zu sein. Sie sind der/die Erste,
der/die über neu erschienene Bücher informiert ist oder das Inhalts-
verzeichnis des neuesten Zeitschriftenheftes erhält. Unser Service ist
kostenlos, schnell und vor allem flexibel. Passen Sie die SpringerAlerts
genau an Ihre Interessen und Ihren Bedarf an, um nur diejenigen Informa-
tion zu erhalten, die Sie wirklich benötigen.

Mehr Infos unter: springer.com/alert


A14445 | Image: Tashatuvango/iStock

Das könnte Ihnen auch gefallen