C++ Kurs

Konstanten

Die Themen:

Überblick

Eine Konstante ist ein Wert, der sich während des Programmablaufes nicht mehr verändern lässt. Ein Beispiel für eine solche Konstante ist z.B. die Zahl Pi. Wie allgemein bekannt, entspricht Pi etwa dem Wert 3.1416. Anstatt nun bei jeder Verwendung von Pi den Zahlenwert 3.1416 zu schreiben, kann (und sollte auch) dafür ein entsprechender Name verwendet werden, z.B. PI. Dieser Name wird dann anstelle des Zahlenwertes an den Stellen im Programm eingesetzt, an denen Pi verwendet wird. Wird nun während des Programmtests z.B. festgestellt, dass die Angabe von Pi zu ungenau ist, so müssen nun nicht mehr im gesamten Programm alle Pi-Werte verändert werden, sondern es wird lediglich die einmal definierte Konstante PI angepasst.

C++ kennt unter anderem folgende Arten von Konstanten:

Generell können aber von jedem beliebigen Datentyp Konstanten gebildet werden.

Außer nach der Konstantenart werden Konstanten noch nach unbenannten und benannten Konstanten unterschieden.

Literal (unbenannte Konstante)

Ein Literal ist eine direkte Angabe eines Wertes, wie z.B. die Zahl 5 oder 3.14 oder auch der String "Dies ist ein Literal".

Um die Lesbarkeit von Literalen zu verbessern, können innerhalb von Literalen einfache Hochkomma zur beliebigen Gruppierung der Ziffern verwendet werden, z.B. 1'000'000. Der tatsächliche numerische Wert ändert sich durch die Gruppierung nicht.

Ganzzahl-Literale

Ganzzahl-Literale besitzen standardmäßig den kleinsten Datentyp, in dem ihr Wert repräsentiert werden kann. Dabei wird folgende Reihenfolge durchlaufen: int, long und long long. Eine Ausnahme von dieser Reihenfolge bilden Ganzzahl-Literale die in oktaler bzw. hexadezimaler Schreibweise angegeben werden. Hier lautet die Reihenfolge: int, unsigned int, long, unsigned long, long long und unsigned long long. Literale in oktaler Schreibweise werden durch das Präfix 0 (die Null!) und Literale in hexadezimaler Schreibweise durch das Präfix 0x bzw. 0X gekennzeichnet.

Um einem Ganzzahl-Literal explizit einen bestimmten Datentyp zuzuordnen, wird der Wert mit einem Präfix oder Suffix erweitert. Bei diesen Erweiterungen wird die Groß-/Kleinschreibung ausnahmsweise ignoriert. Die Tabelle enthält eine Übersicht über die verschiedenen Erweiterungen sowie einige Beispiele dazu:

Literal ohne Suffix 3 -5 +4
Binär-Literal, Präfix 0B 0b000'0011 0b111'11011 0B100
Oktal-Literal, Präfix 0 03 012 0665
Hex-Literal, Präfix 0x 0x40 0XFF00 -0xFF
unsigned Literal, Suffix U 10U 0xFF00u 012u
long Literal, Suffix L -10L 0xFF00L 012l
unsigned long Literal, Suffix UL 10UL 0xFFul 012ul
long long, Suffix LL -10LL 0xfll -034LL
unsigned long long, Suffix ULL 345ULL 0x01ull 0564ULL
AchtungSchreiben Sie niemals 0010 wenn Sie ein Dezimal-Literal definieren wollen! Diese Zahl entspricht der Oktalzahl 10 und ist eine dezimale 8!

Gleitkomma-Literale

Gleitkomma-Literale bestehen aus Ziffern und einem Dezimalpunkt. Optional kann ein Gleitkomma-Literal noch einen Exponenten enthalten. Standardmäßig sind Gleitkomma-Literale vom Datentyp double.

Um einem Gleitkomma-Literal einen von double abweichenden Datentyp zuzuweisen, wird das Literal mit einem Suffix laut nachfolgender Tabelle erweitert. Auch hier spielt die Groß-/Kleinschreibung bei der Erweiterung keine Rolle.

double Literal, ohne Suffix -1.0 1. +3.32 -3.1E+4
float Literal, Suffix F -1.0f 1.F +3.32f -3.1E+4F
long double Literal, Suffix L   -1.0L 1.L +3.32L -3.1e+4L

Zeichen- und String-Literale

Zeichen-Literale sind einzelne Buchstaben oder auch Escape-Sequenzen, die in Hochkomma eingeschlossen werden. Sie besitzen den Datentyp char. Um ein Zeichen-Literal mit einem davon abweichenden Datentyp zu definieren, wird dem Zeichen ein Präfix laut nachfolgender Tabelle vorangestellt.

String-Literale werden in Anführungszeichen eingeschlossen und die einzelnen Zeichen innerhalb des String-Literals sind vom Datentyp char. Und auch hier gilt: um ein String-Literal mit einem davon abweichenden Datentyp zu definieren, wird dem String ein Präfix laut nachfolgender Tabelle vorangestellt.

Datentyp Präfix Zeichen String
char kein 'a' "ein String"
UTF-8 u8 u8'a' u8"ein String"
char16_t u u'a' u"ein String"
char32_t U U'a' U"ein String"
wchar_t L L'a' L"ein String"

Zudem weisen String-Literale noch folgende Besonderheiten auf:

Felder und Speicherklassen werden später noch behandelt.

Hintereinander stehende String-Literale werden stets zusammenfasst. Damit ist die nachfolgende Anweisung korrekt, da die drei String-Literale zu einem Literal zusammengefasst werden. Das Einzige worauf dabei zu achten ist, ist, dass keine String-Literale mit unterschiedlichen Datentypen hintereinander stehen dürfen.

PgmHeader
cout << "Diese ist EIN String-Literal"   // Ein String-Literal über mehrere Zeilen
        "obwohl es über mehrere"
        "Zeilen geht!\n";
AchtungC++ kennt den Stream-Operator nur für die Ein- und Ausgabe von char- und wchar_t Strings. char-Strings werden, wie bereits bekannt, mittels cout ausgegeben und wchar_t String mittels wcout. Die anderen Zeichen und String-Datentypen dienen hauptsächlich zur Verarbeitung von Dateien. Mehr zur Verarbeitung von Dateien erfahren Sie später.

Und es gibt noch eine Besonderheit. Wie bereits bei der Ausgabe mittels des Ausgabestreams cout erwähnt, leitet ein Backslash '\' eine sogenannte Escape-Sequenz ein. Um unter anderem die Sonderstellung des Backslash-Zeichens innerhalb eines Strings aufzuheben, kann ein String als Raw-String definiert werden. Ein Raw-String hat folgende allgemeine Definition:

PRÄFIXR"DEL(text)DEL";

PRÄFIX gibt den Datentyp des Raw-Strings laut obiger Tabelle an. Danach folgen der Buchstabe 'R' und ein Anführungszeichen. Unmittelbar nach dem Anführungszeichen folgt der Delimiter (Begrenzer) DEL, der aus einer beliebigen Anzahl von Zeichen bestehen kann und anschließend, in einem Klammerpaar eingeschlossen, der eigentliche String. Nach der schließenden Klammer folgt nochmals ein Delimiter DEL, der mit dem vor dem Text stehenden übereinstimmen muss. Zum Schluss wird der Ausdruck noch mit einem Anführungszeichen abgeschlossen.

PgmHeader
...
int main()
{
   auto rstring = R"#+(ein Text mit \n Escape Sequenz
     und Zeilenumbruch und Sonderzeichen # +)#+";
   cout << rstring << endl;
   return 0;
}
Programmausgabeein Text mit \n Escape Sequenz
    und Zeilenumbruch und Sonderzeichen # +

Wie im Beispiel zu sehen ist, wird nicht nur die Sonderstellung des Backslash-Zeichens aufgehoben, sondern der String wird exakt so interpretiert, wie er in der Anweisung steht, einschließlich Zeilenumbrüche und sonstiger Steuerzeichen. Der Delimiter im obigen Beispiel besteht aus der Zeichenfolge #+ und steht einmal vor der öffnenden Klammer und einmal nach der schließenden.

HinweisC++ bietet auch die Möglichkeit, eigene Typen von Literalen zu bilden. So kann z.B. mit der Anweisung auto mask = 0100_B + 0001_B; eine Addition im Binärsystem durchgeführt werden. Wie solche anwender-definierten Literale erstellt werden, das erfahren Sie später im Kapitel Überladen spezieller Operatoren.

Benannte Konstanten

Benannte Konstanten werden prinzipiell wie Variablen definiert, d.h. sie haben einen Datentyp und einen Namen. Zusätzlich wird jedoch vor dem Datentyp das Schlüsselwort const gestellt. Und da Konstanten (ihrem Sinn nach) während des Programmlaufs ihren Wert nicht ändern können, müssen sie bei ihrer Definition initialisiert werden.

Datentyp Konstantenname Initialwert
const int NOOFLINES = 24;
const char LINEFEED = '\n';
const auto PI = 3.1416f;
const short NOTTHIS ;

Die letzte Definition in obiger Tabelle erzeugt einen Übersetzungsfehler, da die Konstante nicht initialisiert wird.

Benannte Konstanten sind standardmäßig modulglobal, d.h. sie gelten nur in der Quellcode-Datei, in der sie definiert sind. Wird eine benannte Konstante in mehreren Modulen benötigt, so wird die Konstante am besten in einer eigenen Header-Datei (.h Datei) angelegt, die dann in den entsprechenden Modulen mittels #include "xx.h"; eingebunden wird. Im Beispiel unten wird die Konstante PI in der Header-Datei myinc.h definiert. Diese Datei wird dann sowohl im Modul main.cpp als auch im Modul mod1.cpp eingebunden. Dadurch ist in beiden Modulen die Konstante PI bekannt.

Include-Datei
const auto PI = 3.1416f;
main
#include <iostream>
#include "myinc.h"
using namespace std;

int main ()
{
   cout << "Pi hat den Wert " << PI << endl;
}
mod1
#include <iostream>
#include "myinc.h"
using namespace std;

void PrintIt ()
{
   cout << "Pi hat den Wert " << PI << endl;
}

Damit Konstanten von Variablen unterschieden werden können, werden Konstantennamen innerhalb des Kurses stets in Großbuchstaben geschrieben. Dies ist aber keine Vorschrift!

constexpr Ausdruck

Der Qualifizierer constexpr weist den Compiler an, einen Ausdruck zum Zeitpunkt des Übersetzungsvorgangs auszuwerten. Kann der Ausdruck nicht während des Übersetzungsvorgangs berechnet werden, so erzeugt dies einen Fehler. Und auch dazu wieder ein kleines Beispiel:

PgmHeader
int main(int argn, char** argv)
{
   const auto var1 = argn+10;     // ok
   constexpr auto var2 = argn+20; // Fehler!
   constexpr auto var3 = 10;      // ok
   ...
   return 0;
}

Dieses Beispiel zeigt auch gleich den generellen Unterschied zwischen einer mit const definierten Konstanten und einer mit constexpr definierten. Während const dem Compiler nur mitteilt, dass der Wert der Konstante nach ihrer Definition nicht mehr verändert wird, muss der Wert einer mittels constexpr definierten Konstante schon zum Übersetzungszeitpunkt bekannt sein. So führt die Definition von var2 zu einem Übersetzungsfehler, da der Ausdruck während des Übersetzungsvorgangs nicht berechnet werden kann. Welchen Wert var2 letztendlich annimmt, hängt von der Anzahl der Argumente beim Aufruf des Programms ab.

HinweisDer Einsatz von const und constexpr ist nicht nur auf die Definition von Konstanten beschränkt, sondern kann z.B. auch bei der Definition von Funktionen auftreten. Mehr dazu noch später in den entsprechenden Kapiteln.

Und da auch die alleinige Definition von Konstanten wenig Sinn macht, entfallen auch hier das Beispiel und die Übung.