4.5. Datentypen Kraut und Rüben: Konvertierung
Implizite (automatische) Typumwandlung
Enthält ein Ausdruck Variablen verschiedenen Datentyps, dann nimmt C - im Gegensatz z.B. zu Pascal - selbständig eine Umwandlung des Datentyps einzelner Operanden vor. Diese Konvertierung erfolgt nicht willkürlich, sondern gehorcht vorgegebenen Regeln. Trotzdem kann diese Eigenschaft von C immer wieder Programmierer mit überraschenden Ergebnissen konfrontieren, falls sie sich die Folgen impliziter Typenkonvertierung nicht vergegenwärtigen.
Typumwandlung bei Zuweisungen
Relativ offensichtlich ist, nach welcher Regel Typumwandlungen in Zuweisungen vorgenommen werden: Der Wert auf der rechten Seite einer Zuweisung (sei es der Wert einer Konstanten, Variablen oder eines Ausdrucks) wird an den Datentyp der Variablen links vom Zuweisungsoperator angepaßt. Es ist absehbar, daß Informationen verloren gehen können, wenn der Datentyp rechts vom Zuweisungsoperator eine höhere Genauigkeit oder einen größeren Wertebereich zuläßt als jener auf der linken Seite. Generell gilt, daß die Zuweisung eines integer-Werts an eine Fließkomma-Variable keinerlei zusätzliche Genauigkeit erbringt. Es ändert sich einzig und allein die Darstellungsform.
Folgendes Programmfragment demonstriert verschiedene Varianten impliziter Typumwandlung; dabei wird anschaulich, was geschieht, wenn der Typ der Zielvariablen einen geringeren Wertebereich aufweist als der rvalue:
.
.
{
unsigned char ucCharacter = 'X';
int iInteger = 256;
float fFloat = 3.22;
ucCharacter = iInteger;
/*
* Der Datentyp char ist 1 Byte groß und kann 'unsigned'
* Werte bis 255 aufnehmen. Der Wert 256 ist hexadezimal
* 01 00. Das höherwertige Byte mit dem Inhalt 01 wird
* abgeschnitten, es bleibt also 00.
*/
iInteger = fFloat;
/*
* Bei der Zuweisung von Fließkommawerten an eine Integer-
* Variable werden einfach die Nachkommastellen abge-
* schnitten. iInteger enthält also nach der Zuweisung den
* Wert 3
*/
fFloat = iInteger;
/*
* Der aktuelle Wert von iInteger ist 3. Er wird in den
* Typ float umgewandelt. fFloat enthält dann 3.0
*/
.
.
}
Folgende Aufstellung gibt an, welche Umwandlung bei der Zuweisung von einem Datentyp auf einen anderen vorgenommen wird und welcher Verlust an Informationen dabei erwartet werden kann:
Typ der Zielvariablen | Typ des Ausdrucks | Möglicher Informationsverlust |
---|---|---|
signed char | char | bei Wert > 127, lvalue negativ, nämlich - (256 - rvalue) |
char | short int | höherwertiges Byte |
char | int | entweder 1 oder 3 der vorderen Byte |
char | long int | die höherwertigen 3 Byte |
int | long int | die höherwertigen 2 Byte |
int | float | zumindest Nachkommastellen |
float | double | Genauigkeit, Resultat wird gerundet |
double | long double | Genauigkeit, Resultat wird gerundet |
Typumwandlung bei arithmetischen Ausdrücken
Kommen innerhalb arithmetischer Ausdrücke Operanden verschiedenen Datentyps vor, dann erfolgt ebenfalls eine automatische Konvertierung eines Operanden. Dieser Vorgang heißt übliche arithmetische Umwandlung. Dabei gilt allgemein die Regel, daß der Operand mit geringerem Wertebereich oder geringerer Genauigkeit an den genaueren bzw. größeren Datentyp des zweiten Operanden angepaßt wird. Das Resultat hat dann den Datentyp, den beide Operatoren nach der Konvertierung gemeinsam haben.
.
.
{
float fFloat;
unsigned uUnsigned = 5;
int iInteger = -32767;
fFloat = 1/3;
/*
* fFloat enthält wider Erwarten nicht 0.333333,
* sondern 0.000000
* Die Division der beiden Integer-Konstanten ergibt
* nämlich 0, die dann zu 0.000000 konvertiert wird.
*/
fFloat = 1.0 / 3;
/* Diesmal sind gehen die Nachkommastellen des Resultats
* nicht verloren, weil ein Operand eine Fließkomma-
* Konstante ist. Der zweite Operand wird konvertiert, das
* Resultat ist vom gleichen Typ wie Dividend und Divisor.
*/
uUnsigned += iInteger; /* int vorausgesetzt als 2 Byte */
/* Das Resultat 32774 ist einigermaßen überraschend!
* Es gilt aber zu bedenken, daß bei der Konvertierung von
* signed zu unsigned nicht einfach das Vorzeichen
* wegfällt, sondern das 2er-Komplement, in dem eine
* negative Zahl dargestellt wird, "normal" interpretiert
* wird. Das höchstwertige Bit gilt dann nicht mehr als
* Vorzeichen, sondern repräsentiert ebenfalls einen
* numerischen Wert.
*/
.
.
}
Weitere Beispiele für Typenkonvertierungen in arithmetischen Ausdrücken:
Arithmetischer Ausdruck | Ergebnis | Ergebnistyp |
---|---|---|
4 + 5 | 9 | integer |
3.0 + 2.0 | 5.0 | Fließkomma |
3.0 + 2 | 5.0 | Fließkomma |
22/11 | 2 | integer |
22/11.0 | 2.0 | Fließkomma |
Explizite Typumwandlung: Der Type-cast-Operator
Neben der automatischen Umwandlung von Datentypen, die C vornimmt, sobald innerhalb von Ausdrücken Operanden verschiedenen Typs auftreten, existiert für den Programmierer die Möglichkeit, Datentypen explizit zu ändern. Zu diesem Zweck existiert der sogenannte Type-cast-Operator. Eine explizite Typumwandlung hat die Form:
(Datentyp) Wert
Wert steht für beliebige Konstanten, Variablen und Ausdrücke, Datentyp für jeden in C gültigen elementaren oder zusammengesetzten Datentyp.
.
.
{
float fFloat;
int iInteger = 3;
fFloat = (float) 1 / 3;
/*
* Normalerweise wäre das Ergebnis dieser Division 0, da
* eine Integer-Division vorliegt. Mit Hilfe des Cast-
* Operators wird aber der Operand 1 zu einer Fließ-
* komma-Konstanten konvertiert. Der Divisor erhält dann
* durch automatische Umwandlung den gleichen Typ, das
* Ergebnis ist 0.3333333.
*/
fFloat = (float) ( 1 / 3 );
/*
* fFloat erhält hier den Wert 0. Warum? Aufgrund der
* Klammern wird erst das Ergebnis der Integer-Division
* (also 0) in den Typ float umgewandelt.
*/
fFloat = 32.48
iInteger *= (int) fFloat;
/* ohne Cast würde erst das Ergebnis der Multiplikation
* in den Typ int umgewandelt, iInteger würde daher der
* Wert 97 zugewiesen. So erhält iInteger aber den Wert 96
* weil fFloat schon vor der Multiplikation in int
* konvertiert wird!
*/
.
.
}
Es sei darauf hingewiesen, daß der Cast-Operator den Datentyp einer Variablen oder Konstanten nur temporär verändert. Das bedeutet, daß die Variable fFloat nach der Umwandlung in den Typ int überall im weiteren Programmverlauf nach wie vor vom Typ float wäre. Die Konvertierung gilt nur für die Operation, für die der Cast vorgenommen wird! Deswegen ist eine Anweisung, die nur eine explizite Typumwandlung vornimmt, unsinnig.
.
.
(int) fFloat;
/* Unsinn, da Typkonvertierung nur temporär! */
.
.