Enumerações (ou enum) em C são um tipo de dado que permite definir um conjunto de constantes inteiras nomeadas. Elas tornam o código mais legível, pois substituem números mágicos por nomes significativos, facilitando a manutenção e reduzindo erros. Nesta aula, vamos explorar a sintaxe, os valores padrão e personalizados, os casos de uso mais comuns e as boas práticas para utilizar enumerações de forma eficiente.

Imagine que você precisa representar os dias da semana em um programa. Sem enum, você usaria números (0 a 6) e teria que lembrar o que cada número significa. Com enum, você pode escrever DOMINGO, SEGUNDA, etc., tornando o código mais expressivo. Além disso, enumerações podem ser usadas em switch, como flags e em muitos outros contextos.

enum

A palavra-chave enum é usada para declarar um tipo de enumeração. A sintaxe básica é:

enum nome_do_enum { CONSTANTE1, CONSTANTE2, ... };

Por exemplo, para definir os dias da semana:

enum dias_semana { DOMINGO, SEGUNDA, TERCA, QUARTA, QUINTA, SEXTA, SABADO };

Depois dessa declaração, você pode usar DOMINGO, SEGUNDA, etc., como constantes inteiras. Internamente, o compilador atribui valores inteiros sequenciais a partir de 0, a menos que você especifique outros valores.

Você também pode declarar variáveis do tipo da enumeração:

enum dias_semana hoje;
hoje = QUARTA;

As enumerações são úteis para agrupar constantes relacionadas e dar um nome ao tipo, o que melhora a clareza do código.

Valores

Por padrão, o compilador atribui valores inteiros sequenciais a partir de 0 para cada constante. No exemplo dos dias da semana, DOMINGO = 0, SEGUNDA = 1, e assim por diante. No entanto, você pode especificar valores personalizados para cada constante, como no exemplo a seguir:

enum cores { VERMELHO = 1, VERDE = 3, AZUL = 5 };

Se você não atribuir um valor a uma constante, o compilador usa o valor da constante anterior + 1. Por exemplo:

enum exemplo { A = 10, B, C = 20, D };

Nesse caso, B é 11 (10 + 1) e D é 21 (20 + 1). É importante notar que os valores de uma enumeração são do tipo int, então você pode usar qualquer valor inteiro, inclusive negativos.

Para acessar o valor numérico de uma constante, basta usá-la em uma expressão inteira. Por exemplo, printf("%d", VERDE) imprimirá 3.

Você também pode usar enumerações para definir máscaras de bits, combinando valores com operações bit a bit. Por exemplo:

enum flags { NENHUM = 0, LEITURA = 1 << 0, ESCRITA = 1 << 1, EXECUCAO = 1 << 2 };

Isso permite criar flags que podem ser combinadas com | (OR) e testadas com & (AND).

Casos de uso

Enumerações são amplamente utilizadas em programas C para representar estados, opções, modos, erros, etc. Alguns casos comuns incluem:

  • Estados de uma máquina de estados: Por exemplo, em um sistema de semáforo: VERMELHO, AMARELO, VERDE.
  • Opções de configuração: Por exemplo, MODO_LEITURA, MODO_ESCRITA, MODO_APPEND.
  • Códigos de erro: Por exemplo, ERRO_SUCESSO = 0, ERRO_IO = 1, ERRO_MEMORIA = 2.
  • Flags para funções: Por exemplo, uma função que recebe uma flag para controlar o comportamento (como O_CREAT, O_EXCL em open()).
  • Menus e opções de interface: Para representar a seleção do usuário.

Vamos ver um exemplo prático de uso de enum em um switch:

#include <stdio.h>

enum opcao { SAIR, NOVO, EDITAR, EXCLUIR };

int main() {
    int escolha;
    printf("Escolha: 0-Sair, 1-Novo, 2-Editar, 3-Excluir: ");
    scanf("%d", &escolha);

    switch (escolha) {
        case SAIR:
            printf("Saindo...\n");
            break;
        case NOVO:
            printf("Criando novo...\n");
            break;
        case EDITAR:
            printf("Editando...\n");
            break;
        case EXCLUIR:
            printf("Excluindo...\n");
            break;
        default:
            printf("Opção inválida\n");
    }
    return 0;
}

Observe como o uso de enum torna o switch mais legível e menos propenso a erros do que usar números diretamente.

Boas práticas

Ao usar enumerações, siga estas boas práticas para evitar problemas:

  • Use nomes em maiúsculas com underscores para constantes (ex.: VALOR_MAXIMO), seguindo a convenção comum em C.
  • Dê um nome sufixo ao tipo para evitar conflitos com outras enumerações (ex.: enum cor_primaria).
  • Atribua valores explícitos quando a ordem ou o valor for importante, especialmente se você planeja armazenar ou transmitir os valores (ex.: protocolos de rede).
  • Use enum para agrupar constantes relacionadas, mas não para constantes independentes; nesse caso, use #define ou const.
  • Evite misturar enum com valores fora do intervalo em switch, pois o compilador pode não emitir avisos. Use um default para tratar valores inesperados.
  • Considere usar typedef para simplificar a declaração de variáveis:
typedef enum { FALSO = 0, VERDADEIRO = 1 } bool;

bool ativo = VERDADEIRO;

Isso torna o código mais limpo e portável, especialmente se você não estiver usando C99 com stdbool.h.

Outra boa prática é usar enumerações para códigos de erro, retornando um valor de enum em vez de inteiros mágicos. Por exemplo:

typedef enum { OK = 0, ERRO_ABRIR_ARQUIVO, ERRO_MEMORIA, ERRO_IO } resultado_t;

resultado_t processar_arquivo() {
    // ...
    return OK;
}

Isso facilita o tratamento de erros e a leitura do código.

Referências

Exercícios

  1. Declare uma enumeração chamada nivel_erro com as constantes INFO, AVISO, ERRO e CRITICO, sem valores explícitos. Qual valor inteiro é atribuído a CRITICO?

    ✓ Resposta: CRITICO recebe o valor 3, pois INFO=0, AVISO=1, ERRO=2, CRITICO=3.
  2. Escreva um programa que use uma enumeração para representar os meses do ano (janeiro a dezembro) e imprima o número de dias de cada mês (considere ano não bissexto).

    ✓ Resposta:
    #include <stdio.h>
    
    enum mes { JAN, FEV, MAR, ABR, MAI, JUN, JUL, AGO, SET, OUT, NOV, DEZ };
    
    int main() {
        int dias[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        for (enum mes m = JAN; m <= DEZ; m++) {
            printf("Mês %d tem %d dias\n", m+1, dias[m]);
        }
        return 0;
    }
  3. Crie uma enumeração para representar as cores VERMELHO, VERDE, AZUL com valores 1, 2 e 4 respectivamente. Depois, declare uma variável que combine as cores usando operação bit a bit e verifique se contém a cor verde.

    ✓ Resposta:
    #include <stdio.h>
    
    enum cor { VERMELHO = 1, VERDE = 2, AZUL = 4 };
    
    int main() {
        int minha_cor = VERMELHO | AZUL;
        if (minha_cor & VERDE) {
            printf("Contém verde\n");
        } else {
            printf("Não contém verde\n");
        }
        return 0;
    }
  4. Explique a diferença entre usar enum e #define para constantes. Dê um exemplo em que enum é mais adequado.

    ✓ Resposta: enum cria um tipo de dado e permite que o compilador verifique atribuições; #define é uma substituição textual simples. enum é mais adequado quando você precisa agrupar constantes relacionadas e deseja que elas tenham um tipo específico. Exemplo: enum estado { ATIVO, INATIVO }; permite declarar variáveis do tipo enum estado.
  5. Escreva um código que defina uma enumeração para códigos de erro com valores: SUCESSO = 0, ERRO_ABERTURA, ERRO_LEITURA, ERRO_ESCRITA. Depois, simule uma função que retorna um desses erros e trate com um switch.

    ✓ Resposta:
    #include <stdio.h>
    
    typedef enum { SUCESSO = 0, ERRO_ABERTURA, ERRO_LEITURA, ERRO_ESCRITA } erro_t;
    
    erro_t operacao() {
        return ERRO_LEITURA;
    }
    
    int main() {
        erro_t e = operacao();
        switch (e) {
            case SUCESSO:
                printf("Operação bem-sucedida\n");
                break;
            case ERRO_ABERTURA:
                printf("Erro ao abrir arquivo\n");
                break;
            case ERRO_LEITURA:
                printf("Erro ao ler arquivo\n");
                break;
            case ERRO_ESCRITA:
                printf("Erro ao escrever arquivo\n");
                break;
            default:
                printf("Erro desconhecido\n");
        }
        return 0;
    }