Fundamentos

Capítulo 4: Enumeraciones y Switch Statements

Las enumeraciones te permiten crear tus propios tipos con un conjunto fijo de valores nombrados. En lugar de usar números "mágicos" como 0, 1, 2, usas nombres significativos como Status::Connected. Esto hace tu código más legible y seguro.

Objetivos

El problema: números mágicos

Imagina que estás programando una conexión de red. Podrías representar el estado usando números enteros para cada condición:

// [X] Código confuso con "números mágicos"
int connection_status = 2;

if (connection_status == 0) {
    std::cout << "Desconocido
";
} else if (connection_status == 1) {
    std::cout << "Conectando
";
} else if (connection_status == 2) {
    std::cout << "Conectado
";
}
// Que significa 2? Por que no 3? Confuso...

¿Qué son "números mágicos"? Son valores literales (como 0, 1, 2) cuyo significado no es evidente al leerlos. Se llaman "mágicos" porque parecen aparecer de la nada sin explicación. Cuando lees if (status == 2), no tienes idea de qué significa "2" sin consultar documentación o comentarios.

¿Por qué son problemáticos? Los números mágicos hacen el código difícil de mantener. Si necesitas agregar un nuevo estado, debes encontrar todos los lugares donde se usa ese número. Si dos desarrolladores asignan el mismo número a diferentes estados en partes diferentes del código, tendrás bugs sutiles y difíciles de rastrear.

Problemas de este enfoque
  • No es obvio qué significa cada número
  • Puedes asignar valores inválidos: connection_status = 999;
  • Dificulta el mantenimiento y lectura del código
  • Propenso a errores de lógica

La solución: enum class

#include <iostream>

// Definición de enumeración
enum class Status { Unknown, Connecting, Connected, Disconnected };

int main() {
    Status connection_status = Status::Connected;

    if (connection_status == Status::Connected) {
        std::cout << "Estamos conectados!
";
    }

    return 0;
}

Ventajas

Anatomía de enum class

Estructura de una Enumeración

flowchart TB
    A["enum class Status"] --> B["enum class"]
    A --> C["Status"]
    A --> D["Valores"]

    B --> B1["Palabra clave<br/>enum class"]
    C --> C1["Nombre del tipo"]
    D --> D1["Valores enumerados<br/>(separados por comas)"]

    E["Uso:"] --> E1["Status::Unknown"]
    E1 --> E2["Status = Tipo"]
    E1 --> E3[":: = Operador de scope"]
    E1 --> E4["Unknown = Valor"]

    style A fill:#e1f5fe,stroke:#0277bd,stroke-width:3px
    style B fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style C fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px
    style D fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

Por defecto, cada valor de un enum internamente

Cada valor de un enum se mapea a un entero secuencial empezando desde 0.

Puedes ver el valor con: static_cast<int>(Status::Connected)

También puedes especificar valores manuales: enum class HttpStatus { OK = 200, NotFound = 404 }

se asigna automáticamente un número entero comenzando desde 0.

Por defecto, un enum class usa tipo subyacente de enum

El tipo de dato usado internamente para almacenar los valores del enum. Por defecto es int (4 bytes).

Puedes especificar otro tipo: enum class SmallEnum : uint8_t { A, B, C }; (solo 1 byte).

Útil para ahorrar memoria en arrays grandes de enums.

como almacenamiento (típicamente 4 bytes), pero puedes especificar otro tipo más pequeño.

enum vs enum class: Cual es la diferencia? Intermedio

C++ tiene dos formas de definir enumeraciones: enum (estilo C) y enum class (C++11, recomendado).

// Estilo viejo: enum (sin class)
enum OldStatus { Unknown, Connected };

// Uso:
OldStatus s = Connected;  // No requiere OldStatus::
int x = Connected;        // [X] Se convierte implícitamente a int!

// Estilo moderno: enum class
enum class NewStatus { Unknown, Connected };

// Uso:
NewStatus s = NewStatus::Connected;  // [OK] Requiere scope
int x = NewStatus::Connected;        // [X] ERROR: no convierte a int
Recomendación

Siempre usa enum class en código nuevo. Es más seguro porque:

  • Los valores no contaminan el scope global
  • No se convierten implícitamente a int (previene bugs)
  • Mejor soporte para type checking del compilador

Switch statements

El switch es perfecto para trabajar con enums. Evalúa una expresión y ejecuta código diferente según el caso:

enum class Status { Unknown, Connecting, Connected, Disconnected };

void print_status(Status s) {
    switch (s) {
        case Status::Unknown:
            std::cout << "Estado desconocido
";
            break;

        case Status::Connecting:
            std::cout << "Conectando...
";
            break;

        case Status::Connected:
            std::cout << "¡Conectado!
";
            break;

        case Status::Disconnected:
            std::cout << "Desconectado
";
            break;

        default:
            std::cout << "Estado inválido
";
            break;
    }
}

Componentes del switch

El Peligro del Fall-Through

Si olvidas el break, la ejecución "cae" al siguiente caso:

switch (s) {
    case Status::Connecting:
        std::cout << "Conectando...
";
        // [X] ¡FALTA break!

    case Status::Connected:
        std::cout << "Conectado
";
        break;
}

// Si s == Status::Connecting, imprime:
// Conectando...
// Conectado      ← ¡También ejecutó este caso!

Esto es casi siempre un bug. Algunos compiladores te advierten con -Wimplicit-fallthrough.

A veces quieres fall-through intencional

Cuando múltiples casos de un switch ejecutan el mismo código intencionalmente.

Ejemplo: case Sat: case Sun: cout << 'Fin de semana'; break;

En C++17 puedes documentarlo con [[fallthrough]]; para indicar que no es un error.

para que múltiples casos ejecuten el mismo código:

enum class Day { Mon, Tue, Wed, Thu, Fri, Sat, Sun };

void check_day(Day d) {
    switch (d) {
        case Day::Sat:
        case Day::Sun:
            std::cout << "¡Fin de semana!
";
            break;

        case Day::Mon:
        case Day::Tue:
        case Day::Wed:
        case Day::Thu:
        case Day::Fri:
            std::cout << "Día laboral
";
            break;
    }
}
Como implementa el compilador un switch? Avanzado

El compilador optimiza el switch según el número y distribución de casos. Para casos consecutivos (0, 1, 2, 3...), suele generar una jump table (tabla de saltos): un array de direcciones donde cada índice corresponde a un caso. Esto hace que el switch sea O(1) - tiempo constante, sin importar cuántos casos tengas.

Para casos dispersos (0, 100, 1000...), el compilador puede usar binary search (búsqueda binaria) o simplemente una cadena de if-else. La elección depende de heurísticas del compilador sobre qué será más eficiente según el número de casos y su distribución.

Puedes ver qué estrategia usa tu compilador generando el código assembly: g++ -S -O2 file.cc

Ejemplo completo

#include <iostream>

enum class UserPermission { Read = 1, Write = 2, Execute = 4, Admin = 8 };

enum class Status { Unknown, Connecting, Connected, Disconnected };

void print_permission(UserPermission p) {
    switch (p) {
        case UserPermission::Read:
            std::cout << "Permiso de lectura
";
            break;

        case UserPermission::Write:
            std::cout << "Permiso de escritura
";
            break;

        case UserPermission::Execute:
            std::cout << "Permiso de ejecución
";
            break;

        case UserPermission::Admin:
            std::cout << "Permiso de administrador
";
            break;

        default:
            std::cout << "Permiso desconocido
";
            break;
    }
}

int main() {
    Status s = Status::Connected;

    switch (s) {
        case Status::Unknown:
            std::cout << "?
";
            break;

        case Status::Connecting:
            std::cout << "...
";
            break;

        case Status::Connected:
            std::cout << "✓
";
            break;

        case Status::Disconnected:
            std::cout << "✗
";
            break;
    }

    UserPermission perm = UserPermission::Admin;
    print_permission(perm);

    // Convertir a int si realmente lo necesitas
    int perm_value = static_cast<int>(perm);
    std::cout << "Valor: " << perm_value << "
";  // 8

    return 0;
}

Checklist

Preguntas para reflexionar

¿Qué pasa si omito el break en un caso del switch?
Respuesta:

Se produce fall-through: la ejecución continúa al siguiente caso sin detenerse.

switch (x) {
  case 1: cout << 'A'; // sin break!
  case 2: cout << 'B'; break;
}
// Si x=1, imprime: AB

Esto es casi siempre un bug. Algunos compiladores te advierten con -Wimplicit-fallthrough.

¿Puedo usar switch con strings? ¿Por qué sí o por qué no?
Respuesta:

No. El switch solo funciona con tipos integrales (int, char, enum).

Las strings son objetos (arrays de caracteres), no valores primitivos. Para comparar strings usa if-else:

if (s == 'hola') { ... }
else if (s == 'adiós') { ... }

En C++20+ puedes simular switch con strings usando un mapa de funciones o pattern matching.

¿Cómo convierto un enum class a su valor entero?
Respuesta:

Usa static_cast:

enum class Status { Unknown = 0, Connected = 1 };
Status s = Status::Connected;
int value = static_cast<int>(s); // 1

El cast es explícito (no automático) para evitar conversiones accidentales y mejorar la seguridad de tipos.

¿Es obligatorio incluir default: en todo switch?
Respuesta:

No es obligatorio, pero es muy recomendado.

  • Si cubres todos los casos del enum, el compilador puede advertirte si falta alguno (con -Wswitch)
  • Si no cubres todos, el default: maneja casos inesperados
switch (status) {
  case A: ...; break;
  case B: ...; break;
  default: cerr << 'Error!'; break;
}
¿Por qué enum class es más seguro que enum simple?
Respuesta:

3 razones clave:

  • Scope: Los valores no contaminan el namespace global (Status::Connected vs Connected)
  • No conversión implícita: No se convierten automáticamente a int, previniendo bugs
  • Type safety: No puedes mezclar enums de diferentes tipos
enum Old { A };
int x = A; // OK (peligroso!)

enum class New { A };
int y = New::A; // ERROR ✓
Switch vs If-Else

Usa switch cuando:

  • Comparas una variable contra múltiples valores constantes
  • Trabajas con enums
  • Tienes 3 o más casos

Usa if-else cuando:

  • Tienes condiciones complejas (x > 10 && y < 20)
  • Solo tienes 1-2 casos
  • Comparas con variables (no constantes)
Próximo Capítulo

En el Capítulo 4 aprenderás sobre structs (estructuras), que te permiten agrupar múltiples variables relacionadas en un solo tipo. Es el primer paso hacia la programación orientada a objetos.

Siguiente capítulo: if/else y Loops →