C++ Tutorial

Variablen

Variablendefinition

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:

  • Er muss mit einem Buchstaben oder Unterstrich beginnen,
  • darf nicht mit einem reservierten Schlüsselwort übereinstimmen und
  • darf keine Umlaute oder ß enthalten.

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.

Integer-Datentypen

Um Integer-Daten (Ganzzahlen) in Variablen abzulegen stehen folgende Datentypen zur Verfügung:

Integer mit Vorzeichen
Integer ohne Vorzeichen
signed char
unsigned char
short
unsigned short
int
unsigned int
long
unsigned long
long long
unsigned long long

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.

Datentyp
Wertebereich
signed char
[-128, 127]
short
[-32768, 32767]
long
[-2147483648, 2147483647]
long long
[-9223372036854775808, 9223372036854775807]
unsigned char
[0, 255]
unsigned short
[0, 65535]
unsigned long
[0, 4294967295]
unsigend long long
[0, 18446744073709551615]
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:

Datentyp
Wertebereich
(un)signed char
>= 8Bit
short
>= 16 Bit
int
>= 16 Bit
long
>= 32 Bit
long long
>= 64 Bit

Zeichen-Datentypen

Zeichen-Datentypen dienen zum Abspeichern von Zeichen (Buchstaben). C++ kennt folgende Zeichen-Datentypen:

Datentyp
Verwendugn
char
ASCII Zeichen
wchar_t
Zeichen eines erweiterten Zeichensatzes
char8_t
Zeichen des UTF-8 Zeichensatzes
char16_t
Zeichen des UTF-16 Zeichensatzes
char32_t
Zeichen des UTF-32 Zeichensatzes

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.

Gleitkomma-Datentypen

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.

Datentyp
Wertebereich
Berechnung 1./3.
float
1.17549e-38, 3.40282e+38
0.3333333432674407959
double
2.22507e-308, 1.79769e+308
0.3333333333333333148
long double
3.3621e-4932, 1.18973e+4932
0.3333333333333333333

Weitere Datentypen

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.:

  • enumerated Datentyp
  • Zeiger
  • Strukturen / Klassen
  • Varianten

Initialisierung und auto

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);

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};

inline-Variable

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.

Referenz-Variable

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:

  • Eine Referenz-Variable muss bei ihrer Definition initialisiert werden.
  • Nach der Initialisierung kann der Verweis nicht mehr geändert werden. Wird einer Referenz-Variable ein Datum zugewiesen, wird der Inhalt des Datums geändert, auf das die Referenz-Variable verweist.
  • Der Datentyp der Referenz-Variable muss mit dem Datentyp des Datums übereinstimmen, auf das verwiesen wird. Am sichersten geht dies durch eine auto& Definition

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

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.

Typdefinition mittels decltype

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:

  • Ist AUSDRUCK eine Variable, liefert decltype() den Datentyp der Variable zurück.
  • Ist AUSDRUCK ein Funktionsaufruf, liefert decltype() den Returntyp der Funktion zurück (Funktionen werden später ausführlich behandelt). Der Returntyp der Funktion darf nicht vom Typ void sein.
  • Ist AUSDRUCK ein Funktionsname, liefert decltype() einen Funktionszeiger vom Typ der Funktion zurück.
  • Ist AUSDRUCK ein rvalue Ausdruck, liefert decltype() den Datentyp des Ergebnisses des Ausdrucks zurück.
  • Ist AUSDRUCK ein lvalue Ausdruck, liefert decltype() einen Referenzdatentyp zurück.
  • Wird für AUSDRUCK das Schlüsselwort auto angegeben, liefert decltype() den exakten Datentypen eines rechts vom Zuweisungsoperator stehenden Initialisierungsausdrucks.

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;

Eigenschaften von Datentypen

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() wird innerhalb der spitzen Klammer der Datentyp angegeben, dessen Eigenschaften ermittelt werden sollen.

Aufruf
Datentyp Eigenschaften
PrintNumLimits<unsigned long>();
Minimum: 0 Maximum: 4294967295 Anzahl Bits (ohne Vorzeichen): 32 Vorzeichenbehaftet: false Integer: true keine Rundungsfehler möglich: true kleinster von 1 abweichender Wert: 0
PrintNumLimits<double>();
Minimum: 2.22507e-308 Maximum: 1.79769e+308 Anzahl Bits (ohne Vorzeichen): 53 Vorzeichenbehaftet: true Integer: false keine Rundungsfehler möglich: false kleinster von 1 abweichender Wert: 2.22045e-016

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.

sizeof Operator

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