Enums em C

1. Introdução aos Enums

Enumerações (enums) são um tipo de dado definido pelo programador em C que permite criar um conjunto de constantes nomeadas inteiras. O propósito principal é tornar o código mais legível e auto-documentado, substituindo números mágicos por nomes significativos.

A sintaxe básica é:

enum nome_do_enum {
    valor1,
    valor2,
    valor3
};

Vejamos um primeiro exemplo prático com dias da semana:

#include <stdio.h>

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

int main() {
    enum DiasSemana hoje = QUARTA;
    printf("Hoje é o dia %d da semana\n", hoje);
    return 0;
}

2. Valores Inteiros e Atribuição

Por padrão, o primeiro membro de um enum recebe o valor 0, e cada membro subsequente recebe o valor anterior incrementado em 1:

#include <stdio.h>

enum Prioridade {
    BAIXA,       // 0
    MEDIA,       // 1
    ALTA,        // 2
    URGENTE      // 3
};

int main() {
    printf("BAIXA = %d\n", BAIXA);
    printf("MEDIA = %d\n", MEDIA);
    printf("ALTA = %d\n", ALTA);
    printf("URGENTE = %d\n", URGENTE);
    return 0;
}

Podemos atribuir valores explícitos:

#include <stdio.h>

enum Erros {
    SUCESSO = 0,
    ERRO_ARQUIVO = 10,
    ERRO_MEMORIA = 20,
    ERRO_PERMISSAO = 30,
    ERRO_DESCONHECIDO = 99
};

int main() {
    printf("SUCESSO = %d\n", SUCESSO);
    printf("ERRO_ARQUIVO = %d\n", ERRO_ARQUIVO);
    printf("ERRO_MEMORIA = %d\n", ERRO_MEMORIA);
    return 0;
}

É possível que múltiplos membros tenham o mesmo valor:

enum Estado {
    INATIVO = 0,
    PARADO = 0,
    ATIVO = 1,
    EXECUTANDO = 1
};

3. Escopo e Declaração de Variáveis

Variáveis do tipo enum podem ser declaradas de várias formas:

#include <stdio.h>

// Enum global
enum Cor {
    VERMELHO,
    AZUL,
    VERDE
};

void processarCor(enum Cor c) {
    switch(c) {
        case VERMELHO:
            printf("Cor vermelha\n");
            break;
        case AZUL:
            printf("Cor azul\n");
            break;
        case VERDE:
            printf("Cor verde\n");
            break;
    }
}

int main() {
    // Declaração de variável do tipo enum
    enum Cor minhaCor = VERDE;

    // Enum local à função
    enum Opcao {
        SIM,
        NAO,
        TALVEZ
    };

    enum Opcao resposta = SIM;
    processarCor(minhaCor);
    return 0;
}

4. Enums vs. Constantes com #define

Enums oferecem diversas vantagens sobre #define:

#include <stdio.h>

// Abordagem com #define
#define VERMELHO 0
#define AZUL 1
#define VERDE 2

// Abordagem com enum
enum Cores {
    VERMELHO_ENUM,
    AZUL_ENUM,
    VERDE_ENUM
};

int main() {
    // Com #define, não há verificação de tipo
    int cor1 = VERMELHO;  // Funciona, mas poderia ser qualquer inteiro

    // Com enum, temos um tipo específico
    enum Cores cor2 = VERMELHO_ENUM;

    // Depuração: enums aparecem com nomes em debuggers
    // Escopo: enums respeitam escopo de bloco
    // #define ignora escopo e pode causar conflitos

    return 0;
}

Limitações: enums não são booleanos nativos e podem ser convertidos implicitamente para inteiros:

enum Bool {
    FALSO,
    VERDADEIRO
};

int main() {
    enum Bool flag = VERDADEIRO;

    if (flag) {  // Funciona, mas é conversão implícita
        printf("Verdadeiro\n");
    }

    int valor = flag;  // Conversão implícita permitida
    return 0;
}

5. Enum Anônimo e Tags

Enums anônimos são úteis quando precisamos apenas das constantes:

#include <stdio.h>

// Enum anônimo
enum {
    TAMANHO_MAX = 100,
    TAMANHO_MIN = 1,
    BUFFER_SIZE = 256
};

int main() {
    char buffer[BUFFER_SIZE];
    printf("Tamanho do buffer: %d\n", BUFFER_SIZE);
    return 0;
}

Usando tag para reutilizar o tipo:

#include <stdio.h>

enum Status {
    OK,
    ERRO,
    PENDENTE
};

// Combinando com typedef
typedef enum Status Status;

void verificarStatus(Status s) {
    if (s == OK) {
        printf("Operação bem-sucedida\n");
    }
}

int main() {
    Status estado = PENDENTE;
    verificarStatus(estado);
    return 0;
}

Simplificando com typedef direto:

typedef enum {
    JANEIRO = 1,
    FEVEREIRO,
    MARCO,
    ABRIL
} Mes;

int main() {
    Mes mesAtual = MARCO;
    return 0;
}

6. Enums em Estruturas de Controle

Enums são excelentes para uso com switch-case:

#include <stdio.h>

typedef enum {
    PARADO,
    ACELERANDO,
    FREANDO,
    PARANDO
} EstadoCarro;

void processarEstado(EstadoCarro estado) {
    switch(estado) {
        case PARADO:
            printf("Carro parado\n");
            break;
        case ACELERANDO:
            printf("Acelerando...\n");
            break;
        case FREANDO:
            printf("Freando...\n");
            break;
        case PARANDO:
            printf("Parando o carro\n");
            break;
        default:
            printf("Estado desconhecido\n");
    }
}

int main() {
    processarEstado(ACELERANDO);
    processarEstado(PARANDO);
    return 0;
}

Cuidados com iteração sobre valores de enum:

enum OpcoesMenu {
    NOVO = 1,
    ABRIR = 3,
    SALVAR = 5,
    SAIR = 10
};

// Iteração não é segura devido a lacunas
for (int i = NOVO; i <= SAIR; i++) {
    // Pode pular valores que não existem no enum
}

7. Boas Práticas e Armadilhas Comuns

Nomeação consistente usando maiúsculas:

// Boa prática
typedef enum {
    OPCAO_NOVA,
    OPCAO_ABRIR,
    OPCAO_SALVAR
} Opcao;

// Evitar
typedef enum {
    nova,
    abrir,
    salvar
} OpcaoRuim;

Problemas com tamanho do tipo enum:

#include <stdio.h>

enum Pequeno {
    A, B, C
};

enum Grande {
    X = 1000000000,
    Y = 2000000000,
    Z = 3000000000  // Pode causar overflow em sistemas 32-bit
};

int main() {
    printf("Tamanho de enum Pequeno: %zu bytes\n", sizeof(enum Pequeno));
    // Geralmente 4 bytes (int), mas pode variar
    return 0;
}

Riscos de segurança com conversão implícita:

typedef enum {
    PERM_LEITURA = 1,
    PERM_ESCRITA = 2,
    PERM_EXECUCAO = 4
} Permissao;

int main() {
    Permissao p = 999;  // Conversão implícita permitida, valor inválido!

    if (p == PERM_LEITURA) {
        // Comportamento inesperado
    }
    return 0;
}

8. Exemplo Prático: Máquina de Estados

#include <stdio.h>

typedef enum {
    DESLIGADO,
    LIGANDO,
    OPERANDO,
    EM_ESPERA,
    DESLIGANDO
} EstadoMaquina;

const char* estadoParaString(EstadoMaquina estado) {
    switch(estado) {
        case DESLIGADO: return "DESLIGADO";
        case LIGANDO: return "LIGANDO";
        case OPERANDO: return "OPERANDO";
        case EM_ESPERA: return "EM_ESPERA";
        case DESLIGANDO: return "DESLIGANDO";
        default: return "DESCONHECIDO";
    }
}

EstadoMaquina proximoEstado(EstadoMaquina atual, char comando) {
    switch(atual) {
        case DESLIGADO:
            if (comando == 'L') return LIGANDO;
            break;
        case LIGANDO:
            return OPERANDO;
        case OPERANDO:
            if (comando == 'E') return EM_ESPERA;
            if (comando == 'D') return DESLIGANDO;
            break;
        case EM_ESPERA:
            if (comando == 'R') return OPERANDO;
            if (comando == 'D') return DESLIGANDO;
            break;
        case DESLIGANDO:
            return DESLIGADO;
    }
    return atual;
}

int main() {
    EstadoMaquina estado = DESLIGADO;
    char comandos[] = {'L', ' ', 'E', 'R', 'D', ' '};

    printf("Estado inicial: %s\n", estadoParaString(estado));

    for (int i = 0; i < 6; i++) {
        if (comandos[i] != ' ') {
            estado = proximoEstado(estado, comandos[i]);
            printf("Comando '%c' -> Estado: %s\n", 
                   comandos[i], estadoParaString(estado));
        }
    }

    return 0;
}

Referências