Wofür ein enum gedacht ist
Eine struct fasst mehrere zusammengehörige Werte in einem Objekt zusammen. Ein enum löst ein anderes Problem: Es gibt einer kleinen, festen Menge von Auswahlmöglichkeiten Namen. Statt dir zu merken, dass 0 „rot", 1 „grün" und 2 „blau" bedeutet, schreibst du Color::Red, und der Compiler hält dich ehrlich.
Immer dann zu einem enum zu greifen, wenn eine Variable nur einen aus einer Handvoll benannter Zustände annehmen kann – die Farbe einer Ampel, die Farbe einer Spielkarte, ein Verbindungsstatus – macht den Code selbsterklärend und lässt den Compiler Tippfehler und fehlende Fälle abfangen, die nackte Ganzzahlen niemals erkennen würden.
Ein enum deklarieren
Modernes C++ hat zwei Varianten. Beginne mit der, zu der du fast immer greifen solltest: dem gekapselten enum class. Du listest die Namen auf, und auf jeden Enumerator wird über den Namen des Enums mit :: zugegriffen:
Beachte, dass du Color::Green schreibst, niemals ein nacktes Green. Die Werte selbst sind nur Bezeichnungen – du vergleichst sie, weist sie zu und reichst sie herum, aber die zugrunde liegende Zahl interessiert dich selten. Standardmäßig ist Red gleich 0, Green gleich 1 und Blue gleich 2, von null an aufwärts gezählt.
Einfaches enum vs. enum class
Das ältere, ungekapselte enum (ohne das Schlüsselwort class) wirft seine Namen direkt in den umgebenden Geltungsbereich und wandelt sich von selbst in int um. Das klingt praktisch, verursacht aber zwei echte Probleme:
enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana, Red }; // error: 'Red' already declared
enum Status { Active, Inactive };
int x = Active; // compiles silently - is this what you meant?
if (Active == Banana) { // compares unrelated enums via int - allowed!
}
Da einfache Enumeratoren globale Namen sind, können zwei Enums schon dadurch kollidieren, dass sie eine Bezeichnung teilen. Und weil sie zu int zerfallen, vergleicht der Compiler bereitwillig Werte aus völlig unzusammenhängenden Enums. Ein gekapseltes enum class behebt beides: Die Namen leben innerhalb des Typs, und der Typ verwandelt sich nicht stillschweigend in ein int:
Die Faustregel: Verwende standardmäßig enum class. Greife nur dann auf ein einfaches enum zurück, wenn du gezielt die implizite int-Umwandlung möchtest, etwa bei alten Flag-Konstanten im C-Stil.
Eigene Werte und der zugrunde liegende Typ
Du kannst Enumeratoren explizite Zahlen zuweisen. Diejenigen, die du auslässt, zählen weiter vom vorherigen aufwärts, was praktisch ist für Dinge wie HTTP-Statuscodes oder Bit-Flags:
Jedes enum wird von einem Ganzzahltyp gestützt – standardmäßig int. Du kannst es auf einen kleineren Typ festlegen, wenn die Größe wichtig ist, zum Beispiel um viele Enums in einer kompakten Struktur zu speichern oder um einem Wire-Format zu entsprechen:
Die Wahl des zugrunde liegenden Typs garantiert außerdem den Wertebereich, in den deine Werte passen müssen: Ein uint8_t-enum kann keinen Wert über 255 aufnehmen.
Zwischen enum und int umwandeln
Ein gekapseltes enum class wandelt sich nie implizit um – das ist der ganze Sinn. Wenn du die Zahl wirklich brauchst – um sie auszugeben, ein Array zu indizieren oder sie aus einer Datei zu lesen – greife zu static_cast. Von enum zu int zu gehen ist immer sicher:
Ein int zurück in ein enum umzuwandeln ist die gefährliche Richtung. Der Cast prüft nicht, ob die Zahl einem echten Enumerator entspricht – er gibt dir einen Wert, der technisch außerhalb der benannten Menge des Enums liegt:
Suit s = static_cast<Suit>(2); // fine - that's Clubs
Suit bad = static_cast<Suit>(99); // compiles, but 99 is not a valid Suit
// using `bad` in a switch or as an array index is a lurking bug
Wenn die Ganzzahl aus Benutzereingaben oder einer Datei stammt, prüfe den Wertebereich selbst, bevor du castest, sonst erzeugst du einen Wert, den kein case jemals behandelt – eine subtile Quelle für undefiniertes Verhalten weiter unten.
Enums mit switch verwenden
Da ein enum „eines aus einer festen Menge" ist, passt es perfekt zu einem switch. Wenn du jeden Enumerator abdeckst, warnen dich viele Compiler, falls du später einen neuen Wert hinzufügst und vergisst, ihn zu behandeln – kostenlose Sicherheit, die du mit reinen Ganzzahlen nicht bekommst:
Ein Fallstrick: Es gibt keine eingebaute Möglichkeit, den Namen eines Enumerators auszugeben. cout << TrafficLight::Red lässt sich für ein gekapseltes enum nicht kompilieren, und selbst für ein einfaches enum gibt es die Zahl aus, nicht „Red". Ein kleines switch oder eine Nachschlagetabelle wie die obige ist der übliche Weg, ein enum in eine für Menschen lesbare Zeichenkette zu verwandeln.
Weiter: Ausnahmen
Enums und structs erlauben dir zu modellieren, wie deine Daten aussehen. Aber echte Programme müssen auch damit umgehen, wenn Dinge schiefgehen – eine Datei, die sich nicht öffnen lässt, eine Zahl, die sich nicht parsen lässt, ein Wert außerhalb des Bereichs. C++ behandelt diese Fehlerpfade mit Ausnahmen, und das ist das Thema der nächsten Seite.
Häufig gestellte Fragen
Was ist der Unterschied zwischen enum und enum class in C++?
Ein einfaches enum lässt seine Namen in den umgebenden Geltungsbereich durchsickern und wandelt sich implizit in int um, was Namenskonflikte und versehentliche Vergleiche verursacht. Ein gekapseltes enum class hält seine Namen innerhalb des Enums (Color::Red) und weigert sich, ohne expliziten Cast in int umzuwandeln. Bevorzuge in modernem C++ enum class: Es ist typsicher und vermeidet die klassischen Fallstricke.
Wie wandelt man ein C++ enum in ein int um?
Ein einfaches enum wandelt sich implizit um, daher funktioniert int n = Red; einfach so. Ein gekapseltes enum class erfordert einen expliziten Cast: int n = static_cast<int>(Color::Red);. Für die andere Richtung castest du das int zurück: Color c = static_cast<Color>(2); – aber Vorsicht: Zur Laufzeit wird nicht geprüft, ob der Wert ein gültiger Enumerator ist.
Bei welchem Wert beginnt das erste C++ enum?
Standardmäßig ist der erste Enumerator 0, und jeder folgende ist um eins größer als der vorherige. In enum class Level { Low, Mid, High }; ist also Low gleich 0, Mid gleich 1 und High gleich 2. Du kannst jedem von ihnen explizite Werte zuweisen, um dieses Verhalten zu überschreiben.