Enumerations

Die Enumeration, auf Deutsch Aufzählung, ist ein Datentyp, der nur bestimmte Werte aufnehmen kann. Vorrangig wird er eingesetzt, um Quelltext lesbarer und typsicherer zu gestalten.

Nehmen wir als Beispiel eine Ampel: Möchte man den aktuellen Status einer Ampel speichern, so könnte man hingehen und den einzelnen Möglichkeiten Werte zuweisen:

int printTrafficLight( int light )
{
  if     ( light == 0 ) printf( "Ampel ist aus\n" );
  else if( light == 1 ) printf( "Ampel ist grün\n" );
  else if( light == 2 ) printf( "Ampel ist gelb\n" );
  else if( light == 3 ) printf( "Ampel ist rot\n" );
  else if( light == 4 ) printf( "Ampel ist rot und gelb\n" );
  else                  printf( "Ampel ist kaputt (unbekannter Zustand)\n" );
}

Um nun ein Programm zu schreiben, muss der Entwickler immer im Kopf behalten, welche Zahl welche Bedeutung hat. Man spricht hierbei häufig von MagicNumbers, also Zahlen, die irgendeine magische Bedeutung haben, die man aus dem Quelltext an der Stelle häufig nicht erkennen kann.

Hier kommt nun die Aufzählung ins Spiel, um MagicNumbers zu vermeiden. Die Ampel hat eigentlich nur 5 Zustände, und die kann man aufzählen:

enum TrafficLightState
{
  Off,
  Green,
  Yellow,
  Red,
  RedAndYellow
};

und die Funktion entsprechend anpassen:

int printTrafficLight( enum TrafficLightState light )
{
  if     ( light == Off          ) printf( "Ampel ist aus\n" );
  else if( light == Green        ) printf( "Ampel ist grün\n" );
  else if( light == Yellow       ) printf( "Ampel ist gelb\n" );
  else if( light == Red          ) printf( "Ampel ist rot\n" );
  else if( light == RedAndYellow ) printf( "Ampel ist rot und gelb\n" );
  else                             printf( "Ampel ist kaputt (unbekannter Zustand)\n" );
};

Statt kryptischer Zahlen sieht man nun, was passiert. Und die Funktion erwartet als Eingabe einen Wert vom Typ (enum TrafficLightState), statt einer nichts aussagenden Integerzahl.

Konstantenschreibweise

In C ist es sehr verbreitet, Konstanten vollständig groß zu schreiben. Auch Enumerations sind Konstanten und werden daher häufig vollständig groß geschrieben. Um Wörter zu trennen wird hier häufig ein Unterstrich eingefügt:

enum TrafficLightState
{
  OFF,
  GREEN,
  YELLOW,
  RED,
  RED_AND_YELLOW
};

Namenskonflikte

In C existieren keine Namensräume, so dass Enumerations leider grundsätzlich im globalen Namensraum angelegt werden. Dies hat C++ bedauerlicherweise von C geerbt, so dass in C++ derartige Typen besser als statische Member von Klassen angelegt werden. Enthalten zwei Enumerations den gleichen Bezeichner (z.B. TrafficLight und Color haben beide einen Wert Green), dann sind diese nicht mehr zu unterscheiden und der Compiler meldet einen Fehler. Daher werden enums häufig mit Präfixen versehen, also ein Hinweis auf die Bedeutung vorangestellt, zum Beispiel

enum TrafficLightState
{
  TLSTATE_OFF,
  TLSTATE_GREEN,
  TLSTATE_YELLOW,
  TLSTATE_RED,
  TLSTATE_RED_AND_YELLOW
};

Semikolon am Ende der Deklaration

Am Anfang verwirrt das Semikolon am Ende der Deklaration viele Anfänger, da man bei Anweisungsblöcken in C/C++ kein Semikolon benötigt.

if( a == 1 )
{
  /* Do something */
} // <- Kein Semikolon

In C und C++ darf man Deklarationen und Definitionen in einer Anweisung zusammenfassen. Es ist also auch möglich, direkt eine oder mehrer Variablen dieses Typs anzulegen. Im folgenden Fall handelt es sich um eine kombinierte Deklaration mit anschließender Definition der Variablen myState und myStateBackup:

enum TrafficLightState
{
  TLSTATE_OFF,
  TLSTATE_GREEN,
  TLSTATE_YELLOW,
  TLSTATE_RED,
  TLSTATE_RED_AND_YELLOW
} myState, myStateBackup;

Um dem Compiler also zu verstehen zu geben, dass man keine Variablen anlegen möchte, sondern nur die Enumeration deklarieren möchte, schreibt man nach der schließenden geschweiften Klammer ein Semikolon, um die Anweisung zu beenden.

Verwandschaft mit int

Im Prinzip sind enums nichts anderes als Integers. So kann man ein Enum jederzeit auf ein Integer kopieren, umkehrt ist ein Integer aber kein enum-Typ, denn ein Integer kann ja auch einen Wert besitzen, dem kein enum zugeordnet ist:

#include <stdlib.h>
#include <stdio.h>
 
enum TrafficLightState
{
  TLSTATE_OFF,
  TLSTATE_GREEN,
  TLSTATE_YELLOW,
  TLSTATE_RED,
  TLSTATE_RED_AND_YELLOW
} myState, myStateBackup;
 
int main (void)
{
  enum TrafficLightState state = TLSTATE_GREEN;
  int stateValue = state;
 
  printf( "State Green  %d\n", stateValue );
 
  printf( "State Yellow %d\n", TLSTATE_YELLOW );
  printf( "State Red    %d\n", TLSTATE_RED );
 
  state = 4711;
 
  return EXIT_SUCCESS;
}

Der GCC-C-Kompiler kompiliert das ohne Kommentar und führt so folgendem Ergebnis:

State Green  1
State Yellow 2
State Red    3

TLSTATE_GREEN besitzt also den Integer-Wert 1 - enum fängt bei 0 an und jede neue Identität ist 1 größer als die vorherige.

Der GCC-C++-Kompiler bemängelt dieses Programm jedoch mit einem Fehler:

# g++ intvalue.c
intvalue.c: In function 'int main()':
intvalue.c:20: error: invalid conversion from 'int' to 'TrafficLightState'

Das Integer darf also nicht auf TrafficLightState übertragen werden. Das sollte im Idealfall nie gemacht werden. Falls es doch gemacht werden muss, so muss ein Cast durchgeführt werden. Als C-Programmierer haben wir daher nur den C-Cast zur Verfügung:

  int stateValue = TLSTATE_YELLOW;
  enum TrafficLightState state = (TrafficLightState) state;

Hierzu muss man sich aber auch sicher sein, dass stateValue ein gültiges Element ist. Wenn Dein C-Compiler nicht meckert, sollte man den Cast eher vermeiden. Wenn Du Deinen C-Code mit einem C++-Compiler übersetzt, rate ich hier den C++-Cast zu verwenden, um den C-Cast zu vermeiden:

  int stateValue = TLSTATE_YELLOW;
  enum TrafficLightState state = static_cast< TrafficLightState >( state );

Ein mögliches Szenario wäre beim Laden einer Datei. Hier wird das Enum als int gespeichert und beim Laden muss das int entsprechend zurückinterpretiert werden.

Wertzuweisungen

Manchmal ist es praktisch, wenn man Enums Werte geben kann, die man verrechnen kann:

#include <stdlib.h>
#include <stdio.h>
 
enum Significance
{
  DOUBLE = 2,
  QUADFOLD = 4,
  EIGHTFOLD = 8
};
 
int main (void)
{
  printf( "=> %d\n", EIGHTFOLD * 2 );
 
  return EXIT_SUCCESS;
}

Enum-Klassen

In C++ kann man sogenannte Enum-Klassen erzeugen. Da Enums im Prinzip nur ein Name für ein Integer sind, muss man weitergehende Informationen anschließend aus irgendwelchen Tabellen oder switch-Konstrukten erfragen. Enum-Klassen werden daher in guter C++-Programmierung gerne statt Enums benutzt, um die Identität direkt mit den dazu gehörigen Informationen zu verbinden. Als E-Entwickler hat man diese Möglichkeit so leider noch nicht.


Autorendiskussion