Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

20.1 Typumwandlungen II

20.1 Typumwandlungen II

Wie ich bereits in Kapitel 1.2 erwähnt habe, spielt die Typumwandlung bei Klassen eine größere Rolle und deswegen werde ich an dieser Stelle diese Thematik erneut aufgreifen und etwas ausführlicher besprechen.

Man unterscheidet zwischen füf verschiedenen Grundarten der Typumwandlung.

Des weiteren findet man im Internet noch diverse andere Typumwandlungen (union_cast, number_cast, truncate_cast, ...), welche aber an dieser Stelle viel zu weit führen würden, nicht Standard sind und auch nur für die absoluten Hardcoreprogrammierer interessant sein könnten.

Zum Seitenanfang
Zum Inhaltsverzeichnis

20.1.1 C Casting

20.1.1 C Casting

Das klassische C Casting ist genau diese Art von Typumwandlung, welche ich in Kapitel 1.2 angesprochen habe. In modernen Quelltexten findet man so etwas allerdings eher selten, da diese Art des Castings noch aus den Uhrzeiten stammt. Erstaunlicherweise funktioniert es zwar in Visual Studio auch bei Klassen, aber allein die Tatsache, dass es eigentlich nicht funktionieren dürfte, sollte Sie abschrecken.

Der Vollständigkeit halber, sehen Sie im folgendem Quelltext noch einmal ein klassisches C Casting. Sie sollten aber ab jetzt die Finger davon lassen!

 1
 2
 3
 4
 5
					
float	fWert		= 1234.5678;
int	iGerundet1	= (int)fWert;	// Variante 1
int	iGerundet2	= int(fWert);	// Variante 2

printf("Abgerundet: %i und %i", iGerundet1, iGerundet2);
					

Ausgabe:

Abgerundet 1234 und 1234
		
Zum Seitenanfang
Zum Inhaltsverzeichnis

20.1.5 reinterpret Casting

20.1.5 reinterpret Casting

Diese Typumwandlung ist etwas besonderes und auch die gefährlichste Variante. Mit ihr kann man Speicherbereiche neu interpretieren. Was meine ich damit? Angenommen, Sie haben einen Float und machen ein statisches Casten in einen Int. Dann wird der Wert des Floats so im Speicher mgeschrieben, dass ein Integer daraus wird. Das Ergebnis im Arbeitsspeicher sieht also nach der Konvertierung total anders aus. Beim Reinterpretieren, bleibt jede 1 und 0 an der Stelle wo sie war, was zur folge hat, dass man nicht genau voraussagen kann, was am Ende dabei herauskommt.

Ein denkbarer Anwendungsfall ist, dass man wirklich sehen möchte, wie gewisse Datentypen im Speicher abgelegt werden. In diesem Fall würde man eine reinterpret Typumwandlung auf beispielsweise einen unsigned int machen. Dies will ich jetzt einmal Demonstrieren.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
					
// Ausgabe eines Integers als Binärzahl //////////////////////////////////
void Ausgabe(unsigned int iWert) {
	unsigned int	iErgebnis			= iWert;
	unsigned int	aSpeicher[32];
	int		iPosition			= 31;

	do { // Umwandlung Dezimal -> Binär
		aSpeicher[iPosition--]			= iErgebnis % 2;
		iErgebnis				/= 2;
	} while (iErgebnis > 0);

	// Mit Nullen auffüllen
	while (iPosition >= 0) aSpeicher[iPosition--]	= 0;

	printf("Dezimal: %11i - Binaer: ", iWert);
	
	for (iPosition = 0; iPosition < 32; iPosition++) | 
		printf("%u", aSpeicher[iPosition]);
	} // end of for
	
	printf("\n");
} // Ausgabe //////////////////////////////////////////////////////////////////



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argtv) {
	float* pWert1			= new float(-20.25f);
	float* pWert2			= new float(0.125f);

	unsigned int* pKonvertiert1	= reinterpret_cast<unsigned int*>(pWert1);
	unsigned int* pKonvertiert2	= reinterpret_cast<unsigned int*>(pWert2);

	Ausgabe(*pKonvertiert1);
	Ausgabe(*pKonvertiert2);

	delete pWert1;
	delete pWert2;

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

Dezimal: -1046347776 - Binaer: 11000001101000100000000000000000
Dezimal:  1040187392 - Binaer: 00111110000000000000000000000000
		

Sie sehen also deutlich, dass der Speicherbereich komplett neu interpretiert wird und dass es ohne weiteres nicht möglich ist, den neuen Wert abzuschätzen. Wenn man sich nur den Wert des Integers anschaut, fragt man sich, wie jener zustande kommt. In dem Moment, wo man diese Dezimalzahl in eine binäre umwandelt und ausgibt, sieht man dann, dass der Binärwert, nach IEEE 754, tatsächlich dem Wert des Floats entspricht.

Ein weiterer Anwendungsfall wäre, eine Adresse (also den Wert eines Zeigers) in einem Array aus Integern zwischenzuspeichern. Genauso kann man auch Zeiger auf ganz andere Sachen umbiegen. Diese Art der Typumwandlung ist also eine Art Baseballschläger, mit dem man fast alles gnadenlos umwandeln kann. Also, auch hier sollten Sie sehr vorsichtig sein!

Zum Seitenanfang
Zum Inhaltsverzeichnis

20.1.4 dynamic Casting

20.1.4 dynamic Casting

Ich hatte bereits erwähnt, dass es bisher keinerlei Prüfmechanismen gab, welche eine Konvertierung in nicht voneinander abhängige Typen, verhindert hat und genau hier setzt die dynamische Typumwandlung an. Wenn man jetzt versucht aus einem Zeiger auf ein Fahrzeug, ein Zeiger auf ein Motorrad zu wandeln, dann wird zu mindestens geprüft, ob ein Motorrad in irgend einer Form von Fahrzeug abgeleitet wurde und es somit möglich sein kann, dass es sich bei dem Speicherbereich, auf welchen verwiesen wird, um ein Motorrad handeln kann. Dynamisches Casting funktioniert also nur bei polymorphen Klassen.

 1
 2
 3
 4
					
CMotorrad* pMotorrad1	= new CMotorrad();
CFahrzeug* pFahrzeug	= pMotorrad1;			// hoch casten

CMotorrad* pMotorrad2	= dynamic_cast<CMotorrad*>(pFahrzeug);	// runter casten
					

Diese Art der Typumwandlung wird eher selten gemacht, da sie recht teuer ist. Abgesehen von den zusätzlichen Prüfungen zur Laufzeit, muss jedes Objekt zusätzliche Typinformationen mit sich herumtragen, damit die Überprüfung weiß, was sich ursprünglich hinter einem Zeiger verborgen hat (Objekte benötigen mehr Speicherplatz). Sie sollten sich also von diesem Mechanismus distanzieren und ihn nur im Notfall verwenden.

Zum Seitenanfang
Zum Inhaltsverzeichnis

20.1.3 static Casting

20.1.3 static Casting

Das statische Casten ist die Mutter der Castings in C++ und funktioniert im Grunde genommen wie das klassische C Casting, nur mit dem Unterschied, dass man wieder ein Funktionstemplate benutzt. Bei der Umwandlung selbst werden keine Überprüfungen vorgenommen, aber falls man etwas casten will, was offensichtlich nicht funktioniert, wird man schon durch den Compiler darauf hingewiesen. In den nächsten Kapiteln wird Ihnen genau diese Art der Typumwandlung begegnen.

Im folgenden Beispiel zeige ich, wie man einen spezifischen Zeiger in einen Allgemeinen und wieder zurück wandelt.

 1
 2
 3
 4
 5
 6
 7
					
int*	pWert1	= new int(10);
void*	pWert2	= static_cast<void*>(pWert1);
long*	pWert3	= static_cast<long*>(pWert2);

printf("%li", *pWert3);

delete pWert1;
					

Ausgabe:

10
		

In Zeile 1 erzeuge ich mir einen Pointer auf einen Integer und initialisiere ihn gleich mit dem Wert 10. Anschließend, in Zeile 2, erzeuge ich mir einen "void" Zeiger, welchen ich auf den gleichen Wert zeigen lasse, allerdings jetzt ohne Angabe auf die Struktur, auf welche der Zeiger verweist. In Zeile 3 erzeuge ich jetzt einen Zeiger auf einen "long" Wert und lasse ihn, mittels statischem Casten, auf den selben Wert zeigen. Jetzt wird der Speicher, auf welchen der Zeiger verweist, als "long" interpretiert und wie Sie das an der Ausgabe sehen, hat alles wie gewünscht, funktioniert.

Zum Seitenanfang
Zum Inhaltsverzeichnis

20.1.2 const Casting

20.1.2 const Casting

Hin und wieder steht man vor dem Problem, dass man einen konstanten Wert bzw. eine Variable oder Funktion, welche mit "const" qualifiziert wurde, in eine dynamische Variable überführen möchte oder muss. Hierfür gab es bisher keine Möglichkeit. Das s.g. const Casting bietet hier einen Ausweg. Aber Vorsicht! Diese Umwandlung ist nur vorübergehend! Mann darf anschließend den Wert nicht ändern, da man sonst den konstanten Speicherbereich beschädigt und das Programm somit abstürzen wird. Seien Sie sich dessen immer bewusst und verwenden Sie folgenden Mechanismus, nur im Notfall.

 1
 2
 3
 4
 5
					
const char*	pcstrWert1	= "Hallo Welt";
char*		pstrWert2;
pstrWert2			= const_cast<char*>(pcstrWert1);

printf("%s", pstrWert2);
					

Ausgabe:

Hallo Welt
		

In Zeile 1 erzeuge ich mir also einen konstanten String mit einem konstanten Inhalt. Jene Variable weiße ich dann in Zeile 3, mit Hilfe der "const_case" Funktion, einem Zeiger auf ein dynamischen String zu. Die Syntax sieht ein wenig merkwürdig aus, aber das liegt daran, dass es sich hier um ein Funktionstemplate handelt. Was das genau ist und warum man das so schreiben muss, werde ich im entsprechenden Kapitel erklären. Wichtig im Moment ist nur, dass man den gewünschten Ergebnistyp, in spitze Klammern vor den runden Klammern, angibt. Alle weiteren Typumwandlungen werden auf ähnlichen Funktionstemplates basieren und stehen auch erst ab C++ zur Verfügung.

Der "const cast" kann und wird für ganz üble Hacks benutzt. Es geht nämlich eigentlich weniger darum eine klassische Konstante so zu manipulieren, dass sie nicht mehr konstant ist. Vielmehr hebelt man damit den Mechanismus aus, welcher mit einem konstanten Funktionsparameter erzwungen werden soll. Angenommen Sie übergeben einer Funktion eine Variable als konstante Referenz. Mit dem Modifikator "const", will der Ersteller der Funktion Ihnen zeigen, dass er den Wert nicht manipuliert und normalerweise könnte er es auch nicht. Da es sich aber nicht um eine richtige Konstante handelt und somit eine Änderung theoretisch nicht den Speicher kaputt machen würde, kann man mit dieser Art der Typumwandlung, die Variable wieder beschreibbar machen. Sie merken also, dass man hier eigentlich nur hässliche Sachen machen kann und deswegen Finger weg!

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012