Syntax
Beispiel einer Variante
Anonyme Variante
Initialisierung einer Variante
Mithilfe einer Variante, häufig auch als Union bezeichnet, lassen sich auf ein und dem selben Speicherplatz unterschiedliche Daten ablegen. Und diese Daten müssen dann nicht einmal den selben Datentyp besitzen.
Die Variantenanweisung gleicht bis auf das Schlüsselwort union der bisherigen Definition einer Klasse und hat damit die nachfolgend angegebene Syntax. Die in Klammern stehenden Angaben sind optional. Genau genommen ist eine Variante nur eine spezielle Klasse, die eine Reihe von Einschränkungen besitzt.
|
union [Name] { DATENTYP1 Element1; DATENTYP2 Element2; ... } [variantVar1,...]; |
Der von der Variante letztendlich belegte Speicherplatz wird von dem Variantenelement mit dem größten Speicherbedarf bestimmt.
Varianten können alle bisher bekannten Datentypen enthalten. Auch der Zugriff auf ein Variantenelemente erfolgt analog dem auf ein Klassenelemente, d.h. es folgt zuerst der Name der Variantenvariable, dann bei direktem Zugriff der Punktoperator bzw. bei indirektem Zugriff der Zeigeroperator und zum Schluss der Name des Variantenelements.
|
|
Um eines der Einsatzgebiete einer Variante zu verdeutlichen, sehen Sie sich zunächst einmal das folgende Beispiel an.
|
// Variante definieren union Date { unsigned long dwDate; unsigned char acDate[4]; } date1, date2; // main() Funktion int main () { // 1. Datum ablegen date1.dwDate = 0; date1.acDate[0] = 1; date1.acDate[1] = 2; date1.acDate[2] = 8; // 2. Datum ablegen date2.dwDate = 0; date2.acDate[0] = 9; date2.acDate[1] = 9; date2.acDate[2] = 8; // Datum vergleichen if (date1.dwDate < date2.dwDate) cout << "Datum1 liegt vor Datum2\n"; else cout << "Datum2 liegt vor Datum1\n"; } |
Dort wird eine Variante Date definiert, die zum Abspeichern eines Datums dienen soll. Die Variante enthält die beiden Elemente dwDate und acDate. Da wie erwähnt beide Daten ab der gleichen Speicheradresse beginnen, ergibt sich folgender Speicheraufbau:
| dwDate | |||
| acDate[0] | acDate[1] | acDate[2] | acDate[3] |
Mithilfe dieser Variante kann jetzt ein Datum sowohl als long-Zahl wie auch byteweise ausgewertet werden. Und damit ist es durch diesen kleinen 'Kniff' relativ leicht möglich, zwei Datumsangaben miteinander zu vergleichen. Der eigentliche Trick besteht darin, dass bei einer long-Zahl das niederwertige Byte auch auf der niederwertigen Adresse zu liegen kommt. Da bei einer Variante beide Daten 'übereinander liegen', wird im ersten Byte des char-Feldes der Tag, danach der Monat und zum Schluss das Jahr abgelegt.
|
|
Das Einzige was Sie bei dieser Variante beachten müssen ist, dass auch der nicht für das Datum verwendete Platz acDate[3] initialisiert werden muss, wenn Sie das Datum als long-Zahl auswerten wollen.
Sehen wir uns nun noch an, wie die beiden oben im Beispiel angegebenen Daten in den Varianten zu liegen kommen:
| 0x00080201 | |||
| 0x01 | 0x02 | 0x08 | 0x00 |
| 0x00080909 | |||
| 0x09 | 0x09 | 0x08 | 0x00 |
Sehen wir noch ein weiteres Beispiel für eine Variante an. Da Varianten, wie bereits erwähnt, prinzipiell auch Klassen sind, können Sie auch Memberfunktionen enthalten.
|
// Variante union Convert { unsigned char bytes[2]; unsigned short word; // Memberfunktionen zum Setzen von Bytes void SetByte1(unsigned char byte) { bytes[0] = byte; } void SetByte2(unsigned char byte) { bytes[1] = byte; } // Bytes als Word zurückgeben unsigned short GetWord() const { return word; } }; // main() Funktion int main() { // Varianten-Objekt definieren Convert byte2Word; // Bytes setzen byte2Word.SetByte1(0x11); byte2Word.SetByte2(0x22); // Als unsigned short ausgeben cout << "WORD-Wert: " << hex << byte2Word.GetWord() << endl; } |
Im Beispiel wird eine Variante definiert, die zwei char-Werte zu einem short-Wert zusammenfasst. Zum Setzen der beiden char-Werte werden entsprechende Memberfunktionen SetByteX(...) verwendet. Der aus den beiden char-Werten zusammengesetzte short-Wert wird über die Memberfunktion GetWord() zurückgegeben.
In der main() Funktion wird dann das Varianten-Objekt byte2Word definiert. Beachten Sie bei der Definition des Objekts, dass auch hier das Schlüsselwort union entfallen kann.
Anschließend werden über die Memberfunktionen SetByte1(...) und SetByte2(...) die beiden unsigned char Elemente des Variantenfelds bytes gesetzt. Diese beiden Bytes werden dann zusammen als unsigned short Wert ausgegeben.
|
|
Eine weitere Variantenart ist die anonyme Variante. Anonyme Varianten besitzen keinen Variantennamen und gehören zu keinem Variantenobjekt. Solchermaßen definierte anonyme Varianten weisen zwei Besonderheiten auf:
Anonyme Varianten dienen 'nur' zur Ablage von verschiedenen Daten auf der gleichen Speicherstelle. Alle Member der anonymen Variante werden direkt angesprochen, d.h. ohne die sonst übliche Varianten-Syntax x.y bzw. x->y. Das folgende Beispiel demonstriert den Einsatz einer anonymen Variante zur Konvertierung eines unsigned long Werts in zwei unsigned short bzw. vier unsigned char Werte.
|
#include <iostream> using std::cout; using std::endl; // Definition der anonymen Variante static union { unsigned long lVal; unsigned short sVal[2]; unsigned char cVal[4]; }; // main() Funktion int main() { // ulong-Anteil der anonymen Variante lVal = 0x12345678UL; // Ausgabe mit Basis in Hex cout << std::hex; cout.setf(std::ios::showbase); // Ausgabe als ulong cout << "Long-Wert: " << lVal << endl; // Ausgabe als ushort cout << "Als short: " << sVal[0] << ' ' << sVal[1] << endl; // Ausgabe als uchar cout << "Als char : "; for (int index=0; index<4; index++) cout << static_cast<int>(cVal[index]) << ' '; cout << endl; } |
|
Long-Wert: 0x12345678 |
|
|
Fehlt uns jetzt zum Abschluss nur noch die letzte der am Anfang erwähnten Abweichungen der Variante von einer allgemeinen Klasse. Sie betrifft die Initialisierung der Variante. Soll eine Variante bei ihrer Definition initialisiert werden, so muss der Datentyp des Initialwertes mit dem Datentyp des ersten Elements in der Variante übereinstimmen und in einem Block {...} eingeschlossen werden. Sehen Sie sich dazu besonders die 2. Initialisierung im Beispiel an. Die alleinige Angabe des Initialwertes 10 reicht bei dieser Variante nicht aus, da das Literal 10 hier als int-Wert interpretiert wird. Sie müssen also eine explizite Typkonvertierung des Literals in einen char-Wert vornehmen.
|
// Variante definieren union Month { char cMonth; char *ptrMonth; }; // Ungültige Initialisierungen da Datentyp des Initialwertes nicht übereinstimmt union Month month1 = {"Januar"}; union Month month2 = {10}; // Gültige Initialisierung union Month month3 = {static_cast<char>(10}}; |