Menu

Enum in C: benannte Konstanten, automatische Nummerierung und Enum als Text

Wie man in C ein enum definiert - automatische Nummerierung, explizite Werte, Enums in einem switch, damit der Compiler fehlende Fälle findet, ein Enum in eine Zeichenkette umwandeln, und die Namenskonventionen, die sie lesbar halten.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Code voller nackter Zahlen ist Code, den niemand lesen kann. if (state == 2) sagt dir nichts; if (state == STATE_RUNNING) sagt dir alles. Ein enum ist Cs Weg, einer kleinen, festen Menge von Ganzzahlen ordentliche Namen zu geben - und anders als ein Haufen #defines kommen sie zu einem Typ gruppiert, den der Compiler kennt.

Ein Enum definieren

Die Deklaration benennt ein Tag und listet die Aufzählungswerte in geschweiften Klammern:

Zwei Dinge fallen sofort auf. Erstens beginnt die Nummerierung bei 0 und steigt um eins, RED ist also 0, GREEN ist 1, BLUE ist 2. Zweitens sind die Namen der Aufzählungswerte überall im umgebenden Gültigkeitsbereich sichtbar - du schreibst GREEN, nicht Color.GREEN. Sie leben im selben Namensraum wie deine Variablen, weshalb C-Code sie konventionell GROSS schreibt oder ihnen den Typ voranstellt (COLOR_RED), um Kollisionen zu vermeiden.

Ein Enum auszugeben geht mit %d, denn ein Aufzählungswert ist ein Ganzzahlwert.

Explizite Werte

Weise einem beliebigen Aufzählungswert eine explizite Konstante zu, und die folgenden zählen von dort weiter:

Werte müssen weder eindeutig noch geordnet sein - zwei Aufzählungswerte dürfen sich eine Zahl teilen, was gelegentlich für Aliase nützlich ist (COLOR_DEFAULT = COLOR_BLACK). Sie müssen Ganzzahlkonstanten zur Übersetzungszeit sein, du kannst also keinen aus einer Variablen berechnen.

Ein verbreitetes Idiom nutzt das, um die Anzahl festzuhalten:

enum Suit {
    CLUBS, DIAMONDS, HEARTS, SPADES,
    SUIT_COUNT           /* automatisch 4 - bleibt korrekt, wenn du eine Farbe ergänzt */
};

SUIT_COUNT ist keine echte Farbe; es ist ihre Anzahl, die die automatische Nummerierung kostenlos pflegt. Schleifen und Arraygrößen können sie benutzen und veralten nie.

typedef enum

Wie bei Strukturen ist der vollständige Typname in C enum Color, und ein typedef lässt das Schlüsselwort weg:

Weil Aufzählungswerte gewöhnliche Ganzzahlen ab 0 sind, dienen sie zugleich als Array-Indizes - dx[DIR_EAST] funktioniert ohne jede Umwandlung. Das ist die mit Abstand nützlichste Eigenschaft eines nullbasierten Enums.

Enums in einem switch

Ein Enum mit einem switch zu paaren ist die Stelle, an der sich der Typ bezahlt macht. Kompiliere mit -Wall, und der Compiler sagt dir, wenn du einen Fall vergisst.

Füge dem Enum ein STATE_FAILED hinzu, und GCC warnt: enumeration value 'STATE_FAILED' not handled in switch. Diese Warnung ist das, was C einer Vollständigkeitsprüfung am nächsten kommt, und sie ist ein starkes Argument, solche switches ohne einen default:-Fall zu schreiben - ein default bringt die Warnung zum Schweigen und lässt neue Zustände unbehandelt durchrutschen. Setze den Rückfall stattdessen hinter das switch, wie describe es tut.

Ein Enum in eine Zeichenkette umwandeln

C behält zur Laufzeit keine Namen; printf("%s", GREEN) kann nicht funktionieren, weil GREEN schlicht 1 ist. Zwei Muster lösen das.

Die switch-Variante, die der Compiler prüft:

Und die Nachschlagetabellen-Variante, kürzer, aber ungeprüft - wenn Array und Enum auseinanderdriften, warnt nichts:

Die benannten Array-Indizes ([RED] = "RED") halten die Namen immerhin an ihre Plätze gebunden, sodass ein Umsortieren des Enums die Zeichenketten nicht still durcheinanderbringen kann. Die Bereichsprüfung zählt, weil nichts einen Aufrufer daran hindert, einen Wert außerhalb des Enums zu übergeben - siehe den nächsten Abschnitt.

Enums sind nicht wasserdicht

Eine Enum-Variable ist darunter eine Ganzzahl, und C überwacht den Bereich nicht:

Arithmetik mit Aufzählungswerten funktioniert, Vergleiche mit schlichten ints funktionieren, und ein Cast außerhalb des Bereichs wird akzeptiert. Behandle ein Enum also als ausgezeichnete Dokumentation und einen guten Hinweis für den Compiler, nicht als Garantie. Prüfe jeden Wert, der von außerhalb deines Programms kommt - eine Datei, ein Argument, eine Netznachricht -, bevor du darauf umschaltest.

Größe und Speicherung

In C17 muss jeder Aufzählungswert in ein int passen - enum Huge { BIG = 3000000000 }; ist eine Bedingungsverletzung, die ein strenger Compiler (-pedantic-errors) zurückweist, auch wenn gcc und clang sie in ihrem Standardmodus als Erweiterung akzeptieren. Die Aufzählungswerte selbst haben den Typ int, während die Größe des Enum-Typs implementierungsabhängig ist (in der Praxis int):

(C23 hebt die Einschränkung auf: ein Aufzählungswert darf den int-Bereich überschreiten, und du kannst mit enum E : unsigned long { ... } sogar den zugrunde liegenden Typ wählen. Solange deine Toolchain nicht C23 anvisiert, halte die Aufzählungswerte innerhalb von int.)

Weil die genaue Wahl implementierungsabhängig ist, schreibe ein Enum nie direkt in eine Binärdatei oder ein Netzpaket und erwarte, dass eine andere Maschine es zurücklesen kann. Wandle an der Grenze in einen Typ fester Breite wie uint8_t oder uint32_t aus <stdint.h> um.

Enum gegen #define

Beide geben einer Zahl einen Namen. Das Enum ist im Allgemeinen besser:

enum#define
Behandelt vondem Compilerdem Präprozessor
Gruppierungzusammengehörige Werte teilen einen Typjede Konstante steht für sich
Automatische Nummerierungjanein, du nummerierst von Hand
Im Debugger sichtbaroft, mit Namennein, nur die Zahl
switch-Vollständigkeitswarnungjanein
Gültigkeitsbereichrespektiert Blockgültigkeittextuell, bis #undef

Greife zu #define, wenn die Konstante keine Ganzzahl ist (eine Zeichenkette, ein Gleitkommawert) oder wenn sie für den Präprozessor selbst nutzbar sein muss, wie bei der bedingten Übersetzung. Für einen Satz zusammengehöriger ganzzahliger Zustände, Flags oder Arten nimm ein Enum.

Namenskonventionen

Enums kollidieren leicht, weil ihre Namen im gewöhnlichen Gültigkeitsbereich sitzen. Die Konventionen, die große C-Codebasen bei Verstand halten:

  • Stelle jedem Aufzählungswert den Typ voran: COLOR_RED, STATE_IDLE, HTTP_OK. Ein unpräfigiertes RED in einem Grafikprogramm trifft irgendwann jemand anderes RED.
  • Schreibe die Aufzählungswerte in GROSSBUCHSTABEN, passend dazu, wie andere Konstanten aussehen.
  • Benenne den Typ im Singular in PascalCase oder als snake_case_t: Color, State oder color_t, projektweit einheitlich.
  • Füge zuletzt einen _COUNT-Wächter hinzu, wenn du mit dem Enum iterieren oder indizieren wirst.

Häufig gestellte Fragen

Wie definiert man ein enum in C?

enum Color { RED, GREEN, BLUE }; definiert einen Typ, dessen Werte die drei benannten Konstanten sind. Standardmäßig werden sie ab 0 nummeriert, RED ist also 0, GREEN ist 1 und BLUE ist 2. Deklariere eine Variable mit enum Color c = GREEN; oder füge ein typedef hinzu, damit du nur Color c schreiben kannst.

Kann man einem enum in C bestimmte Werte zuweisen?

Ja: enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };. Du kannst einige setzen und die anderen folgen lassen - jeder Name ohne expliziten Wert zählt vom vorherigen weiter, enum { A = 10, B, C }; ergibt also B = 11 und C = 12.

Wie wandelt man ein enum in C in eine Zeichenkette um?

C hat dafür keinen eingebauten Weg; die Namen der Aufzählungswerte existieren zur Laufzeit nicht. Üblich ist eine kleine Funktion mit einem switch, die pro Wert ein Zeichenkettenliteral liefert, oder ein Array von Zeichenketten, das mit dem Enum indiziert wird. Die switch-Variante ist sicherer, weil -Wall warnt, wenn ein neuer Aufzählungswert keinen Fall hat.

Wie groß ist ein enum in C?

Implementierungsabhängig, in der Praxis aber sizeof(int) - 4 Bytes auf den meisten Systemen. Der Compiler wählt einen Ganzzahltyp, der jeden Aufzählungswert fassen kann. Nimm keine bestimmte Größe an, wenn du eine Binärdatei oder ein Netzpaket aufbaust; benutze dort einen Typ fester Breite aus <stdint.h>.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S