Menu

Enum em C: constantes nomeadas, numeração automática e enum para string

Como definir um enum em C - numeração automática, valores explícitos, usar enums em um switch para o compilador pegar casos faltantes, converter um enum em string e as convenções de nomes que os mantêm legíveis.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Código cheio de números soltos é código que ninguém consegue ler. if (state == 2) não diz nada; if (state == STATE_RUNNING) diz tudo. Um enum é o jeito do C de dar nomes decentes a um conjunto pequeno e fixo de inteiros - e, ao contrário de uma pilha de #defines, eles vêm agrupados em um tipo que o compilador conhece.

Definindo um enum

A declaração dá nome a uma tag e lista os enumeradores entre chaves:

Duas coisas a notar de imediato. Primeiro, a numeração começa em 0 e cresce de um em um, então RED é 0, GREEN é 1, BLUE é 2. Segundo, os nomes dos enumeradores são visíveis em todo o escopo que os contém - você escreve GREEN, não Color.GREEN. Eles vivem no mesmo namespace das suas variáveis, que é por que o código C convencionalmente os GRITA ou os prefixa pelo tipo (COLOR_RED) para evitar colisões.

Imprimir um enum usa %d, porque um valor de enumeração é um valor inteiro.

Valores explícitos

Atribua uma constante explícita a qualquer enumerador e os seguintes continuam dali:

Os valores não precisam ser únicos nem ordenados - dois enumeradores podem compartilhar um número, o que é ocasionalmente útil para apelidos (COLOR_DEFAULT = COLOR_BLACK). Eles precisam ser constantes inteiras de tempo de compilação, então você não pode calcular um a partir de uma variável.

Um idioma comum usa isso para registrar a contagem:

enum Suit {
    CLUBS, DIAMONDS, HEARTS, SPADES,
    SUIT_COUNT           /* automaticamente 4 - continua certo se voce adicionar um naipe */
};

SUIT_COUNT não é um naipe de verdade; é a quantidade deles, mantida de graça pela numeração automática. Laços e tamanhos de array podem usá-la e nunca ficam desatualizados.

typedef enum

Como com structs, o nome completo do tipo em C é enum Color, e um typedef descarta a palavra-chave:

Como os enumeradores são inteiros comuns começando em 0, eles funcionam também como índices de array - dx[DIR_EAST] funciona sem conversão nenhuma. Essa é a propriedade mais útil de um enum baseado em zero.

Enums em um switch

Casar um enum com um switch é onde o tipo começa a se pagar. Compile com -Wall e o compilador vai te avisar quando você esquecer um case.

Acrescente um STATE_FAILED ao enum e o GCC avisa: enumeration value 'STATE_FAILED' not handled in switch. Esse aviso é a coisa mais próxima de uma verificação de exaustividade que C tem, e é um motivo forte para escrever esses switches sem um caso default: - um default silencia o aviso e deixa estados novos passarem sem tratamento. Coloque o fallback depois do switch, como describe faz.

Convertendo um enum em string

C não guarda nomes em tempo de execução; printf("%s", GREEN) não pode funcionar porque GREEN é simplesmente 1. Dois padrões resolvem isso.

A versão com switch, que o compilador verifica:

E a versão com tabela de consulta, mais curta mas não verificada - se o array e o enum se desencontrarem, nada avisa:

Os índices designados do array ([RED] = "RED") pelo menos mantêm os nomes presos às suas posições, então reordenar o enum não pode embaralhar as strings silenciosamente. A verificação de limites importa porque nada impede quem chama de passar um valor fora do enum - veja a próxima seção.

Enums não são à prova de tudo

Uma variável de enum é um inteiro por baixo, e C não fiscaliza a faixa:

Aritmética com enumeradores funciona, comparações com ints comuns funcionam, e uma conversão fora da faixa é aceita. Então trate um enum como excelente documentação e uma boa dica ao compilador, não como uma garantia. Valide qualquer valor que chegue de fora do seu programa - um arquivo, um argumento, uma mensagem de rede - antes de fazer um switch sobre ele.

Tamanho e armazenamento

No C17, todo enumerador precisa caber em um int - enum Huge { BIG = 3000000000 }; é uma violação de restrição que um compilador estrito (-pedantic-errors) rejeita, ainda que o gcc e o clang a aceitem como extensão no modo padrão. Os enumeradores em si têm tipo int, enquanto o tamanho do tipo enum é definido pela implementação (na prática, int):

(O C23 levanta a restrição: um enumerador pode exceder a faixa de int e você pode até escolher o tipo subjacente com enum E : unsigned long { ... }. Até que sua toolchain mire no C23, mantenha os enumeradores dentro de int.)

Como a escolha exata é definida pela implementação, nunca escreva um enum direto em um arquivo binário ou em um pacote de rede esperando que outra máquina consiga lê-lo de volta. Converta para um tipo de largura fixa como uint8_t ou uint32_t de <stdint.h> na fronteira.

Enum vs #define

Os dois dão nome a um número. O enum geralmente é melhor:

enum#define
Tratado poro compiladoro pré-processador
Agrupamentovalores relacionados compartilham um tipocada constante é independente
Numeração automáticasimnão, você numera à mão
Visível em um depuradorfrequentemente, pelo nomenão, só o número
Aviso de exaustividade no switchsimnão
Escoporespeita escopo de blocotextual, até um #undef

Recorra a #define quando a constante não for um inteiro (uma string, um float) ou quando ela precisar ser utilizável pelo próprio pré-processador, como em compilação condicional. Para um conjunto de estados, flags ou tipos inteiros relacionados, use um enum.

Convenções de nomes

Enums colidem com facilidade porque seus nomes ficam no escopo comum. As convenções que mantêm bases de código C grandes sob controle:

  • Prefixe cada enumerador com o tipo: COLOR_RED, STATE_IDLE, HTTP_OK. Um RED sem prefixo em um programa gráfico vai acabar encontrando o RED de outra pessoa.
  • Escreva os enumeradores em MAIÚSCULAS, no mesmo estilo das outras constantes.
  • Nomeie o tipo no singular em PascalCase ou snake_case_t: Color, State ou color_t, de forma consistente no projeto inteiro.
  • Acrescente uma sentinela _COUNT no fim quando for percorrer ou indexar com o enum.

Perguntas frequentes

Como se define um enum em C?

enum Color { RED, GREEN, BLUE }; define um tipo cujos valores são as três constantes nomeadas. Por padrão elas numeram a partir de 0, então RED é 0, GREEN é 1 e BLUE é 2. Declare uma variável com enum Color c = GREEN;, ou acrescente um typedef para poder escrever só Color c.

Dá para atribuir valores específicos a um enum em C?

Sim: enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };. Você pode definir alguns e deixar os outros seguirem - qualquer nome sem valor explícito continua a partir do anterior, então enum { A = 10, B, C }; dá B = 11 e C = 12.

Como converter um enum em string em C?

C não tem um jeito embutido; os nomes dos enumeradores não existem em tempo de execução. A abordagem usual é uma pequena função com um switch devolvendo um literal de string por valor, ou um array de strings indexado pelo enum. A versão com switch é mais segura porque o -Wall avisa quando um enumerador novo não tem case.

Qual é o tamanho de um enum em C?

Definido pela implementação, mas na prática sizeof(int) - 4 bytes na maioria dos sistemas. O compilador escolhe um tipo inteiro capaz de guardar todos os enumeradores. Não suponha um tamanho específico ao montar um arquivo binário ou um pacote de rede; use lá um tipo de largura fixa de <stdint.h>.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR