C++ Kurs

Typkonvertierungen

Die Themen:

Kommen wir nun zu einem recht heiklen Thema, der Typkonvertierung. Mittels Typkonvertierung kann der ursprüngliche Datentyp, z.B. einer Variable, einer Konstante oder eines Ausdrucks, in einen anderen Datentyp konvertiert werden. Wie in der Zwischenzeit bereits mehrfach erwähnt wurde, wird bei Operationen mit ungleichen Datentypen soweit wie möglich eine automatische Typkonvertierung durch den Compiler vorgenommen, da Operatoren nur Daten mit gleichen Datentypen verarbeiten können. Wir werden uns in diesem Kapitel ansehen, wie solche Typkonvertierungen explizit durchgeführt werden können und wie die automatische Typkonvertierung arbeitet.

Typkonvertierung im alten Stil (C-Stil)

Fangen mit der 'alten' Form der Typkonvertierung an, die in Zukunft so gut wie nicht mehr verwendet werden sollte. Sie enthält keinerlei Sicherheitsabfragen (d.h. die Datentypen können beliebig umgewandelt werden) und wird hier nur der Vollständigkeit halber aufgeführt.

Der Datentyp eines Ausdrucks kann mit folgenden Anweisungen explizit in einen anderen Datentyp überführt werden:

(DTYP) AUSDRUCK
DTYP (AUSDRUCK)

DTYP ist der neue Datentyp, in den AUSDRUCK konvertiert werden soll.

Bei der Konvertierung eines Gleitkommawertes in eine Ganzzahl werden die Nachkommastellen einfach abgeschnitten, es erfolgt keine Rundung.

PgmHeader
// Ausgangszustand
short var1 = 10;
short var2 = 4;
double result1, result2;

// Berechnung: double(10) / double(4)
// = 10.0 / 4.0
// = 2.5

result1 = (double)var1 / (double)var2;
// Berechnung: double(10 / 4)
// = double(2)
// = 2.0

result2 = double(10 / 4); 

Wie dem Beispiel auch entnommen werden kann, ist es nicht immer gleichgültig, wann ein Datum konvertiert wird. Eine Division von Ganzzahlen liefert immer ebenfalls eine Ganzzahl, auch wenn das Ergebnis der Division anschließend in eine Gleitkommazahl umgewandelt wird.

C++ Typkonvertierungen

C++ bietet folgende Typkonvertierungen:

const_cast<DTYP>(AUSDRUCK)
static_cast<DTYP>(AUSDRUCK)
dynamic_cast<DTYP>(AUSDRUCK)
reinterpret_cast<DTYP>(AUSDRUCK)

DTYP ist auch hier der neue Datentyp, in den AUSDRUCK konvertiert werden soll. Jede dieser Typkonvertierung wird für ganz spezielle Konvertierungen eingesetzt. Der große Vorteil dieser Konvertierungen liegt darin, dass je nach Konvertierung entweder zur Compilezeit oder zur Laufzeit die Zulässigkeit der Konvertierung überprüft wird.

const_cast Konvertierung

Die const_cast Konvertierung dient dazu, den const oder volatile Modifizierer von einem Datentyp zu entfernen oder ihn hinzuzufügen (volatile wird später noch erklärt). Dabei darf sich der neue Datentyp vom Ursprungsdatentyp nur durch den Modifizierer const oder volatile unterscheiden. Alle anderen Konvertierungen führen zu einem Fehler beim Übersetzen des Programms. Als Ergebnis liefert const_cast den auf DTYP konvertierten Ausdruck. Die erste Zuweisung ohne Typkonvertierung im nachfolgenden Beispiel liefert einen Fehler, da ein Zeiger auf einen konstanten Text nicht einem Zeiger auf einen veränderbaren Text zugewiesen werden kann. Wie's trotzdem geht, ist in der darunter stehenden Anweisung dargestellt. Hier wird vor der Zuweisung der const-Modifizierer entfernt.

PgmHeader
// Zeiger auf konstante Zeichen
const char *pConst = "Text";
// Zeiger auf veränderbare Zeichen
char *pVar;
// Das schlägt fehl!
pVar = pConst;
// Aber so geht's
pVar = const_cast<char*>(pConst);
// Alternativ: pVar = const_cast<decltype(pVar)>(pConst);
AchtungWenn Sie von einem Datentyp das const-Attribut entfernen, sollten Sie genau wissen was Sie tun! In der Regel führt das Entfernen des const-Attributs nicht automatisch dazu, dass Sie danach die ursprüngliche Konstante verändern können. Auf manchen Systemen, insbesondere Embedded Systemen, liegen Konstanten im ROM (Read-Only Speicher, z.B. Flash oder EEPROM) ab. Und Schreibzugriffe auf einen solchen Speicher können bis zum Programmabsturz führen!

static_cast Konvertierung

Die static_cast Konvertierung wird eingesetzt, um zwischen Ganzzahl-Datentypen und Gleitkomma-Datentypen zu konvertieren. Die erste Anweisung im Beispiel konvertiert ein long-Datum in ein char-Datum. Hierbei ist zu beachten, dass bei der Zuweisung nur das niederwertigste Byte übernommen wird, der Rest wird verworfen. Ebenfalls eingesetzt wird der static_cast Operator um einen void-Zeiger (Zeiger ohne bestimmtem Datentyp) in einen beliebigen anderen Zeiger (typisierten Zeiger) zu konvertieren. So konvertiert die letzte Anweisung des Beispiels den void-Zeiger pVal in den int-Zeiger pVar.

PgmHeader
// Konvertierung von long nach char
long longVar = ..;
char charVar = static_cast<char>(longVar);

// void-Zeiger-Konvertierung
void *pVal = ..;
int *pVar = static_cast<int*>(pVal);

Außerdem kann der static_cast Operator noch eine Ganzzahl in einen enum-Wert konvertieren und umgekehrt. Allerdings muss dabei darauf geachtet werden, dass die zu konvertierende Ganzzahl eine der in der enum-Anweisung definierte Konstante ergibt. Der enum-Datentyp wird später noch behandelt.

HinweisDer static_cast Operator kann auch dazu verwendet werden, Objektzeiger zu konvertieren deren Klassen in einer Beziehung zueinander stehen (Stichwort: Ableitung). Diese Konvertierung wird im Kapiel Typkonvertierungen auf Klassenzeiger später behandelt.

reinterpret_cast Konvertierung

Typkonvertierungen mittels reinterpret_cast sind die fehlerträchtigsten, da sie in der Regel plattformabhängige Konvertierungen durchführen. Die reinterpret_cast Konvertierung wird zum einen für die Konvertierung zwischen verschiedenen Zeigertypen und zum anderen für die Konvertierung von Ganzzahlen in Zeiger und umgekehrt eingesetzt.

PgmHeader
// Definitionen
char *pText = "Text";
void *pAny;
const int CONST = 10;
long lVal;

// Konvertierungen Zeiger nach long
lVal = reinterpret_cast<long>(pText);
// Konvertierung nach char-Zeiger
pText = reinterpret_cast<char*>(CONST);
/// Konvertierung zwischen Zeigern, jetzt mal mit decltype
pText = reinterpret_cast<decltype(pText)>(pAny);

dynamic_cast Konvertierung

Die dynamic_cast Konvertierung wird später im Anschluss an die Behandlung von abgeleiteten Klassen beschrieben, da sie nur in diesem Zusammenhang von Bedeutung ist (siehe Kapitel Typkonvertierungen auf Klassenzeiger) Sie ist nur der Vollständigkeit wegen an dieser Stelle erwähnt.

Automatische Typkonvertierungen (Promotions)

Viele 'alltägliche' Typanpassungen kann der Compiler automatisch vornehmen, wenn dadurch keine Information verloren geht. Wie bereits einige Male erwähnt, werden vor dem Ausführen einer Operation (und auch eine Zuweisung ist eine Operation) immer die Datentypen der Operanden einander angepasst. Die Anpassung erfolgt in der Weise, dass der 'kleinere' Datentyp auf den 'größeren' konvertiert wird. Ausnahme: bei Zuweisungen wird der Datentyp des rechten Operanden immer an den Datentyp des linken Operanden angepasst.

Ohne jetzt auf alle Einzelheiten einzugehen, die im Standard mehrere Seiten füllen, gibt es folgende wichtige Konvertierungen.

Integral Promotion

Werte vom Datentyp char, unsigned char, signed char, unsigned short und signed short können vom Compiler auf den Datentyp int erweitert werden, wenn dadurch der gesamte Wertebereich des Original-Datentyps abgedeckt wird. Ist dies nicht der Fall, so wird eine Anpassung auf einen unsigned int Datentyp versucht. Ebenfalls können enum und wchar_t Datentypen nach int bzw. long (sowohl signed als auch unsigned) umgewandelt werden. Und zum Schluss kann ein bool Wert noch in einen int Wert konvertiert werden, wobei für true gleich 1 und für false gleich 0 verwendet wird.

PgmHeader
char var = 0x55;
if (var == 0x80) // Konvertierung von var auf int!

bool bVar = true;
int nVar = bVar; // Konvertierung von bVar auf int

Floating Promotion

Ein Wert vom Typ float kann bei Bedarf auf einen Wert vom Datentyp double konvertiert werden.

Floating-Integral Konvertierung

Ein Gleitkommawert kann in einen Ganzzahlwert konvertiert werden, wobei die Nachkommastellen einfach abgeschnitten werden. Kann ein Ganzzahlwert nicht exakt als Gleitkommawert dargestellt werden, so ist die entsprechende Rundung vom Compiler abhängig.

Zeiger Konvertierungen

Ein Zeiger eines beliebigen Datentyps kann immer in einen void-Zeiger (typloser Zeiger) konvertiert werden. Außerdem kann ein Zeiger auf eine abgeleitete Klasse stets in einen Zeiger auf die entsprechende Basisklasse konvertiert werden. Dieser Sachverhalt spielt später im Kurs noch eine wichtige Rolle.