Konstanten und Variablen

GOD is REAL, unless declared INTEGER.
– Fortran-Witz

Namen für Speicherplätze

Syntax

Typ const name = wert;
Typ const Name{wert};
 
Typ name;
Typ name = wert;
Typ name{wert};
Typ name{};

Daten, Werte werden ins Programm eingegeben, verarbeitet und müssen als Zwischenwerte im Speicher abgelegt werden. Den Speicherort bestimmt der Compiler. Um einen Wert wieder abzurufen, benötigt sein Speicherort einen Namen. Ein Name beginnt mit einem Buchstaben, gefolgt von Buchstaben, Ziffern, Unterstrichen. Sollen einem Namen nacheinander verschiedene Werte zugewiesen werden, sprechen wir von einer Variable, andernfalls können wir den Namen als Konstante markieren. Konstanten müssen bei ihrer ersten Erwähnung einen Wert erhalten. Variablen muss vor dem ersten Auslesen ein Wert zugewiesen worden sein. Andernfalls ist das Programm fehlerhaft.

Anfangswertbelegung (Initialisierung) über geschweifte Klammern (seit C++11) stellt sicher, dass der Wertebereich nicht eingeschränkt wird.1

auto const richtige_antwort = 42;
auto const null = int{};
int zahl;

Wird bei der Definition einer Variable oder Konstante ein Wert angegeben, kann der Compiler den Typ der Variable automatisch herausfinden: auto. Wir können den Typ aber auch selbst angeben. Er muss zum Wert passen. 42 ist eine Ganzzahl, also ist der passende Typ int (integer). C++ ist eine statisch typisierte Sprache: Der Typ einer Variable kann sich zur Laufzeit nicht ändern.

Datentyp, Wertebereich und Speicherbedarf

Der Datentyp bestimmt,

  • wie viele Byte Speicher für den Wert bereitgestellt werden müssen,
  • wie das dort abgelegte Bitmuster zu interpretieren ist,
  • welche Werte überhaupt abgelegt werden können und
  • welche Operationen mit diesem Typ ausführbar sind.

Ganzzahlen sind auf den Bereich , also bis etwa zwei Milliarden beschränkt.2 Gebrochene Zahlen erkennt der Compiler am englischen decimal point (dt. das Dezimalkomma) und speichert sie unter dem Datentyp double (double-precision floating-point number3):

auto naeherungswert_fuer_pi = 3.14;
auto const lichtgeschwindigkeit = 2.99792458e+8; // Meter je Sekunde 

Hier steht e+8 für Exponent, die englische Schreibweise mit abgetrennten Zehnerpotenzen (engl. scientific notation): . Da der decimal point verschoben werden kann, werden sie auf deutsch Gleitkommazahlen genannt. Der Faktor vor der Zehnerpotenz, hier , heißt Mantisse. Der Wertebereich ist löchrig. Die Werte dürfen etwa im Bereich liegen, und es gibt nur endlich viele mögliche Gleitkommazahlen, da sowohl der Exponent als auch die Zahl der Stellen in der Mantisse endlich sein muss. Die Genauigkeit von double-Werten ist auf etwa 16 zählende Dezimalziffern begrenzt. Auch das schränkt die Menge darstellbarer Werte auf eine Teilmenge der rationalen Zahlen ein, von irrationalen Zahlen ganz zu schweigen. Auch ist es unmöglich, den Dezimalbruch wie 0,1 als gemeinen Bruch mit Zweierpotenz in Nenner darzustellen, genauso wenig wie mit Zehnerpotenz in Nenner. Beim Rechnen mit Gleitkommazahlen treten Rundungsfehler auf, ebenso während der Umwandlung von Dezimalzahlen in die interne Binärdarstellung bei der Eingabe und zurück bei der Ausgabe.

Der Speicherbedarf von Typen, Variablen und Werten kann mit dem Operator sizeof erfragt werden. Er wird in Byte angegeben, in Vielfachen des Speicherbedarfs für ein Zeichen. Hier kommt es nur auf den Typ an, der dem Wert zugeordnet ist:

//> cpp_basics/sizeof.cpp
import std;
 
int main()
{
	std::println("Zeichen:        {} Byte", sizeof('0')); // 1
	std::println("Ganzzahl:       {} Byte", sizeof(0));   // 4
	std::println("Gleitkommazahl: {} Byte", sizeof(0.0)); // 8
}

Aufgaben

  1. Mache dich mit den fundamentalen Datentypen vertraut (siehe Übersicht).
  2. Erkunde Speicherbedarf und Wertebereich von Datentypen T mit std::numeric_limits<T> auf deiner Maschine:
//> cpp_basics/numeric_limits.cpp
import std;
 
void show(auto value)
{
    using T = decltype(value);
    std::println("{} Byte: {}...{}",
        sizeof(T),
        std::numeric_limits<T>::min(),
        std::numeric_limits<T>::max()
    );
}
 
void show(std::floating_point auto value)
{
    using T = decltype(value);
    std::println("{} Byte, {} Bits Mantisse: ±0, ±{}...±{}, ±{}",
        sizeof(T),
        std::numeric_limits<T>::digits,
        std::numeric_limits<T>::min(),
        std::numeric_limits<T>::max(),
        std::numeric_limits<T>::infinity()
    );
}
 
int main()
{
    show(true);
    show(0);
    show(0.0);
}

Gültigkeitsbereich und Lebensdauer

Der Name einer Variable oder Konstante gilt lokal ab der Stelle seiner Definition bis zur schließenden geschweiften Klammer des umgebenden Blocks {}. Sie existiert auch nur während der Programmausführung bis zum Verlassen dieses Blocks. In inneren Blöcken definierte Namen verdecken weiter außen definierte gleiche Namen:

//> cpp_basics/scope.cpp
import std;
auto const x = 42;
 
int main()
{
	std::println("global:        {}", x);
	{
		auto x = 17;
		std::println("innerer Block: ", x);
	}
	std::println("global:        ", x);
	auto x = 23;
	std::println("lokal:         ", x);
}

Konstanten dürfen gern auch global, außerhalb von Funktionen definiert werden. Sie existieren während der gesamten Laufzeit des Programms. Globale Variablen sind dagegen meist eine schlechte Idee, welche die Fehlersuche erschwert. Sie wirken wie tote Briefkästen. Ihre Inhalte können in weit entfernten Abschnitten umfangreicher Programme Schaden anrichten. Die Grundregel lautet: Definiere Variablen so lokal wie möglich.

Aufgaben

  1. Was gibt obiges Programm aus? Warum?
  2. Formuliere Regeln für die Sichtbarkeit von Namen.

Anmerkungen

  1. „Blut ist im Schuh!“ Narrowing, das von C geerbte Beschneiden eines zu umfangreichen Bitmusters, ist ein Trick von Aschenputtels Stiefschwestern: char(257) wird zu char(1). Dagegen erkennt der C++-Compiler char{257} als Fehler.

  2. Die Hälfte der Zahlen mit gesetztem höchstwertigen Bit wird als negativ interpretiert (Zweierkomplement). https://de.wikipedia.org/wiki/Zweierkomplement

  3. Speicherlayout und Eigenschaften von Gleitkommazahlen werden durch den Standard IEEE 754 festgelegt.