Zur Programmlaufzeit veränderbare Daten werden in Variablen abgelegt. Eine Variable muss vor ihrer ersten Verwendung definiert werden.
DTYP vName;
DTYP bestimmt den Datentyp der Variable (wird gleich erklärt) und vName ist der Variablenname. Der Name muss eindeutig sein, d.h., es darf keine gleichnamige zweite Variable, Funktion usw. im gleichen Gültigkeitsbereich geben. Was Gültigkeitsbereiche sind, wird später Thema sein. Eine Ausnahme von dieser "One-Definition-Rule (ODR)" bilden inline-Variablen, auf die gleich eingegangen wird.
Für den Namen einer Variable gelten folgende Einschränkungen:
Die maximale Länge eines Namens ist abhängig vom verwendeten Compiler, jedoch unterstützen die meisten C++-Compiler mindestens 64 Zeichen für Namen.
Im Augenblick spielt es keine Rolle, ob eine Variablendefinition vor oder innerhalb von main() erfolgt. Worin sich diese beiden Definitionen unterscheiden, folgt später. Variablen können an beliebiger Stelle im Programm definiert werden, was den Vorteil bietet, dass sie an der Stelle definiert werden können, an der sie das erste Mal verwendet werden.
Um Integer-Daten (Ganzzahlen) in Variablen abzulegen stehen folgende Datentypen zur Verfügung:
Der Unterschied zwischen den Datentypen liegt im Wertebereich, den ein Datentyp abdeckt. Welchen Wertebereich die Datentypen besitzen, hängt vom Compiler und der Rechner-Plattform ab. In der nachfolgenden Tabelle sind beispielhaft die jeweiligen Wertebereiche des Microsoft- und MinGW-Compilers unter WINDOWS aufgeführt.
1: // short-Variable definieren
2: short myValue;
3: // Variable mit großem Wertebereich definieren
4: long long counter;
5: // Variable mit nur positivem Wertebereich
6: unsigned long time;
Wie erwähnt, liegt der Unterschied zwischen den einzelnen Datentypen im Wertebereich und damit auch in dem von einer Variable benötigten Speicherplatz. Ab C++20 sind folgende Größen für die Datentypen (signed und unsigned) garantiert:
Zeichen-Datentypen dienen zum Abspeichern von Zeichen (Buchstaben). C++ kennt folgende Zeichen-Datentypen:
Zum Abspeichern eines Zeichens werden in der Regel die Datentypen char und wchar_t verwendet. Der Datentyp char dient zur Aufnahme eines 1 Byte großen Zeichens, während der Datentyp wchar_t ein Zeichen eines erweiterten Zeichensatzes aufnehmen kann, wie z.B. chinesische Schriftzeichen.
1: // char Variable definieren
2: char anyCharacter;
3: // Variable für den erweiterten Zeichensatz definieren
4: wchar_t extChar;
Die Datentypen char8_t, char16_t und char32_t dienen zum Verarbeiten von UTF-Zeichensätzen. Da diese nur in speziellen Fällen verwendet werden, wird auf diese Datentypen nicht weiter eingegangen.
Zum Verarbeiten von Gleitkomma-Daten stehen 3 Datentypen zur Verfügung: float, double und long double. Der 'kleinste' Gleitkomma-Datentyp ist der Datentyp float, gefolgt von double und long double.
Diese Datentypen unterscheiden sich zum einen im Wertebereich und zum anderen durch die Anzahl der Stellen, die für Berechnungen verwendet werden.
bool allDone;
Variablen vom Datentyp bool können nur die Zustände true und false annehmen.
Dieser Datentyp wird eingesetzt, wenn Bedingungen auf erfüllt (true) oder nicht erfüllt (false) zu vergleichen sind.
Der Datentyp void ist ein unvollständiger Datentyp, der niemals alleine auftritt und nicht für die Definition von Variablen verwendet werden kann. Er wird hauptsächlich als Returntyp von Funktionen/Methoden oder bei Zeigern verwendet und wird in den entsprechenden Kapiteln besprochen.
Es gibt noch weitere Datentypen, die erst später behandelt werden. Dies sind u.a.:
Eine Variable kann bei ihrer Definition auf mehrere Arten initialisiert werden.
1: signed char c1 = 10; // Zuweisung
2: signed char c2 = (10); // Zuweisung in runden Klammern
3: signed char c3 = {10}; // Zuweisung in geschweiften Kl.
4: signed char c4(10); // Init-Wert in runden Klammern
5: signed char c5{10}; // Init-Wert in geschweiften Klammern
6: signed char c6 = 300; // Init-Wert zu gross, aber ok
7: signed char c7{300}; // Init-Wert zu gross, Fehlermeldung
Die Initialisierungen ohne Klammern oder mit runden Klammern sind gleichwertig. Bei der Initialisierung mit geschweiften Klammern überprüft der Compiler beim Übersetzen des Programms zusätzlich, ob der Initialwert ohne Informationsverlust übernommen werden kann und gibt ansonsten eine Fehlermeldung aus (Zeile 3, 6 und 7). Diese Überprüfung zur Compilezeit wird als preventing narrowing bezeichnet.
Wird eine Variable bei ihrer Definition initialisiert, kann der Compiler aufgrund des Datentyps des Initialisierungsausdrucks automatisch den Datentyp der Variable bestimmen. Dazu wird bei der Variablendefinition der Datentyp durch das Schlüsselwort auto ersetzt.
1: // int-Variable initialisieren
2: auto startValue{10};
3: auto endValue{startValue+20};
4: // double-Variable initialisieren
5: auto oneThird{1.0/3.0};
6: // bool-Variable initialisieren
7: auto isDone(false);
auto var = {3};
definiert keine int-Variable, sondern eine int-Initialisiererliste. Deshalb mein Rat: Initialisieren Sie eine Variable entweder per Zuweisung ohne Klammern oder ohne Zuweisung mit Klammern.
auto var1 = 3;
auto var2{3};
Mehrere Variablen des gleichen Datentyps können in einer Anweisung definiert werden. Die zu definierenden Variablen werden durch Komma voneinander getrennt. Zusätzlich können die Variablen initialisiert werden.
1: // zwei int-Variablen definieren
2: int startValue, endValue;
3: // zwei double-Variablen definieren und initialisieren
4: double beginOfRange = -2.5, endOfRange = 2.5;
5: // oder einfacher
6: auto first{-2.5}, second{2.5};
Durch Voranstellen des Spezifizierers inline vor dem Datentyp wird eine inline-Variable definiert. Sie kann innerhalb eines Programms mehrfach definiert werden, innerhalb einer Übersetzungseinheit (Quellcode-Datei) jedoch nur einmal. Sehen Sie sich hierzu das folgende Beispiel an.
1: // Datei common.h
2: inline int globalVar = 10;
1: // Datei main.cpp
2: #include "common.h"
3: #include <print>
4: int main()
5: {
6: std::println("{}",globalVar);
7: }
1: // Datei another.cpp
2: #include "common.h"
3: #include <print>
4: void AnyFunc()
5: {
6: std::println("{}",globalVar);
7: }
In der Datei common.h wird die Variable globalVar definiert und initialisiert. Diese Datei wird sowohl von main.cpp wie auch von another.cpp eingebunden. Ohne die Definition von globalVar als inline-Variable wäre die Variable damit doppelt definiert, was beim Linken des Programms zu einem Fehler führt. Dadurch, dass die Variable als inline definiert ist, kann der Linker diese Mehrfach-Definition auflösen und legt die Variable nur einmal an.
Eine Referenz-Variable enthält einen Verweis auf ein bestehendes Datum und wird dadurch gekennzeichnet, dass nach dem Datentyp das Symbol & steht.
Eine Referenz-Variable unterliegt folgenden Einschränkungen:
Das nachfolgende Beispiel zeigt, wenn auch nicht ganz praxisnah, die Verwendung von Referenz-Variablen.
1: // int-Variablen definieren
2: int val10 = 10;
3: int val20 = 20;
4: // Referenz-Variable definieren
5: // refVar verweist danach auf val10
6: auto& refVar = val10; // Alternativ: int& refVar{val10};
7: // Ausgabe von val10 über Referenz
8: std::println("{}",refVar); // gibt 10 aus
9: // Zuweisung an eine Referenzvariable
10: // refVar verweist danach immer noch auf val10
11: // jedoch enthaelt val10 danach den Wert von val20
12: refVar = val20;
13: // Ausgabe
14: std::println("{}\n",refVar); // gibt 20 aus
DTYP &refName = datum; // oder auch
DTYP & refName = datum;
Die Definition einer Variable mittels auto entfernt u.a. die Referenz. Soll die Referenz erhalten bleiben, ist die Variable mittels auto& zu definieren (siehe Zeile 6). Vielleicht mag im Augenblick die Verwendung von Referenzen etwas seltsam erscheinen. Später aber wird deren Einsatz sich als vorteilhaft erweisen.
Der Spezifizierer decltype() liefert den Datentyp eines Ausdrucks und kann eingesetzt werden, um unter anderem den Datentyp einer Variable indirekt festzulegen. Die Syntax lautet:
decltype (AUSDRUCK)
Abhängig von AUSDRUCK liefert decltype() folgende Datentypen zurück:
So ist in
erg = 1 + var;
erg ein lvalue Ausdruck, da erg eine Variable ist und somit eine Adresse hat. 1 + var ist dagegen ein rvalue Ausdruck, da das Ergebnis des Ausdrucks nach der Zuweisung 'gelöscht' wird.
Im folgenden Beispiel besitzt die Variable dvar stets denselben Datentyp wie die Variable var. D.h., wird der Datentyp von var geändert, ändert sich automatisch der Datentyp von dvar. In Zeile 6 hat svar den Datentyp des Returnwertes der Funktion MyFunc(). Liefert MyFunc() z.B. einen int-Wert zurück, ist der Datentyp von svar ebenfalls ein int.
1: // Variable definieren
2: int var;
3: // dvar erhaelt den gleichen Datentyp wie var
4: decltype(var) dvar;
5: // Datentyp aus Returntyp einer Funktion bestimmen
6: decltype(MyFunc()) svar;
Wird decltype() zusammen mit auto verwendet, ist die Variable zu initialisieren. Die Variable hat dann den Datentyp des Initialisierungsausdrucks und damit ist es möglich, einer auto-Variable eine Referenz zuzuweisen.
1: // Variable definieren
2: long var1;
3: // Datentyp von refVar ist long&
4: auto& refVar = var1;
5: // Datentyp von var2 ist nun long& und nicht mehr long
6: decltype(auto) var2 = refVar;
Im Anhang H: Eigenschaften von Datentypen können ist ein kleines Programm angegeben, das einige Eigenschaften der vorgestellten Datentypen ausgibt. Beim Aufruf der Funktion PrintNumLimits
Wie der Tabelle u.a. entnommen werden kann, gibt es beim Rechnen mit unsigned long Daten keine Rundungsfehler (wie bei allen Integer-Daten), während es bei double-Daten (wie bei allen Gleitkomma-Daten) Rundungsfehler geben kann. Dies liegt darin begründet, dass wegen der internen Darstellung von Gleitkomma-Daten nicht jedes beliebige Datum exakt darstellbar ist.
Der Operator sizeof(ARG) liefert die von einem Datentyp oder einem Datum benötigte Anzahl von Bytes als size_t-Datum zurück. Ist ARG ein Datum, kann die Klammerung entfallen.
1: auto anyValue = 10; // int-Variable definieren
2: std::cout << sizeof(char) << '\n'; // sizeof(datentyp)
3: std::cout << sizeof anyValue << '\n'; // sizeof datum
4: std::cout << sizeof(long long); // sizeof(datentyp)
1
4
8
sizeof ist ein Compilezeit-Ausdruck, d.h., der Compiler berechnet beim Übersetzen des Programms das Ergebnis des sizeof-Operators und setzt das Ergebnis in den Code ein.
Copyright 2024 © Wolfgang Schröder
E-Mail mit Fragen oder Kommentaren zu dieser Website an: info@cpp-tutor.de
Impressum & Datenschutz