C++-Programmierung/ Einführung in C++

Aus Wikibooks

Wechseln zu: Navigation, Suche
Einführung in C++

Zielgruppe:

Programmieranfänger, für Umsteiger reicht die Zusammenfassung.

Lernziel:
Grundelemente in C++

Inhaltsverzeichnis

Hallo, du schöne Welt!

Es ist eine alte Tradition, eine neue Programmiersprache mit einem „Hello-World“-Programm einzuweihen. Auch dieses Buch soll mit der Tradition nicht brechen, hier ist das „Hello-World“-Programm in C++:

Crystal Clear app terminal.png
#include <iostream>                                     // Ein- und Ausgabebibliothek

int main(){                                             // Hauptfunktion
    std::cout << "Hallo, du schöne Welt!" << std::endl; // Ausgabe

    return 0;                                           // Optionale Rückgabe an das Betriebssystem
}
Crystal Clear app kscreensaver.png
Ausgabe:
Hallo, du schöne Welt!

Zugegebenermaßen ist es nicht die Originalversion, sondern eine Originellversion von „Hello-World“. Wenn Sie das Programm ausführen, bekommen Sie den Text „Hallo, du schöne Welt!“ am Bildschirm ausgegeben. Sie wissen nicht, wie Sie das Programm ausführen können? Dann lesen Sie doch einmal das Kapitel über Compiler.

#include <iostream> stellt die nötigen Befehle zur Ein- und Ausgabe bereit. Als nächstes beginnt die „Hauptfunktion“ main(). Diese Hauptfunktion wird beim Ausführen des Programms aufgerufen. Sie ist also der zentrale Kern des Programms. Wenn Funktionen im Text erwähnt werden stehen dahinter übrigens immer Klammern, um sie besser von anderen Sprachbestandteilen wie beispielsweise Variablen unterscheiden zu können.

std::cout beschreibt den Standardausgabe-Strom. Dabei wird der Text meist in einem Terminal angezeigt (wenn die Ausgabe nicht in eine Datei oder an ein anderes Programm umgeleitet wird). Die beiden Pfeile ( <<) signalisieren, dass der dahinterstehende Text auf die Standardausgabe „geschoben“ wird. Das std::endl gibt einen Zeilenumbruch aus und sorgt dafür das der Text jetzt am Bildschirm ausgegeben wird.

return 0 beendet das Programm und zeigt dem Betriebssystem an, dass es erfolgreich ausgeführt wurde. Auf die Einzelheiten wird in den folgenden Kapiteln (oder Abschnitten) noch ausführlich eingegangen. Diese Zeile ist optional, wird Sie nicht angegeben, gibt der Compiler implizit 0 zurück.

Im Moment sollten Sie sich merken, dass jeder C++-Befehl mit einem Semikolon ( ;) abgeschlossen wird und dass geschweifte Klammern ( {...}) Zusammengehörigkeit symbolisieren. So auch oben in der Hauptfunktion. Alles was zwischen den geschweiften Klammern steht, gehört zu ihr. Leerzeichen, Tabulatorzeichen und Zeilenumbrüche spielen für den C++-Compiler keine Rolle. Sie können das folgende Programm genauso übersetzen wie seine gut lesbare Version von weiter oben:

Crystal Clear app terminal.png
Dieses Programm funktioniert zwar, ist aber für Menschen sehr schwer zu lesen…
#include        <iostream>
            int
   main    (
         )   {std        ::
     cout
 <<         "Hallo, du schöne Welt!"<<std
   ::  endl
;return 0;
      }
Crystal Clear app kscreensaver.png
Ausgabe:
Hallo, du schöne Welt!

Vorsichtig sollten Sie bei Zeilen sein die mit # beginnen. Leerzeichen und Tabulatoren sind zwar auch hier bedeutungslos, aber Zeilenumbrüche dürfen nicht stattfinden.

Es ist übrigens (für Ihren Rechner) auch irrelevant, ob Sie etwas wie // Ein- und Ausgabebibliothek mit in Ihr Programm schreiben oder nicht. Es handelt sich dabei um sogenannte Kommentare, die Sie in Kürze auch genauer kennenlernen werden. Beachten sollten Sie übrigens, dass bei C++ die Groß- und Kleinschreibung relevant ist. Schlüsselwörter und Namen der Standardbibliothek werden stets kleingeschrieben. Für die Groß-/Kleinschreibung von selbstdefinierten Namen gibt es gewisse Konventionen, auf welche in einem späteren Kapitel eingegangen wird.

Symbol opinion vote.svg
Hinweis

Bei allen in diesem Buch beschriebenen Programmen handelt sich um so genannte Kommando­zeilen­programme. Falls Sie eine IDE zur Entwicklung benutzten und das Programm direkt aus dieser heraus aufrufen, kann es Ihnen passieren, dass Sie nur einen kurzen Blitz von Ihrem Programm sehen, weil sich das Kommando­zeilen­fenster nach der Programm­beendigung sofort schließt.

In diesem Fall haben Sie 2 Optionen:

  • Rufen Sie eine Kommandozeile auf und führen Sie das Programm von dort manuell aus. (empfohlen!)
  • Schauen Sie nach ob es in Ihrer IDE eine Funktion gibt, die das Fenster nach Programmende noch einen Tastendruck lang offen hält. (Muss nicht vorhanden sein!)

Einfache Ein- und Ausgabe

Ein- und Ausgaberoutinen geben Ihnen die Möglichkeit, mit einem Programm zu interagieren. Dieses Kapitel beschäftigt sich mit der Eingabe über die Tastatur und der Ausgabe auf der Konsole in C++-typischer Form. Die C-Variante werden Sie später noch kennenlernen.

Um die C++ Ein- und Ausgabe nutzen zu können, müssen Sie die Bibliothek iostream einbinden. Das geschieht mit:

Crystal Clear app terminal.png
#include <iostream>

Danach müssen die Befehle daraus bekannt gegeben werden, da sie sich in einem speziellen Namensraum befinden. Was Namensräume sind und wofür man sie einsetzt, werden Sie später noch erfahren. Um nun die Ein- und Ausgabebefehle nutzen zu können, müssen sie dem Compiler sagen: Benutze den Namensraum std. Da der Compiler das aber so nicht versteht, können Sie folgende gleichbedeutende Zeile benutzen:

Crystal Clear app terminal.png
using namespace std;

Der Namensraum „ std“ heißt so viel wie Standard. Wenn etwas in diesem Namensraum steht, dann gehört es zur C++-Standardbibliothek und die sollten Sie (in der Regel) wann immer möglich einsetzen. In den Programmen dieses Buches wird der Namensraum immer direkt angegeben. Das heisst, wenn beispielsweise ein Objekt benutzen werden soll das im Namensraum „ std“ liegt, wird ihm „ std::“ vorangestellt. Die beiden Doppelpunkte heißen Bereichsoperator.

[Bearbeiten] Einfache Ausgabe

Nun wollen wir aber endlich auch mal was Praktisches tun. Zugegebenermaßen nichts Weltbewegendes und im Grunde nicht einmal etwas wirklich Neues, denn Text haben wir ja schon im „Hello-World“-Programm ausgegeben.

Crystal Clear app terminal.png
#include <iostream>

int main(){
    std::cout << "Dieser Text steht nun in der Kommandozeile!";
    std::cout << "Dieser Text schließt sich direkt an...";
}
Crystal Clear app kscreensaver.png
Ausgabe:
Dieser Text steht nun in der Kommandozeile!Dieser Text schließt sich direkt an...

Wie der Text bereits selbst sagte, er erscheint in der Kommandozeile und der zweite schließt sich sehr direkt an. Wenn sie innerhalb einer Zeichenkette einen Zeilenumbruch einfügen möchten, gibt es zwei Möglichkeiten. Sie können die Escape-Sequenz \n in die Zeichenkette einfügen oder den Manipulator endl benutzen. Was genau Escape-Sequenzen oder Manipulatoren sind ist Thema eines späteren Kapitels, aber folgendes Beispiels demonstriert schon mal die Verwendung für den Zeilenumbruch:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    std::cout << "Text in der Kommandozeile!\n";                 // Escape-Sequenz \n
    std::cout << "Dieser Text schließt sich an...\n";            // Das steht in einer eigenen Zeile

    std::cout << std::endl;                                      // Leerzeile mittels endl

    std::cout << "Text in der Kommandozeile!" << std::endl;      // Zeilenumbruch mit endl
    std::cout << "Dieser Text schließt sich an..." << std::endl; // Das steht in einer eigenen Zeile
}
Crystal Clear app kscreensaver.png
Ausgabe:
Text in der Kommandozeile!
Dieser Text schließt sich an...

Text in der Kommandozeile!
Dieser Text schließt sich an...

Beide Methoden haben scheinbar den gleichen Effekt, wo der kleine Unterschied liegt, ist allerdings auch Thema eines späteren Kapitels und im Moment noch nicht relevant.

[Bearbeiten] Einfache Eingabe

Für die Eingabe muss ein wenig vorgegriffen werden, denn um etwas einzulesen, ist ja etwas nötig, worin das Eingelesene gespeichert werden kann. Dieser „Behälter“ nennt sich Variable. Eine Variable muss zunächst einmal angelegt werden, am besten lässt sich die Eingabe an einem Beispiel erklären:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    int Ganzzahl;

    std::cout << "Benutzereingabe: ";

    std::cin >> Ganzzahl;

    std::cout << "Sie haben " << Ganzzahl << " eingegeben.";
}
Crystal Clear app kscreensaver.png
Ausgabe:
Benutzereingabe: 643
Sie haben 643 eingegeben.

Es wird, wie bereits erwähnt, erst eine Variable angelegt ( int Ganzzahl;), welcher dann ein Wert zugewiesen wird ( cin >> Ganzzahl;). Diese zweite Zeile bedeutet so viel wie: lies eine ganze Zahl von der Tastatur und speichere sie in der Variablen Ganzzahl.

cin ist sozusagen die Tastatur, Ganzzahl ist der Behälter und >> bedeutet so viel wie „nach“. Zusammen ergibt sich „Tastatur nach Behälter“, es wird also der „Inhalt“ der Tastatur in die Variable Ganzzahl verschoben. Dass eine Ganzzahl von der Tastatur gelesen wird, ist übrigens vom Datentyp der Variable abhängig, aber dazu später mehr.

Um den Inhalt der Variable wieder auszugeben, müssen Sie nichts weiter tun, als sie mit einem weiteren Schiebeoperator ( <<) hinter cout anzuhängen. Es ist ohne Weiteres möglich, mehrere solcher Schiebeoperatoren hintereinander zu schalten, solange Sie nur die letzte Ein- oder Ausgabe mit einem Semikolon ( ;) abschließen. Bei der Eingabe muss natürlich der >>-Operator statt dem <<-Operator benutzt werden. Die Reihenfolge der Ein- oder Ausgabe bei solchen Konstruktionen entspricht der eingegebenen Folge im Quelltext. Was zuerst hinter cout oder cin steht, wird also auch zuerst ausgeführt.

Crystal source cpp.png

Im Anhang zu diesem Kapitel finden Sie:

  • Fragen und die dazugehörigen Antworten.
Fragen

Symbol question.svg Frage 1:

Was ist hier verkehrt:

Crystal Clear action button cancel.png
int Ganzzahl;
std::cin << Ganzzahl;

Symbol question.svg Frage 2:

Funktioniert folgendes Beispiel:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    int Ganzzahl;

    std::cin >> Ganzzahl;

    std::cout << "Sie haben "
              << Ganzzahl
              << " eingegeben.";
}

Kommentare

In allen Programmiersprachen gibt es die Möglichkeit, im Quelltext Notizen zu machen. Für andere oder auch für sich selbst, denn nach ein paar Wochen werden Sie möglicherweise Ihren eigenen Quelltext nicht mehr ohne Weiteres verstehen. Kommentare helfen Ihnen und anderen besser und vor allem schneller zu verstehen, was der Quelltext bewirkt. In C++ gibt es zwei Varianten, um Kommentare zu schreiben:

Crystal Clear app terminal.png
// Ein Kommentar, der mit zwei Schrägstrichen eingeleitet wird, geht bis zum Zeilenende

/* Ein Kommentar dieser Art kann
   sich über mehrere Zeilen
   erstrecken oder ... */


a = b /* ... vor dem Zeilenende enden. */ + c;

Die erste, bis zum Zeilenende geltende Sorte, ist die moderne Art des Kommentars. Sie ist in der Regel vorzuziehen, da sie einige Vorteile gegenüber der alten, noch aus C stammenden Variante hat. Die zweite Sorte (manchmal auch als C-Kommentar bezeichnet) in seiner mehrzeiligen Form sollte nur an Stellen verwendet werden, an denen längere Textpassagen stehen und möglichst nicht zwischen Code. Anwendung finden solche Kommentare oft am Dateianfang, um den Inhalt kurz zusammenzufassen oder Lizenzrechtliches zu regeln.

Der hauptsächliche Nachteil bei den mehrzeiligen Kommentaren besteht darin, dass man sie nicht „verschachteln“ kann. Oft werden beispielsweise Teile des Quellcodes zu Testzwecken kurzweilig auskommentiert. Folgendes Beispiel soll dies demonstrieren:

Crystal Clear action button cancel.png
#include <iostream>                                     /* Ein- und Ausgabebibliothek */

int main(){                                             /* Hauptfunktion */
/*
    std::cout << "Hallo, du schöne Welt!" << std::endl; /* Ausgabe */

*/
}

Das würde nicht funktionieren. Wenn hingegen die anderen Kommentare benutzt werden, gibt es solche Probleme nicht:

Crystal Clear action apply.png
#include <iostream>                                     // Ein- und Ausgabebibliothek

int main(){                                             // Hauptfunktion
    /*
    std::cout << "Hallo, du schöne Welt!" << std::endl; // Ausgabe
    */

}

Im ersten Beispiel wird die Einleitung von /* Ausgabe */ einfach ignoriert. Der abschließende Teil beendet den Kommentar, der die Code-Zeile auskommentieren soll und der eigentliche Abschluss führt zu einem Kompilierfehler. Zugegeben, das ist nicht weiter schlimm, denn solch ein Fehler ist schnell gefunden, aber ihn von vorn herein zu vermeiden, ist eben noch zeitsparender. Im übrigen muss bei einzeiligen Kommentaren oft weniger geschrieben werden. Eine Ausnahme bilden Kommentare, die einfach zu lang sind, um sie auf eine Zeile zu schreiben. Dennoch sollten auch sie durch //-Kommentare realisiert werden.

Crystal Clear app terminal.png
viel Code…
// Ich bin ein Beispiel für einen langen Kommentar, der durch doppelte
// Schrägstriche über mehrere Zeilen geht. Würde ich am Dateianfang stehen,
// hätte man mich wahrscheinlich mit anderen Kommentarzeichen ausgestattet,
// aber da ich hier eindeutig von einer Riesenmenge Quelltext umgeben bin,
// hat man sich trotz der erhöhten Schreibarbeit für // entschieden
noch viel mehr Code…
Symbol kept vote.svg
Tipp

Viele Texteditoren enthalten eine Tastenkombination, über die sich Text ein- und auskommentieren lässt. Besonders für längere Codepassagen ist dies nützlich.

Rechnen (lassen)

In diesem Kapitel soll unser Rechner einmal das tun, was er ohnehin am besten kann: Rechnen. Wir werden uns derweil zurücklehnen und zusehen oder besser gesagt, werden wir das tun, nachdem wir ihm mitgeteilt haben, was er rechnen soll.

[Bearbeiten] Einfaches Rechnen

Crystal Clear app terminal.png
#include <iostream>

int main(){
    std::cout << "7 + 8 = " << 7 + 8 << std::endl; // Ausgabe einer Rechnung
}
Crystal Clear app kscreensaver.png
Ausgabe:
7 + 8 = 15

Zugegebenermaßen hätten Sie diese Rechnung wahrscheinlich auch im Kopf lösen können, aber warum sollten Sie sich so unnötig anstrengen. Ihr Rechner liefert doch auch das richtige Ergebnis und wenn Sie dies mit der Eingabe von Zahlen kombinieren, können Sie sogar bei jedem Programmdurchlauf zwei unterschiedliche Zahlen addieren:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    int Summand1, Summand2;                    // Anlegen von 2 Variablen

    std::cin >> Summand1 >> Summand2;          // 2 Zahlen eingeben

    std::cout << Summand1 << " + " << Summand2 // beide durch " + " getrennt Wieder ausgeben
              << " = "                         // " = " ausgeben
              << Summand1 + Summand2           // Ergebnis berechnen und ausgeben
              << std::endl;                    // Zeilenumbruch
}
Crystal Clear app kscreensaver.png
Ausgabe:
Benutzereingabe: 774
Benutzereingabe: 123
774 + 123 = 897

Das Ergebnis lässt sich natürlich auch in einer Variable zwischenspeichern. Folgendes Beispiel demonstriert diese Möglichkeit:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    int Summand1, Summand2, Ergebnis;          // Anlegen von 3 Variablen

    std::cin >> Summand1 >> Summand2;          // 2 Zahlen eingeben

    Ergebnis = Summand1 + Summand2;            // Ergebnis berechnen

    std::cout << Summand1 << " + " << Summand2 // beide durch " + " getrennt wieder ausgeben
              << " = "                         // " = " ausgeben
              << Ergebnis                      // Ergebnis ausgeben
              << std::endl;                    // Zeilenumbruch
}
Crystal Clear app kscreensaver.png
Ausgabe:
Benutzereingabe: 400
Benutzereingabe: 300
400 + 300 = 700

[Bearbeiten] Die großen 4

C++ beherrscht die 4 Grundrechenarten: Addition ( +), Subtraktion ( -), Multiplikation ( *) und Division ( /). Genau wie in der Mathematik gilt auch in C++ die Regel Punktrechnung geht vor Strichrechnung und Klammern gehen über alles. Das folgende Beispiel soll eine komplexere Rechnung demonstrieren:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    int Ergebnis;                           // Anlegen einer Variable

    Ergebnis = ((3+3*4)/5-1)*512-768;       // Ergebnis berechnen

    std::cout << "((3+3*4)/5-1)*512-768 = " // Aufgabe ausgeben
              << Ergebnis                   // Ergebnis ausgeben
              << std::endl;                 // Zeilenumbruch
}
Crystal Clear app kscreensaver.png
Ausgabe:
((3+3*4)/5-1)*512-768 = 256

Gerechnet wird in dieser Reihenfolge:

   3 *   4 =   12
   3 +  12 =   15
  15 /   5 =    3
   3 -   1 =    2
   2 * 512 = 1024
1024 - 768 =  256

Sie sollten darauf achten, immer die gleiche Anzahl öffnende und schließende Klammern zu haben, denn dies ist ein beliebter Fehler, der von Anfängern meist nicht so schnell gefunden wird. Compiler bringen in solchen Fällen nicht selten Meldungen, die einige Zeilen unter dem eigentlichen Fehler liegen.

[Bearbeiten] Zusammengesetzte Operatoren

C++ ist eine Sprache für schreibfaule Menschen, daher gibt es die Möglichkeit, die Rechenoperatoren mit dem Zuweisungsoperator zu kombinieren. Dies sieht dann folgendermaßen aus:

Crystal Clear app terminal.png
Zahl  = 22;
Zahl += 5; // Zahl = Zahl + 5;
Zahl -= 7; // Zahl = Zahl - 7;
Zahl *= 2; // Zahl = Zahl * 2;
Zahl /= 4; // Zahl = Zahl / 4;

Als Kommentar sehen Sie die Langfassung geschrieben. Diese Kurzschreibweise bedeutet nicht mehr, als dass die vor (!) dem Zuweisungsoperator stehende Rechenoperation mit der Variable auf der linken Seite und dem Wert auf der rechten Seite ausgeführt und das Ergebnis der Variable auf der linken Seite zugewiesen wird. Sie sollten diese Kurzschreibweise der ausführlichen vorziehen, da sie nicht nur die Finger schont, sondern auch noch ein wenig schneller ist.

Am besten werden Sie dies wahrscheinlich verstehen, wenn Sie es einfach ausprobieren. Stehen auf der rechten Seite noch weitere Rechenoperationen, so werden diese zuerst ausgeführt. Das Ganze stellt sich dann also folgendermaßen dar:

Crystal Clear app terminal.png
Zahl  = 5;
Zahl *= 3+4; // Zahl = Zahl * (3+4);

[Bearbeiten] Inkrement und Dekrement

Inkrementieren bedeutet, den Wert einer Variable um 1 zu erhöhen, entsprechend bedeutet dekrementieren 1 herunterzählen. Dem Inkrementoperator schuldet C++ übrigens seinen Namen. Die beiden Operatoren gibt es jeweils in der Präfix und der Postfix Variante. Insgesamt ergeben sich also 4 Operatoren:

Crystal Clear app terminal.png
Zahl = 5;

Zahl++; // Inkrement Postfix (Zahl == 6)
++Zahl; // Inkrement Präfix  (Zahl == 7)
Zahl--; // Dekrement Postfix (Zahl == 6)
--Zahl; // Dekrement Präfix  (Zahl == 5)

Der Unterschied zwischen Inkrement ( ++) und Dekrement ( --) ist sofort ohne größeres Nachdenken erkennbar. Der Sinn von Präfix und Postfix ergibt sich hingegen nicht sofort von selbst. C++ schuldet seinen Namen der Postfix-Variante.

Der Unterschied zwischen Präfix und Postfix besteht im Rückgabewert. Die Präfix-Variante erhöht den Wert einer Zahl um 1 und gibt diesen neuen Wert zurück. Die Postfix-Variante erhöht den Wert der Variable ebenfalls um 1, gibt jedoch den Wert zurück, den die Variable vor der Erhöhung hatte.

Das folgende kleine Programm zeigt den Unterschied:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    int Zahl;                             // Anlegen einer Variable

    std::cout << "Zahl direkt ausgeben:\n";

    Zahl = 5;                             // Zahl den Wert 5 zuweisen

    std::cout << Zahl << ' ';             // Zahl ausgeben

    Zahl++;                               // Inkrement Postfix (Zahl == 6)
    std::cout << Zahl << ' ';             // Zahl ausgeben

    ++Zahl;                               // Inkrement Präfix  (Zahl == 7)
    std::cout << Zahl << ' ';             // Zahl ausgeben

    Zahl--;                               // Dekrement Postfix (Zahl == 6)
    std::cout << Zahl << ' ';             // Zahl ausgeben

    --Zahl;                               // Dekrement Präfix  (Zahl == 5)
    std::cout << Zahl << ' ';             // Zahl ausgeben

    std::cout << "\nRückgabewert des Operators ausgeben:\n";

    Zahl = 5;                             // Zahl den Wert 5 zuweisen

    std::cout << Zahl   << ' ';           // Zahl ausgeben
    std::cout << Zahl++ << ' ';           // Inkrement Postfix (Zahl == 6)
    std::cout << ++Zahl << ' ';           // Inkrement Präfix  (Zahl == 7)
    std::cout << Zahl-- << ' ';           // Dekrement Postfix (Zahl == 6)
    std::cout << --Zahl << ' ';           // Dekrement Präfix  (Zahl == 5)

    std::cout << "\nEndwert von Zahl: " << Zahl << std::endl;
}
Crystal Clear app kscreensaver.png
Ausgabe:
Zahl direkt ausgeben:
5 6 7 6 5
Rückgabewert des Operators ausgeben:
5 5 7 7 5
Endwert von Zahl: 5
Symbol move vote.svg
Thema wird später näher erläutert…

In einem späteren Kapitel werden Sie noch ein paar zusätzliche Informationen erhalten, was beim Rechnen schief gehen kann und wie Sie es vermeiden. Wenn Sie beim Herumexperimentieren mit Rechenoperationen plötzlich scheinbar unerklärliche Ergebnisse erhalten, dann ist es an der Zeit eine Blick auf dieses Kapitel zu werfen.

Crystal source cpp.png

Im Anhang zu diesem Kapitel finden Sie:

  • Aufgaben und zugehörigen Musterlösungen.
Aufgaben

Symbol neutral vote.svg Aufgabe 1:

Rechnen Sie die Ergebnisse die folgenden Aufgaben aus und schreiben Sie ein Programm, welches das Gleiche tut.

Zahl  = (500-100*(2+1))*5
Zahl  = (Zahl-700)/3
Zahl += 50*2
Zahl *= 10-8
Zahl /= Zahl-200

Variablen, Konstanten und ihre Datentypen

Variablen sind Behälter für Werte, sie stellen gewissermaßen das Gedächtnis eines Programms bereit. Konstanten sind Variablen, die ihren Wert nie verändern. Der Datentyp einer Variable oder Konstante beschreibt, wie der Inhalt zu verstehen ist. Ein Rechner kennt nur zwei Zustände: 0 und 1. Durch eine Aneinanderreihung solcher Zustände lassen sich verschiedene Werte darstellen. Mit 8 aufgereihten Zuständen (= 8 Bit = 1 Byte) lassen sich bereits 256 verschiedene Werte darstellen. Diese Werte kann man als Ganzzahl, Zeichen, Wahrheitswert oder Gleitkommazahl interpretieren. Der Datentyp gibt Auskunft darüber, um was es sich handelt.

[Bearbeiten] Datentypen

Zunächst sollen die Datentypen von C++ beschrieben werden, denn sie sind grundlegend für eine Variable oder Konstante. Es gibt 4 Gruppen von Datentypen: Wahrheitswerte, Zeichen, Ganzzahlen und Gleitkommazahlen.

[Bearbeiten] Wahrheitswerte

Der Datentyp für Wahrheitswerte heißt in C++ bool, was eine Abkürzung für Boolean ist. Er kann nur 2 Zustände annehmen: true (Wahr) oder false (Falsch). Obwohl eigentlich 1 Bit ausreichen würde, hat bool mindestens eine Größe von einem Byte (also 8 Bit), denn 1 Byte ist die kleinste adressierbare Einheit und somit die Minimalgröße für jeden Datentyp. Es ist auch durchaus möglich, dass ein bool beispielsweise 4 Byte belegt, da dies auf einigen Prozessorarchitekturen die Zugriffsgeschwindigkeit erhöht.

[Bearbeiten] Zeichen

Zeichen sind eigentlich Ganzzahlen. Sie unterscheiden sich von diesen nur bezüglich der Ein- und Ausgabe. Jeder Zahl ist ein Zeichen zugeordnet, mit den Zahlen lässt sich ganz normal rechnen, aber bei der Ausgabe erscheint das zugeordnete Zeichen auf dem Bildschirm. Welches Zeichen welcher Zahl zugeordnet ist, wird durch den verwendeten Zeichensatz festgelegt.

Die meisten Zeichensätze beinhalten den sogenannten ASCII-Code (American Standard Code for Information Interchange), welcher die Zeichen 0 – 127 belegt. Er enthält 32 Steuerzeichen (0 – 31) und 96 druckbare Zeichen (32 – 127).

char ist der Standard-Datentyp für Zeichen. Er ist in der Regel 1 Byte groß und kann somit 256 verschieden Zeichen darstellen. Diese genügen für einen erweiterten ASCII-Code, welcher zum Beispiel auch deutsche Umlaute definiert. wchar_t ist ein Datentyp für Unicodezeichen und hat gewöhnlich eine Größe von 2 Byte, ist aber zunehmend auch mit 4 Byte zu finden. Die folgende Liste enthält einige nützliche Links zu Artikeln der Wikipedia:

[Bearbeiten] Ganzzahlen

Für ganze Zahlen sind die Datentypen short, int und long definiert. Weiterhin sind, wie schon gesagt, auch char und wchar_t ganzzahlige Datentypen. Mit Ausnahme von wchar_t kann jedem dieser Datentypen ein signed oder unsigned vorangestellt werden. signed bedeutet mit, unsigned ohne Vorzeichen, entsprechend hat der Datentyp dann einen negativen und einen positiven ( signed) oder nur einen positiven ( unsigned) Wertebereich, welcher dann aber doppelt so groß ist. wchar_t entspricht meistens dem Datentyp unsigned short, dies ist in ISO-C++ aber nicht vorgeschrieben.

ISO-C++ schreibt auch die genaue Größe der Datentypen nicht vor, es gibt lediglich die Reihenfolge bezüglich der Größe vor:

char <= short <= int <= long

Außerdem ist festgelegt, dass short mindestens 2 Byte und long mindestens 4 Byte lang sein müssen. int hat (üblicherweise) auf 16-Bit-Rechnern eine Größe von 2 Byte und auf 32-Bit-Rechnern eine Größe von 4 Byte. Die nachfolgende Tabelle zeigt die ganzzahligen Datentypen mit ihrer üblichen Größe und dem entsprechendem Wertebereich:

Typ Speicherplatz Wertebereich (dezimal)
char 1 Byte -128 bis +127 bzw. 0 bis 255
signed char 1 Byte -128 bis +127
unsigned char 1 Byte 0 bis 255
short 2 Byte -32768 bis +32767
unsigned short 2 Byte 0 bis 65535
int 4 Byte -2147483648 bis +2147483647
unsigned int 4 Byte 0 bis 4294967295
long 4 Byte -2147483648 bis +2147483647
unsigned long 4 Byte 0 bis 4294967295

Auffällig ist, dass in der Tabelle nur für char eine signed Variante vorkommt. Das liegt daran, dass die übrigen Datentypen ohne den Zusatz signed immer vorzeichenbehaftet sind, für char ist dies hingegen nicht festgelegt. Dennoch können Sie das Schlüsselwort signed natürlich auch in Verbindung mit den anderen Datentypen ( signed short, signed int und signed long) benutzen. In bestimmten Situationen kann dies zum Beispiel helfen die Übersicht zu erhöhen. Wenn Sie char für Zeichen benutzen, ist eine Angabe von signed oder unsigned nicht nötig, möchten Sie eine Variable dieses Datentyps zum Rechnen benutzen, sollten Sie die Schlüsselworte hingegen angeben.

Das waren jetzt viele Informationen auf wenig Raum. Merken Sie sich einfach in etwa die Größe der 4 Datentypen, den Wertebereich können Sie dann entsprechend ableiten (2Anzahl der Bits, 1 Byte = 8 Bit). Merken Sie sich weiterhin das signed vorzeichenbehaftet, unsigned vorzeichenlos bedeutet und dass im Falle einer fehlenden Angabe signed angenommen wird.

Wählen Sie ein int, wenn dieser Typ alle Zahlen des nötigen Wertebereichs aufnehmen kann, bei vorzeichenlosen Zahlen verwenden Sie unsigned. Reicht dieser Wertebereich nicht aus und ist long größer, dann nehmen Sie long (bzw. unsigned long). short und unsigned short sollte nur Verwendung finden, wenn Speicherplatz knapp ist, etwa bei Verwendung großer Arrays, oder wenn low-level-Datenstrukturen festgelegter Größe benutzt werden müssen. Achten Sie darauf, dass der theoretisch größte Wert, welcher in Ihrer Variable gespeichert wird, den größten möglichen Wert nicht überschreitet. Selbiges gilt natürlich auch für die Unterschreitung des kleinstmöglichen Wertes.

Ein Unter- oder Überlauf ist übrigens durchaus möglich. Die meisten Compiler bieten zwar eine Option an, um in einem solchem Fall einen Fehler zu erzeugen, aber diese Option ist standardmäßig nicht aktiv. Im folgenden kleinen Beispiel werden die Datentypen short (min: -32768, max: 32767) und unsigned short benutzt und bei beiden wird je ein Unter- und ein Überlauf ausgeführt:

Crystal Clear app terminal.png
Davon ausgehend, dass short eine Größe von 2 Byte hat, finden je 2 Über- bzw. Unterläufe statt.
#include <iostream>

int main(){
    short Variable1=15000;
    unsigned short Variable2=15000;

    std::cout << "short Variable:          " << Variable1 << std::endl
              << "unsigned short Variable: " << Variable2 << std::endl
              << "+30000\n\n";

    Variable1 += 30000;
    Variable2 += 30000;

    std::cout << "short Variable:          " << Variable1 << " (Überlauf)" << std::endl
              << "unsigned short Variable: " << Variable2 << std::endl
              << "+30000\n\n";

    Variable1 += 30000;
    Variable2 += 30000;

    std::cout << "short Variable:          " << Variable1 << std::endl
              << "unsigned short Variable: " << Variable2 << " (Überlauf)" << std::endl
              << "-30000\n\n";

    Variable1 -= 30000;
    Variable2 -= 30000;

    std::cout << "short Variable:          " << Variable1 << std::endl
              << "unsigned short Variable: " << Variable2 << " (Unterlauf)" << std::endl
              << "-30000\n\n";

    Variable1 -= 30000;
    Variable2 -= 30000;

    std::cout << "short Variable:          " << Variable1 << " (Unterlauf)" << std::endl
              << "unsigned short Variable: " << Variable2 << std::endl;
}
Crystal Clear app kscreensaver.png
Ausgabe:
short Variable:          15000
unsigned short Variable: 15000
+30000

short Variable:          -20536 (Überlauf)
unsigned short Variable: 45000
+30000

short Variable:          9464
unsigned short Variable: 9464 (Überlauf)
-30000

short Variable:          -20536
unsigned short Variable: 45000 (Unterlauf)
-30000

short Variable:          15000 (Unterlauf)
unsigned short Variable: 15000

Verständlicher wird dieses Phänomen, wenn man die Zahlen binär (Duales Zahlensystem) darstellt. Der Einfachheit halber beginnen wir mit der Darstellung der unsigned short Variable:

Addition im Dualsystem mit  unsigned short als Datentyp

Rechnung 1
        |0011101010011000| 15000
     +  |0111010100110000| 30000
--------------------------------
Merker  |111      11     |
--------------------------------
     =  |1010111111001000| 45000

Rechnung 2
        |1010111111001000| 45000
     +  |0111010100110000| 30000
--------------------------------
Merker 1|1111111         |
--------------------------------
     = 1|0010010011111000| 9464

Die beiden Rechnungen weisen keinerlei Besonderheiten auf. Da nur die letzten 16 Ziffern beachtet werden (2 Byte = 16 Bit), entfällt in der zweiten Rechnung die 1 vor dem Horizontalstrich, wodurch das Ergebnis (in dezimaler Schreibweise) 9464 und nicht 75000 lautet.

Subtraktion im Dualsystem mit  unsigned short als Datentyp

Rechnung 3
          |0010010011111000| 9464
     -    |0111010100110000| 30000
----------------------------------
Merker...1|1111111         |
----------------------------------
     =...1|1010111111001000| 45000

Rechnung 4
          |1010111111001000| 45000
     -    |0111010100110000| 30000
----------------------------------
Merker    |111      11     |
----------------------------------
     =    |0011101010011000| 15000

In diesem Fall ist die zweite Rechnung unauffällig. In der ersten Rechnung wird hingegen eine große Zahl von einer kleineren angezogen, was zu einem negativen Ergebnis führt oder besser führen würden, denn die Untergrenze ist in diesem Fall ja 0. Dort wo die 3 Punkte stehen, folgt eine unendliche Anzahl von Einsen. Dies ist keineswegs nur bei Dualzahlen der Fall, wenn Sie im dezimalen System eine große Zahl von einer kleineren nach den üblichen Regeln der schriftlichen Subtraktion abziehen, so erhalten Sie ein ähnliches Ergebnis:

          24
     -    31
------------
Merker...1
------------
     =...993

Die duale Darstellung der 4 Rechnungen mit der short-Variable wird Ihnen sehr bekannt vorkommen:

Addition im Dualsystem mit  short als Datentyp

Rechnung 1
        |0|011101010011000| 15000
     +  |0|111010100110000| 30000
--------------------------------
Merker  |1|11      11     |
--------------------------------
     =  |1|010111111001000| -20536

Rechnung 2
        |1|010111111001000| -20536
     +  |0|111010100110000| 30000
--------------------------------
Merker 1|1|111111         |
--------------------------------
     = 1|0|010010011111000| 9464

Subtraktion im Dualsystem mit  short als Datentyp

Rechnung 3
          |0|010010011111000| 9464
     -    |0|111010100110000| 30000
----------------------------------
Merker...1|1|111111         |
----------------------------------
     =...1|1|010111111001000| -20536

Rechnung 4
          |1|010111111001000| -20536
     -    |0|111010100110000| 30000
----------------------------------
Merker    |1|11      11     |
----------------------------------
     =    |0|011101010011000| 15000

Die dualen Ziffern sind die gesamte Zeit über exakt die gleichen, der einzige Unterschied besteht darin, dass die erste Ziffer (welche durch einen weiteren Horizontalstrich abgegrenzt wurde) nun als Vorzeichenbit interpretiert wird (   – Positiv, 1 – Negativ). Dies führt zu einer veränderten Darstellung im Dezimalsystem.

[Bearbeiten] Gleitkommazahlen

Eine Gleitkommavariable kann sich eine bestimmte Anzahl Ziffern merken und dazu die Position des Kommas. Das Wissen über den internen Aufbau einer solchen Zahl werden Sie wahrscheinlicher eher selten bis nie brauchen, daher sei an dieser Stelle auf den Wikipediaartikel über Gleitkommazahlen verwiesen. In C++ werden Sie Gleitkommazahlen/-variable für das Rechnen mit Kommazahlen verwenden. Es gibt 3 Datentypen für Gleitkommazahlen, die in der folgenden Tabelle mit ihren üblichen Werten aufgelistet sind:

Typ Speicherplatz Wertebereich kleinste Positive Zahl Genauigkeit
float 4 Byte \pm3,4 \cdot 10^{38} 1,2 \cdot 10^{-38} 6 Stellen
double 8 Byte \pm1,7 \cdot 10^{308} 2,3 \cdot 10^{-308} 15 Stellen
long double 10 Byte \pm1,1 \cdot 10^{4932} 3,4 \cdot 10^{-4932} 19 Stellen

Die Auswahl eines Gleitkommadatentyps ist weniger einfach als die einer Ganzzahl. Wenn Sie nicht genau wissen, was Sie nehmen sollen, ist double in der Regel eine gute Wahl. Sobald Sie erst einmal ausreichend Erfahrung haben, wird es Ihnen leichter fallen abzuschätzen, ob float oder long double für Ihr Problem vielleicht eine bessere Wahl sind.

[Bearbeiten] Variablen

Bevor eine Variable verwendet werden kann, muss sie dem Compiler bekannt gegeben werden. Dies bezeichnet man als Deklaration der Variable.

Im vorherigen Kapitel haben wir bereits mit Variablen vom Typ int gerechnet. Nun sollen Sie lernen, wie Variablen in C++ angelegt werden. Die allgemeine Syntax lautet:

Datentyp Name;

Außerdem ist es möglich, mehrere Variablen des gleichen Typs hintereinander anzulegen:

Datentyp Variable1, Variable2, Variable3;

Auch kann man einer Variable einen Anfangswert geben, dies bezeichnet man als Initialisierung. Es gibt 2 syntaktische Möglichkeiten (Schreibweisen) für Initialisierungen, welche anhand einer int Variable gezeigt werden soll:

Crystal Clear app terminal.png
int Zahl=100;  // Möglichkeit 1
int Zahl(100); // Möglichkeit 2

Die erste Variante ist weit verbreitet, aber nicht besser. Bei den fundamentalen Datentypen von C++ spielt es keine Rolle welche Variante Sie verwenden, aber bei komplexeren Datentypen (Klassen) kann es zu Verwechslungen mit dem Zuweisungsoperator kommen, wenn Sie Möglichkeit 1 benutzen. Den genauen Unterschied zwischen einer Initialisierung und einer Zuweisung werden Sie kennen lernen, sobald es um Klassen geht. Für den Moment sollten Sie sich für eine Variante entscheiden.

Für Möglichkeit 1 spricht die große Verbreitung und die damit verbundene „gewohnte Benutzung“. Für Möglichkeit 2 spricht hingegen die bessere Lesbarkeit sobald man sich daran gewöhnt hat.

Variablen mit Anfangswerten können natürlich auch hintereinander angelegt werden, sofern Sie den gleichen Datentyp besitzen:

Crystal Clear app terminal.png
int Zahl1(77), Zahl2, Zahl3=58; // 3 int-Variablen von denen 2 Anfangswerte haben

Wenn Sie einer Variable keinen Anfangswert geben, müssen Sie ihr später im Programm noch einen Wert zuweisen, bevor Sie mit ihr arbeiten (also damit rechnen oder den Inhalt ausgeben lassen). Weisen Sie einer solchen Variable keinen Wert zu und benutzen sie, so ist der Inhalt zufällig. Genaugenommen handelt es sich dann um die Bitfolge, die an der Stelle im Speicher stand, an der Ihre Variable angelegt wurde.

Das nachfolgende kleine Programm zeigt dies:

Crystal Clear app terminal.png
#include <iostream>                                         // Ein-/Ausgabe

int main(){
    int Zahl;                                               // Ganzzahlige Variable
    double Kommazahl1, Kommazahl2;                          // Gleitkommavariablen
    char Zeichen;                                           // Zeichenvariable

    std::cout << "Zahl: "       << Zahl       << std::endl  // Ausgabe der Werte
              << "Kommazahl1: " << Kommazahl1 << std::endl  // welche jedoch
              << "Kommazahl2: " << Kommazahl2 << std::endl  // nicht festgelegt
              << "Zeichen: "    << Zeichen    << std::endl; // wurden
}
Crystal Clear app kscreensaver.png
Ausgabe:
Zahl: -1211024315
Kommazahl1: 4.85875e-270
Kommazahl2: -3.32394e-39
Zeichen: f

Die Ausgabe lautet bei jedem Ausführen des Programms anders. Sollte dies bei Ihnen nicht der Fall sein, so stehen nur zufällig die gleichen Werte an der Stelle im Speicher welchen die jeweilige Variable belegt. Variablen keinen Anfangswert zu geben ist beispielsweise sinnvoll, wenn Sie vorhaben über cin einen Wert in die Variable einzulesen.

Crystal Clear app terminal.png
#include <iostream>                                         // Ein-/Ausgabe
 
int main(){
    int Zahl;                                               // Ganzzahlige Variable
    double Kommazahl1, Kommazahl2;                          // Gleitkommavariablen
    char Zeichen;                                           // Zeichenvariable

    std::cout << "Geben Sie bitte durch Leerzeichen getrennt eine Ganzzahl, 2 Kommazahlen "
            "und ein Zeichen ein:\n";

    std::cin >> Zahl                                        // Eingabe von Werten
             >> Kommazahl1                                  // mit denen die 4
             >> Kommazahl2                                  // Variablen gefüllt
             >> Zeichen;                                    // werden

    std::cout << "Zahl: "       << Zahl       << std::endl  // Ausgabe der Werte
              << "Kommazahl1: " << Kommazahl1 << std::endl  // welche zuvor
              << "Kommazahl2: " << Kommazahl2 << std::endl  // eingegeben
              << "Zeichen: "    << Zeichen    << std::endl; // wurden
}
Crystal Clear app kscreensaver.png
Ausgabe:
Geben Sie bitte durch Leerzeichen getrennt eine Ganzzahl, 2 Kommazahlen und ein Zeichen ein:
Benutzereingabe: 6 8.4 6.0 g
Zahl: 6
Kommazahl1: 8.4
Kommazahl2: 6
Zeichen: g

[Bearbeiten] Konstanten

Konstanten sind, wie schon oben beschrieben, Variablen, welche ihren Wert nicht verändern. Daraus folgt logisch, dass eine Konstante immer mit einem Anfangswert initialisiert werden muss, andernfalls hätten Sie eine Konstante mit einem zufälligen Wert und das ergibt keinen Sinn. Das Schlüsselwort, um eine Variable zu einer Konstante zu machen, ist const. Es gehört immer zu dem, was links davon steht, es sei denn, links steht nichts mehr, dann gehört es zu dem Teil auf der rechten Seite. Dies klingt zwar kompliziert, ist es aber eigentlich gar nicht. Für uns bedeutet es im Moment nur, dass Sie 2 Möglichkeiten haben, eine Variable zu einer Konstante zu machen:

Crystal Clear app terminal.png
const int Zahl(400); // Alternativ: const int Zahl=400;
// oder
int const Zahl(400); // Alternativ: int const Zahl=400;

Beides hat die gleiche Wirkung, wieder ist die erste Variante weit verbreitet und wieder ist die zweite Variante der besseren Lesbarkeit bei komplexeren Datentypen (Arrays von Zeigern Konstante auf Memberfunktionen…) vorzuziehen. Entscheiden Sie sich für die Variante, die Ihnen besser gefällt und verwenden Sie diese. Wichtig ist, dass Sie der Variante, für die Sie sich entscheiden, treu bleiben, wenigstens für die Dauer eines Projekts. Denn Code, in dem sich der Schreibstil ständig ändert, ist schwerer zu lesen, als alles andere.

[Bearbeiten] Literale und ihre Datentypen

Ein Literal ist eine Zeichenkette, die zu Darstellung des Wertes einer der oben beschriebenen Datentypen dient.

Crystal Clear app terminal.png
#include <iostream>                 // Ein-/Ausgabe

int main() {
    std::cout << 100  << std::endl  // 100 ist ein int-Literal
              << 4.7  << std::endl  // 4.7 ist ein double-Literal
              << 'h'  << std::endl  // 'h' ist ein char-Literal
              << true << std::endl; // true ist ein bool-Literal
}
Crystal Clear app kscreensaver.png
Ausgabe:
100
4.7
h
1

Bei der Ausgabe ist zu beachten, dass Boolean-Werte als 0 (false) bzw. 1 (true) ausgeben werden.

Da die Bestimmung des Typs für einen ganzzahligen Wert etwas schwieriger ist als bei den übrigen, werden wir diese zuletzt behandeln. Bei Gleitkommaliteralen ist festgelegt, dass es sich um double-Werte handelt. Um einen Gleitkommaliteral mit einem anderen Typ zu erhalten, ist ein so genanntes Suffix nötig.

Crystal Clear app terminal.png
5.0  // double
6.7f // float
2.6F // float
9.4l // long double
4.0L // long double

Eine Zahl mit Komma ( .) ist also ein double-Wert. Folgt der Zahl ein f oder ein F wird sie zu einem float-Wert und folgt ihr ein l oder ein L wird sie zu einem long double-Wert. Gleitpunktzahlen können auch in der wissenschaftlichen Schreibweise dargestellt werden.

Crystal Clear app terminal.png
5.7e10
3.3e-3
8.7e876L
4.2e-4F

Wie die letzten beiden Beispiele zeigen, können auch hierbei die Suffixe für den Dateityp genutzt werden. Um ein Zeichen beziehungsweise eine Zeichenketten als wchar_t zu kennzeichnen, stellt man ihr ein L voran:

Crystal Clear app terminal.png
'a'                 // char
L'b'                // wchar_t
"Ich bin ein Text"  // char*
L"Ich bin ein Text" // wchar_t*

Was das Sternchen ( *) hinter dem Datentyp im Kommentar bedeutet, werden Sie in einem späteren Kapitel erfahren. bool kann nur 2 Zustände annehmen, entsprechend gibt es auch nur 2 bool-Literale: true und false.

Nun zu den Ganzzahlen. Neben der dezimalen Darstellung von Zahlen gibt es in C++ auch die Möglichkeit der Oktalen und Hexadezimalen Darstellung. Um eine Zahl als Oktal zu kennzeichnen, wird ihr eine   (Null) vorangestellt, für eine Hexadezimalzahl wird 0x (zu empfehlen, da deutlich besser Lesbar) oder 0X vorangestellt. Die Groß-/Kleinschreibung der hexadezimalen Ziffern spielt keine Rolle.

Crystal Clear app terminal.png
756        // Dezimal,     Dezimal: 756
046        // Oktal,       Dezimal: 38
0757       // Oktal,       Dezimal: 495
0xffff     // Hexadezimal, Dezimal: 65535
0x1234ABcd // Hexadezimal, Dezimal: 305441741

Der Datentyp wird durch die Größe des Wertes bestimmt, wobei die folgende Reihenfolge gilt: int, unsigned int, long, unsigned long. Weiterhin kann jeder Ganzzahl das Suffix u oder U für unsigned und l oder L für long angehängt werden. Auch beide Suffixe gleichzeitig sind möglich. Die Reihenfolge ändert sich entsprechend den durch die Suffixe festgelegten Kriterien.

Das Wissen über die Datentypen von Literalen werden Sie wahrscheinlich eher selten benötigen, daher reicht es „mal etwas davon gehört zu haben“ und es, wenn nötig, nachzuschlagen.

Rechnen mit unterschiedlichen Datentypen

Sie kennen nun die Datentypen in C++ und haben auch schon mit int-Variablen gerechnet. In diesem Kapitel erfahren Sie, wie man mit Variablen unterschiedlichen Typs rechnet. Es geht also weniger um das Ergebnis selbst, als viel mehr darum wie der Ergebnisdatentyp lautet.

[Bearbeiten] Ganzzahlen unter sich

Das Rechnen mit Ganzzahlen ist leicht zu begreifen. Die „kleinen“ Datentypen werden als int behandelt. Bei den größeren entscheidet der größte Datentyp über den Ergebnistyp. Die folgende Liste zeigt die Zusammenhänge:

char           + char           => int           | wchar_t        + char           => int
char           + wchar_t        => int           | wchar_t        + wchar_t        => int
char           + signed char    => int           | wchar_t        + signed char    => int
char           + unsigned char  => int           | wchar_t        + unsigned char  => int
char           + short          => int           | wchar_t        + short          => int
char           + unsigned short => int           | wchar_t        + unsigned short => int
char           + int            => int           | wchar_t        + int            => int
char           + unsigned int   => unsigned int  | wchar_t        + unsigned int   => unsigned int
char           + long           => long          | wchar_t        + long           => long
char           + unsigned long  => unsigned long | wchar_t        + unsigned long  => unsigned long

signed char    + char           => int           | unsigned char  + char           => int
signed char    + wchar_t        => int           | unsigned char  + wchar_t        => int
signed char    + signed char    => int           | unsigned char  + signed char    => int
signed char    + unsigned char  => int           | unsigned char  + unsigned char  => int
signed char    + short          => int           | unsigned char  + short          => int
signed char    + unsigned short => int           | unsigned char  + unsigned short => int
signed char    + int            => int           | unsigned char  + int            => int
signed char    + unsigned int   => unsigned int  | unsigned char  + unsigned int   => unsigned int
signed char    + long           => long          | unsigned char  + long           => long
signed char    + unsigned long  => unsigned long | unsigned char  + unsigned long  => unsigned long

short          + char           => int           | unsigned short + char           => int
short          + wchar_t        => int           | unsigned short + wchar_t        => int
short          + signed char    => int           | unsigned short + signed char    => int
short          + unsigned char  => int           | unsigned short + unsigned char  => int
short          + short          => int           | unsigned short + short          => int
short          + unsigned short => int           | unsigned short + unsigned short => int
short          + int            => int           | unsigned short + int            => int
short          + unsigned int   => unsigned int  | unsigned short + unsigned int   => unsigned int
short          + long           => long          | unsigned short + long           => long
short          + unsigned long  => unsigned long | unsigned short + unsigned long  => unsigned long

int            + char           => int           | unsigned int   + char           => unsigned int
int            + wchar_t        => int           | unsigned int   + wchar_t        => unsigned int
int            + signed char    => int           | unsigned int   + signed char    => unsigned int
int            + unsigned char  => int           | unsigned int   + unsigned char  => unsigned int
int            + short          => int           | unsigned int   + short          => unsigned int
int            + unsigned short => int           | unsigned int   + unsigned short => unsigned int
int            + int            => int           | unsigned int   + int            => unsigned int
int            + unsigned int   => unsigned int  | unsigned int   + unsigned int   => unsigned int
int            + long           => long          | unsigned int   + long           => long oder unsigned long
int            + unsigned long  => unsigned long | unsigned int   + unsigned long  => unsigned long

long           + char           => long          | unsigned long  + char           => unsigned long
long           + wchar_t        => long          | unsigned long  + wchar_t        => unsigned long
long           + signed char    => long          | unsigned long  + signed char    => unsigned long
long           + unsigned char  => long          | unsigned long  + unsigned char  => unsigned long
long           + short          => long          | unsigned long  + short          => unsigned long
long           + unsigned short => long          | unsigned long  + unsigned short => unsigned long
long           + int            => long          | unsigned long  + int            => unsigned long
long           + unsigned int   => unsigned long | unsigned long  + unsigned int   => unsigned long
long           + long           => long          | unsigned long  + long           => unsigned long
long           + unsigned long  => unsigned long | unsigned long  + unsigned long  => unsigned long

Zugegebenermaßen wirkt dies erst einmal erschlagend, aber es ist eigentlich nicht schwer zu begreifen. Bei jeder Rechenoperation hat jeder der 2 Operanden, sowie das Ergebnis der Rechnung einen Datentyp:

Crystal Clear app terminal.png
#include <iostream>

int main(){
    char  Zahl1=22;
    short Zahl2=40;

    std::cout << Zahl1 * Zahl2 << std::endl; // 22   * 40    =  880
                                             // char + short => int
}

[Bearbeiten] Gleitkommarechnen

Beim Rechnen mit Gleitkommazahlen gelten im Grunde die gleichen Regeln wie bei Ganzzahlen. Der Ergbnistyp entspricht auch hier dem des Operanden mit dem „größeren“ Typ. Die Reihenfolge lautet: float, double, long double. Es gilt also:

float       + float       => float
float       + double      => double
float       + long double => long double

double      + float       => double
double      + double      => double
double      + long double => long double

long double + float       => long double
long double + double      => long double
long double + long double => long double

[Bearbeiten] Casting

Casting meint in diesem Zusammenhang, die Umwandlung eines Datentyps in einen anderen. Diese Typumwandlung kann sowohl automatisch (implizit) stattfinden, als auch vom Programmierer angegeben (explizit) werden.

[Bearbeiten] Implizite Typumwandlung

Mit impliziter Typumwandlung hatten Sie bereits reichlich zu tun, denn es kann ausschließlich mit Zahlen gerechnet werden die den gleichen Typ besitzen.

Beispiele:

char  + int          => int          | int          + int          => int
short + unsigned int => unsigned int | unsigned int + unsigned int => unsigned int
float + double       => double       | double       + double       => double

[Bearbeiten] Umformungsregeln

Viele binäre Operatoren, die arithmetische oder Aufzählungsoperanden erwarten, verursachen Umwandlungen und ergeben Ergebnistypen auf ähnliche Weise. Der Zweck ist, einen gemeinsamen Typ zu finden, der auch der Ergebnistyp ist. Dieses Muster wird "die üblichen arithmetischen Umwandlungen" genannt, die folgendermaßen definiert sind:

  • Wenn ein Operand vom Typ long double ist, dann wird der andere zu long double konvertiert.
  • Andernfalls, wenn ein Operand vom Typ double ist, dann wird der andere zu double konvertiert.
  • Andernfalls, wenn ein Operand vom Typ float ist, dann wird der andere zu float konvertiert.
  • Ansonsten werden die integralen Umwandlungen auf beide Operanden angewendet:
  • Wenn ein Operand vom Typ unsigned long ist, dann wird der andere zu unsigned long konvertiert.
  • Andernfalls, wenn ein Operand vom Typ long und der andere vom Typ unsigned int, dann wird, falls ein long alle Werte eines unsigned int darstellen kann, der unsigned int-Operand zu long konvertiert; andernfalls werden beide Operanden zu unsigned long konvertiert.
  • Andernfalls, wenn ein Operand vom Typ long ist, dann wird der andere zu long konvertiert.
  • Andernfalls, wenn ein Operand vom Typ unsigned int ist, dann wird der andere zu unsigned int konvertiert.

Hinweis: Der einzig verbleibende Fall ist, dass beide Operanden vom Typ int sind.

Diese Regeln wurden so aufgestellt, dass dabei stets ein Datentyp in einen anderen Datentyp mit "größerem" Wertebereich umgewandelt wird. Das stellt sicher, dass bei der Typumwandlung keine Wertverluste durch Überläufe entstehen. Es können allerdings bei der Umwandlung von Ganzzahlen in float-Werte Rundungsfehler auftreten:

Crystal Clear app terminal.png
std::printf( "17000000 + 1.0f = %f.\n", 17000000 + 1.0f );

Diese Programmzeile gibt auf einem Computer, wo float eine 32-Bit-Gleitkommazahl ist, folgendes aus:

17000000 + 1.0f = 17000000.000000.

Warum? Für die Berechnung werden beide Operanden in den Datentyp float konvertiert und anschließend addiert. Eine 32-Bit-Gleitkommazahl ist aber nicht in der Lage, Zahlen in der Größenordnung von 17 Mio. mit der nötigen Genauigkeit zu speichern, um zwischen 17000000 und 17000001 zu unterscheiden. Das Ergebnis der Addition wird daher wieder auf 17000000 gerundet. Dies geschieht in diesem Falle sogar, obwohl der Wert anschließend auf double "aufgeweitet" wird, da die printf-Methode Gleitkommawerte standardmäßig als double erhält (und durch die Formatangabe "%f" auch als double erwartet).

[Bearbeiten] Explizite Typumwandlung

In C++ gibt es dafür zwei Möglichkeiten. Zum einen den aus C übernommenen Cast (Typ)Wert und zum anderen die vier (neuen) C++ Casts.

Crystal Clear app terminal.png
static_cast< Zieltyp >(Variable)
const_cast< Zieltyp >(Variable)
dynamic_cast< Zieltyp >(Variable)
reinterpret_cast< Zieltyp >(Variable)
Symbol kept vote.svg
Tipp
Die Leerzeichen zwischen dem Zieltyp und den spitzen Klammern sind nicht zwingend erforderlich, Sie sollten sich diese Notation jedoch angewöhnen. Speziell wenn Sie später mit Templates oder Namensräumen arbeiten, ist es nützlich Datentypen ein wenig von ihrer Umgebung zu isolieren. Sie werden an den entsprechenden Stellen noch auf die ansonsten möglichen Doppeldeutigkeiten hingewiesen.
Symbol move vote.svg
Thema wird später näher erläutert…
Im Moment benötigen Sie nur den static_cast. Was genau die Unterschiede zwischen diesen Casts sind und wann man welchen einsetzt erfahren Sie in einem späteren Kapitel. Die C-Casts sollten Sie aber auf keinen Fall einsetzen, weshalb erfahren Sie ebenfalls später.

[Bearbeiten] Ganzzahlen und Gleitkommazahlen

Wird mit einer Ganzzahl und einer Gleitkommazahl gerechnet, so ist das Ergebnis vom gleichen Typ wie die Gleitkommazahl.

[Bearbeiten] Rechnen mit Zeichen

Mit Zeichen zu rechnen ist besonders praktisch. Um beispielsweise das gesamte Alphabet auszugeben zählen Sie einfach vom Buchstaben 'A' bis einschließlich 'Z':

Crystal Clear app terminal.png
#include <iostream>

int main(){
    for(char i = 'A'; i <= 'Z'; ++i){
        std::cout << i;
    }
}
Crystal Clear app kscreensaver.png
Ausgabe:
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Für eine Erklärung des obigen Quellcodes, lesen Sie bitte das Kapitel Schleifen.

Wenn Sie binäre Operatoren auf Zeichen anwenden ist das Ergebnis (mindestens) vom Typ int. Im folgenden Beispiel wird statt eines Buchstabens, der dazugehörige ASCII-Wert ausgeben. Um also wieder ein Zeichen auszugeben, müssen Sie das Ergebnis wieder in den Zeichentyp casten. (Beachten Sie im folgenden Beispiel, dass die Variable i - im Gegensatz zum vorherigen Beispiel - nicht vom Typ char ist):

Crystal Clear app terminal.png
#include <iostream>

int main(){
    char zeichen = 'A';

    for(int i = 0; i < 26; ++i){
        std::cout << zeichen + i << ' ';               // Ergebnis int
    }

    std::cout << std::endl;

    for(int i = 0; i < 26; ++i){
        std::cout << static_cast< char >(zeichen + i); // Ergebnis char
    }
}
Crystal Clear app kscreensaver.png
Ausgabe:
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Verzweigungen

Eine Verzweigung (bedingte Anweisung, conditional statement) dient dazu, ein Programm in mehrere Pfade aufzuteilen. Beispielsweise kann so auf Eingaben des Benutzers reagiert werden. Je nachdem, was der Benutzer eingibt, ändert sich der Programmablauf.

[Bearbeiten] Falls

Verzweigungen werden mit dem Schlüsselwort if begonnen. In der einfachsten Form sieht das so aus:

Crystal Clear app tutorials.png
Syntax:
if(«Bedingung») «Anweisung»

Wenn die Bedingung erfüllt ist, wird die Anweisung ausgeführt, ansonsten wird sie übersprungen. Sollen nicht nur eine, sondern mehrere Anweisungen ausgeführt werden, fassen Sie diese mit {...} zu einer Blockanweisung zusammen:

Crystal Clear app tutorials.png
Syntax:
if(«Bedingung»){
    «Anweisungen»
}

Als Bedingung darf jeder Ausdruck verwendet werden, der einen bool zurückgibt oder dessen Ergebnis sich in einen bool umwandeln lässt. Ganzzahlige und Gleitkommadatentypen lassen sich nach bool umwandeln, die Regel lautet einfach: Ist eine Zahl gleich 0 so wird sie als false ausgewertet, andernfalls als true.

Crystal Clear app terminal.png
int i;
cin >> i;
if(i){
    cout << "Der Benutzer hat einen Wert ungleich 0 eingegeben\n";
}

[Bearbeiten] Andernfalls

Das Schlüsselwort else erweitert die Einsatzmöglichkeiten der Verzweigung. Während ein normales (also einzelnes) if einen bestimmten Teil des Codes ausführt, falls eine Bedingung erfüllt ist, stellt else eine Erweiterung dar, anderen Code auszuführen, falls die Bedingung nicht erfüllt ist.

Crystal Clear app terminal.png
int i;
cin >> i;
if (i)
    cout << "Sie haben einen Wert ungleich 0 eingegeben!\n";
else
    cout << "Sie haben 0 eingegeben!\n";

Natürlich könnten auch hier, sowohl für die if-Anweisung, als auch für die else-Anweisung, ein Anweisungsblock stehen. Wenn Sie Pascal oder eine ähnliche Programmiersprache kennen, wird Ihnen auffallen, dass auch die Anweisung vor dem else mit einem Semikolon abgeschlossen wird. Da auf eine if- oder else-Anweisung immer nur eine Anweisung oder ein Anweisungsblock stehen kann, muss zwangsläufig direkt danach ein else stehen, um dem if zugeordnet zu werden.

Sie können in einer Verzweigungsanweisung auch mehr als zwei Alternativen angeben:

Crystal Clear app terminal.png
int i;
cin >> i;
if (i == 10)
    cout << "Sie haben zehn eingegeben\n";
else
    if (i == 11)
        cout << "Sie haben elf eingegeben\n";
    else
        cout << "Sie haben weder zehn noch elf eingegeben\n";

Es können beliebig viele Zweige mit else if vorkommen. Allerdings ist es üblich, eine andere Einrückung zu wählen, wenn solche „ if- else-Bäume“ ausgebaut werden:

Crystal Clear app terminal.png
int i;
cin >> i;
if (i == 10)
    cout << "Sie haben zehn eingegeben\n";
else if (i == 11)
    cout << "Sie haben elf eingegeben\n";
else
    cout << "Sie haben weder zehn noch elf eingegeben\n";

Außerdem ist es zu empfehlen, auch bei einer Anweisung einen Anweisungsblock zu benutzen. Letztlich ist die Funktionalität immer die gleiche, aber solche Blöcke erhöhen die Übersichtlichkeit und wenn Sie später mehrere Anweisungen, statt nur einer angeben möchten, brauchen Sie sich um etwaige Klammern keine Gedanken zu machen, weil sie sowieso schon vorhanden sind.

Crystal Clear app terminal.png
int i;
cin >> i;
if (i == 10) {
    cout << "Sie haben zehn eingegeben\n";
} else if(i == 11) {
    cout << "Sie haben elf eingegeben\n";
} else {
    cout << "Sie haben weder zehn noch elf eingegeben\n";
}

Sie werden für die Positionierung der Klammern übrigens auch oft auf eine andere Variante treffen:

Crystal Clear app terminal.png
int i;
cin >> i;
if (i == 10)
{
    cout << "Sie haben zehn eingegeben\n";
}
else
{
    if (i == 11)
    {
        cout << "Sie haben elf eingegeben\n";
    }
    else
    {
        cout << "Sie haben weder zehn noch elf eingegeben\n";
    }
}

Einige Programmierer finden dies übersichtlicher, für dieses Buch wurde jedoch die Variante mit den öffnenden Klammern ohne Extrazeile zu verwenden. Das hat den Vorteil, dass weniger Platz benötigt wird und da die Einrückung ohnehin die Zugehörigkeit andeutet, ist eine zusätzliche Kennzeichnung nicht unbedingt nötig.

Die Einrückung von Quelltextzeilen hat für den Compiler übrigens keine Bedeutung. Sie ist lediglich eine grafische Darstellungshilfe für den Programmierer. Auch die in diesem Buch gewählte Einrückungstiefe von vier Leerzeichen ist optional, viele Programmierer verwenden etwa nur zwei Leerzeichen. Andere hingegen sind davon überzeugt, dass acht die ideale Wahl ist. Aber egal, wofür Sie sich entscheiden, wichtig ist, dass Sie Ihren Stil einhalten und nicht ständig ihren Stil wechseln. Das verwirrt nicht nur, sondern sieht auch nicht schön aus.

Symbol kept vote.svg
Tipp

Wenn es Sie nicht stört, in Ihrem Texteditor Tabulatorzeichen und Leerzeichen anzeigen zu lassen, dann sollten Sie für die Einrückung Tabulatorzeichen verwenden und für alles hinter der normalen Einrückung (etwa den Abstand bis zum Kommentar) Leerzeichen. Das hat den Vorteil, dass Sie die Einrückungstiefe jederzeit ändern können, indem Sie angeben wie viele Leerzeichen einem Tabulatorzeichen entsprechen.

[Bearbeiten] Vergleichsoperatoren

Im obigen Beispiel kam schon der Vergleichsoperator == zum Einsatz. In C++ gibt es insgesamt sechs Vergleichsoperatoren. Sie liefern jeweils den Wert true, wenn die beiden Operanden (die links und rechts des Operators stehen) dem Vergleichskriterium genügen, ansonsten den Wert false.

== identisch
<= ist kleiner (oder) gleich
>= ist größer (oder) gleich
< ist kleiner
> ist größer
!= ist ungleich
Symbol opinion vote.svg
Hinweis
Der Vergleichsoperator == wird von Anfängern oft mit dem Zuweisungsoperator = verwechselt. Da es absolut legal ist, eine Zuweisung innerhalb einer if-Bedingung zu machen, führt das oft zu schwer zu findenden Fehlern:
Crystal Clear app terminal.png
int a = 5, b = 8;
if (a = b) cout << "5 ist gleich 8.";
Crystal Clear app kscreensaver.png
Ausgabe:
5 ist gleich 8.

Das weist b an a ( a = 8) zu und wertet dann die Rückgabe von a (also 8) zu true aus. Prüfen Sie bei seltsamen Verhalten also immer ob vielleicht der Zuweisungsoperator = statt des Gleichheitsoperators == verwendet wurde.

Eine weitere Falle ist der Ungleichheitsoperator !=, wenn er falsch herum geschrieben wird ( =!). Letzteres sind in Wahrheit zwei Operatoren, nämlich die Zuweisung = und die logische Negierung !, die Sie gleich kennen lernen werden. Um das zu unterscheiden, machen Sie sich einfach klar, was das in Worten heißt:

  • != – nicht gleich
  • =! – gleich nicht

[Bearbeiten] Logische Operatoren

Mit logischen Operatoren können Sie mehrere Bedingungen zu einem Ausdruck verknüpfen. C++ bietet folgende Möglichkeiten:

! Logisches Nicht Resultat wahr, wenn der Operand falsch ist
&& Logisches Und Resultat wahr, wenn beide Operanden wahr sind
|| Logisches Oder Resultat wahr, wenn mindestens ein Operand wahr ist (inclusive-or)

Bei den Operatoren && (Logik-und) und || (Logik-oder) werden die Teilausdrücke von links nach rechts bewertet, und zwar nur so lange, bis das Resultat feststeht. Wenn z. B. bei einer &&-Verknüpfung schon die erste Bedingung falsch ist, wird die zweite nicht mehr untersucht, da beide Bedingungen true sein müssen. Der Rückgabewert dieser Operatoren ist genau wie bei den Vergleichsoperatoren von Typ bool.

Crystal Clear app terminal.png
int i = 10, j = 20;
if (i == 10 && j == 10) {
    cout << "Beide Werte sind gleich zehn\n";
}
if (i == 10 || j == 10) {
    cout << "Ein Wert oder beide Werte sind gleich zehn\n";
}
Crystal Clear app kscreensaver.png
Ausgabe:
Ein Wert oder beide Werte sind gleich zehn

Aus Gründen der Lesbarkeit sollten Vergleichsausdrücke grundsätzlich von Klammern umgeben sein. Der obige Code würde folglich so aussehen:

Crystal Clear app terminal.png
int i = 10, j = 20;
if ((i == 10) && (j == 10)) {
    cout << "Beide Werte sind gleich zehn\n";
}
if ((i == 10) || (j == 10)) {
    cout << "Ein Wert oder beide Werte sind gleich zehn\n";
}
Crystal Clear app kscreensaver.png
Ausgabe:
Ein Wert oder beide Werte sind gleich zehn
Symbol opinion vote.svg
Hinweis
Beachten Sie bitte, dass der UND-Operator ( &&) eine höhere Priorität als der ODER-Operator ( ||) hat. Das heißt, Sie müssen bei Ausdrücken wie dem Folgenden vorsichtig sein.
Crystal Clear app terminal.png
int i = 10, j = 20;
// Erwartete Reihenfolge    ((((i == 10) || (j == 20)) && (j == 20)) && (i == 5))
// Tatsächliche Reihenfolge ((i == 10) || (((j == 20) && (j == 20)) && (i == 5)))
if (i == 10 || j == 20 && j == 20 && i == 5) {
    cout << "i ist Zehn und fünf oder (j ist Zwanzig und i fünf)!\n";
} else {
    cout << "i ist nicht Zehn oder (j ist Zwanzig oder i nicht fünf)!\n";
}
Crystal Clear app kscreensaver.png
Ausgabe:
i ist Zehn und fünf oder (j ist Zwanzig und i fünf)!
Die Ausgabe ist von der Logik her falsch, weil der Ausdruck in der Reihenfolge ((i == 10) || (((j == 20) && (j == 20)) && (i == 5))) ausgewertet wird. Solche Fehler sind sehr schwer zu finden, also sollten Sie sie auch nicht machen. Daher der Tipp: Verwenden Sie bei solch komplexen Bedingungen immer Klammern, um klar zu machen, in welcher Reihenfolge Sie die Ausdrücke auswerten wollen. Das ist unmissverständlich, und der menschlicher Leser liest den Ausdruck genauso wie der Compiler. Um zu erkennen, an welcher Stelle eine Klammer wieder geschlossen wird, beherrschen die meisten Editoren das sogenannte Bracket Matching. Dabei hebt der Editor (automatisch oder über einen bestimmten Hotkey) die schließende Klammer hervor.

Die Operatoren lassen sich übersichtlich mit Wahrheitstafeln beschreiben:

Logisches Und
a true true false false
b true false true false
a && b true false false false


Logisches Oder
a true true false false
b true false true false
a || b true true true false
Logisches Nicht
a true false
!a false true
Symbol kept vote.svg
Tipp

Wenn Sie mit mehreren && und || arbeiten, dann schreiben Sie den Ausdruck, der am wahrscheinlichsten zutrifft, auch am weitesten links, also vor den anderen Ausdrücken. Das ist eine (zugegebenermaßen) sehr geringfügige Optimierung, aber es gibt Situationen in denen Sie trotzdem sinnvoll ist. Beispielsweise wenn die Bedingung innerhalb einer Schleife (siehe Kapitel Schleifen) sehr oft ausgeführt wird.

Hinweis für fortgeschrittene Leser: Beachten Sie bitte, dass für überladende Operatoren andere Regeln gelten. Alle diejenigen, die noch nicht wissen, was überladende Operatoren sind, brauchen sich um diesen Hinweis (noch) nicht zu kümmern.

[Bearbeiten] Bedingter Ausdruck

Häufig werden Verzweigungen eingesetzt, um abhängig vom Wert eines Ausdrucks eine Zuweisung vorzunehmen. Das können Sie mit dem Auswahloperator ? ... : ... auch einfacher formulieren:

Crystal Clear app terminal.png
min = a < b ? a : b;
// Alternativ ginge:
if (a < b) {
    min = a;
} else {
    min = b;
}

Grafisch sieht das so aus:

KonditionalOperator1.png

Der Variablen min wird der kleinere, der beiden Werte a und b zugewiesen. Analog zum Verhalten der logischen Operatoren wird nur derjenige „Zweig“ bewertet, der nach Auswertung der Bedingung ( a < b) tatsächlich ausgeführt wird.

Schleifen

Mit dem, was Sie bis jetzt gelernt haben, sollte es für Sie eine leichte Übung sein, die Zahlen von eins bis zehn ausgeben zu lassen. So könnte ein Programm aussehen, das dies tut:

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    cout << "1\n";
    cout << "2\n";
    cout << "3\n";
    cout << "4\n";
    cout << "5\n";
    cout << "6\n";
    cout << "7\n";
    cout << "8\n";
    cout << "9\n";
    cout << "10\n";
}
Crystal Clear app kscreensaver.png
Ausgabe:
1
2
3
4
5
6
7
8
9
10

Dieses Programm ist einfach – aber was wäre, wenn die Zahlen eins bis einer Million ausgegeben werden sollen? Oder schlimmer noch - Ja, es geht noch schlimmer - die Ausgabe hängt von der Benutzereingabe ab. Dann müssten Sie von der größtmöglichen Zahl ausgehen (bei unsigned int üblicherweise 4.294.967.295) und auch noch nach jeder Eingabe überprüfen, ob die vom Benutzer eingegebene Zahl erreicht ist.

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    unsigned int i=1, benutzer;  // 2 Variablen anlegen

    cin >> benutzer;             // Benutzer gibt Zahl ein

    if (i <= benutzer) {         // Benutzereingabe erreicht?
        cout << "1\n";           // Ausgabe der Zahl 1
        ++i;                     // Ausgegebene Zahlen mitzählen
    } else return 0;             // Anwendung beenden
    if (i <= benutzer) {         // Benutzereingabe erreicht?
        cout << "2\n";           // Ausgabe der Zahl 2
        ++i;                     // Ausgegebene Zahlen mitzählen
    } else return 0;             // Anwendung beenden
    // ...
    if (i <= benutzer) {         // Benutzereingabe erreicht?
        cout << "4294967295\n";  // Ausgabe der Zahl 4294967295
        ++i;                     // Ausgegebene Zahlen mitzählen
    } else return 0;             // Anwendung beenden

    // Wenn Ihr Compiler diese Stelle erreichen soll, brauchen Sie einen leistungsstarken
    // Rechner und ein robustes Betriebssystem.
    // Um dieses Problem zu lösen, setzen Sie sich einfach in die nächste Zeitmaschine
    // und bestellen Sie sich einen Rechner aus dem Jahr 2020!
}

Es sei Ihnen überlassen, den fehlenden Code von Hand einzutragen und wenn Sie fertig sind können Sie mit Stolz behaupten, dass Sie ein simples Problem mit einer Anzahl von über 17,1 Milliarden Zeilen Code gelöst haben. Die GCC hat gerade einmal etwas mehr als 2,1 Millionen Zeilen Code, aber da geht es ja auch nur darum, Compiler zu schreiben und nicht Zahlen auszugeben. Trotzdem, die Unterschiede zwischen dem Compilerbau-Projekt und unserem hoch komplexen Programm zum Ausgeben einer durch den Benutzer angegebenen Menge von Zahlen ist dermaßen horrend, dass es da doch eigentlich noch einen Trick geben muss.

Und tatsächlich, denn an diesem Punkt kommen Schleifen ins Spiel. Bis jetzt wurden alle Programme einfach der Reihe nach abgearbeitet und zwischendurch wurde eventuell mal eine Abzweigung genommen. Mit einer Schleife können Sie erreichen, dass ein Programmteil mehrfach abgearbeitet wird. C++ stellt 3 Schleifenkonstrukte zur Verfügung. Die kopfgesteuerte while-Schleife, die fußgesteuerte do-while-Schleife und die ziemlich universelle (ebenfalls kopfgesteuerte) for-Schleife. Was kopf- und fußgesteuerte Schleife bedeutet erfahren Sie in Kürze. Vielleicht wissen Sie es aber auch schon aus dem Kapitel „Grundlegende Elemente“, aus dem Abschnitt „Für Programmieranfänger“.

[Bearbeiten] Die while-Schleife

Eine while-Schleife hat die folgende allgemeine Form:

Crystal Clear app tutorials.png
Syntax:
while(«Bedingung») «Anweisung»

Natürlich können Sie, wie bei den Verzweigungen auch, hier wieder mehrere Anweisungen zu einem Anweisungsblock zusammenfassen, da diese als eine Anweisung gilt:

Crystal Clear app tutorials.png
Syntax:
while(«Bedingung»){
    «Anweisungen»
}

Solange die Bedingung erfüllt ist, wird die Anweisung oder der Anweisungsblock ausgeführt. Da es sich hier um eine kopfgesteuerte Schleife handelt, wird erst die Bedingung ausgewertet. Ist diese erfüllt, so wird die Anweisung ausgeführt und dann erneut die Bedingung überprüft. Ist die Bedingung nicht erfüllt, wird der Schleifeninhalt kurzerhand übersprungen und mit dem Quelltext nach der Schleife fortgesetzt. Was eine fußgesteuerte Schleife macht, erfahren Sie unter der Überschrift do-while. Eine Gegenüberstellung der beiden Schleifen gibt es in der Zusammenfassung dieses Kapitels.

Symbol kept vote.svg
Tipp

Wie die Bedingungen ausgewertet werden, können Sie im Kapitel „Verzweigungen“ nachlesen. Einige von Ihnen werden sich vielleicht noch daran erinnern, dass dies das vorherige Kapitel war, aber für die Gedächtnisschwachen unter Ihnen und jene die es einfach nicht lassen können in der Mitte mit dem Lesen zu beginnen, steht es hier noch mal Schwarz auf Grün.

Symbol opinion vote.svg
Hinweis

Beachten Sie, dass Schleifen so lange ausgeführt werden, bis die Bedingung nicht mehr erfüllt ist. Wenn Sie also nicht innerhalb der Schleife dafür sorgen, dass die Bedingung irgendwann nicht mehr erfüllt ist, dann haben Sie eine sogenannte Endlosschleife. Das heißt, der Schleifenrumpf (so nennt man die Anweisung oder den Anweisungsblock einer Schleife) wird immer wieder ausgeführt. Das Programm wird also nie beendet. In Kombination mit einem Schlüsselwort, das Sie in Kürze kennen lernen werden, kann eine solche Endlosschleife durchaus gewollt und sinnvoll sein, aber in der Regel entsteht so etwas versehentlich. Wenn Ihr Programm also mal „abgestützt“ ist, im Sinne von „Es reagiert nicht mehr! Wie schrecklich!“, dann haben Sie fast todsicher irgendwo eine Endlosschleife fabriziert.

Nun aber zurück zu unserer Schleifenaufgabe mit 17,1 Milliarden Zeilen Code. Da Ihr Compiler immer noch nicht damit fertig ist, die oben vorgestellte Lösung zu übersetzen, versuchen wir jetzt mal das ganze mit einer while-Schleife zu lösen.

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    unsigned int i=1, benutzer;  // 2 Variablen anlegen

    cin >> benutzer;             // Benutzer gibt Zahl ein

    while (i <= benutzer) {      // Benutzereingabe erreicht?
        cout << i << endl;       // Ausgabe von i und einem Zeilenumbruch
        i++;                     // i um eins erhöhen (-> ausgegebene Zahlen mitzählen)
    }
}

Nun ja, das sieht dem Programm von oben doch irgenwie ähnlich, nur die knapp 4,3 Milliarden if-Anweisungen sind weggefallen und haben einer vom Aufbau fast identischen while-Schleife platz gemacht. Nun haben wir natürlich ein Problem: Ihr Superrechner aus dem Jahr 2020 ist immer noch mit der Übersetzung der ersten Programmversion beschäftigt und wird es wohl auch noch bis zu seiner Erfindung bleiben. Aber zum Glück haben Sie ja noch einen alten Rechner von 1980. Also versuchen Sie das Programm auf diesem zu übersetzen und auszuführen. Tatsächlich. Es funktioniert. Erstaunlich, dass so eine alte Kiste einen Rechner überholt, den Sie sich extra aus der Zukunft haben liefern lassen, um die maximale Leistungsstärke zu bekommen.

Wenn Sie Schwierigkeiten haben das Beispiel nachzuvollziehen, dann sehen Sie sich noch mal die Schleifen-Version für die Zahlen von 1 bis 10 an.

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    int i = 1;                // Anlegen von i

    while (i <= 10) {         // Ist i noch kleiner-gleich 10?
        cout << i << endl;    // Ausgabe von i und neue Zeile
        i++;                  // i um eins erhöhen
    }
}
Crystal Clear app kscreensaver.png
Ausgabe:
1
2
3
4
5
6
7
8
9
10

So sieht das Ganze schon viel kompakter und vielleicht auch übersichtlicher aus als die Version von ganz oben. Die Ausgabe dagegen ist völlig identisch. Hier wird i am Anfang auf eins gesetzt. Somit ist die Bedingung (eins ist kleiner oder gleich zehn) erfüllt. Damit wird der Schleifenrumpf ausgeführt. „1“ wird ausgegeben. Es folgt ein Zeilenumbruch. Danach wird der Wert von i um eins erhöht. Danach wird wieder geprüft, ob die Bedingung erfüllt ist. Da i jetzt zwei ist, lautet die Bedingung „zwei ist kleiner oder gleich zehn“, da dies eine wahre Aussage ist, wird wieder der Schleifenrumpf ausgeführt. Das wiederholt sich bis i Schließlich den Wert elf hat. Die Bedingung lautet dann „elf ist kleiner oder gleich zehn“, diese Aussage ist zweifellos falsch. Daher wird der Schleifenrumpf nun übersprungen und mit dem Code dahinter weitergemacht. Da in unserem Beispiel dort aber kein Code mehr folgt, wird das Programm beendet.

Zusammengefasst:

1: Quelltext vor der Schleife
2: Schleifen Bedingung
  • Erfüllt:
2.1: Schleifenrumpf
2.2: weiter mit 2
  • Nicht Erfüllt:
2.1: weiter mit 3
3: Quelltext nach der Schleife

[Bearbeiten] Die do-while-Schleife

Wie versprochen lüften wir nun das Geheimnis um die fußgesteuerten Schleifen. do-while ist eine fußgesteuerte Schleife, dass heißt, als erstes wird der Schleifenrumpf ausgeführt, danach die Bedingung überprüft und dann abhängig von der Bedingung, wieder der Rumpf ausgeführt (Bedingung erfüllt) oder mit dem Quelltext nach der Schleife fortgesetzt (Bedingung nicht erfüllt). Eine fußgesteuerte Schleife zeichnet sich also dadurch aus, dass der Schleifenrumpf mindestens ein mal ausgeführt wird.

Crystal Clear app tutorials.png
Syntax:
do «Anweisung» while(«Bedingung»);

Bei dieser Schleife finden wir die obige Syntax nur selten, da der Schleifenrumpf hier zwischen den beiden Schlüsselwörtern do und while steht, wird fast immer ein Anweisungsblock benutzt, auch wenn nur eine Anweisung vorhanden ist. Für Ihren Compiler spielt das natürlich keine Rolle, aber für einen Menschen der den Quelltext liest ist es übersichtlicher.

Crystal Clear app tutorials.png
Syntax:
do{
    «Anweisungen»
}while(«Bedingung»);

Unser anfängliches Riesenprogramm sieht mit einer do-while Schleife so aus:

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    unsigned int i=1, benutzer;  // 2 Variablen anlegen

    cin >> benutzer;             // Benutzer gibt Zahl ein

    do {                         // Schleifenanfang
        cout << i << endl;       // Ausgabe von i
        ++i;                     // Ausgegebene Zahlen mitzählen
    } while (i <= benutzer);     // Benutzereingabe erreicht?
}

Sie werden feststellen das die Ausgabe dieses Programms mit der Ausgabe in der while-Schleifen Version übereinstimmt. Den Unterschied bemerken Sie, wenn Sie 0 eingeben, während die while-Version keine Ausgabe macht, gibt diese do-while-Version „1“ und einen Zeilenumbruch aus, denn der Schleifenrumpf wird im erst einmal ausgeführt, erst danach wird die Bedingung überprüft und entschieden ob er noch einmal ausgeführt werden muss.

Zusammengefasst:

1: Quelltext vor der Schleife
2: Schleifenrumpf
3: Schleifen Bedingung
  • Erfüllt:
3.1: weiter mit 2
  • Nicht Erfüllt:
3.1: weiter mit 4
4: Quelltext nach der Schleife

[Bearbeiten] Die for-Schleife

Die for-Schleife ist etwas komplexer als die vorherigen beiden Schleifen. Sie gliedert sich in Teile:

Crystal Clear app tutorials.png
Syntax:
for(«Initialisierungsteil»; «Bedingungsteil»; «Anweisungsteil») «Schleifenrumpf»

Um etwas Platz zu sparen wurde in dieser Beschreibung einfach Schleifenrumpf geschrieben, anstatt wieder einzeln aufzuführen das sowohl eine einzelne (durch Semikolon abgeschlossene) Anweisung, als auch ein Anweisungsblock möglich sind. Der Bedingungsteil verhält sich genau wie bei der while- und der do-while-Schleife oder sagen wir fast genau so, denn einen kleinen aber feinen Unterschied gibt es doch. Während while und do-while immer eine Bedingung erwarten, muss bei einer for-Schleife nicht unbedingt eine Bedingung angegeben werden. Wenn keine Bedingung angegeben ist, wird einfach angenommen das die Bedingung immer erfüllt ist, Sie erhalten eine Endlosschleife. Wie das sinnvoll eingesetzt wird erfahren Sie in kürze.

Im Anweisungsteil können Sie eine beliebige Anweisung ausführen, dieser Teil wird oft verwendet, um Variablen bei jedem Schleifendurchlauf hoch oder runter zu zählen. Im nächsten Beispiel wird dies auch demonstriert. Es ist auch möglich, mehrere solcher „hoch- oder runter-Zähl“-Anweisungen durch Komma getrennt anzugeben, das wird im nächstem Beispiel aber nicht gemacht. Der Anweisungsteil wird übrigens direkt nach dem Schleifenrumpf und vor dem nächsten Bedingungstest ausgeführt. Der Initialisierungsteil ist dem Anweisungsteil dahingehend ähnlich, als dass auch hier eine beliebige Anweisung ausgeführt werden kann. Zusätzlich ist es hier aber noch möglich Variablen eines Datentyps anzulegen. Sie können also problemlos 2 int-Variablen anlegen, aber nicht eine int- und eine char-Variable. Der Initialisierungsteil wird nur einmal am Beginn der Schleife ausgeführt.

Symbol move vote.svg
Thema wird später näher erläutert…

Im Kapitel Lebensdauer und Sichtbarkeit von Variablen werden Sie noch etwas genaueres darüber erfahren wo Sie die im Initialisierungsteil der Schleife angelegten Variablen verwenden können. Dort werden Sie auch erfahren wie eine for-Schleife mit Hilfe einer while-Schleife „nachgebaut“ werden kann.

Jetzt sollten Sie sich jedoch merken das Sie eine solche Variable, nur innerhalb der Schleife, also nicht mehr nach ihrem verlassen, verwenden können.

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    unsigned int benutzer;                       // Variablen für Benutzereingabe

    cin >> benutzer;                             // Benutzer gibt Zahl ein

    for(unsigned int i = 1; i <= benutzer; ++i)  // for-Schleife
        cout << i << endl;                       // Ausgabe von i
}

Zusammengefasst:

1: Quelltext vor der Schleife
2: Initialisierungsteil der Schleife
3: Schleifen Bedingung
  • Erfüllt:
3.1: Schleifenrumpf
3.2: Anweisungsteil
3.3: weiter mit 3
  • Nicht Erfüllt:
3.1: weiter mit 4
4: Quelltext nach der Schleife

[Bearbeiten] Die break-Anweisung

Jetzt ist es an der Zeit, das Geheimnis um die sinnvolle Verwendung von Endlosschleifen zu enthüllen. Die break-Anweisung wird innerhalb von Schleifen verwendet, um die Schleife sofort zu beenden. Der Quelltext wird dann ganz normal nach der Schleife fortgesetzt. break können Sie in jeder der drei Schleifen verwenden. Das folgende Beispiel demonstriert den Einsatz von break, anhand unseres Lieblingsprogramms in diesem Kapitel.

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    unsigned int benutzer, i=1;    // Variablen für Benutzereingabe

    cin >> benutzer;               // Benutzer gibt Zahl ein

    for(;;){                       // Endlos-for-Schleife
        cout << i << "\n";         // Ausgabe von i
        ++i;                       // Variable erhöhen
        if(i>benutzer)break;       // Abbrechen, wenn Bedingung erfüllt
    }
}

Sie können aus jeder Schleife eine Endlosschleife machen, hier wurde die for-Schleife gewählt um zu zeigen wie Sie ohne Bedingung aussieht. Bei einer while- oder do-while-Schleife könnten Sie beispielsweise true als Bedingung angeben. Die for-Schleife legt jetzt übrigens das gleiche Verhalten an den Tag, wie eine do-while-Schleife. Sie können auch problemlos nur einen oder zwei Teile des Schleifenkopfes bei for-Schleife übergeben, wichtig ist nur dass Sie die beiden Semikolons immer angeben, da Sie dem Compiler mitteilen, was welcher Teil des Schleifenkopfes ist.

Nun wissen Sie wieder etwas mehr über for, dabei sollte es unter dieser Überschrift doch eigentlich um break gehen. Wie Sie aber sehen, hängt letztlich alles mit allem zusammen und so ist es oft schwer eine richtige Abgrenzung zu schaffen.

[Bearbeiten] Die continue-Anweisung

Das zweite wichtige Schlüsselwort für Schleifen ist continue. Es wird genau so verwendet wie break, bricht die Schleife allerdings nicht völlig ab, sondern setzt die Codeausführung am Endes des Schleifenrumpfes fort. Für while und do-while bedeutet das beim Bedingungstest, für die for-Schleife beim Anweisungsteil. Mit continue können Sie also den Rest des aktuellen Schleifendurchlaufs überspringen. Wir sehen uns das wieder anhand des Beispiels an.

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    unsigned int benutzer;                        // Variablen für Benutzereingabe

    cin >> benutzer;                              // Benutzer gibt Zahl ein

    for(unsigned int i = 1; i <= benutzer; ++i) { // for-Schleife
        if (i%2 == 0) continue;                   // alle geraden Zahlen überspringen
        cout << i << endl;                        // Ausgabe von i
    }
}

Hier werden nur ungrade Zahlen ausgegeben, da der %-Operator (liefert den Rest einer Ganzzahligen Division) bei allen geraden Zahlen, (teilt man durch 2 ist als Rest ja nur 0 oder 1 möglich,) 0 zurückliefert und somit continue ausgeführt wird.

Natürlich könnten Sie das auch noch auf eine andere Weise realisieren. Aber es gibt ja beim Programmieren viele Wege, die nach Rom führen, wie in diesem Kapitel anhand der verschieden Schleifen schon bewiesen wurde. Leider gibt es aber noch mehr Wege, die auf direktem Wege an Rom vorbeiführen... Aber hier ist für das Beispiel von eben noch ein Pfad, der sicher nach Rom führt:

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main() {
    unsigned int benutzer;                         // Variablen für Benutzereingabe

    cin >> benutzer;                               // Benutzer gibt Zahl ein

    for(unsigned int i = 1; i <= benutzer; i += 2) // for-Schleife mit 2er Schritten
        cout << i << endl;                         // Ausgabe von i
}

[Bearbeiten] Kapitelanhang

Crystal source cpp.png

Im Anhang zu diesem Kapitel finden Sie:

  • Aufgaben und zugehörigen Musterlösungen.
Aufgaben

Symbol neutral vote.svg Aufgabe 1:

Schreiben Sie mithilfe einer Schleife ein kleines Spiel. Der Spielablauf lauten:

  • Am Anfang gibt der Spieler einen Zahlenbereich ein. (Zum Beispiel: 1-100)
  • Der Spieler muss sich innerhalb dieses Bereiches eine Zahl merken (eingegebene Grenzzahlen sind nicht zulässig).
  • Das Programm soll dann die Zahl erraten. Der Benutzer teilt dem Programm mit, ob die Zahl an die er denkt kleiner, größer oder gleich der vom Programm geratenen Zahl ist. Die kann zum Beispiel über die Eingabe von <, > und = erfolgen.

Auswahl

Verzweigungen kennen Sie schon, daher sollte es Ihnen nicht schwer fallen, ein Programm zu schreiben, welches abfragt, welche der verschiedenen, in der Überschrift benannten Leckereien, Sie am liebsten mögen. Das ganze könnte etwa so aussehen:

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main(){
    int auswahl;

    cout << "Wählen Sie Ihre Lieblingsleckerei:\n"
            "1 - Käsesahnetorte\n"
            "2 - Streuselkuchen\n"
            "3 - Windbeutel\n";

    cin >> auswahl;

    if(auswahl==1)
        cout << "Sie mögen also Käsesahnetorte!\n";
    else if(auswahl==2)
        cout << "Streuselkuchen ist ja auch lecker...\n";
    else if(auswahl==3)
        cout << "Windbeutel sind so flüchtig wie ihr Name, das können Sie sicher bestätigen?\n";
    else
        cout << "Wollen Sie wirklich behaupten, das Ihnen nichts davon zusagt?\n";

    return 0;
}
Crystal Clear app kscreensaver.png
Ausgabe:
Wählen Sie Ihre Lieblingsleckerei:
1 - Käsesahnetorte
2 - Streuselkuchen
3 - Windbeutel
<Eingabe>2</Eingabe>
Streuselkuchen ist ja auch lecker...

Das funktioniert natürlich, aber finden Sie nicht auch, dass der (Schreib-)Aufwand ein wenig zu groß ist? Außerdem ist diese Schreibweise nicht gerade sofort einleuchtend. Für unseren Fall, wäre es schöner, wenn wir die Variable auswahl als zu untersuchendes Objekt festlegen könnten und dann einfach alle Fälle die uns interessieren durchgehen könnten. Praktischerweise bietet C++ ein Schlüsselwort, das genau dies ermöglicht.

[Bearbeiten] switch und break

Eine switch-Anweisung hat die folgende Form:

Crystal Clear app tutorials.png
Syntax:
switch(«Ganzzahlige Variable»){
    case «Ganzzahl»: «Anweisungsblock»
    case «Ganzzahl»: «Anweisungsblock»
    «...»
    default: «Anweisungsblock»
}

Auf unser Beispiel angewandt, würde das dann so aussehen:

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main(){
    int auswahl;

    cout << "Wählen Sie Ihre Lieblingsleckerei:\n"
            "1 - Käsesahnetorte\n"
            "2 - Streuselkuchen\n"
            "3 - Windbeutel\n";

    cin >> auswahl;

    switch(auswahl){
        case 1:  cout << "Sie mögen also Käsesahnetorte!";
        case 2:  cout << "Streuselkuchen ist ja auch lecker...";
        case 3:  cout << "Windbeutel sind so flüchtig wie ihr Name, das können Sie sicher bestätigen?";
        default: cout << "Wollen Sie wirklich behaupten, dass Ihnen nichts davon zusagt?";
    }

    return 0;
}
Crystal Clear app kscreensaver.png
Ausgabe:
Wählen Sie Ihre Lieblingsleckerei:
1 - Käsesahnetorte
2 - Streuselkuchen
3 - Windbeutel
<Eingabe>2</Eingabe>
Streuselkuchen ist ja auch lecker...
Windbeutel sind so flüchtig wie ihr Name, das können Sie sicher bestätigen?
Wollen Sie wirklich behaupten, dass Ihnen nichts davon zusagt?

Nun ja, Sie haben ein neues Schlüsselwort kennen gelernt, setzen es ein und kommen damit auch prompt zum falschen Ergebnis. Sieht so aus, als würde jetzt jeder der Sätze ab dem Ausgewählten ausgegeben. In der Tat handelt switch genau so. Es führt alle Anweisungen, ab dem Punkt aus, an dem das case-Argument mit der übergebenen Variable (in unserem Fall auswahl) übereinstimmt. Um das Ausführen nachfolgender Anweisungen innerhalb von switch zu verhindern, benutzen Sie das Schlüsselwort break. Unser Beispiel sieht dann also folgendermaßen aus:

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main(){
    int auswahl;

    cout << "Wählen Sie Ihre Lieblingsleckerei:\n"
            "1 - Käsesahnetorte\n"
            "2 - Streuselkuchen\n"
            "3 - Windbeutel\n";

    cin >> auswahl;

    switch(auswahl){
        case 1:
            cout << "Sie mögen also Käsesahnetorte!";
            break;
        case 2:
            cout << "Streuselkuchen ist ja auch lecker...";
            break;
        case 3:
            cout << "Windbeutel sind so flüchtig wie ihr Name, das können Sie sicher bestätigen?";
            break;
        default:
            cout << "Wollen Sie wirklich behaupten, dass Ihnen nichts davon zusagt?";
    }

    return 0;
}
Crystal Clear app kscreensaver.png
Ausgabe:
Wählen Sie Ihre Lieblingsleckerei:
1 - Käsesahnetorte
2 - Streuselkuchen
3 - Windbeutel
<Eingabe>2</Eingabe>
Streuselkuchen ist ja auch lecker...

Nun haben Sie wieder das ursprüngliche, gewünschte Verhalten. Was Sie noch nicht haben, ist eine Erklärung, warum extra ein break aufgerufen werden muss, um das Ausführen folgender case-Zweige zu unterbinden. Aber das werden Sie gleich erfahren.

[Bearbeiten] Nur Ganzzahlen

Wie bereits aus der oben stehenden Syntaxangabe hervorgeht, kann switch nur mit Ganzzahlen benutzt werden. Dies hängt damit zusammen, dass switch ausschließlich auf Gleichheit mit einem der case-Zweige vergleicht. Bei int-Werten (mit der üblichen Größe von 4 Byte) ist das noch eine „überschaubare“ Menge von maximal 4.294.967.296 (232) case-Zweigen. Ein float kann die gleiche Menge unterschiedlicher Zahlen darstellen, sofern er ebenfalls 4 Byte groß ist. Aber versuchen Sie mal einen genauen float-Wert aufzuschreiben. Sie arbeiten also immer mit Näherungen, wenn Sie Gleitkommazahlen in Ihrem Code benutzen. Ihr Compiler regelt das für Sie, er weist der Gleitkommazahl den zu Ihrem Wert am nächsten liegenden, darstellbaren Wert zu. Deshalb ist es so gut wie nie sinnvoll (aber natürlich trotzdem möglich), einen Test auf Gleichheit für Gleitkommazahlen durchzuführen. Bei switch wurde das jedoch unterbunden.

[Bearbeiten] Zeichen und mehrere case-Zweige

Eine if-Anweisung trifft Ihre Entscheidung hingegen, anhand der Tatsache ob eine Bedingung erfüllt ist, oder nicht. Für die if-Anweisung gibt es also nur zwei Möglichkeiten. Allerdings gibt es eine ganze Menge von Möglichkeiten, wie der Zustand true, oder false erreicht werden kann. Die Rede ist von Vergleichen und Verknüpfungen ( wie && (= und) oder || (= oder) ) und der Umwandlung von anderen Datentypen nach true, oder false. Für ganzzahlige Datentypen gilt etwa, dass jeder Wert ungleich 0 true ergibt, aber 0 hingegen wird als false ausgewertet. Für eine genaue Beschreibung dessen, was hier noch mal kurz umrissen wurde, sehen Sie sich das Kapitel über Verzweigungen an.

Also gut, switch kann nur auf Gleichheit testen und erlaubt auch keine Verknüpfungen. Oder sagen wir besser: fast keine. Denn genau deshalb gibt es diese scheinbar aufwendige Regelung mit dem break. Das folgende Beispiel demonstriert die Verwendung von Zeichen und die Anwendung von mehreren case-Zweigen, die den gleichen Code ausführen (was einer Verknüpfung mit || zwischen mehreren Vergleichen mittels == gleichkommt):

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main(){
    char auswahl;

    cout << "Wählen Sie Ihre Lieblingsleckerei:\n"
            "K - Käsesahnetorte\n"
            "S - Streuselkuchen\n"
            "W - Windbeutel\n";

    cin >> auswahl;

    switch(auswahl){
        case 'k':
        case 'K':
            cout << "Sie mögen also Käsesahnetorte!";
            break;
        case 's':
        case 'S':
            cout << "Streuselkuchen ist ja auch lecker...";
            break;
        case 'w':
        case 'W':
            cout << "Windbeutel sind so flüchtig wie ihr Name, das können Sie sicher bestätigen?";
            break;
        default:
            cout << "Wollen Sie wirklich behaupten, dass Ihnen nichts davon zusagt?";
    }

    return 0;
}
Crystal Clear app kscreensaver.png
Ausgabe:
Wählen Sie Ihre Lieblingsleckerei:
K - Käsesahnetorte
S - Streuselkuchen
W - Windbeutel
<Eingabe>s</Eingabe>
Streuselkuchen ist ja auch lecker...

Dieses Programm benutzt die Anfangbuchstaben anstatt der bisherigen Durchnummerierung für die Auswahl genutzt. Außerdem ist es möglich sowohl den großen, als auch den kleinen Buchstaben einzugeben. Beides führt den gleichen Code aus.

Ein Taschenrechner wird geboren

In diesem Kapitel wollen wir einen kleinen Taschenrechner für die Kommandozeile schreiben. Dieser wird in späteren Kapiteln noch verbessert, aber fürs Erste lernt er nur die vier Grundrechenarten und kann auch nur mit je 2 Zahlen rechnen.

[Bearbeiten] Die Eingabe

Ein Aufgabe besteht aus zwei Zahlen, die durch ein Rechenzeichen getrennt sind. Für die Zahlen verwenden wir den Typ double. Das Rechenzeichen lesen wir als char ein. Außerdem brauchen wir noch eine Variable in der wir das Ergebnis speichern. Diese soll ebenfalls vom Typ double sein.

Crystal Clear app terminal.png
double zahl1, zahl2, ergebnis;
char rechenzeichen;

cin >> zahl1 >> rechenzeichen >> zahl2;

[Bearbeiten] Die 4 Grundrechenarten

Wie sicher jeder weiß, sind die 4 Grundrechenarten Addition ( +), Subtraktion ( -), Multiplikation ( *) und Division ( /). Da die Variable rechenzeichen vom Typ char und char ein ganzzahliger Typ ist, bietet es sich an, die switch-Anweisung zu verwenden um die richtige Rechnung zu ermitteln und durchzuführen. Wenn ein ungültiges Rechenzeichen eingegeben wird, geben wir eine Fehlermeldung aus und beenden das Programm.

Crystal Clear app terminal.png
switch(rechenzeichen){
    case '+': ergebnis = zahl1+zahl2; break;
    case '-': ergebnis = zahl1-zahl2; break;
    case '*': ergebnis = zahl1*zahl2; break;
    case '/': ergebnis = zahl1/zahl2; break;
    default: cout << "unbekanntes Rechenzeichen...\n"; return 1;
}

Der Rückgabewert 1 - ausgelöst von return 1; - beendet das Programm, die 1 (und jeder andere Wert ungleich   auch) sagt dabei aus, dass im Programm ein Fehler auftrat.

[Bearbeiten] Das ganze Programm

Hier ist noch einmal eine Zusammenfassung unseres Taschenrechners:

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main(){
    double zahl1, zahl2, ergebnis;                // Variablen für Zahlen
    char rechenzeichen;                           // Variable fürs Rechenzeichen

    cout << "Geben Sie eine Rechenaufgabe ein: "; // Eingabeaufforderung ausgeben
    cin >> zahl1 >> rechenzeichen >> zahl2;       // Aufgabe einlesen

    switch(rechenzeichen){                        // Wert von rechenzeichen ermitteln
        case '+': ergebnis = zahl1+zahl2; break;  // entsprechend dem
        case '-': ergebnis = zahl1-zahl2; break;  // Rechenzeichen
        case '*': ergebnis = zahl1*zahl2; break;  // das Ergebnis
        case '/': ergebnis = zahl1/zahl2; break;  // berechnen
        // Fehlerausgabe und Programm beenden, falls falsches Rechenzeichen eingegeben wurde
        default: cout << "unbekanntes Rechenzeichen...\n"; return 1;
    }

    // Aufgabe noch mal komplett ausgeben
    cout << zahl1 << ' ' << rechenzeichen << ' ' << zahl2 << " = " << ergebnis << '\n';
}
Crystal Clear app kscreensaver.png
Ausgabe:
Geben Sie eine Rechenaufgabe ein: <Eingabe>99 / 3</Eingabe>
99 / 3 = 33

Zusammenfassung

Ein C++-Programm beginnt mit der Hauptfunktion main().

[Bearbeiten] Ein- und Ausgabe

Die Ein- und Ausgabe erfolgt in C++ über Streams, mehr dazu erfahren Sie in einem der folgenden Abschnitte. Um die Ein- und Ausgabe zu nutzen müssen Sie die Headerdatei iostream einbinden. Die Streamobjekte cin und cout liegen im Namensraum std. Auf Elemente innerhalb eines Namensraums wird mit dem Bereichsoperator :: zugegriffen.

Crystal Clear app terminal.png
#include <iostream>

int main(){
    std::cout << "Das wird Ausgegeben.\n";

    int wert;
    std::cin >> wert;
    std::cout << "Wert ist: " << wert << std::endl;
}

endl fügt einen Zeilenumbruch ein und leert anschließend den Ausgabepuffer.

Mit der using-Direktive könne auch alle Elemente eines Namensraums verfügbar gemacht werden.

Crystal Clear app terminal.png
#include <iostream>

using namespace std;

int main(){
    cout << "Das wird Ausgegeben.\n";

    int wert;
    cin >> wert;
    cout << "Wert ist: " << wert << endl;
}

[Bearbeiten] Kommentare

Es gibt in C++ zwei Arten von Kommentaren

Crystal Clear app terminal.png
// Kommentare über eine Zeile

/* und Kommentare über mehrere
Zeilen*/

[Bearbeiten] Rechnen

C++ beherrscht die 4 Grundrechenarten, die Operatoren lauten +, -, * und /. Es gilt Punkt- vor Strichrechnung und Klammern ändern die Auswertungsreihenfolge. Weiterhin bietet C++ mit dem Zuweisungsoperator kombinierte Rechenoperatoren:

Crystal Clear app terminal.png
lvalue += rvalue;
lvalue -= rvalue;
lvalue *= rvalue;
lvalue /= rvalue;

Für Ganzzahlige Datentypen stehen weiterhin der Inkrement- und der Dekrementoperator, jeweils in einer Prä- und einer Postfixvariante zur Verfügung:

Crystal Clear app terminal.png
++lvalue; // Erhöht den Wert und gibt ihn zurück
lvalue++; // Erhöht den Wert und gibt den alten Wert zurück
--lvalue; // Erniedriegt den Wert und gibt ihn zurück
lvalue--; // Erniedriegt den Wert und gibt den alten Wert zurück

Die Rückgabe der Präfixvariante ist ein lvalue, die Postfixvariante gibt ein rvalue zurück.

[Bearbeiten] Variablen

Die Syntax zur Deklaration einer Variable lautet:

Crystal Clear app tutorials.png
Syntax:
«Datentyp» «Name»;

[Bearbeiten] Datentypen

Die C++-Basisdatentypen sind:

  • Typlos
    • void (wird später erläutert)
  • Logisch
    • bool
  • Zeichen (auch Ganzzahlig)
    • char ( signed char und unsigned char)
    • wchar_t
  • Ganzzahlig
    • short ( signed short und unsigned short)
    • int ( signed int und unsigned int)
    • long ( signed long und unsigned long)
  • Gleitkommazahlen
    • float
    • double
    • long double

Die Typmodifizierer signed und unsigned geben an, ob es sich um einen Vorzeichenbehafteten oder Vorzeichenlosen Typ handelt. Ohne diese Modifizierer wird ein Vorzeichenloser Typ angenommen, außer bei char. Hier ist es Implementierungs­abhängig. signed und unsigned können ohne Datentyp verwendet werden, dann wird int als Typ angenommen.

Das Schlüsselwort const zeichnet einen Datentyp als konstant aus. Es steht links vom, zu dem es gehört, außer links steht nichts mehr, in diesem Fall gehört es zu dem was rechts davon steht. Es ist somit egal ob Sie const vor oder hinter einen Basisdatentyp schreiben:

Crystal Clear app tutorials.png
Syntax:
const «Datentyp» «Variablenname»;
«Datentyp» const «Variablenname»;

C++ macht keine Angaben über die Größen der Datentypen, es ist lediglich folgendes festgelegt:

  • char <= short <= int <= long
  • float <= double <= long double

[Bearbeiten] Initalisierung

In C++ gibt es 2 Möglichkeiten für eine Initialisierung:

Crystal Clear app tutorials.png
Syntax:
Datentyp «Variablenname» = «Wert»;
Datentyp «Variablenname»(«Wert»);

Die erste Variante ist verbreiteter, aber nur möglich wenn zur Initialisierung nur 1 Wert benötigt wird. Alle Basisdatentypen erwarten genau einen Wert.

[Bearbeiten] Typaufwertung

Wird ein Operator auf verschiedene Datentypen angewendet, müssen vor der Operation beide in den gleichen Typ konvertiert werden. Der Zieltyp ist üblicherweise der kleinste, der beide Werte darstellen kann. Für genauere Informationen lesen Sie bitte im Kapitel Rechnen mit unterschiedlichen Datentypen.

[Bearbeiten] Kontrollstrukturen

[Bearbeiten] Anweisungsblöcke

Mit einem Anweisungsblock können mehrere Anweisungen zusammengefasst werden. Er kann überall stehen wo auch eine Anweisung stehen könnte. Die Syntax sieht folgendermaßen aus:

Crystal Clear app tutorials.png
Syntax:
{
    «Anweisung»
    «...»
}

[Bearbeiten] Verzweigung

Eine Verzweigung hat in C++ folgende Syntax

Crystal Clear app tutorials.png
Syntax:
if(«Bedingung»)
    «Anweisung»
else
    «Anweisung»

Die Bedingung ist immer ein Logischer Wert. Zahlenwerte werden implizit in logische Werte konvertiert.  0 entspricht dabei immer false, alle anderen Werte werden zu true umgewandelt.

[Bearbeiten] Schleifen

Es gibt 2 Arten von Schleifen, wobei die for-Schleife sehr mächtig ist und entsprechend öfter verwendet wird.

Crystal Clear app tutorials.png
Syntax:
// while-Schleife (Kopfgesteuert)
while(«Bedingung»)
    «Anweisung»

// do-while-Schleife: Alle Anweisungen werden mindestens einmal ausgeführt. (Fußgesteuert)
do
    «Anweisung»
while(«Bedingung»);

// for-Schleife: Ermöglicht die Initialisierung, die Bedingung (Kopfgesteuert) und eine Aktion,
// die nach jedem Schleifendurchlauf stattfindet, im Schleifenkopf zu platzieren
for(«Deklarationen (z.B. Zählvariablen)»; «Bedingung»; «Aktion nach einem Durchlauf (z.B. hochzählen)»)
    «Anweisung»

[Bearbeiten] Auswahl

Mit der switch-Anweisung, kann eine Variable auf mehrere (konstante) Werte verglichen werden. Jedem dieser Werte können eine oder mehrere Anweisungen folgen, welcher im Falle einer Übereinstimmung ausgeführt werden, bis das Ende des switch-Blocks erreicht wird.

Crystal Clear app tutorials.png
Syntax:
switch(«Variable»){
    case «Wert»:
        «Anweisung»
        «...»
        »break;«

    «...»

    default: // Wird ausgeführt, falls die Variable keinem der Werte entsprach
        «Anweisung»
        «...»
}
Symbol opinion vote.svg
Hinweis
Wird das break; am Ende eines Anweisungsblockes weggelassen, werden auch die darauf folgenden Blöcke ausgeführt, bis ein break; gefunden oder das Ende der switch-Anweisung erreicht ist.
Persönliche Werkzeuge