Mithilfe einer Variante, auch als Union bezeichnet, lassen sich auf ein und demselben Speicherplatz unterschiedliche Daten ablegen bzw. dort abgelegte Daten unterschiedlich interpretieren.
Die Definition einer Variante gleicht bis auf das Schlüsselwort union der bisherigen Definition einer Klasse. Genau genommen ist eine Variante ein spezieller Klassentyp, der eine Reihe von Einschränkungen besitzt.
union [NAME]
{
DTYP1 eigen1;
DTYP2 eigen2;
... // weitere Eigenschaften und Memberfkt.
} [varObj1,...];
Die in Klammern stehenden Angaben sind wiederum optional.
Alle Eigenschaften einer Variante liegen auf derselben Adresse. Demzufolge wird der für die Variante benötigte Speicherplatz von der Eigenschaft mit dem größten Speicherbedarf bestimmt.
Varianten können alle bisher bekannten Datentypen enthalten mit folgenden Einschränkungen: Ein Variante darf
Die meisten der Einschränkungen dürften im Augenblick noch nicht viel sagen, da die dort aufgeführte Funktionalität erst später beschrieben wird. Diese Einschränkungen wurden hier aber trotzdem aufgeführt, um das Thema Varianten so weit wie möglich zusammenzuhalten.
Der Zugriff auf ein Variantenmember erfolgt analog zum Zugriff auf ein Klassenmember, d.h., es folgt zuerst der Name des Variantenobjekts, dann bei direktem Zugriff der Punktoperator bzw. bei indirektem Zugriff der Zeigeroperator und zum Schluss der Name des Variantenmembers.
1: #include <iostream>
2: #include <print>
3:
4: // Variante
5: union Convert
6: {
7: private:
8: unsigned char bytes[2];
9: unsigned short word;
10: public:
11: // Methoden zum Setzen der Bytes
12: void SetByte1(unsigned char byte)
13: {
14: bytes[0] = byte;
15: }
16: void SetByte2(unsigned char byte)
17: {
18: bytes[1] = byte;
19: }
20: // Bytes als Word zurückgeben
21: auto GetWord() const
22: {
23: return word;
24: }
25: };
26: // main() Funktion
27: int main()
28: {
29: const unsigned char val1=0x11, val2=0x22;
30:
31: std::print("Byte {:#x} und {:#x} als Wort: ",
32: val1,val2);
33: // Varianten-Objekt definieren
34: Convert byte2Word;
35: // Bytes setzen
36: byte2Word.SetByte1(val1);
37: byte2Word.SetByte2(val2);
38: // Als unsigned short ausgeben
39: std::println("{:#x}\n",byte2Word.GetWord());
40: }
Byte 0x11 und 0x22 als Wort: 0x2211
Im Beispiel wird eine Variante Convert definiert, die zwei unsigned char-Werte zu einem unsigned short-Wert zusammenfasst. Zum Setzen der char-Werte werden die Methoden SetByteX() verwendet. Der aus den beiden char-Werten zusammengesetzte short-Wert wird dann über die Methode GetWord() ausgelesen.
Anonyme Varianten besitzen keinen Variantennamen und es können von diesem Typ keine Variantenobjekte definiert werden. Anonyme Varianten weisen zwei Besonderheiten auf:
1.) Sie müssen immer der Speicherklasse static angehören wenn sie im globalen oder benannten Namensraum definiert werden. Werden sie lokal definiert, können sie jeder Speicherklasse angehören, die an der entsprechenden Stelle erlaubt ist.
2.) Sie können keine Methoden enthalten.
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.
1: #include <iostream>
2: #include <print>
3:
4: // Definition der anonymen Variante
5: static union
6: {
7: unsigned long lVal;
8: unsigned short sVal[2];
9: unsigned char cVal[4];
10: };
11:
12: // main() Funktion
13: int main()
14: {
15: // ulong-Anteil der anonymen Variante
16: lVal = 0x12345678UL;
17: // Ausgabe als ulong
18: std::println("long-Wert: {:#x}", lVal);
19: // Ausgabe als ushort
20: std::println("Als short: {:#x} {:#x}",sVal[0], sVal[1]);
21: // Ausgabe als uchar
22: std::cout << "Als char : ";
23: for (auto elem: cVal)
24: std::print("{:#x} ",elem);
25: std::cout << '\n';
26: }
long-Wert: 0x12345678
Als short: 0x5678 0x1234
Als char : 0x78 0x56 0x34 0x12
Initialisierung einer Variante
Fehlt zum Abschluss noch eine Abweichung der Variante von einer struct bzw. class Klasse. Sie betrifft die Initialisierung der Variante. Soll ein Variantenobjekt bei seiner Definition initialisiert werden, muss bis auf Weiteres der Datentyp des Initialwertes mit dem Datentyp des ersten Elements in der Variante übereinstimmen. Außerdem ist der Initialisierungswert in einen Block {...} einzuschließen. Sehen Sie sich dazu insbesondere die Initialisierung in der Zeile 10 des Beispiels an. Die alleinige Angabe des Initialwertes 10 reicht bei dieser Variante nicht aus, da das Literal 10 als int-Wert interpretiert wird. Es muss eine explizite Typkonvertierung des Literals in einen char-Wert erfolgen.
1: // Variante definieren
2: union Month
3: {
4: char cMonth;
5: char *ptrMonth;
6: };
7: // Ungültige Initialisierungen da Datentyp des
8: // Initialwertes nicht übereinstimmt
9: union Month month1 {"Januar"};
10: union Month month2 {10};
11: // Gültige Initialisierung
12: union Month month3 {static_cast<char>(10)};
Später werden wir ein Verfahren kennenlernen, das es erlaubt, jedes beliebige Element einer Variante zu initialisieren (Stichwort: Überladen des Konstruktors).
Copyright 2024 © Wolfgang Schröder
E-Mail mit Fragen oder Kommentaren zu dieser Website an: info@cpp-tutor.de
Impressum & Datenschutz