C++: Eine kompakte Einführung - André Willms - ebook

C++: Eine kompakte Einführung ebook

André Willms

0,0

Opis

Mit diesem Buch lernen Programmiereinsteiger C++ praxisorientiert und in kompakter Form. André Willms erklärt zunächst die leichter zu verstehenden prozeduralen Mechanismen der Sprache, um dann auf die höhere Abstraktionsebene der objektorientierten Programmierung und die dafür notwendigen Sprachmittel, einschließlich der Standardbibliothek, einzugehen. Dazu hat der Autor einen besonderen Ansatz gewählt: Gleich im ersten Kapitel stellt er das Ergebnis eines größeren Text-Adventure- Projekts vor und gibt dem Leser die Möglichkeit, sich Schritt für Schritt die erforderlichen Kenntnisse anzueignen, so dass er das Programm nicht nur verstehen, sondern auch erweitern und schließlich selbst programmieren kann. Zusätzlich wird jedes Thema in kleineren Beispielen erläutert, um die Anwendung der Sprachelemente zu verdeutlichen. Behandelt werden im Einzelnen die Grundelemente eines C++-Programms, Arithmetik in C++, Verzweigungen, Schleifen, Funktionen, Klassen, Arrays und Verweise, Strings, dynamische Speicherverwaltung, Namensbereiche, Operatoren, Templates, die Standard Template Library (STL), Vererbung sowie Ausnahmen. Das in diesem Buch verwendete C++ entspricht dem C++14-Standard. Alle Programmcodes lassen sich sowohl mit einem C++14- als auch mit einem C++11-Compiler übersetzen.

Ebooka przeczytasz w aplikacjach Legimi na:

Androidzie
iOS
czytnikach certyfikowanych
przez Legimi
Windows
10
Windows
Phone

Liczba stron: 458

Odsłuch ebooka (TTS) dostepny w abonamencie „ebooki+audiobooki bez limitu” w aplikacjach Legimi na:

Androidzie
iOS
Oceny
0,0
0
0
0
0
0



André Willms hat bereits während des Studiums der Allgemeinen Informatik mit dem Schreiben von Büchern zum Thema C++ begonnen. Heute ist er Autor mehrerer erfolgreicher Bücher zu C und C++. Hauptberuflich ist er IT-Trainer mit inzwischen 17 Jahren Berufserfahrung.

Zu diesem Buch – sowie zu vielen weiteren dpunkt.büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+:

www.dpunkt.de/plus

C++: Eine kompakte Einführung

André Willms

André Willms

[email protected]

Lektorat: Christa Preisendanz

Copy-Editing: Ursula Zimpfer, Herrenberg

Herstellung: Frank Heidt

Umschlaggestaltung: Helmut Kraus, www.exclam.de

Druck und Bindung: M.P. Media-Print Informationstechnologie GmbH, 33100 Paderborn

Bibliografische Information der Deutschen Nationalbibliothek

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

ISBN:

Buch  978-3-86490-229-1

PDF   978-3-86491-651-9

ePub  978-3-86491-652-6

1. Auflage 2015

Copyright © 2015 dpunkt.verlag GmbH

Wieblinger Weg 17

69123 Heidelberg

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.

Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.

Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen.

5 4 3 2 1 0

Inhaltsverzeichnis

1       Einführung

1.1    Über das Buch

1.2    Vorstellung des Projekts

1.3    Identifizieren der Programmteile

1.3.1     Objekte

1.3.2     Kontrollstrukturen

1.4    Abstraktion

1.4.1     Datenabstraktion

1.4.2     Algorithmische Abstraktion

2       Grundelemente eines C++-Programms

2.1    Das erste Programm

2.1.1     Implizites return

2.2    Die Ausgabe

2.2.1     cout

2.3    include

2.4    Namensbereiche

2.5    Kommentare

2.6    Escape-Sequenzen

2.7    Zusammenfassung

2.8    Spielprojekt

3       Arithmetik in C++

3.1    Variablen

3.1.1     Integrierte Datentypen

3.1.2     Vorzeichenlose Typen

3.2    Definieren einer Variablen

3.2.1     Initialisierung

3.2.2     Lebensdauer

3.2.3     Automatische Typbestimmung

3.2.4     Definitionen zusammenfassen

3.3    Eingabe

3.4    Grundrechenarten

3.5    Konstanten

3.5.1     Konstante Ausdrücke

3.5.2     Unveränderliche Werte

3.6    Restwert

3.7    Verknüpfen unterschiedlicher Datentypen

3.8    Explizite Typumwandlung

3.9    Kombinierte Zuweisungsoperatoren

3.10  Inkrement und Dekrement

3.11  Mathematische Funktionen

3.12  Bitweise Operatoren

3.13  Zusammenfassung

3.14  Spielprojekt

4       Verzweigungen

4.1    Zusammengesetzte Anweisungen

4.2    Bedingungen

4.3    if

4.4    else

4.5    Logische Operatoren

4.6    Der ?:-Operator

4.7    Die Fallunterscheidung

4.8    static_assert

4.9    assert

4.10  Zusammenfassung

4.11  Spielprojekt

5       Schleifen

5.1    while

5.2    do-while

5.3    for

5.4    break

5.5    continue

5.6    Zusammenfassung

5.7    Spielprojekt

6       Funktionen

6.1    Funktionsdefinition

6.2    return

6.3    Standardwerte

6.4    Suffixrückgabetyp

6.5    Funktionsdeklaration

6.6    Module

6.7    Funktionen überladen

6.7.1     Unterscheidung in der Parameteranzahl

6.7.2     inline

6.7.3     Unterscheidung im Parametertyp

6.8    Lambda-Funktionen

6.9    Zusammenfassung

6.10  Spielprojekt

7       Klassen

7.1    Objektorientierte Programmierung

7.1.1     Objekte als abgrenzbare Einheiten

7.1.2     Nicht objektorientierte Objekte

7.2    Klassen als Bauplan

7.2.1     Definition

7.3    Zugriffsrechte

7.4    Konstruktoren

7.4.1     Standardkonstruktor

7.4.2     Der Destruktor

7.5    Methoden

7.5.1     Zugriffsmethoden

7.5.2     Konstante Methoden

7.6    Externe Definition

7.7    Mehrfachdefinition

7.8    Typalias

7.8.1     typedef

7.8.2     using

7.8.3     Zugriffsrecht

7.9    cv-Qualifizierung

7.10  Zusammenfassung

7.11  Spielprojekt

8       Arrays und Verweise

8.1    Arrays definieren

8.1.1     sizeof

8.2    Arbeiten mit Arrays

8.2.1     Initialisierung

8.2.2     Arrays durchlaufen

8.3    Arrays als Funktionsparameter I

8.4    Zeiger

8.4.1     Hexadezimalsystem

8.4.2     Der Adressoperator

8.4.3     Definition eines Zeigers

8.4.4     Dereferenzierung

8.4.5     Zeiger als Funktionsparameter

8.4.6     Zeiger auf Zeiger

8.4.7     Arrays als Funktionsparameter II

8.4.8     Zeigerarithmetik

8.5    Referenzen

8.6    Objekte als Funktionsparameter

8.6.1     Referenzen auf Objekte

8.6.2     Zeiger auf Objekte

8.6.3     Objekte als Methodenparameter

8.7    Zusammenfassung

8.8    Spielprojekt

9       Strings

9.1    char

9.1.1     cctype

9.2    C-Strings

9.2.1     cstring

9.2.2     Beispiel

9.3    Strings

9.3.1     Tastatureingabe von Strings

9.3.2     Methoden von string

9.4    Zusammenfassung

9.5    Spielprojekt

10     Dynamische Speicherverwaltung

10.1  Zeiger

10.1.1   Zeiger und Konstanten

10.1.2   Zeiger auf Funktionen

10.1.3   Zeiger auf Klassenelemente

10.2  Referenzen

10.3  new und delete

10.3.1   Die Klasse Name

10.4  Smart Pointer

10.4.1   Unique Pointer

10.4.2   Shared Pointer

10.4.3   Weak Pointer

10.4.4   Smart Pointer und Arrays

10.4.5   Auto-Pointer

10.5  Zusammenfassung

10.6  Spielprojekt

11     Klassen – Vertiefung

11.1  Reihenfolge der Zugriffsrechte

11.2  Der this-Zeiger

11.3  Konstruktoren

11.3.1   Standardkonstruktor

11.3.2   Kopierkonstruktor

11.3.3   Die Klasse Name

11.3.4   Elementinitialisierungsliste

11.3.5   Verschiebekonstruktor

11.3.6   Implizite Typumwandlung

11.3.7   Konstruktordelegation

11.4  Destruktoren

11.5  Konstante Objekte und Elemente

11.5.1   Implizite Objektparameter

11.5.2   mutable

11.6  Funktionen als deleted oder default definieren

11.7  Zusammenfassung

11.8  Spielprojekt

12     Klassen – Abschluss

12.1  Standardwerte für Attribute

12.2  Verschachtelte Klassendefinitionen

12.3  Statische Klassenelemente

12.3.1   Statische Methoden

12.3.2   Statische Attribute

12.3.3   Statische Variablen

12.4  Konstruktoren und ihre Anwendung

12.4.1   Funktionsaufruf aus Konstruktoren heraus

12.4.2   Unvollendet konstruierte Objekte

12.5  Implizite Klassenelemente

12.5.1   Impliziter Standardkonstruktor

12.5.2   Impliziter Kopierkonstruktor

12.5.3   Impliziter Verschiebekonstruktor

12.5.4   Impliziter Kopier-Zuweisungsoperator

12.5.5   Impliziter Verschiebe-Zuweisungsoperator

12.5.6   Impliziter Destruktor

12.6  Aufzählungen

12.7  Zusammenfassung

12.8  Spielprojekt

13     Namensbereiche

13.1  Deklarative Bereiche, potenzielle und tatsächliche Bezugsrahmen

13.2  Namensbereiche definieren

13.3  Die Using-Direktive

13.4  Ein Alias für Namensbereiche

13.5  Unbenannte Namensbereiche

13.6  Die Using-Deklaration

13.7  Zusammenfassung

13.8  Spielprojekt

14     Operatoren überladen

14.1  Zuweisungsoperatoren

14.1.1   Kopier-Zuweisungsoperator

14.1.2   Verschiebe-Zuweisungsoperator

14.1.3   Kombinierte Zuweisungsoperatoren

14.2  Rechenoperatoren

14.2.1   Operation als Methode

14.2.2   Operation als Funktion

14.2.3   Methode oder Funktion

14.2.4   Operatoren mit Verschiebe-Semantik

14.2.5   Standardverhalten nachbilden

14.3  Vergleichsoperatoren

14.3.1   Operator-Templates

14.4  Die Operatoren << und >>

14.4.1   operator<<

14.4.2   operator>>

14.5  Der Operator []

14.6  Der Operator ()

14.7  Die Operatoren -> und *

14.8  Umwandlungsoperatoren

14.9  Die Operatoren ++ und --

14.9.1   Präfixoperatoren

14.9.2   Postfixoperatoren

14.9.3   Weitere Operatoren

14.10 Probleme mit Operatoren

14.11 Zusammenfassung

14.12 Spielprojekt

15     Templates

15.1  Klassen-Templates

15.2  Funktions-Templates

15.3  Template-Parameter

15.3.1   Standardargumente

15.4  Template-Spezialisierung

15.5  typename

15.6  Zusammenfassung

16     STL

16.1  Die Komponenten der STL

16.2  Container

16.2.1   Laufzeitklassen

16.2.2   Die Container im Überblick

16.2.3   STL-konforme Container

16.3  Iteratoren

16.3.1   Entwurf eines Iterators

16.3.2   Ein Iterator für Name

16.3.3   Iteratorkategorien

16.3.4   STL-konforme Iteratoren erstellen

16.3.5   Iteratoren erzeugen

16.3.6   Insert-Iteratoren

16.3.7   Stream-Iteratoren

16.4  Algorithmen

16.4.1   Die Algorithmen im Überblick

16.5  Die STL im Einsatz

16.5.1   Variable Funktionsaufrufe

16.5.2   Aufrufübergreifende Zustände

16.5.3   Element suchen

16.5.4   Element suchen mit eigener Bedingung

16.5.5   Elemente löschen

16.5.6   Elemente kopieren

16.5.7   Elemente sortieren

16.5.8   Eigene Listeninitialisierungskonstruktoren

16.6  Zusammenfassung

16.7  Spielprojekt

17     Vererbung I

17.1  Das Klassendiagramm der UML

17.2  Vererbung in C++

17.3  Die Vererbungssyntax

17.4  Geschützte Elemente

17.4.1   Zugriff auf Basisklassenelemente

17.5  Polymorphie

17.6  Verdecken von Methoden

17.7  Überschreiben von Methoden

17.8  Virtuelle Methoden

17.8.1   Virtuelle Methoden und Konstruktoren

17.8.2   Downcasts

17.8.3   Virtuelle Destruktoren

17.9  Rein-virtuelle Methoden

17.9.1   Rein-virtuelle Methoden mit Implementierung

17.9.2   Rein-virtuelle Destruktoren

17.10 Vererbung und Arrays

17.11 Vererbung und Standardwerte

17.12 Vererbung und überladene Operatoren

17.13 Versiegelte Elemente

17.13.1 Versiegelte Klasse

17.13.2 Versiegelte Methode

17.13.3 Warum Elemente versiegeln?

17.14 Geerbte Konstruktoren verwenden

17.15 Überschreibungshilfe

17.16 Zusammenfassung

17.17 Spielprojekt

18     Vererbung II

18.1  Beziehungen

18.1.1   ist ein

18.1.2   ist implementiert mit

18.1.3   hat ein

18.2  Was wird vererbt?

18.2.1   Schnittstelle mit verbindlicher Implementierung

18.2.2   Schnittstelle mit überschreibbarer Implementierung

18.2.3   Schnittstelle

18.2.4   Implementierung

18.3  Das Offen-geschlossen-Prinzip

18.4  Operationen oben, Daten unten

18.5  Das Umkehrung-der-Abhängigkeit-Prinzip

18.6  Das Einzelne-Verantwortung-Prinzip

18.7  Zusammenfassung

18.8  Spielprojekt

19     Mehrfachvererbung

19.1  Gemeinsame Basisklassen

19.2  Virtuelle Basisklassen

19.3  Konstruktoren virtueller Basisklassen

19.4  Einsatz von Mehrfachvererbung

19.5  Zusammenfassung

19.6  Spielprojekt

20     Ausnahmen

20.1  Warum Ausnahmen?

20.2  Einsatz von Ausnahmen

20.3  Vordefinierte Ausnahmen

20.3.1   Header-Datei »exception«

20.3.2   Header-Datei »typeinfo«

20.3.3   Header-Datei »memory«

20.3.4   Header-Datei »new«

20.3.5   Header-Datei »stdexcept«

20.4  Ausnahmen im Detail

20.4.1   terminate

20.4.2   Das Verlassen eines Try-Blocks

20.4.3   uncaught_exception

20.4.4   Das Werfen einer Ausnahme

20.4.5   Das Fangen einer Ausnahme

20.5  Ausnahmespezifikationen

20.5.1   Ausnahmespezifikationen in der Praxis

20.6  Ausnahmen und Konstruktoren

20.7  Ausnahmen und Destruktoren

20.8  Ausnahmen und dynamische Speicherverwaltung

20.9  Ressourcenerwerb ist Initialisierung

20.10 Funktions-Try-Blöcke

20.11 Ausnahmensicherheit

20.12 Zusammenfassung

21     Das Spiel

21.1  Aktionen

21.2  Spielregeln

21.3  Räume

21.4  Die Erzeugung der Spielwelt

21.4.1   Erstellen der Räume

21.4.2   Erzeugen der Gegenstände

21.4.3   Erstellen der Ausgänge

21.4.4   Erzeugen von Türen

21.4.5   Erstellen der Interaktionen

Literatur

Index

1 Einführung

»Und was kann ich alles programmieren, wenn ich das Buch durchgelesen habe?«

Eine beliebte Frage auch in Schulungen, wenn die Sprache nicht mit einem speziellen Ziel im Hinterkopf gelernt wird, sondern als erster Einstieg in die Welt des Programmierens gewählt wurde und die genaue Reiseroute noch nicht feststeht.

Um diese Frage zu beantworten, geht dieses Buch einen besonderen Weg. Anstatt die einzelnen Elemente der Sprache isoliert zu behandeln und es dem Leser zu überlassen, wo sich das Gelernte in einem größeren Programm wiederfindet, werden wir uns in diesem Kapitel das Ergebnis eines größeren Projekts anschauen und uns dann schrittweise die notwendigen Kenntnisse aneignen, um das Programm verstehen, erweitern und sogar selbst programmieren zu können.

Abgesehen von diesem großen buchumspannenden Projekt gibt es noch Beispiele, die über mehrere Kapitel entwickelt werden. Zusätzlich wird jedes Thema in genügend kleineren Beispielen erläutert, um die Anwendung der Sprachelemente zu verstehen.

1.1 Über das Buch

Bevor ich Ihnen das große Projekt vorstelle, möchte ich noch kurz abgrenzen, um was es in diesem Buch geht und an wen es gerichtet ist.

Zielgruppe

Dieses Buch richtet sich an Einsteiger in die Programmierung sowie Personen, die bereits erste Erfahrungen mit C++ gemacht haben.

Jedes Thema beginnt mit einer einfachen Einführung und vielen Beispielen, die immer weiter ausgebaut werden, bis auch Details von C++ zur Sprache kommen.

Je nach Vorwissen kann Ihnen eventuell die Einführung zu ausführlich oder die Details zu detailliert erscheinen. Das ist überhaupt kein Problem, überspringen Sie die Abschnitte einfach. Immer, wenn ein bereits im Buch behandeltes Thema an anderer Stelle Anwendung findet, steht ein Querverweis im Text, sodass Sie die tiefer gehenden Abschnitte auch erst bei Bedarf lesen können.

C++14

Das in diesem Buch verwendete C++ entspricht dem C++14-Standard, der bei Drucklegung dieses Buches kurz vor der offiziellen Verabschiedung steht. Die Erweiterungen von C++11 zu C++14 sind aber nur minimal und beziehen sich in vielen Fällen auf fortgeschrittene Themen, die in diesem Buch nicht behandelt werden. Alle Programmcodes im Buch lassen sich sowohl mit einem C++14-Compiler übersetzen als auch mit einem C++11-Compiler. Im Buch wird an vielen Stellen auf den internationalen Standard ISO/IEC 14882-2011 in der Form [C++ #] verwiesen, wobei # für eine (Kapitel-)Nummer steht.

Objektorientierte Programmierung

Natürlich beschäftigt sich das Buch mit der objektorientierten Programmierung (OOP). Nur: Im Gegensatz zu anderen Sprachen wie zum Beispiel Java erzwingt C++ keine objektorientierte Programmierung. Viele Sprachmittel setzen keine Elemente der objektorientierten Programmierung ein oder sind sogar flexibler, wenn sie außerhalb einer Klasse stehen.

Als ehemaliger Java-Programmierer erscheint Ihnen daher die OOP vielleicht zu spät im Buch, als C-Programmierer kommt sie unter Umständen zu früh.

Um das Ganze mit Zahlen zu hinterlegen: Über 40% des Buches handeln ausschließlich von den Mechanismen der Klassen und der Vererbung und weitere 30% setzen diese Mechanismen ein. Trotzdem gebe ich dem Leser die Zeit, die Sprache zunächst mit ihren weniger abstrakten prozeduralen Mechanismen kennenzulernen, bevor wir die höheren Abstraktionsebenen der objektorientierten Programmierung betreten, die in C++ anspruchsvoller sind als in anderen Sprachen.

1.2 Vorstellung des Projekts

Der Mensch lernt am leichtesten und effektivsten spielerisch. Was liegt bei der Erlernung einer Programmiersprache also näher, als ein Computerspiel zu entwerfen?

Die Herausforderung bestand darin, ein Spielkonzept zu entwickeln, das mit reinem ISO-C++ realisiert werden kann, ohne plattformspezifische Bibliotheken einsetzen zu müssen. Grafische Darstellungen fielen damit schon mal weg. Es musste ein Spiel auf Textebene werden. Weil das Genre der Point&Click-Adventure eines meiner liebsten ist, habe ich mich dazu entschieden, ein Spiel im Stile seiner Vorgänger zu programmieren: ein Text-Adventure.

Den gesamten Programmcode des Spiels, zusätzliche Informationen sowie eine plattformunabhängige Java-Version zum direkten Ausprobieren finden Sie auf meiner Homepage unter http://cpp.andrewillms.de.

Ziel des Spiels ist es, aus einem Haus herauszukommen. Dazu muss das Haus erkundet, neue Räume erschlossen und Gegenstände eingesammelt und kombiniert werden. Grundsätzlich gilt: Sie können auch verlieren, entweder direkt, weil Sie beispielsweise einen Stromschlag bekommen haben und deshalb ohnmächtig wurden, bis der Hausbesitzer zurückkommt, oder indirekt, weil Sie sich den Weg verbaut haben, zum Beispiel, weil Sie einen notwendigen Gegenstand zerstört, verloren oder erst gar nicht eingesammelt haben. Die vielen unterschiedlichen und mitunter auch skurrilen Arten des Verlierens machen aber einen Teil des Spielspaßes aus. Sie sollten daher Folgendes beachten:

Regelmäßig Spielstände speichern.

Alle Gegenstände untersuchen und gegebenenfalls benutzen.

Auch verschiedene Aktionen probieren. Es macht oft einen Unterschied, ob Gegenstände benutzt, gezogen, gedrückt, geöffnet oder geschlossen werden.

Erst einmal nichts tun, was Sie auch in Ihrem eigenen Haus unterlassen würden, wie zum Beispiel alles abfackeln. (Es sei denn, es muss sein.)

Um die Programmierung einfach zu halten, ist die Syntax der Eingabemöglichkeiten rudimentär. »gehe treppe«, »verwende haarknäuel + toilette« gelten innerhalb des Spiels als gutes Deutsch. Präpositionen und Artikel sind nicht erlaubt.

Es reicht immer aus, so viel von einem Wort anzugeben, dass es eindeutig ist. Bei den Befehlen können bis auf »Speichern« alle mit nur einem Buchstaben abgekürzt werden. Damit ist »g v« eine gültige Schreibweise für »gehe vor«.

Es ist für das Lesen des Buches nicht notwendig, das Spiel durchgespielt oder überhaupt gespielt zu haben. Es erleichtert aber das Verständnis, wenn Sie die Programmbeispiele den Aktionen im Spiel zuordnen können.

Als grobe Richtschnur gilt: Sie haben einen guten Einblick in das Spiel, wenn Sie es unfallfrei in den Keller geschafft haben. Haben Sie gar den Vorratsraum erreicht, kennen Sie alle programmtechnischen Spielelemente.

1.3 Identifizieren der Programmteile

An dieser Stelle wollen wir das Spiel einer genaueren Betrachtung unterziehen und typische Elemente eines Computerprogramms ausmachen. Diese werden dann in den weiteren Kapiteln im Detail besprochen.

1.3.1 Objekte

Im Spiel springen zwei Elemente direkt ins Auge, die Räume und die darin befindlichen Gegenstände. Solche klar abgegrenzten und unterscheidbaren Entitäten nennt man in der objektorientierten Programmierung Objekte.

Objekte besitzen üblicherweise zu jedem Zeitpunkt einen eindeutigen Zustand. Das Handtuch besitzt zu Beginn den Zustand »In der Toilette befindlich«. Nachdem der Spieler es genommen hat, wechselt der Zustand zu »Im Inventar befindlich«. Das Feuerzeug ist zunächst unsichtbar. Wenn der Spieler die Jacken untersucht, wechselt der Zustand zu »Sichtbar, im Inventar und 25% gefüllt«. Die zustandsbeschreibenden Elemente eines Objekts nennt man Attribute oder Objektdaten.

Die vorangegangenen Beispiele offenbaren eine weitere Eigenschaft von vielen Objekten; die Fähigkeit, ihren Zustand zu verändern. Die zustandsverändernden Elemente eines Objekts werden als Methoden, Nachrichten und speziell in C++ auch als Elementfunktionen bezeichnet.

1.3.2 Kontrollstrukturen

Es reicht aber nicht aus, dass die Objekte einen Zustand besitzen, den sie ändern können. Sie müssen zu dieser Zustandsänderung auch aufgefordert werden, aber nicht unkontrolliert, sondern nur, wenn bestimmte Bedingungen gelten.

Falls der Spieler in der Toilette steht und das Handtuch im Raum liegt und der Spieler »nimm handtuch« als Befehl angibt, genau dann wechselt das Handtuch seinen Zustand zu »Im Inventar befindlich«. Dieses bedingte Abarbeiten von Anweisungen wird in der Programmierung Verzweigung genannt.

Eine andere Form der Kontrollstruktur ist bei der Eingabe des Anwenders involviert. Dort hat der Anwender die Möglichkeit, einen Befehl und betroffene Objekte einzugeben. Gibt er »ende« ein, dann ist das Spiel beendet. In allen anderen Fällen wird der Text zerlegt, der Befehl und die Objekte identifiziert, der Befehl ausgeführt und der Anwender erneut nach einem Befehl gefragt.

Die Anweisung lässt sich so formulieren: Solange der Anwender nicht »ende« eingegeben hat, wird der Befehl ausgeführt und erneut nach einem Befehl gefragt. Diese Art der Kontrollstruktur wird Wiederholung oder Schleife genannt.

1.4 Abstraktion

Ein weiteres Thema bei der Programmierung des Spiels ist der Weg von der Idee zum Programm. In begrenztem Umfang spiegelt das Spiel die reale Welt wider. Genau genommen ist das Spiel eine vereinfachte und abstrahierte Form der Realität.

1.4.1 Datenabstraktion

Bei der Datenabstraktion werden die Attribute, die den Zustand in der realen Welt beschreiben, auf die für das Programm absolut notwendigen Elemente reduziert. So einfach wie möglich, aber nicht einfacher.

Nehmen wir wieder das Handtuch. In der Realität besitzt es eine Breite, eine Länge, eine Dicke und ein Gewicht. Es besitzt eine Farbe, die je nach Qualität nach jedem Waschen mehr oder weniger ausbleicht. Das Handtuch besteht aus 100% Baumwolle mit 50%iger Polyesterbeimischung (frei nach Loriot), es ist mehr oder weniger mit Weichspüler gesättigt und daher weicher oder kratziger. Ich könnte diese Liste bis zum Ende des Buches fortführen.

Im Spiel kann sich das Handtuch nur auf der Toilette befinden oder im Inventar sein, es kann nass oder trocken sein. Mehr muss es nicht können. Die Reduktion aller möglichen Zustände eines realen Handtuchs auf diese beiden Attribute im Spiel nennt man Datenabstraktion.

1.4.2 Algorithmische Abstraktion

Dass die Zustände der Objekte innerhalb des Programms abgebildet werden können, ist aber nur die halbe Miete. Sinnvoll sind Zustände nur, wenn sie sich auch ändern können.

Jedes Programm besitzt einen Startzustand (definiert über die zu Beginn des Programms vorhandenen Objekte und deren Zustände) und einen Endzustand (definiert über die am Ende des Programms vorhandenen Objekte und deren Zustände). Die Regeln, wann und wie sich die Zustände während des Programmlaufs ändern, werden über den Algorithmus definiert.

Nehmen wir exemplarisch die Berechnung des größten gemeinsamen Teilers zweier positiver ganzer Zahlen. Dieser wird unter anderem beim Kürzen von Brüchen verwendet. Der ggT (die Abkürzung für »größter gemeinsamer Teiler«) von 6 und 12 ist beispielsweise 6, der ggT von 6 und 9 ist 3. Konkrete ggT zu bestimmen, fällt nicht sehr schwer.

Schwieriger wird es, eine allgemeingültige Lösung zur Bestimmung des ggT zu formulieren.

Algorithmus

Ein Algorithmus ist eine Menge von Regeln, durch deren Befolgung in festgelegter Reihenfolge ein bestimmtes Problem gelöst wird.

Eine einfache Beschreibung einer Lösung könnte so aussehen:

Der ggT zweier Zahlen kann naturgemäß nicht größer sein als die kleinere der beiden Zahlen. Deshalb beginnen wir mit ihr als potenziellem ggT.

Ist die so gefundene Zahl nicht ggT der beiden Zahlen, dann vermindere sie um 1 und wiederhole diesen Schritt.

Spätestens bei 1 terminiert diese Schleife, denn die 1 ist Teiler von allem.

Um die Lösung zu visualisieren, verwende ich das Aktivitätsdiagramm der UML. Die Unified Modeling Language, kurz »UML«, ist eine grafische Sprache, mit deren Hilfe Sachverhalte der Softwareentwicklung (Programmfluss, Klassen- und Objektbeziehungen, Zustände etc.) dargestellt werden.

In Abbildung 1–1 wird das Aktivitätsdiagramm der UML eingesetzt, um einen Algorithmus grafisch darzustellen.

Abb. 1–1 Der größte gemeinsame Teiler als Aktivitätsdiagramm

Der ausgefüllte Kreis definiert den Start der Aktivität, sie endet am ausgefüllten Kreis mit Ring.

Einzelne Schritte oder Anweisungen werden in Form von Rechtecken mit abgerundeten Ecken dargestellt. Die Abarbeitungsreihenfolge ist über die Pfeile definiert, die aus den Symbolen heraustreten beziehungsweise zu ihnen hin führen.

Das auf der Spitze stehende Quadrat definiert eine Verzweigung. Aus einer Verzweigung tritt immer mehr als ein Pfeil aus. An den Pfeilen stehen in eckigen Klammern die sogenannten Wächter. Die bestimmen, unter welcher Bedingung dem jeweiligen Pfeil gefolgt wird.

Jedem Programm liegt zwangsläufig ein Algorithmus zugrunde. Wir werden im alltäglichen Leben andauernd mit Algorithmen konfrontiert. Alle nach Regeln ablaufenden Tätigkeiten sind gewissermaßen Algorithmen. Typische Algorithmen sind zum Beispiel einen Kuchen backen oder einen Fahrschein aus dem Automaten ziehen. Selbst das Abschließen der Wohnungstür ist ein Algorithmus, wenn auch ein recht primitiver.

Wo immer bestimmte Tätigkeiten ein Problem lösen oder eine Aufgabe bewältigen, haben wir es mit Algorithmen zu tun. Dabei werden Algorithmen in der Weise formuliert, die auch Personen ohne Fachkenntnis das Lösen des Problems ermöglicht. Dies setzt eine Beschreibung mit möglichst klein gehaltenem Vokabular voraus.

Nehmen wir als Beispiel ein Kuchenrezept. Selbst wenn absolut kein Wissen um die Kunst des Kuchenbackens vorliegt, ist man in der Lage, einen Kuchen nach Rezept zu backen, weil sich die Anweisungen einer Sprache bedienen, die jedem Nichtbäcker verständlich ist (z. B. rühren, kneten, in die Backform füllen, Zucker abwiegen).

Und wenn man einen Algorithmus so formuliert, dass ein Computer ihn befolgen kann, dann spricht man von einem Programm.

Programm

Einen Algorithmus, der für den Computer verständlich formuliert wurde, nennt man Programm.

2 Grundelemente eines C++-Programms

In diesem Kapitel legen wir den Grundstein zur Programmierung in C++. Wir schauen uns an, welche Elemente immer in einem C++-Programm vorkommen und wie Texte auf dem Bildschirm ausgegeben werden können.

2.1 Das erste Programm

Schreiben wir nun unser erstes C++-Programm:

int main(){}

Um das Programm übersetzen und starten zu können, sollten Sie in der Entwicklungsumgebung Ihrer Wahl ein neues Projekt anlegen, dort eine C++-Datei hinzufügen (an der Endung .cpp zu erkennen) und dort das obere Programm einfügen.

Das Programm sollte sich fehlerfrei kompilieren und starten lassen, allerdings wird noch nichts passieren.

Wir haben bisher lediglich das Kernstück eines jeden C++-Programms programmiert, die main-Funktion. Jedes C++-Programm muss genau eine main-Funktion besitzen, sie ist der Einstiegspunkt in unser Programm. Jeder Start eines C++-Programms beginnt mit der main-Funktion.

Dem Funktionsnamen folgt ein Paar runder Klammern. Diese Klammern dienen später dazu, Informationen an die Funktion zu übergeben, bleiben aber fürs Erste leer.

Hinter dem Funktionskopf stehen geschweifte Klammern, mit denen in C++ eine zusammengesetzte Anweisung (compound statement) gebildet wird. Alle Anweisungen innerhalb der geschweiften Klammern werden beim Aufruf der Funktion ausgeführt. Da die Klammern bisher leer sind, passiert beim Start auch noch nichts.

2.1.1 Implizites return

Vor main steht immer int, das fordert der ISO-Standard. Ohne an dieser Stelle bereits genauer auf die Datentypen von C++ einzugehen, bedeutet dieses int, dass main immer einen ganzzahligen Wert zurückgibt. Über diesen Wert teilt das Programm der aufrufenden Umgebung mit, ob es fehlerfrei beendet wurde oder nicht. In einigen Fällen wird auch ein Fehlercode zurückgegeben, der den aufgetretenen Fehler genauer spezifiziert.

Der Compiler übersetzt die main-Funktion immer so, dass der zurückgegebene Wert die Bedeutung »alles in Ordnung« hat, und das ist üblicherweise der Wert 0. Das vom Compiler erzeugte Programm sieht damit so aus:

int main(){ return 0;}

Diese automatische Ergänzung mit einer return-Anweisung, falls der Programmierer kein eigenes return programmiert hat, nimmt der Compiler nur bei der main-Funktion vor. In allen anderen Fällen ist der Programmierer dafür verantwortlich, eine geeignete return-Anweisung zu programmieren.

2.2 Die Ausgabe

Um unser erstes C++-Programm aus dem Stadium der Sinnlosigkeit herauszuheben, werden wir nun einen der wichtigsten Aspekte besprechen: die Ausgabe. Schauen wir uns dazu zunächst das erweiterte Programm an:

#include<iostream>int main() { std::cout << "Hello World";}

Auf dem Bildschirm sollte der Text »Hello World« erscheinen, je nach Entwicklungsumgebung noch direkt gefolgt von der Aufforderung, das Programm mit einem Tastendruck zu beenden.

Dieses kleine Beispiel bietet uns bereits die Möglichkeit, einige grundlegende Dinge von C++ zu besprechen.

Einfache Anweisungen werden in C++ mit einem Semikolon abgeschlossen.

Und noch eine Regel ist wichtig:

Konstante Zeichenfolgen stehen in C++ in Anführungszeichen.

Darüber hinaus muss in C++ penibel auf Groß- und Kleinschreibung geachtet werden. Der Name »Andre« und der Name »andre« sind zwei unterschiedliche Bezeichner.

2.2.1 cout

Der Befehl zur Ausgabe auf die Konsole heißt in C++ cout. Warum davor noch ein std:: steht, besprechen wir gleich.

cout ist der sogenannte Standardausgabe-Stream beziehungsweise das Objekt, das der Abstraktion wegen für den Standardausgabestrom steht. Dadurch wird der Programmierer nicht mehr mit den plattformspezifischen Eigenarten der Ausgabe belastet. Er gibt die auszugebenden Daten einfach an das cout-Objekt und dieses sorgt dann für eine ordnungsgemäße Ausgabe. Als Standardausgabe wird im Allgemeinen der Bildschirm verwendet.

Der <<-Operator schiebt bildlich gesprochen die Daten in den Ausgabestrom. In diesem Fall handelt es sich bei den auszugebenden Daten um eine Stringkonstante.

Stringkonstante

Stringkonstante ist eine in doppelten Anführungszeichen stehende Folge von Zeichen, die implizit mit dem Wert 0 ('\0') abgeschlossen wird.

Gehen wir den Ablauf des Programms einmal schrittweise durch.

Wenn Sie das Programm kompilieren und starten, wird zuerst die Funktion main aufgerufen. Die erste Anweisung ist die Anweisung, die die auszugebenden Daten an das cout-Objekt schickt, also in den Standardausgabe-Stream schiebt. cout gehört zur Standardbibliothek von C++.

Nachdem die auszugebenden Daten zu cout geschickt wurden, fährt das Programm hinter dem Semikolon der Anweisung fort. Dort ist aber nur das Ende der Funktion main, was dem Ende des gesamten Programms gleichkommt.

Sie werden sich vielleicht gewundert haben, dass im Programmtext die cout-Anweisung (und vorher auch schon die return-Anweisung) nach rechts eingerückt ist. Dies ist nicht notwendig, dient aber der Übersichtlichkeit. Wenn Sie die gleiche Einrückung wie hier im Buch verwenden möchten, dann sollten Sie die Abstände auf zwei Zeichen einstellen.

Zeilenumbruch

Um bei der Ausgabe eine neue Zeile zu beginnen, reicht es nicht aus, eine zweite Ausgabe zu tätigen:

#include<iostream>int main() { std::cout << "Hello World!"; std::cout << "Jetzt komme ich!";}

Stattdessen muss an der Stelle, an der die neue Zeile beginnen soll, ein Zeilenumbruch in die Zeichenfolge eingefügt werden. Dies geschieht in Form einer Escape-Sequenz. Tabelle 2–1 listet alle in C++ verfügbaren Escape-Sequenzen auf. An dieser Stelle wollen wir die Escape-Sequenz für Newline einsetzen, sie lautet \n.

Das Programm mit Zeilenumbrüchen sieht damit so aus:

#include<iostream>int main() { std::cout << "Hello World!\n"; std::cout << "Jetzt komme ich!\n";}

endl

Eine weitere Möglichkeit, einen Zeilenumbruch zu erhalten, ist der Manipulator endl, der über den Ausgabestrom ausgegeben wird:

#include<iostream>int main() { std::cout << "Hello World!"; std::cout << std::endl; std::cout << "Jetzt komme ich!"; std::cout << std::endl;}

Manipulator

Als Manipulator bezeichnet man ein Objekt, das über den Ausgabestrom ausgegeben wird und das Verhalten des Stroms manipuliert.

Das endl macht aber noch mehr, als einen Zeilenumbruch zu erzeugen. Dazu müssen wir uns anschauen, wie die Ausgabe funktioniert.

Statt direkt auf dem Bildschirm ausgegeben zu werden, landen die Ausgaben zunächst einmal in einem internen Speicherbereich, dem Ausgabepuffer. Erst wenn dieser Puffer voll ist, wird er auf dem Bildschirm ausgegeben. Unter Umständen werden Ausgaben deshalb nicht sofort angezeigt, weil der Puffer einfach noch nicht voll ist.

Dieses Problem vermeidet endl, denn mit der Ausgabe von endl wird zusätzlich ein flush ausgeführt. Dieser Flush (aus dem Englischen »to flush«, was unter anderem die Bedienung der Toilettenspülung bedeutet) sorgt dafür, dass der Inhalt des Ausgabepuffers auf den Bildschirm »gespült« wird, auch wenn er noch nicht komplett gefüllt war.

Das endl ist aber nicht immer notwendig. Vor einer Eingabe oder am Programmende wird der Ausgabepuffer immer geleert, unabhängig von dessen Füllstand.

2.3 include

Mit der Ausgabe hat noch ein weiterer Befehl Einzug in unser Programm gehalten: die include-Direktive des Präprozessors:

#include<iostream>

Der Präprozessor – wie die Silbe »Prä« erahnen last – durchläuft die Datei vor dem eigentlichen Prozess des Kompilierens. Aber was genau bedeutet »kompilieren«? Abbildung 2–1 zeigt den Vorgang.

Abb. 2–1 Der Vorgang des Kompilierens

C++ ist eine Hochsprache, die vom Prozessor des Computers nicht verstanden wird, denn dieser kennt nur seine Maschinensprache. Maschinensprache ist eine sehr einfache, aus wenigen Befehlen bestehende Sprache. Entsprechend viele dieser Befehle sind notwendig, um selbst einfachste Dinge zu programmieren. Eine typische Szene könnte sein:

Lade Wert an Adresse $92E2 in Register1. Addiere Wert an Adresse $92E6 auf Register1. Speichere Inhalt von Register1 an Adresse $92EA.

Dasselbe in C++ wäre etwa x=a+b – viel kürzer und vor allem für einen Menschen viel verständlicher.

Damit also ein in C++ geschriebenes Programm auf einem Rechner laufen kann, muss es in die Maschinensprache des Prozessors übersetzt werden. Und diesen Vorgang nennt man »kompilieren«.

Kompilieren

Das Übersetzen eines Hochsprachenprogramms in die Maschinensprache des Zielprozessors bezeichnet man als Kompilieren. Der Übersetzer wird Compiler genannt.

In C++ besteht dieser Übersetzungsprozess aus mehreren Schritten. Bevor eine cpp-Datei dem Compiler übergeben wird, durchläuft sie der Präprozessor, der nach an ihn gerichteten Befehlen sucht. Der Präprozessor selbst versteht C++ nicht, er arbeitet rein auf Textebene.

Präprozessordirektive

Diese an den Präprozessor gerichteten Befehle beginnen mit einem # in der ersten Spalte. Präprozessordirektiven dürfen nicht nach rechts eingerückt werden.

Befehle an den Präprozessor, sogenannte Präprozessordirektiven, beginnen immer mit einem »#«. Der wohl häufigste Befehl ist #include, was übersetzt so viel wie »Einbinden« bedeutet. Mithilfe dieses Befehls kann eine andere Datei in die Quellcodedatei eingebunden werden. In unserem Fall binden wir die Datei »iostream« ein, in der alle für die Textein- und -ausgabe notwendigen Elemente der C++-Standardbibliothek enthalten sind, unter anderem das in unserem Programm verwendete cout und endl. Würden wir die Include-Direktive aus dem Programm entfernen, käme bei der Kompilation die Fehlermeldung des Compilers, er würde cout und endl nicht kennen.

Abbildung 2–1 zeigt noch eine weitere Besonderheit von C++: Jede Quellcodedatei des Programms wird isoliert von den anderen kompiliert, der Compiler hat auch keinerlei Erinnerungen an sein Tun.

Für das Beispiel in der Abbildung heißt das: Während er die Datei »Quell-code1.cpp« kompiliert, weiß er nicht, dass er noch die Datei »Quellcode2.cpp« kompilieren wird. Und während er die Datei »Quellcode2.cpp« kompiliert, weiß er nicht, dass er die Datei »Quellcode1.cpp« bereits kompiliert hat.

Die einzeln kompilierten Dateien werden im letzten Schritt vom Linker (auf Deutsch so viel wie »Binder«) zu einer einzigen Datei zusammengebunden, die dem lauffähigen Programm entspricht.

2.4 Namensbereiche

Bleibt noch zu klären, warum vor cout und endl dieses std:: steht.

Bildlich betrachtet ist std vergleichbar mit einer Vorwahl. Stellen Sie sich vor, es gäbe keine Vorwahlen. Dann müsste die Vergabe von Telefonnummern global geregelt werden, schließlich dürfen Teilnehmer in Köln und Timbuktu nicht dieselbe Telefonnummer bekommen. Ländervorwahlen lösen das Problem, denn jedes Land kann hinter seiner Vorwahl die Telefonnummern nach eigenen Regeln vergeben. Jetzt dürfen auch Teilnehmer in München und Rom dieselbe Nummer besitzen, denn sie unterscheiden sich in der Vorwahl.

Dieses Prinzip nennt sich in C++ Namensbereich. Ein Namensbereich ist nichts anderes als eine programmiertechnische Vorwahl, hinter der Namen beliebig vergeben werden können. Der Namensbereich der C++-Standardbibliothek lautet std als Abkürzung von »Standard«.

In C++ kann eine Gruppe von Namen (das können Namen von Konstanten, Funktionen, Klassen etc. sein) zu einem Namensbereich zusammengefasst werden. Ganz konkret gehört die Definition von cout zum Namensbereich std.

Die Namensbereiche wurden eingeführt, um die Möglichkeit einer Doppelbenennung verhindern zu können. Man ist dadurch in der Lage, sein eigenes cout zu definieren, wenn man es einem anderen Namensbereich zuordnet.

using namespace

Bei den Telefonnummern gibt es eine Besonderheit: Möchte ich einen Teilnehmer mit derselben Vorwahl wie meine eigene Telefonnummer anrufen, dann muss ich die Vorwahl nicht mit wählen.

Etwas Ähnliches existiert auch in C++: Wir können dem Compiler mitteilen, dass er in bestimmten Namensbereichen automatisch suchen soll. Auf diese Weise können wir uns die explizite Angabe von std sparen, wenn wir ein Element der Standardbibliothek ansprechen möchten.

Der Befehl dazu lautet using namespace:

#include<iostream>using namespace std;int main() { cout << "Hello World!"; cout << endl; cout << "Jetzt komme ich!"; cout << endl;}

Man spricht auch davon, einen Namensbereich global verfügbar zu machen. Die Elemente des Namensbereichs lassen sich dann ansprechen, als ständen sie überhaupt nicht in einem Namensbereich.

In unserem Beispiel brauchen wir dann bei Namen, die im Namensbereich std definiert sind, nicht mehr explizit angeben, dass wir die Definition aus std verwenden wollen. Elemente aus anderen Namensbereichen müssen weiterhin explizit mit ihrem Namensbereich angegeben werden. Es können aber mehrere using namespace-Anweisungen verwendet werden, falls weitere Namensbereiche global verfügbar gemacht werden sollen.

Der Einsatz von using namespace spart eine Menge Tipparbeit und gestaltet das Programm übersichtlicher. Und wo wir gerade bei dem Thema »Tipparbeit sparen« sind: Bei der Ausgabe lässt sich der Operator << auch verketten:

#include<iostream>using namespace std;int main() { cout << "Hello World!" << endl; cout << "Jetzt komme ich!" << endl;}

Das erste endl ist eigentlich unnötig, weil es an dieser Stelle nur um einen Zeilenumbruch geht und nicht um das Leeren des Ausgabepuffers. Wir könnten es daher auch mit der Escape-Sequenz \n ersetzen:

#include<iostream>using namespace std;int main() { cout << "Hello World!\nJetzt komme ich!" << endl;}

Wie eigene Namensbereiche erstellt werden können, besprechen wir in Kapitel 13.

2.5 Kommentare

Nachdem uns nun das erste Programm komplett verständlich ist, wollen wir in diesem Abschnitt die Möglichkeit besprechen, Kommentare in das eigentliche Programm einfügen zu können.

Kommentare dienen dazu, Informationen im Programm unterzubringen, die nicht Bestandteil des tatsächlichen Programms sind und auch vom Compiler nicht verstanden werden könnten.

So können Sie zum Beispiel hinter bestimmten Programmzeilen Bemerkungen schreiben, damit Sie auch später noch wissen, welche Funktion sie haben. Oder Sie können vor jedem größeren Abschnitt ein paar Zeilen über seine Funktion schreiben. Obwohl immer wieder manche Programmierer behaupten, ein Programmtext sei selbsterklärend und nur unfähige Leute bräuchten Kommentare, sollten Sie sich diesbezüglich nicht einschüchtern lassen und nie mit Kommentaren geizen. Sie tun sich und anderen, die Ihr Programm einmal verstehen müssen, einen großen Gefallen.

Mehrzeilige Kommentare

Mehrzeilige Kommentare beginnen mit den Zeichen /* und werden mit */ beendet. Alles, was zwischen diesen Zeichen steht, wird vom Compiler ignoriert.

Achtung

Mehrzeilige Kommentare dürfen nicht verschachtelt werden!

Deshalb ist das folgende Konstrukt ungültig:

/* Innerhalb dieses Kommentares ist /* ein Kommentar */ eingebettet */

Einzeilige Kommentare

Benötigen Sie für Ihren Kommentar nur maximal eine Zeile (z.B. um eine kurze Bemerkung hinter eine Anweisung zu schreiben), dann verwenden Sie //.

Wenn Sie die beiden Formen der Kommentare mischen, dann sind auch Verschachtelungen möglich:

// /* Kommentar *//* // Kommentar */

Schauen wir uns als Beispiel unser Programm mit Kommentaren versehen an:

/*** Programm: Einführungsbeispiel zu C++** Autor: André Willms** Letzte Änderung: 03.01.2015*/#include<iostream> /* Einbinden von iostream */using namespace std; // std global verfügbar machen// Hauptfunktionint main() {// Textausgabe cout << "Hello World!\nJetzt komme ich!" << endl;}

Wenn Sie sich die Kommentare einmal genau ansehen, müssten Sie sich fragen, warum hinter der Präprozessordirektive die Syntax der mehrzeiligen Kommentare verwendet wurde, obwohl der Kommentar nur eine Zeile lang ist.

Tipp

Benutzen Sie hinter Präprozessordirektiven nur /* ... */-Kommentare.

Dafür gibt es eine einfache Erklärung. Die /* ... */-Kommentare existierten auch schon in C, wohingegen die //-Kommentare eine Neuerung von C++ sind. Auch den Präprozessor gab es bereits in der Programmiersprache C, weswegen er bei vielen C++-Compilern nicht neu programmiert, sondern einfach von einem C-Compiler übernommen wurde. Dadurch kann es passieren, dass ein Präprozessor den neuen C++-Kommentar nicht versteht und einen Fehler meldet.

2.6 Escape-Sequenzen

Der Vollständigkeit halber möchte ich Ihnen am Ende dieses Kapitels noch die restlichen in C++ verfügbaren Escape-Sequenzen vorstellen.

Escape-Sequenz

Zeichen

\'

Das Zeichen '

\"

Das Zeichen "

\?

Das Zeichen ?

\\

Das Zeichen \

\a

BEL (bell), akustisches Warnsignal. Ob es hörbar ist, hängt vom Rechner ab.

\b

BS (backspace), Cursorposition ein Zeichen nach links (Zeichen wird gelöscht)

\f

FF (formfeed), Seitenvorschub

\n

NL (new line), Cursorposition wird auf Anfang der nächsten Zeile gesetzt.

\r

CR (carriage return), Cursorposition wird auf Anfang der aktuellen Zeile gesetzt.

\t

HT (horizontal tab), nächste horizontale Tabulatorposition

\v

VT (vertical tab), nächste vertikale Tabulatorposition

\aaa

Zeichencode aaa in oktaler Schreibweise

\xaa

Zeichencode aa in hexadezimaler Schreibweise

Tab. 2–1 Die Escape-Sequenzen von C++

2.7 Zusammenfassung

Was wir in diesem Kapitel gelernt haben:

Die Hauptfunktion eines C++-Programms heißt main (Das erste Programm).

Mit cout werden Ausgaben auf dem Bildschirm getätigt (Die Ausgabe).

Über den Befehl #include lassen sich andere Dateien einbinden (include).

Die Elemente der C++-Standardbibliothek stehen im Namensbereich std (Namensbereiche).

Es gibt einzeilige ( // ) und mehrzeilige (/* … */) Kommentare (Kommentare).

2.8 Spielprojekt

Mit dem Wissen dieses Kapitels können wir unser Spiel noch nicht sehr weit voranbringen. Aber die Startmeldung können wir bereits programmieren:

#include <iostream>using namespace std;int main() { cout << "\nTextadventure-Engine V1, " << "programmiert in C++ von Andre Willms\n" << endl;}

3 Arithmetik in C++

In diesem Kapitel besprechen wir die ersten Elemente, die wir zur Programmierung des Spiels brauchen.

Wir haben in der Einführung gelernt, dass Objekte Zustände besitzen. Diese Zustände müssen im Programm gespeichert und gelesen werden können. Dazu werden wir uns mit dem Prinzip der Variablen beschäftigen.

Diese Zustände müssen aber auch geändert werden können. Dies geschieht mithilfe von Operatoren, von denen wir einige in diesem Kapitel kennenlernen werden.

3.1 Variablen

Auch die interessanteste Textausgabe wird irgendwann langweilig. Wir wollen daher im weiteren Verlauf unsere Kenntnisse dahin entwickeln, auch Daten einlesen zu können.

Dazu müssen wir die Daten in unserem Programm speichern können. Dabei spielt es keine Rolle, woher die Daten kommen. Ob sie aus einer Datenbank, einer Datei, von der Tastatur des Anwenders oder von einem gedrückten Knopf an einer Spielkonsole stammen, zu dem Zeitpunkt, zu dem unser Programm mit ihnen arbeiten soll, müssen sie im Arbeitsspeicher des Computers gespeichert sein.

Wir brauchen also Arbeitsspeicher. Zum Glück müssen wir uns den nicht manuell organisieren, denn das ist mit den modernen Multitasking-Betriebssystemen kein Pappenstiel. Hochsprachen bieten einen einfachen Mechanismus zur Beschaffung von Arbeitsspeicher: die Variablen.

Programmiertechnisch gibt es bei den Variablen verschiedene Ansätze. Sprachen wie beispielsweise PHP oder Perl besitzen nicht typisierte Variablen, bei denen es keine Rolle spielt, von welchem Typ die in ihnen gespeicherten Daten sind. Vergleichbar mit einer Gewürzdose in der Küche, die manchmal Pfeffer, manchmal aber auch Salz oder Desinfektionsmittel enthält, je nach Bedarf. Das bietet hohe Flexibilität im Einsatz, aber es liegt in der Verantwortung des Kochs, den Überblick über den aktuellen Inhalt zu behalten, wenn er einen Aufstand der Restaurantgäste vermeiden möchte.

Sprachen wie C++ dagegen sind typisiert. Sobald eine Variable angelegt wird, muss dem Compiler mitgeteilt werden, von welchem Typ die Daten sind. Und von da an wacht der Compiler darüber, dass in dieser Variablen keine anderen Typen abgelegt werden. Im Bild der Gewürzdose hätte diese jetzt ein Etikett, auf dem das enthaltene Gewürz geschrieben steht. Bei dem Versuch, ein anderes Gewürz einzufüllen, gäbe es von der Küchenhilfe eins mit dem Löffel.

3.1.1 Integrierte Datentypen

Für einen ersten Überblick der verfügbaren Datentypen betrachten wir zunächst Tabelle 3–1 mit den fundamentalen Datentypen von C++:

Name

Abkürzung

Größe

Suffix

bool

true, false

char

Implementierungsabhängig, mind. 1 Byte

wchar_t

Implementierungsabhängig, mind. 1 Byte

L

char16_t

mind. 2 Byte

u

char32_t

mind. 4 Byte

U

short int

short

≥ char

l

int

≥ short int

l

long int

long

≥ int

l, L

long long int

long long

≥ long int

ll, LL

float

Implementierungsabhängig

f, F

double

≥ float

long double

≥ double

l, L

Tab. 3–1 Die integrierten Datentypen von C++

Für die wenigsten dieser Datentypen definiert der C++-Standard eine feste Größe. Die meisten Größen sind implementierungsspezifisch oder stehen in Relation zu den Größen anderer Datentypen.

Die Datentypen in der Tabelle von bool bis long long int werden integrale Typen genannt und können nur ganze Zahlen aufnehmen. Die Datentypen float, double und long double können auch Werte mit Nachkommastellen speichern, man bezeichnet sie daher als Gleitkommatypen. Die integralen und die Gleitkommatypen zusammen (plus der noch später zu besprechenden Zeiger und Referenzen) bilden die Gruppe der integrierten Datentypen. Weitere Einzelheiten zu den Datentypen aus Tabelle 3–1 finden Sie im weiteren Verlauf dieses Kapitels.

3.1.2 Vorzeichenlose Typen

Die ganzzahligen Datentypen (short int bis long long int) sind standardmäßig vorzeichenbehaftet, sie können also positive und negative Ganzzahlen aufnehmen. Um diesen Sachverhalt explizit auszudrücken, könnten wir das Schlüsselwort signed vor den Datentyp setzen (z. B. signed int), aber das ist unüblich.

Interessanter wird es, wenn wir Datentypen ohne negativen Bereich haben möchten. Das machen wir mit dem Schlüsselwort unsigned kenntlich. Wir haben damit vier mögliche vorzeichenlose Datentypen (unsigned short int (abgekürzt unsigned short), unsigned int (abgekürzt unsigned), unsigned long int (abgekürzt unsigned long) und unsigned long long int (abgekürzt unsigned long long).

Die Gleitkommatypen sind immer vorzeichenbehaftet.

3.2 Definieren einer Variablen

Greifen wir uns den ganzzahligen Datentyp int heraus und schauen wir uns an, wie damit eine Variable definiert wird:

int wert1;

Es wird einfach der gewünschte Datentyp mit einem frei erfundenen Namen angegeben. Dazwischen muss mindestens ein Leerzeichen stehen. Eine Anweisung wird in C++ immer mit einem Semikolon beendet.

Namen werden in der Programmierung »Bezeichner« genannt und müssen – vereinfacht dargestellt – in C++ folgendermaßen aufgebaut werden [C++ 2.11]:

Das erste Zeichen muss ein Groß- oder Kleinbuchstabe oder ein Unterstrich sein.

Alle weiteren Zeichen dürfen Großbuchstaben, Kleinbuchstaben, Unterstriche und Ziffern sein.

Groß- und Kleinschreibung wird unterschieden.

Schlüsselwörter der Sprache sind als eigene Bezeichner verboten.

Nach der Definition einer solchen Variablen (sie wird als lokale Variable bezeichnet) besitzt sie einen undefinierten Wert, gebildet aus den Daten, die ursprünglich in dem von der Variable belegten Speicher standen. Wie der Compiler auf diesen Sachverhalt reagiert (mit einer Warnung oder einem Fehler oder schlichtweg mit Ignoranz), hängt von der Implementierung des Compilers ab.

Wir können die Reaktion des Compilers testen, indem wir die uninitialisierte Variable ausgeben:

cout << wert1 << endl;

Variablen werden in C++ genauso ausgegeben wie konstante Zeichenketten; sie werden mit << in den Ausgabestrom cout geschoben. Voraussetzung dafür ist wie bei den vorigen Beispielen das Einbinden von iostream mit #include und die entsprechende using namespace-Anweisung.

3.2.1 Initialisierung

Prinzipiell sollte das Arbeiten mit nicht initialisierten Variablen vermieden werden. Über eine Zuweisung speichern wir den gewünschten Wert in der Variablen. Hier ein komplettes Beispiel:

Initialisierung

Das erstmalige Zuweisen eines Wertes an eine Variable wird Initialisierung genannt und sollte immer vor einem Zugriff auf die Variable erfolgen.

Die Initialisierung kann auch direkt mit der Variablendefinition erfolgen:

Eine andere Möglichkeit ist der Einsatz des Listeninitialisierers:

int wert1{42};

Der Listeninitialisierer unterscheidet sich von der initialisierenden Zuweisung dadurch, dass er keine einschränkenden Typumwandlungen erlaubt.

Die Zuweisung von x an wert1 ist in C++ erlaubt. Wegen der unterschiedlichen Größe der Datentypen (long long ist größer als int) kann diese Zuweisung bei zu großen Werten aber gefährlich sein. Deshalb warnen manche Compiler davor.

Die Initialisierung von wert2 über den Listeninitialisierer erzeugt einen Fehler, weil der Datentyp im Wertebereich eingeschränkt würde und Einschränkung bei der Listeninitialisierung nicht erlaubt ist.

Betrachten Sie folgende Anweisung:

float pi{ 3.14 }; // Fehler

Um den Fehler zu verstehen, muss man wissen, dass in C++ kein Wert ohne Datentyp existiert. In oberen Fall muss auch 3.14 (in C++ wird bei Gleitkommazahlen ein Punkt anstatt eines Kommas geschrieben) einen Datentyp haben, und der ist bei Gleitkommawerten automatisch double.

Um dem Wert (auch Literal genannt) einen anderen Datentyp mitzugeben, müssen wir die in Tabelle 3–1 aufgeführten Suffixe verwenden. Das Suffix für float ist f oder F:

float pi{ 3.14f }; // Korrekt

3.2.2 Lebensdauer

Eine Variable, die wie hier erklärt innerhalb eines Anweisungsblocks (in unserem Fall innerhalb der geschweiften Klammern von main) definiert wurde, besitzt eine automatische Speicherdauer. Das bedeutet, die Variable existiert vom Zeitpunkt ihrer Definition an bis zum Verlassen des Anweisungsblocks, in dem sie definiert wurde.

3.2.3 Automatische Typbestimmung

C++ bietet zwei Möglichkeiten der automatischen Typbestimmung.

auto

Der Befehl auto erlaubt die Bestimmung des Datentyps einer Variablen anhand des Datentyps des zugewiesenen Ausdrucks:

Zur Klarstellung: auto bedeutet nicht, dass eine Variable unterschiedliche Datentypen speichern kann. Der Datentyp der mit auto definierten Variablen wird lediglich über den Datentyp des zugewiesenen Ausdrucks bestimmt. Diesen automatisch bestimmten Datentyp besitzt die Variable dann für ihre gesamte Lebensdauer.

Beachten Sie, dass auto in Kombination mit dem Listeninitialisierer in den meisten Fällen ein unerwünschtes Ergebnis liefert:

auto f{ 42 }; // f ist NICHT int

Im oberen Beispiel ist f vom Typ der Initialisierungsliste und nicht int.

decltype

Eine weitere Möglichkeit der Typbestimmung bietet decltype, der den deklarierten Typ (declared type) eines Ausdrucks ermittelt. Mit dem über decltype ermittelten Typ lassen sich weitere Deklarationen durchführen:

int x;

decltype(x) y;

Die Variable y bekommt denselben Datentyp wie x. Mit decltype kann der Datentyp eines beliebigen Ausdrucks bestimmt werden:

Die Addition von einem int und einem double liefert den Datentyp double. Deshalb ist c vom Datentyp double.

Auch Funktionsaufrufe sind bei decltype erlaubt.

3.2.4 Definitionen zusammenfassen

Werden mehrere Variablen desselben Typs auf einen Schlag benötigt, dann kann man dies mit einer Anweisung erledigen:

Die zu definierenden Variablen werden mit Komma getrennt.

3.3 Eingabe

Analog zu cout können Daten von der Tastatur mit cin eingelesen werden. Der dafür notwendige Operator lautet >>. Auch hier kann man sich den Operator gut merken, wenn man sich vorstellt, die Daten werden von cin in die Variable geschoben. Schauen wir uns dazu ein Beispiel an:

#include<iostream>using namespace std;int main() { cout << "Wie alt bist du:"; int alter; cin >> alter; cout << "Wow, du bist schon " << alter << " Jahre alt!" << endl;}

Die Variable alter wurde bei ihrer Definition nicht mit einem Initialisierungswert versehen, weil sie gleich im Anschluss den vom Anwender eingegebenen Wert aufnimmt.

Die letzte cout-Anweisung wurde aus Platzgründen auf zwei Zeilen verteilt. Da vor und hinter Operatoren Leerzeichen und Zeilenumbrüche eingefügt werden können, ist dies problemlos möglich. Bei einem Bildschirm mit größerer Breite als dem Buch kann die Anweisung auch ohne Einbußen in eine Zeile geschrieben werden.

Die Eingabe mit cin kann auch verkettet werden:

#include <iostream>using namespace std;int main(){ int a,b; cout << "Bitte zwei Werte eingeben:"; cin >> a >> b; cout << "Sie haben " << a << " und "; << b << " eingegeben." << endl;}

Standardmäßig betrachtet cin Whitespaces (wie Leerzeichen, Tabulator, Newline etc.) als Trenner zwischen Eingaben. Sie können die vom Programm geforderten Werte daher auf zwei Arten eingeben:

1. Sie geben den ersten Wert ein und drücken die Enter-Taste. Danach geben Sie den zweiten Wert ein und drücken wieder die Enter-Taste. Dass kein Text erscheint, nachdem Sie den ersten Wert eingegeben haben, ist verständlich.

2. Sie geben den ersten Wert und den zweiten Wert hintereinander durch ein Leerzeichen getrennt ein und drücken die Enter-Taste.

Meist wird cin auf diese Art nur eingesetzt, wenn aus einem Datenstrom gelesen oder eine komplexere Eingabe in einzelne Teile zerlegt werden soll. Für den Anwender ist die folgende Variante auf jeden Fall anwendungsfreundlicher:

#include <iostream>using namespace std;int main(){ int a,b; cout << "Bitte einen Wert eingeben:"; cin >> a; cout << "Bitte noch einen Wert eingeben:"; cin >> b; cout << "Sie haben " << a << " und "; cout << b << " eingegeben." << endl;}

3.4 Grundrechenarten

Mit der Möglichkeit der Ein- und Ausgabe von Daten können wir uns jetzt an die Datenverarbeitung wagen, die fürs Erste auf die Grundrechenarten beschränkt bleiben wird.

Operation

Operator

Kombinierte Zuweisung

Addition

+

+=

Subtraktion

-

-=

Multiplikation

*

*=

Division

/

/=

Ganzzahliger Rest

%

%=

Tab. 3–2 Die Grundrechenarten

Bei der Division ganzzahliger Werte wird immer in Richtung 0 gerundet, was einem Abschneiden der Nachkommastellen entspricht [C++ 5.6]. Dies ist eine Änderung ab C++11 gegenüber dem vorhergehenden Standard (C++2003), der bei positiven Werten Richtung 0 und bei negativen Werten Richtung negativ unendlich gerundet hat.

Wird bei den Operatoren / und % ein Divisor mit dem Wert 0 verwendet, dann ist das Verhalten undefiniert. Pauschal gilt aber wie in der Mathematik die Regel, Divisionen durch 0 sind zu vermeiden.

x=a+b+4;

Die obige Anweisung bildet die Summe der Inhalte von a und b sowie 4 und weist das Ergebnis der Variablen x zu. Dabei gilt grundsätzlich folgende Regel:

Funktionsweise des Zuweisungsoperators

Zuerst wird der Ausdruck rechts vom Zuweisungsoperator komplett berechnet und dann das Ergebnis der Variablen links vom Zuweisungsoperator zugewiesen.

Sie können die Rechenoperatoren in allen möglichen Kombinationen verwenden. Die Operatoren * und / binden stärker als + und -. Das heißt, 3+4*5 ergibt 23 und nicht 35.

Möchten wir jedoch zuerst die Summe 3+4 bilden und dann mit 5 multiplizieren, dann müssen wir den Ausdruck klammern.

x=(3+4)*5;

Schauen wir uns ein komplettes Beispiel zu den Rechenoperatoren an:

#include<iostream>using namespace std;int main() { cout << "Wie viele Euro hast du in der Tasche:"; double euro; cin >> euro; double dm=euro*1.96; cout << "Das waren mal etwa " << dm << " Mark." << endl;}

Wenn der Umrechnungskurs öfter im Programm gebraucht wird, bietet sich der Einsatz einer Konstanten an.

3.5 Konstanten

Von allen Datentypen können nicht nur Variablen, sondern auch Konstanten definiert werden. Wie der Name schon sagt, lässt sich der Wert einer Konstanten nachträglich nicht mehr ändern; er ist konstant.

C++ unterscheidet zwei Arten von Konstanten.

3.5.1 Konstante Ausdrücke

Die stärkste Form sind Konstanten, deren Wert einmal zur Kompilationszeit bestimmt wird und der dann für das kompilierte Programm immer gleich bleibt. Die Definition einer solchen Konstanten erfolgt wie die einer Variablen, nur dass constexpr davor gesetzt wird:

constexpr double dm_euro_kurs=1.95583;

Konstanten müssen zusammen mit ihrer Definition auch initialisiert werden, weil sie danach konstant sind und eine Wertänderung nicht mehr möglich ist.

Der Vorteil von Konstanten ist ihr (hoffentlich aussagekräftig gewählter) Name. Eine Abwandlung des bekannten Sprichworts lautet: »Eine Konstante sagt mehr als 1000 Werte.«

Wenn irgendwo beispielsweise der Wert 6,673 × 10-11 steht, dann kann das alles bedeuten, vielleicht auch nur den Diäterfolg des Nachbarn im letzten Monat. Hätte dort stattdessen gravitationskonstante gestanden, wäre die Bedeutung klar gewesen:

Und da haben wir auch gleich ein schönes Beispiel für die Exponentialschreibweise von Werten in C++.

Es gibt noch einen weiteren Vorteil von Konstanten: Sollte sich ihr Wert einmal ändern (was für die Gravitationskonstante natürlich niemand hofft), dann kann der Wert im Programm zentral an einer einzigen Stelle geändert werden. Ohne den Einsatz einer Konstanten hätte jedes Vorkommnis des Wertes im Programm gesucht und ersetzt werden müssen. Aber nur, wenn es die Gravitationskonstante war und nicht der Diäterfolg des Nachbarn.

Darüber hinaus lässt sich ein Wert, der bereits bei Erstellung des Programms ermittelt und ab dann unverändert bleibt, auch dort speichern, wo Änderungen technisch nicht mehr möglich sind, zum Beispiel im ROM eines Mikrocontrollers.

Sollte Ihr Compiler constexpr noch nicht kennen, dann verwenden Sie stattdessen das im nächsten Abschnitt besprochene const.

3.5.2 Unveränderliche Werte

Eine schwächere Form der Konstante ist das Versprechen, dass eine Variable nach ihrer Initialisierung nicht mehr geändert werden kann. Der Wert, mit dem die Konstante initialisiert wird, darf aber durchaus variieren:

Unter der Voraussetzung, dass get_monat den aktuellen Monat (1-12) liefert, speichern wir diesen in der Konstanten monat, die ab dann nicht mehr verändert werden kann:

Im Unterschied zu konstanten Ausdrücken kann die Konstante monat, obwohl während ihrer Lebensdauer unveränderlich, beim nächsten Programmstart einen anderen Wert enthalten (spätestens mit Beginn des nächsten Monats). Deshalb müssen solche Konstanten in einem Speicherbereich abgelegt werden, der veränderlich ist, auch wenn es die Konstante während ihrer Lebensdauer nicht ist.

3.6 Restwert

Mit dem Operator %, Modulo-Operator oder auch Restwertoperator genannt, kann bei ganzzahligen Variablen der Rest einer Division bestimmt werden:

In der Mitte durchgesägte Spielzeugautos rufen bei den wenigsten Kindern Begeisterungsstürme hervor, die Eltern sind daher gut beraten, jedem Kind gleich viele Autos zu geben und den Rest unter Verschluss zu halten. Das obere Beispiel löst die Problematik mit der Bestimmung des Restes einer Division über den Modulo-Operator und unter Zuhilfenahme der Eigenschaft ganzzahliger Datentypen, bei der Division nur ganze Werte zu liefern.

3.7 Verknüpfen unterschiedlicher Datentypen

Manchmal kann es aber durchaus sinnvoll sein, Dinge wirklich in der Mitte zu teilen. Sollen etwa Kuchen auf Personen aufgeteilt werden, entbehrt es jeden Sinn, nur ganze Kuchen zu verteilen. Wir könnten also einen ähnlichen Ansatz wählen wie bei den Spielzeugautos, nur dass jetzt als Ergebnis ein Gleitkommatyp (double) verwendet wird:

Der auf den ersten Blick vielleicht nicht direkt erkennbare Fehler steckt in der Division.

Die Idee, die Variable kuchen_pro_verwandter als double, also als einen Typ mit Nachkommastellen, zu definieren, ist korrekt. Allerdings wird bei einem Zuweisungsoperator immer erst der Ausdruck auf der rechten Seite ausgewertet, bevor dessen Ergebnis der Variablen auf der linken Seite zugewiesen wird. Der Ausdruck ist aber eine Division zweier ganzzahliger Datentypen, die als Ergebnis einen Wert ohne Nachkommastellen liefert. Und dieser um seine Nachkommastellen beraubte Wert wird dann der Gleitkommavariablen zugewiesen.

Während Verwandte meist als Ganzes zu Besuch kommen und ein ganzzahliger Datentyp deshalb angemessen ist, hätten wir bei den Kuchen noch den Argumentationsspielraum, dass es auch möglich sein sollte, halbe Kuchen oder zweieinviertel Kuchen im Kühlschrank zu haben. Dazu müssten wir in unserem Programm die Variable kuchen ebenfalls als Gleitkommatyp definieren:

Jetzt klappt die Kuchenaufteilung perfekt. Der Hintergrund ist folgender:

Wird ein Operator auf Operanden unterschiedlichen Typs angewendet, dann versucht der Compiler über implizite Umwandlung eines der beiden Typen eine Typgleichheit der Operanden herzustellen.

Die Regeln der impliziten Typumwandlung sind etwas komplizierter [C++ 4], aber im Großteil aller Fälle lassen sich die Regeln auf zwei Faustregeln runterbrechen:

Sind beide Operanden entweder ganzzahlig oder mit Nachkommastellen, dann ist das Ergebnis vom größeren Typ der beiden Operanden.

Ist ein Operand ein ganzzahliger Typ und der andere ein Gleitkommatyp, dann ist das Ergebnis ein Gleitkommatyp.

3.8 Explizite Typumwandlung

Was aber, wenn es die Situation erfordert, zwei ganzzahlige Werte so zu dividieren, dass das Ergebnis Nachkommastellen besitzen kann? Die Lösung ist eine explizite Typumwandlung. Die explizite Typumwandlung numerischer Werte wird mit einem static_cast durchgeführt. Die Syntax sieht so aus:

static_cast<Typ>(Wert)

Ein Beispiel könnte folgender Sachverhalt sein: Die Spieldauer von DVDs ist immer in ganzen Minuten angegeben. Wenn ich diese Spieldauer auf mehrere Tage aufteilen möchte, dann sind auch die Anzahl der Tage ganzzahlig. Das Ergebnis dieser Division wäre aber mit Nachkommastellen behaftet. Die Lösung würde so aussehen:

#include<iostream>using namespace std;int main() { cout << "Wie viele Spielminuten:"; int minuten; cin >> minuten; cout << "Aufgeteilt auf wie viele Tage:"; int tage; cin >> tage; double minuten_pro_tag=minuten/static_cast<double>(tage); cout << "Jeden Tag " << minuten_pro_tag << " Minuten schauen." << endl;}

3.9 Kombinierte Zuweisungsoperatoren

In Tabelle 3–2 gibt es als dritte Spalte noch die »kombinierte Zuweisung«. Unter kombinierten Zuweisungsoperatoren versteht man die Kombination einer Operation mit einer Zuweisung. Statt beispielsweise

zu schreiben, hätten wir auch den kombinierten Zuweisungsoperator einsetzen können:

preis *= inflation;

Die kombinierten Zuweisungsoperatoren besitzen gegenüber der ausführlichen Schreibweise Vorteile:

Sie sind kürzer zu schreiben.

Ihre Ausführung ist schneller oder schlimmstenfalls gleich schnell.

Sie können eigenständig überladen werden.

Es ist aber darauf zu achten, dass der Wechsel zu einem kombinierten Zuweisungsoperator nicht die Reihenfolge der arithmetischen Operationen ändert. Während der Ausdruck

zuerst das Produkt und dann die Summe bildet (Punkt- vor Strich-Rechnung), würde

preis *= inflation + zusatzgewinn;

erst einmal den Ausdruck auf der rechten Seite des Zuweisungsoperators (die Summe) berechnen und anschließend die Multiplikation durchführen. Das Ergebnis wäre ein anderes.

3.10 Inkrement und Dekrement

Abgesehen von den Zuweisungsoperatoren sind die Inkrement- und Dekrementoperatoren die einzigen, die den Wert einer Variablen oder eines Objekts verändern, nämlich um 1 erhöhen beziehungsweise um 1 vermindern.

Sie kommen jeweils in zwei Varianten vor, Präfix und Postfix. Das folgende Programm zeigt die beiden Möglichkeiten am Beispiel des Inkrementoperators.

In Zeile 6 bekommt die Variable wert1 den Wert 42 zugewiesen. In Zeile 7 wird der Ausdruck ++wert1 der Variablen wert2 zugewiesen. Wir wissen, dass der Inkrementoperator den Wert der entsprechenden Variablen um 1 erhöht, wert1 hat damit den Wert 43, der anschließend wert2 zugewiesen wird. Die Variable wert2 hat damit ebenfalls den Wert 43.

Betrachten wir Zeile 8. Sie ist nahezu identisch mit der Zeile davor, nur dass die Postfix-Schreibweise verwendet wird. Auch hier wird wert1 um 1 erhöht, aber: Der Ausdruck der Postfix-Schreibweise steht für den alten, in diesem Fall noch nicht erhöhten Wert. Deshalb hat wert1 zwar wegen des Inkrements den Wert 44, wert3 jedoch bekommt den alten Wert, die 43 zugewiesen.

Über die Inkrement- und Dekrementoperatoren sollten Sie Folgendes wissen:

Inkrement- und Dekrementoperatoren sind schneller als die jeweiligen Additions- und Subtraktionsoperatoren.

Die Präfixoperatoren sind schneller als die Postfixoperatoren.

Die Inkrement- und Dekrementoperatoren kommen oft in Zusammenhang mit der Zeigerarithmetik vor.

3.11 Mathematische Funktionen

C++ stellt über die Header-Datei »cmath« die üblichen mathematische Funktionen bereit. Diese Header-Datei muss lediglich mit #include eingebunden werden. In cmath sind die Funktionen für alle Gleitkommatypen enthalten, nicht nur für das in Tabelle 3–3 exemplarisch aufgeführte double.

Funktion

Beschreibung

double abs(double a);

Betrag von a ( |a| )

double acos(double a);

Arcus Kosinus für a im Bogenmaß im Bereich [0, pi]

double asin(double a);

Arcus Sinus für a im Bogenmaß im Bereich [-pi/2, pi/2]

double atan(double a);

Arcus Tangens für a im Bogenmaß im Bereich [-pi/2, pi/2]

double atan2(double a, double b);

Arcus Tangens für a/b im Bogenmaß im Bereich [-pi, pi]

double ceil(double a);

Nächstgrößere Ganzzahl von a

double cos(double a);

Kosinus von a im Bogenmaß

double cosh(double a);

Kosinus Hyperbolicus von a im Bogenmaß

double exp(double a);

Exponentialwert von a (ea)

double floor(double a);

Nächstkleinere Ganzzahl von a

double fmod(double a, double b);

Modulo von a/b

HUGE_VAL

Konstante für Rückgabewert bei Wertüberschreitung

double log(double a);

Natürlicher Logarithmus von a zur Basis e

double log10(double a);

Logarithmus von a zur Basis 10

double modf(double a, int *b);

Liefert die Nachkommastellen von a sowie den ganzzahligen Teil von a in i

double pow(double a, double b);

ab

double sin(double a);

Sinus von a im Bogenmaß

double sinh(double a);

Sinus Hyperbolicus von a im Bogenmaß

double sqrt(double a);

Quadratwurzel von a

double tan(double a);

Tangens von a im Bogenmaß