Un enum (enumeración) es una forma de definir un conjunto de constantes enteras con nombres descriptivos. En lugar de usar “números mágicos” (como 0, 1 o 2) esparcidos por tu código, que no explican su propósito, usas etiquetas con significado. Internamente, el compilador trata a cada etiqueta como un número entero; por defecto, el primer elemento vale 0 y los siguientes aumentan de uno en uno, aunque puedes asignar valores explícitos si lo necesitas.
Usamos enum cuando tenemos un grupo de opciones discretas y relacionadas, como los días de la semana, los estados de un proceso o los niveles de prioridad. Esto hace que el código sea mucho más legible y fácil de mantener. Sin embargo, ten cuidado: en C, los enum no son tipos “fuertes” como en Rust o Swift (donde se conocen como sum types); en C, un enum es básicamente un int con nombres bonitos, lo que significa que el compilador no te impedirá asignar un valor entero cualquiera a una variable de tipo enum. Si lo usas mal, podrías terminar con un valor que no pertenece a ninguna de tus categorías, rompiendo la lógica de tu programa sin que el compilador te lo advierta de forma inmediata.
#include <stdio.h>
/* Usamos typedef para evitar escribir 'enum EstadoSensor'
cada vez que queramos declarar una variable. */
typedef enum {
ESTADO_INICIAL, // Valor por defecto: 0
ESTADO_LECTURA, // Valor por defecto: 1
ESTADO_PROCESANDO, // Valor por defecto: 2
ESTADO_ERROR = -1, // Valor asignado explícitamente
ESTADO_DESCONOCIDO // Valor por defecto: 0 (porque el anterior fue -1)
} EstadoSensor;
/* Esta función mapea el enum a una cadena de texto para depuración. */
const char* obtener_nombre_estado(EstadoSensor s) {
/* El uso de switch con enums es una práctica estándar.
Si omites el 'default' y el compilador tiene activada
la opción -Wswitch, te avisará si olvidas un caso. */
switch (s) {
case ESTADO_INICIAL: return "Inicial";
case ESTADO_LECTURA: return "Leyendo...";
case ESTADO_PROCESANDO: return "Procesando";
case ESTADO_ERROR: return "Error";
case ESTADO_DESCONOCIDO: return "Desconocido";
}
/* Si el valor no coincide con ningún caso anterior,
llegamos aquí. */
return "Valor fuera de rango";
}
int main(void) {
EstadoSensor sensor = ESTADO_LECTURA;
printf("Estado actual: %s\n", obtener_nombre_estado(sensor));
/* Como los enums son tratados como enteros, podemos hacer
operaciones o ver su valor numérico. */
printf("El valor numérico de ESTADO_ERROR es: %d\n", ESTADO_ERROR);
/* Demostración de la debilidad de C:
Podemos forzar un valor que no está definido en el enum. */
sensor = (EstadoSensor)42;
printf("Estado tras forzar valor 42: %s\n", obtener_nombre_estado(sensor));
return 0;
}
En el ejemplo anterior, hemos definido EstadoSensor mediante un typedef. Esto nos permite declarar EstadoSensor sensor; directamente, en lugar de tener que escribir enum EstadoSensor sensor;.
Fíjate en cómo el compilador ha gestionado los valores: ESTADO_INICIAL es 0, y ESTADO_LECTURA es 1. Al asignar ESTADO_ERROR = -1, hemos roto la secuencia automática, lo que provoca que ESTADO_DESCONOCIDO tome el siguiente valor, que es 0. Aunque esto es legal, es una práctica que suele confundir a otros programadores; es mejor mantener la secuencia o ser explícito con todos los valores.
En la función obtener_nombre_estado, utilizamos un switch. Aquí hay un detalle crucial de diseño: he omitido la cláusula default. En C, esto es una técnica para trabajar con el compilador. Si añades un nuevo estado al enum en el futuro pero olvidas actualizar el switch, un compilador con la bandera -Wswitch (o -Werror=switch) te lanzará un aviso o un error. Si pones un default genérico, el compilador asumirá que ya has cubierto todos los casos y no te avisará de ese olvido.
Finalmente, en el main, la línea sensor = (EstadoSensor)42; ilustra que, a nivel de memoria y de CPU, sensor es simplemente un entero. El compilador permite esta asignación mediante un cast, y la función obtener_nombre_estado simplemente fallará al no encontrar el caso 42 en el switch, devolviendo el valor de error al final de la función.
El error frecuente
Un error común es asumir que el compilador validará que el valor de una variable de tipo enum pertenezca realmente a la lista de constantes definidas.
typedef enum {
OK,
ERROR
} Resultado;
Resultado r = 5; // Error lógico: '5' no es OK ni ERROR
Este código compilará sin problemas porque el estándar C trata a Resultado como un tipo compatible con int. El problema real aparecerá cuando uses r en un switch o en una lógica de control, ya que estarás operando con un valor “imposible” según tu modelo de datos, lo que puede causar comportamientos impredecibles o que tu programa ignore estados críticos. Si necesitas seguridad total sobre los rangos, deberás implementar validaciones manuales o usar otras estructuras de datos más robustas.
Mantén tus enums consistentes y aprovecha los avisos del compilador para evitar estados huérfanos en tus estructuras de control.
N° 65