C++: Die Basics - eps1.4_Datentypen
Letztes Wochenende habe ich einen tollen und charmanten Typen kennengelernt. Naja, „kennengelernt“ ist etwas übertrieben. Es war eher eine flüchtige Begegnung. Seitdem geht er mir nicht mehr aus dem Kopf. Aber ich weiß nicht, ob er der Richtige für mich ist. Was mache ich jetzt?
Liest sich wie ein Post in einem Dating Forum. - Is aber nicht! - Nein, es sind die einleitenden Gedanken, wenn ich versuche, einen geeigneten Datentypen auszuwählen.
Datentypen
Bei der Deklaration von Variablen verlangt die Syntax von C++ nicht nur den Namen, sondern auch die Angabe des Datentyps.
Was ist ein Datentyp?
Formal ist ein Datentyp definiert als eine Menge von Werten und eine Menge von Operationen, die auf diese Werte angewendet werden können.
So bildet beispielsweise die Menge aller ganzen Zahlen eine Menge von Werten. Diese Zahlenreihe bildet jedoch erst dann einen Datentyp, wenn eine Reihe von Operationen enthalten ist. Diese Operationen sind die bekannten mathematischen und vergleichenden Operationen. Die Kombination aus einem Satz von Werten und Operationen führt zu einem echten Datentyp.
Und warum diese Kombination? Eigentlich recht ersichtlich. Nicht alle existierenden Operationen sind für jede Art von Daten passend oder anwendbar. Beispielsweise ist es nicht sinnvoll, zwei Namen zu dividieren. Um dich davor zu bewahren, unangebrachte Operationen auf bestimmte Daten anwenden zu können, gestattet dir die Programmiersprache C++ eben nur die eingeschränkte Auswahl des jeweiligen Datentyps.
In C++ kannst du entweder Datentypen als eigene Klasse selbst erstellen oder du greifst auf die integrierten Datentypen zurück. Die integrierten Datentypen sind ein integraler Bestandteil der Programmiersprache und werden auch als primitive Typen bezeichnet. In listing1 siehst du Beispiele für die Definition jeweils einer Variablen mit ganzer und mit einer reellen Zahl.
int integerNumber = 7;
float realNumber = 7.0;
// listing1: definition of integer and floating point variables
Doch die Kombinationen aus Werten und Operationen sind nicht die alleinigen Eigenschaften eines Datentyps. Es werden auch der Wertebereich sowie die Größe des reservierten Platzes im Speicher einer Variablen vorgegeben.
Insgesamt gibt es vier grundlegenden Arten von primitiven Datentypen:
- Wahrheitswerte (Boolean)
- Zeichen (Characters)
- Ganzzahlen (Integers)
- Gleitkommazahlen (Floating Point)
Eine große Anzahl an Datentypen in C++
Bevor wir uns einzelne Datentypen genauer anschauen, möchte ich dir noch kurz die Datatypen Modifiers vorstellen:
- signed
- unsigned
- short
- long
Der Name lässt es dich schon erahnen, damit kannst du die eingebauten Datentypen modifizieren. Und zwar, lassen sich mit ihrer Verwendung der Wertebereich oder auch zusätzlich die Größe der Datentypen anpassen.
Die Modifikatoren signed oder unsigned legen fest, ob nur positive Werte ohne Vorzeichen oder auch negative Werte mit Vorzeichen zum Wertebereich gehören. Dadurch wird der Wertebereich nur verschoben und die Größe bleibt erhalten.
Um die Größe zu verändern, nimmst du einfach die Modifikatoren short oder long. Auch hier ist der Name Programm und die Wertebereiche werden entweder kleiner oder größer.
Wie die Modifier richtig eingesetzt werden, siehst du gleich. Jetzt kommen wir endlich zu den primitiven Datentypen in C++.
Boolean
Die boolsche Variable ist nach dem englischen Mathematiker George Boole benannt und hat als Besonderheit nur zwei Werte, die sie einnehmen kann: Wahr
oder Falsch
, High oder Low, 0 oder 1. Diese Sonderform mit nur zwei Zuständen ist die Grundlage der formalen Logik und der heutigen Rechnertechnik. Boolean werden auf als logische Werte bezeichnet und haben nur die logischen Operatoren (UND, ODER, NICHT, ENTWEDER-ODER) der boolschen Algebra. Das Schlüsselwort für diesen Datentyp ist bool
.
Boolean | kleinster Wert | größter Wert | Speicherplatz |
---|---|---|---|
bool | 0 | 1 | 1 Byte |
bool | false | true | 1 Byte |
Character
In der Character Variablen kannst du einzelne Zeichen speichern. Mit dem Schlüsselwort char
kannst du einen Byte reservieren und mit 256 Werten ASCII kodierte Zeichen hinterlegen.
Character | kleinster Wert | größter Wert | Speicherplatz |
---|---|---|---|
signed char | -128 | 127 | 1 Bytes |
char | -128 | 127 | 1 Bytes |
unsigned char | 0 | 255 | 1 Bytes |
Integer
Das Schlüsselwort int
bezeichnet eine Integer Variable. Ohne einen Modifier hast du 4 Bytes zur Verfügung und kannst dort maximal die ganze Zahl 2147483647 oder minimal -2147483648 speichern. In der folgenden Tabelle siehst du, welchen Einfluss die Modifier auf Integer Variablen hat und welche Wertebereiche damit abgedeckt werden können.
Integer | kleinster Wert | größter Wert | Speicherplatz |
---|---|---|---|
short int | -32,767 | 32,768 | 2 Bytes |
unsigned short int | 0 | 65,535 | 2 Bytes |
int | -2,147,483,647 | 2,147,483,648 | 4 Bytes |
unsigned int | 0 | 4,294,967,295 | 4 Bytes |
long int | -2,147,483,647 | 2,147,483,648 | 4 Bytes |
unsigned long int | 0 | 4,294,967,295 | 4 Bytes |
long long int | -9.223.372.036.854.775.807 | 9.223.372.036.854.775.808 | 8 Bytes |
unsigned long long int | 0 | 18.446.744.073.709.551.615 | 8 Bytes |
Floating Point
Möchtest du allerdings reelle Zahlen mit Nachkommastellen speichern, dann benötigst du eine Floating Point Variable. Mit dem Schlüsselwort float
kannst du Dezimalzahlen mit einfacher Genauigkeit einer Variablen mit 4 Byte zuweisen.
Reicht dir die einfache Genauigkeit nicht, hast du die Möglichkeit, eine Double Floating Point Variable mit dem Schlüsselwort double
zu deklarieren. Dadurch erhöht sich die Genauigkeit von 6 (6,92) Nachkommastellen auf 15 (15,95) und der Speicherbedarf auf 8 Byte.
Floating Point | Genauigkeit | kleinster Wert | größter Wert | Speicherplatz |
---|---|---|---|---|
float | 6 digits | -3,4*10^38 | +3,4*10^38 | 4 Bytes |
double | 15 digits | -1.7*10^308 | +1.7*10^308 | 8 Bytes |
Seit C++11 gibt es auch den Datentypen long double
. Dieser erhöht die Genauigkeit einer Gleitkommazahl, ist aber nicht ganz vergleichbar mit seinen Verwandten, da der Prozessor mit diesem Typ nicht direkt rechnen kann. Aus diesem Grund wird er auch von verschiedenen Compilern unterschiedlich behandelt.
Valueless
Das Schlüsselwort void
bezeichnet den besonderen Datentypen Valueless. Damit ist eine Entität ohne jeglichen Wert gemeint. Jetzt fragst du dich zurecht, wozu man einen Datentypen ohne Wert benötigt. Ich möchte nur kurz dem Thema Funktionen vorgreifen. Void wird als Rückgabewert von Funktionen verwendet, die keinen Wert zurückgeben. Wenn dir diese Erklärung etwas seltsam vorkommt und du es genauer verstehen willst, musst du leider warten, bis wir Funktionen anschauen.
Wide Character
Eben haben wir über den Datentyp Character, mit dem du ASCII-kodierte Zeichen verarbeiten kannst, gesprochen. Der ASCII Zeichensatz beschränkt sich allerdings nur auf die englische Tastatur und beinhaltet z. B. nicht die deutschen Umlaute ä, ö und ü. Um die wichtigsten Schriftzeichen aller Schriften und Zeichensystemen in der Welt kodieren zu können, wurde der Unicode Zeichensatz eingeführt.
Da Unicode viel mehr Zeichen umfasst als in einem Byte hineingehen, bietet C++ für diesen Zweck den Datentyp Wide Character an. Variablen mit dem Schlüsselwort wchar_t
sind einem char
sehr ähnlich, können aber mit 4 Byte einen viel größeren Zeichensatz abdecken. Eingesetzt werden Wide Characters also im Zusammenhang mit internationalen Sprachen, egal ob Deutsch, Schwedisch oder Japanisch.
C++ Datentyp | Speicherplatz |
---|---|
char | 1 Bytes |
short wchar_t | 2 Bytes |
wchar_t | 4 Bytes |
long wchar_t | 8 Bytes |
Auf die Größe kommt es an
Und wozu gibt es diese Menge an Datentypen? Der Grund ist historisch und war den knappen Ressourcen geschuldet. Die heutigen Leistungsmonster sind mit den ersten Computern nicht vergleichbar. Es musste ganz genau mit Speicher und Rechenaufwand gehaushaltet werden, um die schwachen Maschinen nicht noch langsamer zu machen, als sie eh schon waren.
Jetzt kannst du natürlich sagen, dass eine Optimierung des Quellcodes auf den letzten Byte heute, danke der raschen Entwicklung und dem Mooreschen Gesetz, nicht mehr notwendig ist. Doch wir unterhalten uns im Kontext der Embedded Systems. Dort besteht die Herausforderung, so viel wie möglich auch aus der kleinsten Hardware herauszuholen. Außerdem fühlt es sich gut an und ist ein Zeichen des eigenen Qualitätsanspruchs, seinen Code effizient zu entwickeln.
Also mach dir ruhig die Gedanken, ob der Integerwert unbedingt 16 Bit benötigt oder auch 8 Bit dafür reichen.
Natürlich musst du nicht eine Tabelle mit allen Datentypen griffbereit haben oder auswendig lernen. Du kannst ganz bequem die Größe einer Variablen mit der Funktion sizeof()
bestimmen. Die Größe ist der Platz im Speicher, den der Compiler für deklarierte Variablen reserviert, um dort Daten zuweisen zu können. sizeof()
gibt dir den Speicherplatz in Bytes an.
Die Funktion ist sehr einfach anzuwenden. Du musst lediglich den Datentypenbezeichner als Argument an sizeof()
übergeben (siehe Listing2).
// listing2: Size of primitive datatypes
#include <iostream>
int main()
{
std::cout << "Size of bool = " << sizeof(bool) << " Bytes" << std::endl;
std::cout << "Size of char = " << sizeof(char) << " Bytes" << std::endl;
std::cout << "Size of int = " << sizeof(int) << " Bytes" << std::endl;
std::cout << "Size of float = " << sizeof(float) << " Bytes" << std::endl;
return 0;
}
Typisierung
Die Zuweisung eines Objekts zu einem Datentypen wird in der Informatik Typisierung genannt. Wie sollte es auch anders sein, so ist dieses Thema nicht in einem Satz behandelt. Es bietet viel Stoff für Diskussionen, da typisierte Programmiersprachen verschiedene Typsysteme haben. Grundsätzlich dienen diese hauptsächlich dazu, den Wertebereich von Variablen sinnvoll einzuschränken und syntaktisch oder semantisch fehlerhafte Operationen zu vermeiden. Deshalb spricht man von Typsicher, wenn es um die Fähigkeit einer Programmiersprache geht, Typfehler während der Laufzeit zu verhindern.
Ein Beispiel für ein Typsystem ist die Typisierung von
C++:
- statisch (standardmäßig; optional dynamisch)
- explizit
- stark typisiert
Dynamisch oder Statisch
Bei der statischen Typprüfung musst du deine Typen bereits zum Zeitpunkt der Entwicklung kennen und entweder explizit angegeben, oder aber implizit vom System ableiten lassen. Findet eine Prüfung der Typen erst während der Laufzeit statt, dann ist es eine dynamische Typprüfung.
int year = 2019; // int
std::string firstName = "embeddingchris"; // std::string
// listing3: Static type in C++
Explizit oder Implizit
Bei der expliziten Angabe der Typen wirst du dazu angehalten, auch Daten mit passendem Typen einer Variablen zuzuweisen. In Listing5 ist es die Ganzzahl 2019
, die der Integer Variablen year
zugewiesen wird. Anders sieht es bei einer impliziten Ableitung aus. Hierbei nimmt das System den passenden Datentyp zum Inhalt an.
int year = 2019; // 2019 is an integer
std::string firstName = "embeddingchris"; // "embeddingchris" is a string
// listing4: Explicit declaration in C++
Stark oder Schwach
Ob ein Typsystem stark oder schwach ist, kannst du auch als Grad der Typsicherheit verstehen. Stark typisierte Programmiersprachen achten sehr strikt auf die Einhaltung der Datentypen. Wenn überhaupt, sind dir nur implizite Typumwandlungen ohne Datenverlust gestattet. Das bedeutet, dass du beispielsweise einen ganzzahligen Datentyp mit kleinerem Wertebereich (short
) einem mit größerem Wertebereich (long
) zuweisen darfst. Schwächer typisierte Programmiersprachen nehmen es hierbei nicht so genau und drücken eher ein Auge zu.
int length = 2.54; // compiling error
size = (int)2.54; // int size = 2
// listing5: Strongly typed in C++
Was hängen bleibt
Wir wissen nun, dass ein Datentyp die Kombination aus einer Menge von Werten und einem Satz an Operationen ist. Du kannst selbst Datentyp erstellen oder greifst auf die primitiven Datentypen der Programmiersprache zurück. Natürlich kannst du auch Datentypen anderer Entwickler nehmen. Das populärste Beispiel ist vielleicht string
aus der C++-Standard-Bibliothek.
Die primitiven Datentypen von C++ lassen sich in Wahrheitswerte, Zeichen, Ganzzahlen oder Gleitkommazahlen untergliedern. Jeder Datentyp hat dabei seine eigenen Werte, einen Wertebreich und Operatoren. Mit den Modifiern signed, unsigned, short, long lässt sich der Wertebereich besser auf deinen Anwendungsfall anpassen.
Und wenn du gerade die Größe eines Datentypen nicht kennst, dann behilfst du dir mit der Funktion sizeof()
.
C++ ist eine statisch, explizit und stark typisierte Programmiersprache und verlangt einen gut durchdachten Einsatz von Typen. Doch mit diesem Wissen bist du sehr gut gerüstet und kannst den richtigen Datentypen in jeder Situation wählen.
Ich wünsche dir maximalen Erfolg!
Quellen
- [1] B. Stroustrup, A Tour of C++. Pearson Education, 2. Auflage, 29. Juni 2018.
- [2] B. Stroustrup, Programming: Principles and Practice Using C++. Addison Wesley, 2. Auflage, 15. Mai 2014.
- [3] U. Breymann, Der C++ Programmierer. C++ lernen – professionell anwenden – Lösungen nutzen. Aktuell zu C++17. München: Carl Hanser Verlag GmbH & Co. KG; 5. Auflage, 6. November 2017