Variablen helfen dem Programmierer Daten temporär für eine bestimmte Zeit zu speichern. Konstanten sind Variablen, die nach der Initialisierung (nach der Zuweisung mit einem Wert) nicht mehr geändert werden dürfen. Sie bleiben halt konstant.

Alle Computer haben einen Microprozessor und einen Speicher, der für die temporäre Speicherung von Daten zuständig ist (Random Access Memory: RAM). Zusätzlich gibt es noch den Speicher, der Daten ständig speichert, oder zumindest könnten sie permanent gespeichert bleiben (Festplatte). Der Microprozessor führt das Programm aus und arbeitet mit dem RAM zusammen, um auf das Programm und eventuell gespeicherte Daten für das Programm zuzugreifen.

Der RAM Speicher ist eine große Lagerhalle mit einzelnen Räumen, auf die man über eine bestimmte Adresse zugreifen kann … zumindest kann man sich das so vorstellen. Angenommen man schreibt ein Programm, dass zwei Zahlen addiert, die der User vorher über die Tastatur eingegeben hat. In C++ (und auch in anderen Programmiersprachen) definiert man hierfür Variablen, die diese Werte dann speichern. Somit muss man sich nicht komplizierte Speicheradressen merken. Man kann diese einfach mithilfe der Namen der Variablen erreichen und darauf zugreifen.

Eine Variable zu definieren ist denkbar einfach und sieht in etwa so aus:

VariableType VariableName

oder

VariableType VariableName = InitialValue

Der Variablentyp (variable type) sagt dem Compiler, um was für eine Art von Variable es sich handelt und der Compiler reserviert dann den benötigten Platz dafür. Der Name (variable name), den der Programmierer bestimmt, ist ein gut lesbarer Ersatz für die Adresse im Speicher, in der der Inhalt der Variablen gespeichert wird.

Solange man keinen Anfangswert (initial value) angibt, kann man sich nicht sicher sein, welcher Inhalt gespeichert wird. Die Initialisierung mit einem Anfangswert ist zwar optional, aber gute Programmierpraxis. Im folgenden Beispiel sehen wir, wie man Variablen deklariert, initialisiert und in einem Programm benutzt, welches zwei Zahlen addiert.

#include <iostream>

using namespace std;

int main() {
    cout << "This program will add two numbers." << endl;
    cout << "Enter the first number: ";
    int firstNumber = 0;   // Integer Variable
    cin  >> firstNumber;

    cout << "Enter the second number: ";
    int secondNumber = 0;  // Integer Variable 2
    cin  >> secondNumber;

    // Add to numbers, store ressult in a variable:
    int result = firstNumber + secondNumber;

    // Display result:
    cout << firstNumber << " + " << secondNumber;
    cout << " = " << result << endl;

    return 0;
}

Ausgabe:

This program will add two numbers.
Enter the first number: 34
Enter the second number: 45
34 + 45 = 79

Das Programm fragt den Nutzer nach zwei Zahlen, welche addiert werden und dann wird das Ergebnis angezeigt. Hier nutzen wir gleich mal das Eingabependat zu cout, nämlich cin. Hier wird so lange auf eine Eingabe gewartet bis der User die Return Taste drückt. Mehr zur Ein- und Ausgabe folgt in einem späteren Artikel. Die eingegebenen Zahlen werden direkt in Variablen (firstNumber und secondNumber) abgelegt.

Die Auswahl eines geeigneten Namens für eine Variable ist wichtig, um guten Code zu schreiben. Variablen Namen können aus Buchstaben, Zahlen und Unterstrichen bestehen. Sie dürfen aber nicht mit einer Zahl beginnen. Leerzeichen dürfen sie nicht enthalten. Des Weiteren darf man kein reserviertes Schlüsselwort für eine eigene Variable verwenden. Variablen sollten nach Möglichkeit einen beschreibenden Namen haben, damit man im Idealfall beim Lesen des Namens schon weiß, was ihre Funktion ist.

Es besteht auch die Möglichkeit mehrere Variablen gleichzeitig zu initialisieren.

int firstNumber = 0, secondNumber = 0, result = 0;

Also könnte man am Anfang einer Funktion gleich alle Variablen deklarieren und initialisieren. Es ist guter Programmierstil alle Variablen an einem Platz am Anfang einer Funktion oder Klasse zu deklarieren.

Wichtig ist noch zu wissen, dass Variablen einen gewissen Gültigkeitsbereich haben, je nachdem wo sie deklariert wurden. Dazu erfahren wir aber an anderer Stelle noch mehr.

Grundlegende Variablentypen

Bisher haben wir nur Integer Variablen benutzt. C++ kennt natürlich noch weitere fundamentale Datentypen. Den richtigen Datentyp für eine Aufgabe zu wählen ist äußerst wichtig. Eine Integerzahl ohne Vorzeichen (unsigned) eignet sich schlecht um eine negative Zahl zu speichern. Im Folgenden sehen wir die verschiedenen grundlegenden Variablentypen.

bool     // true or false
char     // 256 character values
unsigned short int     // 0 to 65.535
short int     // -32.768 to 32.767
unsigned long int     // 0 to 4.294.967.295
long int     // -2.147.483.648 to 2.147.483.647
unsigned long int     // 0 to 18.446.744.073.709.551.615
long long     // -9.223.372.036.854.775.808 to
              // 9.223.372.036.854.775.807
int (16 bit)     // -32.768 to 32.767
int (32 bit)     // -2.147.483.648 to 2.147.483.647
unsigned int (16 bit)     // 0 to 65.535
unsigned int (32 bit)     // 0 to 4.294.967.295
float     // 1.2e-38 to 3.4e38
double     // 2.2e-308 to 1.8e308

Ok, was sind das alles für Datentypen?

Mit bool speichert man Boolesche Variablen

C++ bietet einen Variablentypen an, der speziell dafür gedacht ist Boolesche Werte zu speichern: true oder false. Diese Werte sind entweder an oder aus, wahr oder falsch, 1 oder 0. Ein Beispiel für eine Deklarierung einer initialisierten Booleschen Variablen:

bool programmingIsFun = true;

Ein Ausdruck, der einen Booleschen Wert zuweist:

bool friendOfJake = (name == "Finn");

Was passiert hier? Falls der Text in der Variablen name „Finn“ entspricht, wird der Booleschen Variablen friendOfJake ein true zugewiesen, falls nicht, ein false. Aber zu Bedingungen kommen wir einem späteren Artikel.

Der Typ char speichert Zeichen

Der Variablentyp char speichert ein einzelnes Zeichen, z.B.:

char firstLetter = 'B';

Man sollte nicht vergessen, dass der Speicher in einem Computer aus Bits und Bytes besteht. Ein Bit ist entweder eine 1 oder eine 0 und ein Byte besteht aus mehreren Einsen und Nullen, um Zahlen darzustellen. Ein Zeichen wird im Speicher daher ebenfalls als Zahl dargestellt. Welche Zahl welches Zeichen entspricht kann man in einer ASCII Tabelle nachlesen. Der Buchstabe ‚B‚ entspricht der Dezimalzahl 66 und daher wird für die Variable firstLetter eine 66 gespeichert.

Integer Zahlen mit und ohne Vorzeichen (signed / unsigned)

Ein Vorzeichen kann entweder positiv oder negativ ein. Wie bereits erwähnt werden Zahlen im Speicher als Bits oder Bytes abgelegt. Ein Speicherbereich von einem Byte beinhaltet 8 Bits. Jedes dieser Bits kann entweder 0 oder 1 sein (also einer von 2 Zuständen). Also kann ein Speicherbereich von 1 Byte maximal \(2^8\) Werte speichern, das sind 256 unterschiedliche Werte. Ein Speicherbereich von 16 Bit (2 Byte) kann demnach \(2^{16}\), also 65.536 unterschiedliche Werte speichern.

Falls diese Werte nun ohne Vorzeichen sind, also nur positiv, dann könnte 1 Byte Werte von 0 bis 255 speichern und 2 Byte dementsprechend Werte von 0 bis 65.535. Es ist also recht einfach positive Werte in Bits und Bytes darzustellen. Aber wie sieht es mit negativen Zahlen aus?

Eine Möglichkeit wäre, ein Bit zu opfern um das Vorzeichen darzustellen, also ob eine Zahl positiv oder negativ ist. Somit hätte man 1 Bit für das Vorzeichen und 15 Bits (Bit 0 bis 14) für die Werte zur Darstellung der Zahl, bei einem Speicherbereich von 2 Byte. Man könnte somit die Zahlen von -32.768 bis 32.767 darstellen.

Positive und negative Integer Typen short, int, long und long long

Diese Variablentypen unterschieden sich in ihrer Größe und somit auch in den Zahlenbereichen, die sie speichern können. int wird wohl am meisten benutzt und von den wichtigsten Compilern 32 Bit lang umgewandelt. Es ist wichtig zu wissen, welchen Zahlenbereich man benötigt und ausgehend davon den richtigen Typen zu wählen. Integer Typen deklariert man folgendermaßen:

short int numberOfSisters = 3;
int moneySpentLastMonth = -2780;
long moneySpentLastDecade = -255000;

Integer Typen ohne Vorzeichen unsigned short, unsigned int, unsigned long und unsigned long long

Ganze Zahlen ohne Vorzeichen müssen kein Bit für das Vorzeichen verschwenden und können somit doppelt so große Zahlen speichern, wie ihre mit Vorzeichen belasteten Kollegen:

unsigned short presentsForBirthday = 5;
unsigned int numOfStamps = 1355;
unsigned long numOfCarsInBremen = 41000;

Wenn man davon ausgehen kann, dass eine Zahl mit Sicherheit nicht negativ werden kann (wenn man etwas zählt zum Beispiel), sollte man einen unsigned Typen verwenden. Ansonsten muss man einen Typen mit Vorzeichen wählen (z.B. bei einem Bankkonto).

Fließkommatypen float und double

Fließkommazahlen können selbstverständlich auch positiv und negativ sein. Und sie können Nachkommastellen haben. Wenn ich also die Zahl Pi (3.14) in einer Variablen speichern möchte, dann nehme ich einen Fließkommatypen.

Die Deklaration von Fließkommazahlen sieht genauso aus wie die von bspw. int Zahlen. Ein float, der es einem erlaubt Zahlen mit Nachkommastellen zu speichern, könnte so aussehen:

float pi = 3.14;

Und ein double precision float (einfach double) könnte dann so aussehen:

double morePrecisePi = 22 / 7;

Übrigens: Seit der Version C++14 kann man bei größeren Zahlen einen Tausender-Trennungspunkt angeben. Allerdings wird er als einfaches Anführungszeichen geschrieben. Das macht die Darstellung von Zahlen im Code übersichtlicher:

int moneyInBank = -70'000;    // -70000
double pi = 3.141'592'653'59;    // 3.14159265359

Die Größe einer Variablen mit sizeof bestimmen

Die Größe einer Variablen ist die Größe des Speichers, den die Variable für einen bestimmten Typen benötigt. C++ besitzt einen praktischen Operator namens sizeof, der einem die Speichergröße einer Variablen in Bytes sagen kann.

Die Nutzung von sizeof ist einfach. Man ruft die Funktion einfach auf und übergibt den gewünschten Typen als Parameter in Klammern.

// sizeOfAVariable.cpp
// Determine the size of a variable with sizeof 

#include <iostream>

using namespace std;

int main() {
    cout << "Computing the size of some C++ variable types" << endl;
    cout << "Size of bool: " << sizeof(bool) << endl;
    cout << "Size of char: " << sizeof(char) << endl;
    cout << "Size of unsigned short int: "
         << sizeof(unsigned short) << endl;
    cout << "Size of short int: " << sizeof(short) << endl;
    cout << "Size of unsigned long int: " 
         << sizeof(unsigned long) << endl;
    cout << "Size of long: " << sizeof(long) << endl;
    cout << "Size of unsigned int: " << sizeof(unsigned int) << endl;
    cout << "Size of int: " << sizeof(int) << endl; 
    cout << "Size of long long: " << sizeof(long long) <<endl;
    cout << "Size of unsigned long long: " 
         << sizeof(unsigned long long) << endl;
    cout << "Size of float: " << sizeof(float) << endl;
    cout << "Size of double: " << sizeof(double) << endl;

    cout << "The output changes with compiler, hardware and OS" << endl;

    return 0;
}

Die Ausgabe zeigt die Größe einiger Variablentypen auf dem Computer, auf dem das Programm ausgeführt wird.

Verengende Konvertierungsfehler vermeiden

Wenn ich eine Zahl vom Typ float einer Variablen vom Typ int zuweise, wird der Dezimalteil abgeschnitten und wenn ich eine große long Zahl in einer short Variablen speichere, dann bekomme ich ebenfalls einen falschen Wert. Bei einer normalen Zuweisung ist das aber kein Fehler während der Kompilierung – bestenfalls schmeißt der Compiler eine Warnung aus.

int largeNum = 5000000; 
short smallNum = largeNum; // compiles OK, yet narrowing error

Um solche Konvertierungsfehler zu vermeiden, gibt es seit C++11 die sogenannte List Initialisierung. Um das Feature zu benutzen, muss man die Werte / Variablen in geschweifte Klammern schreiben.

int largeNum = 5000000; 
short anotherNum{ largeNum }; // error! Amend types 
int anotherNum{ largeNum }; // OK! 
float someFloat{ largeNum }; // error! An int may be narrowed 
float someFloat{ 5000000 }; // OK! 5000000 can be accomodated

Konstanten

Mal angenommen, wir schreiben ein Programm zur Berechnung der Fläche und des Umfangs eines Kreises. Die Formeln lauten:

area = pi * radius * radius;
circumference = 2 * pi * radius;

In diesen Formeln hat pi immer einen konstanten Wert von 22/7. Dieser Wert ändert sich an keiner Stelle des Programms und wir wollen auch nicht, dass man den Wert aus Versehen irgendwo ändern kann. C++ ermöglicht es, pi als Konstante zu definieren, welcher nach der Deklaration nicht mehr verändert werden kann. Änderungen einer konstanten Variable verursachen einen Kompilierfehler. Konstanten in C++ könnten sein:

  • Literale Konstanten
  • Deklarierte Konstanten mit dem Schlüsselwort const

Literale Konstanten

Literale Kontanten können beliebigen Typs sein – Integer, Zeichenketten usw…. Im allerersten C++ Programm haben wir „Hello, C++ world!“ auf dem Bildschirm dargestellt, mit folgendem Ausdruck:

cout << "Hello, C++ world!" << endl;

In diesem Beispiel ist „Hello, C++ world!“ eine literale Zeichenkettenkonstante. Literale benutzt man ständig. Wenn ich schreibe:

int ageOfFinn = 12;

Dann ist die Dezimalzahl 12 ein Teil des Codes und wird in das Programm kompiliert. Sie kann nicht verändert werden und ist literal konstant.

Variablen als Konstanten mit const deklarieren

Die wichtigsten Arten von Konstanten in C++ Programmen sind Variablen, die vor dem Typen das Schlüsselwort const stehen haben. Hier ein kleines Beispiel, dass den Inhalt einer konstanten Variablen PI anzeigt:

// showPi.cpp
#include <iostream>

using namespace std;

int main() {
    const double PI = 22.0 / 7;
    cout << "The value of constant pi is: " << PI << endl;
    // Uncomment next line to view compile error:
    // PI = 345;

    return 0;
}

Wenn ich den Kommentar nun wegnehme und versuche das Programm zu kompilieren, dann bekomme ich folgenden Fehler:

$>  c++ showPi.cpp -o showPi
showPi.cpp: In function 'int main()':
showPi.cpp:10:11: error: assignment of read-only variable 'PI'
      PI = 345;
           ^~~

Man beachte die Deklaration der Konstanten PI in Zeile 7. Wir haben das Schlüsselwort const benutzt um dem Compiler mitzuteilen, dass es sich bei PI um eine Konstante handelt. Wenn wir jetzt in Zeile 10 versuchen PI einen neuen Wert zuzuweisen, dann erhalten wir den obigen Compilerfehler. Genau deswegen sind Konstanten ein guter Weg zu verhindern, dass bestimmte Variablen später nochmal geändert werden.

Übrigens: Es ist ein guter Programmierstil, wenn man konstante Variablen komplett in Großbuchstaben benennt.

Ok, heute ging es darum Speicher für das Ablegen von bestimmten Werten mithilfe von Variablen und Konstanten bereitzustellen. Wir haben gesehen, dass Variablen eine bestimmte Größe haben, abhängig von ihrem Typ und dass man mithilfe von sizeof diese Größe ermitteln kann. Die Auswahl des geeigneten Variablentyps für einen Wert ist wichtig für eine effektive Programmierung.

Im nächsten Teil geht es dann um Arrays und Strings