Konstanten

Konstanten sind Daten, die nach ihrer Definition nicht mehr geändert werden können. Sie können jeden beliebigen Datentyp besitzen.

Literale (unbenannte Konstanten)

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

Um die Lesbarkeit von numerischen Literalen zu verbessern, können innerhalb des Literals Hochkomma zur Gruppierung von Ziffern verwendet werden, z.B. 1'000'000.

Integer-Literale

Integer-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 Integer-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 (eine Null!), Literale in hexadezimaler Schreibweise durch das Präfix 0x bzw. 0X und binäre Literale durch das Präfix 0b bzw. 0B gekennzeichnet.

Um ein Integer-Literal explizit einem Datentyp zuzuordnen, wird der Wert mit einem Suffix erweitert. Bei diesen Erweiterungen ist die Groß-/Kleinschreibung nicht relevant. Die nachfolgende Tabelle enthält eine Übersicht über die verschiedenen Erweiterungen sowie Beispiele dazu:

Dezimal-Literal 3 -5 +4
Binär-Literal, Präfix 0b 0b0000'0011 0b1111'1011 0B100
Oktal-Literal, Präfix 0 03 012 0665
Hex-Literal, Präfix 0x 0x40 0XFF00 -0xFF
unsigned Literal, Suffix U 10U 0xFF00u 012u
unsigned long Literal, Suffix UL 10UL 0xFFul 012ul
long long, Suffix LL -10LL 0xfll -034LL
unsigned long long, Suffix ULL -345ULL 0x01ull 0564ULL
size_t (Ergebnis von sizeof), Z UZ 3Z 5UZ 04z

Schreiben Sie niemals 0010, wenn Sie ein Dezimal-Literal definieren! Diese Zahl entspricht der Oktalzahl 10 und ist dezimal 8. Die letzte Spalte in den 'Suffix'-Tabelle enthält nur Oktalzahlen.

Verwenden Sie für die long-Suffixe Großbuchstaben. Dies erleichtert die Lesbarkeit.

10l // long 10 oder 101 ?
10L // long 10

Gleitkomma-Literale

Gleitkomma-Literale bestehen aus Ziffern und einem Dezimalpunkt. Optional kann ein Gleitkomma-Literal 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.32E-2
float Literal, Suffix F 1.0f 1.F +3.32e-2f
long double Literal, Suffix L -1.0L 1.L +3.32E-2L

Es gibt weitere Suffixe für Gleitkomma-Literale, wie z.B. F32 für den Datentyp std::float32_t. Diese Datentypen werden hauptsächlich in zeitkritischen Anwendungen eingesetzt, wenn eine definierte Genauigkeit erforderlich ist. Mehr dazu auf https://en.cppreference.com/ unter dem Stichwort Fixed width floating-point types.

Gleitkomma-Literale können ebenfalls in hexadezimaler Darstellung angegeben werden, d.h. in der Form, wie sie prinzipiell innerhalb der Gleitkomma-Recheneinheit des Prozessors verwendet wird. Dadurch werden Rundungsfehler bei der Konvertierung von der dezimalen Darstellung des Gleitkomma-Literals ins intern verwendet Gleitkommaformat vermieden. Jedoch ist die Umrechnung von der dezimalen Darstellung in die hexadezimale Darstellung nicht trivial. Wenn Sie mehr hierüber erfahren wollen, suchen Sie im Internet nach IEEE 754.

Zeichen- und String-Literale

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

char 'a' "ein String"
char8_t, Präfix u8 u8'a' u8"ein String"
char16_t, Präfix u u'a' u"ein String"
char32_t, Präfix U U'a' U"ein String"
wchar_t, Präfix L L'a' L"ein String"

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

Des Weiteren weisen String-Literale folgende Besonderheiten auf:

Direkt hintereinanderstehende String-Literale werden stets zusammengefasst. Das Einzige worauf dabei zu achten ist, ist, dass keine String-Literale mit unterschiedlichen Datentypen hintereinanderstehen.

#include <print>

int main()
{
    std::println ("Dies ist EIN String- "    // String-
            "Literal obwohl es ueber "       // Literal über
            "mehrere Zeilen geht!\n");       // 3 Zeilen
}

Dies ist EIN String- Literal obwohl es ueber mehrere Zeilen geht!

Eine Sonderform des String-Literals ist der Raw-String. Er hebt unter anderem die Sonderstellung des Backslash-Zeichens innerhalb eines Strings auf. Ein Raw-String hat folgende allgemeine Definition:

[PRÄFIX]R"DEL(text)DEL"

Das optionale PRÄFIX gibt den Datentyp des Raw-Strings laut vorheriger 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 der Delimiter DEL, der mit dem vor dem Text stehenden Delimiter übereinstimmen muss. Abgeschlossen wird der gesamte Ausdruck mit einem Anführungszeichen.

#include <print>

int main()
{
   auto rstring = R"#+(ein Text mit \n Escape Sequenz und
....Zeilenumbruch und Sonderzeichen # +)#+";
   std::println("{}",rstring);
}

ein Text mit \n Escape Sequenz und
....Zeilenumbruch und Sonderzeichen # +

Wie in der Ausgabe 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 des Zeilenumbruchs. Der Delimiter im obigen Beispiel besteht aus der Zeichenfolge #+ und steht einmal vor der öffnenden Klammer und einmal nach der schließenden.

Ferner gibt es die Möglichkeit, eigene Typen von Literalen zu bilden. So kann z.B. mit der Anweisung auto distance = 100_m + 20_km; eine Addition von Meter und Kilometer durchgeführt werden. Wie solche anwenderdefinierte Literale erstellt werden, wird im Kapitel Überladen spezieller Operatoren beschrieben.

Benannte Konstante

Benannte Konstanten werden prinzipiell wie initialisierte Variablen definiert, d.h., sie haben einen Datentyp, einen Namen und einen Initialisierungswert. Zusätzlich wird entweder vor dem Datentyp oder vor dem Namen des Datums das Schlüsselwort const gestellt.

#include <print>

int main()
{
    int const NOOFLINES = 24;   // Init. per Zuweisung
    const auto PI{3.1416f};     // Preventing narrowing
    const char LINEFEED('\n');  // Initialisierung

    std::print("lines: {}, PI:{}{}",NOOFLINES,PI,LINEFEED);
}

lines: 24, PI:3.1416

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, sollte die Konstante in einer Header-Datei (.h Datei) definiert werden, die dann in den Quellcode-Dateien mittels #include "xx.h"; eingebunden wird. Im nachfolgenden Beispiel wird die Konstante PI in der Header-Datei myinc.h definiert. Diese Datei wird dann in den Quellcode-Datei main.cpp und mod1.cpp eingebunden. Dadurch ist in beiden Quellcode-Dateien die Konstante bekannt.

// Datei myinc.h
const auto PI{3.1416f};
// Datei main.cpp

#include <print>
#include "myinc.h"

extern void PrintIt();

int main ()
{
   std::println("Pi hat den Wert {}", PI);
   // PrintIt ausfuehren
   PrintIt();
}
// Datei mod1.cpp
#include <print>
#include "myinc.h"

void PrintIt ()
{
   std::println("Pi hat den Wert {}", PI);
}

Pi hat den Wert 3.1416
Pi hat den Wert 3.1416

Auch für Konstanten sollte eine einheitliche Schreibweise verwendet werden. In diesem Tutorial werden Konstantennamen stets in Großbuchstaben geschrieben.

Für häufig benötigte numerische Konstanten, wie z.B. PI, enthält die Header-Datei numbers vordefinierte Konstanten, die im Sub-Namensraum numbers liegen.

#include <print>
#include <numbers>

int main()
{
   std::println("PI = {}",std::numbers::pi);
}

PI = 3.141592653589793

Eine Übersicht über die vordefinierten Konstanten finden Sie u.a. auf https://www.cppreference.com unter dem Stichwort 'Mathematical constants'.

constexpr Spezifizierer

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

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

Das Beispiel zeigt den generellen Unterschied zwischen einer mit const definierten Konstanten und einer constexpr. Während const dem Compiler nur mitteilt, dass die Konstante nach ihrer Definition nicht mehr verändert wird, muss der Wert einer constexpr 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 annimmt, hängt vom jeweiligen Inhalt des Parameters argn ab.

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