Menu

Enum in C: costanti con nome, numerazione automatica e conversione in stringa

Come definire un enum in C: numerazione automatica, valori espliciti, enum in uno switch perché il compilatore segnali i casi mancanti, conversione di un enum in stringa e le convenzioni di nomi che li mantengono leggibili.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Il codice pieno di numeri nudi è codice che nessuno riesce a leggere. if (state == 2) non ti dice nulla; if (state == STATE_RUNNING) ti dice tutto. Un enum è il modo del C per dare nomi veri a un piccolo insieme fisso di interi, e a differenza di una pila di #define, arrivano raggruppati in un unico tipo che il compilatore conosce.

Definire un enum

La dichiarazione dà un nome a un tag ed elenca gli enumeratori tra parentesi graffe:

Due cose da notare subito. Primo, la numerazione parte da 0 e aumenta di uno, quindi RED vale 0, GREEN vale 1, BLUE vale 2. Secondo, i nomi degli enumeratori sono visibili ovunque nello scope che li contiene: scrivi GREEN, non Color.GREEN. Vivono nello stesso spazio dei nomi delle tue variabili, ed è per questo che il codice C per convenzione li scrive in MAIUSCOLO o li prefissa con il tipo (COLOR_RED) per evitare collisioni.

Per stampare un enum si usa %d, perché un valore di un'enumerazione è un valore intero.

Valori espliciti

Assegna a un enumeratore qualsiasi una costante esplicita, e quelli successivi continuano da lì:

I valori non devono essere unici né ordinati: due enumeratori possono condividere un numero, cosa ogni tanto utile per gli alias (COLOR_DEFAULT = COLOR_BLACK). Devono essere costanti intere di compilazione, quindi non puoi calcolarne uno da una variabile.

Un idioma comune sfrutta questo per registrare il conteggio:

enum Suit {
    CLUBS, DIAMONDS, HEARTS, SPADES,
    SUIT_COUNT           /* vale automaticamente 4 e resta corretto se aggiungi un seme */
};

SUIT_COUNT non è un vero seme; è il loro numero, mantenuto gratis dalla numerazione automatica. Cicli e dimensioni di array possono usarlo senza mai diventare obsoleti.

typedef enum

Come per le struct, il nome completo del tipo in C è enum Color, e un typedef elimina la parola chiave:

Poiché gli enumeratori sono normali interi che partono da 0, fanno anche da indici di array: dx[DIR_EAST] funziona senza alcuna conversione. È la proprietà più utile in assoluto di un enum che parte da zero.

Enum in uno switch

Abbinare un enum a uno switch è il punto in cui il tipo inizia a ripagarsi. Compila con -Wall e il compilatore ti dirà quando dimentichi un case.

Aggiungi uno STATE_FAILED all'enum e GCC avvisa: enumeration value 'STATE_FAILED' not handled in switch. Quell'avviso è la cosa più vicina a un controllo di esaustività che il C abbia, ed è un forte motivo per scrivere questi switch senza un case default:: un default zittisce l'avviso e lascia passare senza gestione i nuovi stati. Metti invece il caso di ripiego dopo lo switch, come fa describe.

Convertire un enum in stringa

Il C non conserva nomi a runtime; printf("%s", GREEN) non può funzionare perché GREEN è semplicemente 1. Due schemi risolvono il problema.

La versione con lo switch, che il compilatore controlla:

E la versione con tabella di lookup, più breve ma non controllata: se l'array e l'enum si disallineano, nessuno avvisa:

Gli indici designati dell'array ([RED] = "RED") almeno tengono i nomi legati alle loro posizioni, quindi riordinare l'enum non può mescolare le stringhe in silenzio. Il controllo dei limiti conta perché niente impedisce a chi chiama di passare un valore fuori dall'enum: vedi la prossima sezione.

Gli enum non sono a tenuta stagna

Una variabile enum sotto sotto è un intero, e il C non sorveglia l'intervallo:

L'aritmetica sugli enumeratori funziona, i confronti con semplici int funzionano e un cast fuori intervallo viene accettato. Quindi tratta un enum come un'ottima documentazione e un buon suggerimento per il compilatore, non come una garanzia. Valida qualsiasi valore che arriva dall'esterno del programma, un file, un argomento, un messaggio di rete, prima di usarlo in uno switch.

Dimensione e memorizzazione

In C17 ogni enumeratore deve stare in un int: enum Huge { BIG = 3000000000 }; è una violazione di vincolo che un compilatore rigoroso (-pedantic-errors) rifiuta, anche se gcc e clang lo accettano come estensione nella modalità predefinita. Gli enumeratori stessi hanno tipo int, mentre la dimensione del tipo enum è definita dall'implementazione (in pratica, int):

(C23 elimina la restrizione: un enumeratore può superare l'intervallo di int e puoi perfino scegliere il tipo sottostante con enum E : unsigned long { ... }. Finché la tua toolchain non punta a C23, tieni gli enumeratori dentro int.)

Poiché la scelta esatta è definita dall'implementazione, non scrivere mai un enum direttamente in un file binario o in un pacchetto di rete aspettandoti che un'altra macchina lo rilegga. Convertilo al confine in un tipo a larghezza fissa come uint8_t o uint32_t di <stdint.h>.

Enum contro #define

Entrambi danno un nome a un numero. In genere l'enum è migliore:

enum#define
Gestito dail compilatoreil preprocessore
Raggruppamentoi valori collegati condividono un tipoogni costante è indipendente
Numerazione automaticasìno, numeri a mano
Visibile in un debuggerspesso, per nomeno, solo il numero
Avviso di esaustività nello switchsìno
Scoperispetta lo scope di bloccotestuale, fino a #undef

Usa #define quando la costante non è un intero (una stringa, un float) o quando deve essere usabile dal preprocessore stesso, come nella compilazione condizionale. Per un insieme di stati, flag o tipi interi collegati, usa un enum.

Convenzioni per i nomi

Gli enum collidono facilmente perché i loro nomi stanno nello scope ordinario. Le convenzioni che mantengono sane le grandi codebase C:

  • Prefissa ogni enumeratore con il tipo: COLOR_RED, STATE_IDLE, HTTP_OK. Un RED senza prefisso in un programma grafico prima o poi incontrerà il RED di qualcun altro.
  • Scrivi gli enumeratori in SHOUT_CASE, come le altre costanti.
  • Dai al tipo un nome singolare in PascalCase o snake_case_t: Color, State o color_t, in modo coerente in tutto il progetto.
  • Aggiungi una sentinella _COUNT in fondo quando userai l'enum per cicli o indici.

Domande frequenti

Come si definisce un enum in C?

enum Color { RED, GREEN, BLUE }; definisce un tipo i cui valori sono le tre costanti con nome. Di default sono numerate a partire da 0, quindi RED vale 0, GREEN vale 1 e BLUE vale 2. Dichiara una variabile con enum Color c = GREEN;, oppure aggiungi un typedef per poter scrivere solo Color c.

Si possono assegnare valori specifici a un enum in C?

Sì: enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };. Puoi impostarne alcuni e lasciare che gli altri seguano: ogni nome senza un valore esplicito continua da quello precedente, quindi enum { A = 10, B, C }; dà B = 11 e C = 12.

Come si converte un enum in stringa in C?

Il C non ha un modo predefinito; i nomi degli enumeratori non esistono a runtime. L'approccio abituale è una piccola funzione con uno switch che restituisce una stringa letterale per ogni valore, oppure un array di stringhe indicizzato dall'enum. La versione con lo switch è più sicura, perché -Wall avvisa quando un nuovo enumeratore non ha un case.

Qual è la dimensione di un enum in C?

È definita dall'implementazione, ma in pratica è sizeof(int): 4 byte sulla maggior parte dei sistemi. Il compilatore sceglie un tipo intero in grado di contenere ogni enumeratore. Non dare per scontata una dimensione specifica quando definisci il layout di un file binario o di un pacchetto di rete; lì usa un tipo a larghezza fissa di <stdint.h>.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA