C++ Kurs

enum Datentyp

Die Themen:

Syntax

Der enumerated Datentypen (im Folgenden vereinfacht enum genannt) wird immer dann verwendet, wenn eine Menge logisch zusammengehöriger Konstanten definiert werden soll. Der enum Datentyp wird oft auch als Aufzählungsdatentyp bezeichnet.

Die enum Anweisung hat folgende Syntax:

enum [NAME] [:DTYP] { KONST1[=VAL1],KONST2[=VAL2],... } [enumVar1,...];

Nach dem Schlüsselwort enum folgt optional ein Name, der den enum-Datentyp eindeutig kennzeichnet. Durch Angabe dieses Namens können danach entsprechende enum-Variablen oder Funktionsparameter definiert werden.

Nach dem Namen folgt, ebenfalls optional, der dem enum zugrundeliegende Datentyp DTYP. Dieser kann ein beliebiger Ganzzahl-Datentyp sein, jedoch muss er in der Lage sein, die innerhalb der geschweiften Klammern aufgeführten Konstanten aufzunehmen. Standardmäßig wird der "kleinste" Ganzzahl-Datentyp verwendet der es erlaubt, die Konstanten aufzunehmen.

Innerhalb der geschweiften Klammern folgen dann die Konstanten, die mit dem enum-Datentyp verbunden werden. Welche Werte diese Konstanten letztendlich besitzen, das sehen wir uns gleich an.

Nach der schließenden geschweiften Klammer können optional in der gleichen Anweisung noch Variablen des entsprechenden enum-Datentyps definiert werden.

PgmHeader
// enum-Datentyp definieren und gleichzeitig
// eine enum-Variable definieren

enum Colors
{
   RED, YELLOW, GREEN, BLUE
} myColors;
// weitere enum-Variablen definieren
Colors color1, color2;

Im Beispiel wird der enum-Datentyp Colors, mit den Konstanten RED, YELLOW, GREEN und BLUE, und die enum-Variable myColors definiert. Im Anschluss daran werden zwei weitere Variablen color1 und color2 mit dem gleichen enum-Datentyp definiert.

Einer solchermaßen definierten enum-Variable kann dann im weiteren Verlaufe des Programms nur eine innerhalb des enum-Blocks definierte Konstante zugewiesen werden.

Beispiel

Sehen wir uns die Wirkungsweise des enum-Datentyps anhand eines kleinen Beispiels an. Ziel der nachfolgenden Anweisungen ist es, die für eine Schaltfläche (Button) möglichen Zustände durch Konstanten festzulegen. Dabei soll eine Schaltfläche z.B. folgende 3 Zustände annehmen können: gesperrt, ausgewählt und gedrückt. Für jeden dieser Zustände werden entsprechende Konstanten definiert. Die Schaltfläche selbst wird durch eine int-Variable repräsentiert, der dann je nach Zustand eine der Konstanten zugewiesen wird.

PgmHeader
// Definition der Schaltflächen-Konstanten
const int DISABLED = 0;
const int FOCUS = 1;
const int PRESSED = 2;
// Definition der Schaltflächen-Variablen
int button;
...
// Zuweisen eines Zustands
button = DISABLED;

Mit Hilfe einer enum-Anweisung können nun die Definitionen der Konstanten und die Definition der Variable in einer einzigen Anweisung zusammengefasst werden. Dazu werden innerhalb des Blocks der enum-Anweisung einfach die Konstanten aufgelistet.

PgmHeader
// enum Definition
enum Button {DISABLED, FOCUS, PRESSED} button1;
// Definition einer weiteren Schaltfläche
Button button2;

int main()
{
   int var;
   ...
   button2 = FOCUS;
   // enum-Konstante einem int zuweisen
   var = DISABLED;
   ...

Die mittels einer enum-Anweisung definierten Konstanten können ebenfalls wie normale Konstanten verwendet werden (siehe letzte Anweisung oben im Beispiel). Daraus folgt, dass alle definierten enum-Konstanten unterschiedliche Namen besitzen müssen. Ausnahme: enum-Konstanten die innerhalb von Klassen definiert werden und enum-Klassen, die wir uns gleich noch ansehen werden.

Werte der enum-Konstanten

enum Konstanten werden standardmäßig automatisch durchnummeriert, wobei die erste Konstante den Wert 0, die zweite den Wert 1 usw. besitzt. Soll eine Konstante einen davon abweichenden Wert erhalten, so wir ihr explizit ein bestimmter Ganzzahlwert, und nur ein Ganzahlwert, zugewiesen. Hierzu wird nach dem Konstantennamen der Zuweisungsoperator angegeben, gefolgt von dem gewünschten Wert.

PgmHeader
// Definition
enum Button {DISABLED=1, FOCUS, PRESSED=FOCUS+10} button1;

Alle nach einer solchen Zuweisung folgenden Konstanten werden, wenn ihnen nicht erneut ein expliziter Wert zugewiesen wird, wieder fortlaufend jeweils um eins erhöht. Zusätzlich ist es möglich, eine bereits in der Liste definierte Konstante zur Berechnung des Wertes einer weiteren Konstante zu verwenden. Im Beispiel besitzt die Konstante DISABLED den Wert 1, FOCUS den Wert 2 und PRESSED den Wert 12.

Und wie jede andere Variable auch, kann eine enum-Variable bei ihrer Definition gleich initialisiert werden, jedoch nur mit einer zu ihrem Datentyp zugehörigen Konstante.

Fehlerfallen

Beim Einsatz von enums sind folgende zwei Punkte besonders zu beachten:

  1. Einer enum-Variable kann nicht ohne weiteres ein Ganzzahlwert zugewiesen werden, selbst wenn dieser Wert durch eine entsprechende Konstante definiert ist.
  2. Eine enum-Variablen kann zwar innerhalb einer Schleife verwendet werden, jedoch können mit dieser Variable keine Rechenoperationen durchgeführt werden, d.h. die in der zweiten for-Schleife angegebene Anweisung button1++ führt zu einem Fehler.
PgmHeader
// Definition
enum Button{DISABLED=1, FOCUS, PRESSED=FOCUS+10} button1;

// Keine int-Zuweisung erlaubt
button1 = 1;

// enum in einer for-Schleife
for (button1=FOCUS; button1!=DISABLED;)
   ...
// Aber das geht nicht!
for (button1=FOCUS; button1!=DISABLED; button1++)
   ...

Eine weitere Fehlerfalle lauert bei der Übergabe eines enum-Parameters an eine Funktion. Im nachfolgenden Beispiel wird der enum-Datentyp Style zur Aufnahme von verschiedenen Stilen definiert. Die Funktion Func(...) soll jetzt als Parameter ein Datum dieses enum-Typs erhalten. Damit sieht die Deklaration der Funktion Func(...) wie unten angegeben aus. Wird diese Funktion nun mit einem einzigen enum-Wert aufgerufen, so ist alles in Ordnung (erster Funktionsaufruf). Doch Vorsicht! Werden mehrere enum-Werte beim Aufruf der Funktion z.B. verodert, so meldet der Compiler beim Übersetzen einen Fehler (zweiter Funktionsaufruf). Der Grund hierfür liegt darin, dass die Veroderung von zwei Werten einen Integral-Datentyp (Ganzzahl-Datentyp) zurückliefert. Und damit passt der Aufruf nicht mehr zur Funktionsdeklaration. Das Ergebnis der Veroderung muss explizit wieder in den entsprechenden enum-Datentyp konvertiert werden (letzter Funktionsaufruf).

PgmHeader
// Enum-Definition
enum Style {STYLE1, STYLE2, STYLE3};

// Funktionsdeklaration
void Func(Style newStyle);

// So geht's
Func(STYLE2);
// Das geht schief
Func(STYLE1|STYLE2);
// So geht's wieder
Func(static_cast<Style>(STYLE1|STYLE2));

Beispiel

Das Beispiel simuliert den Schaltzyklus einer Verkehrsampel. Die einzelnen Ampelphasen werden hierbei durch entsprechende enum-Konstanten definiert.

In der main() Funktion werden in einer while-Schleife die Ampelphase durchlaufen. Es gilt dabei zu beachten, dass die Ampelphasen explizit der enum-Variable zugewiesen werden müssen, da keine Rechenoperationen mit enum-Variablen erlaubt sind.

PgmHeader
// Beispiel zum enum Datentyp

// Dateien einbinden

#include <iostream>

using std::cout;
using std::endl;

// main() Funktion
int main()
{
   // enum-Variable definieren und initialisieren
   enum eAMPEL {eROT1, eROTGELB, eGRUEN, eGELB, eROT2};
   auto eAmpel=eROT1;

   // Alle Ampelphasen durchlaufen
   do
   {
      // Aktuelle Ampelphase auswerten, den entsprechenden Text
      // ausgeben und nächste Ampelphase aufschalten

      switch (eAmpel)
      {
      case eROT1:
      case eROT2:
         cout << "Die Ampel hat rot!\n";
         if (eAmpel == eROT1)
            eAmpel = eROTGELB;
         else
            eAmpel = eROT1;
         break;
      case eROTGELB:
         cout << "Die Ampel hat rot-gelb\n";
         eAmpel = eGRUEN;
         break;
      case eGRUEN:
         cout << "Die Ampel hat grün\n";
         eAmpel = eGELB;
         break;
      case eGELB:
         cout << "Die Ampel hat gelb\n";
         eAmpel = eROT2;
         break;
      }
   } while (eAmpel != eROT1);
}
ProgrammausgabeDie Ampel hat rot!
Die Ampel hat rot-gelb
Die Ampel hat grün
Die Ampel hat gelb
Die Ampel hat rot!

Enum-Klassen

Außer diesem, von der Programmiersprache C übernommenen, enum-Datentyp, stellt C++ auch eine enum-Klasse zur Verfügung, die folgenden allgemeinen Aufbau besitzt:

enum class [NAME] [:DTYP] { KONST1[=VAL1],KONST2[=VAL2],... } [enumVar1,...];

Bis auf das Schlüsselwort class nach enum gleicht diese Anweisung der eben beschriebenen enum Anweisung. Anstelle von class darf hier auch struct stehen, was das Verhalten des enums aber nicht verändert.

Eine enum-Klasse unterscheidet sich vom 'gewöhnlichen' enum in folgenden Punkten:

  1. der enum-Wert kann nicht implizit in einen int-Wert konvertiert werden,
  2. die enum-Konstanten können nicht wie normale Konstanten verwendet werden, sondern müssen stets voll qualifiziert werden.

Der erste Punkt dient zum typsicheren Umgang mit den enum-Konstanten.

PgmHeader
// Definition
enum class TVColor {RED, GREEN, BLUE} tvColors;
enum TLColor {RED, YELLOW, GREEN} tlColors;
...
int tl = tlColors;   // erlaubt bei gewöhnlichen enums
int tv = tvColors;   // nicht erlaubt bei Klassen-enums

// Erlaubt bei gewöhnlichen enums
if (tlColors == 1)
   ....
// Nicht erlaubt bei Klassen-enums
if (tvColors == 1)
   ....

Soll ein enum in eine Ganzzahl konvertiert werden, was z.B. dann erforderlich wird wenn ein enum ausgegeben werden soll, so kann dies wie folgt erfolgen:

PgmHeader
// Konvertierung des enums in den zugrundeliegende Datentyp
auto intVal = static_cast<typename std::underlying_type<ECLASS>::type>(EVAR);

ECLASS ist Name der enum-Klasse und EVAR die zu konvertierende enum-Variable. Sie können die Wirkung dieser Anweisung einmal austesten, indem Sie für den der enum-Klasse zugrundeliegenden Datentyp einmal char und das andere Mal int verwenden und dann den konvertierten Wert ausgeben.

Der zweite Punkt in der Liste verhindert die Doppeldeutigkeit von Konstanten in enum-Klassen, d.h. es können jetzt Konstanten mit gleichem Namen in verschiedenen enum-Klassen definiert werden. Allerdings muss dafür jetzt beim Zugriff auf die enum-Konstante immer der voll qualifizierte Name verwendet werden.

PgmHeader
// Gewoehnlich enums
// Erzeugt Fehler beim Uebersetzen, da die Konstanten
// RED und GREEN doppelt vorhanden sind

enum TVColor {RED, GREEN, BLUE} tvColors;
enum TLColor {RED, YELLOW, GREEN} tlColors;

// Klassen-enums
// Erlaubt, da Konstanten jetzt eindeutig

enum class TVColor {RED, GREEN, BLUE} tvColors;
enum class TLColor {RED, YELLOW, GREEN} tlColors;

// Zuweisung, nur voll qualifiziert erlaubt
tvColor = GREEN;           // Das erzeugt einen Fehler
tvColor = TVColor::GREEN;  // Aber so geht es
AchtungDa Konstanten in enum-Klassen nicht mehr implizit in Ganzzahlen konvertiert werden, ist es jetzt nicht mehr so ohne weiteres möglich, Operationen (z.B. eine Veroderung) mit den Konstanten durchzuführen. Wenn Sie dies trotzdem durchführen wollen, so müssen Sie für die Operation den entsprechenden Operator selbst definieren. Wie das geht, erfahren Sie bei der Behandlung von überladenen Operatoren.