ejemplo - typedef enum c
Una forma adecuada de asociar enumeraciones con cadenas (3)
Digamos que tengo una serie de cadenas que uso a menudo en mi programa (para almacenar el estado y cosas así). Las operaciones de cadena pueden ser costosas, por lo que cada vez que las dirijo me gustaría usar una enumeración. He visto un par de soluciones hasta ahora:
typedef enum {
STRING_HELLO = 0,
STRING_WORLD
} string_enum_type;
// Must be in sync with string_enum_type
const char *string_enumerations[] = {
"Hello",
"World"
}
El otro me encuentro bastante a menudo:
typedef enum {
STRING_HELLO,
STRING_WORLD
} string_enum_type;
const char *string_enumerations[] = {
[STRING_HELLO] = "Hello",
[STRING_WORLD] = "World"
}
¿Cuáles son los contras / pros de estos dos métodos? Hay alguno mejor?
La única ventaja con la primera es que es compatible con los estándares C antiguos.
Aparte de eso, la última alternativa es superior, ya que garantiza la integridad de los datos incluso si se modifica la enumeración o si los elementos cambian de lugar. Sin embargo, se debe completar con una verificación para asegurar que el número de elementos en la enumeración se corresponda con el número de elementos en la tabla de consulta:
typedef enum {
STRING_HELLO,
STRING_WORLD,
STRING_N // counter
} string_enum_type;
const char *string_enumerations[] = {
[STRING_HELLO] = "Hello",
[STRING_WORLD] = "World"
};
_Static_assert(sizeof string_enumerations/sizeof *string_enumerations == STRING_N,
"string_enum_type does not match string_enumerations");
El anterior es el mejor método para un acoplamiento simple "enum - lookup table". Otra opción sería utilizar estructuras, pero eso es más adecuado para tipos de datos más complejos.
Y finalmente, más como una nota al margen, la tercera versión sería usar "macros X". Esto no se recomienda a menos que tenga requisitos especializados con respecto a la repetición y el mantenimiento del código. Lo incluiré aquí para completar, pero no lo recomiendo en el caso general:
#define STRING_LIST /
/* index str */ /
X(STRING_HELLO, "Hello") /
X(STRING_WORLD, "World")
typedef enum {
#define X(index, str) index,
STRING_LIST
#undef X
STRING_N // counter
} string_enum_type;
const char *string_enumerations[] = {
#define X(index, str) [index] = str,
STRING_LIST
#undef X
};
_Static_assert(sizeof string_enumerations/sizeof *string_enumerations == STRING_N,
"string_enum_type does not match string_enumerations");
Otra posibilidad es para el usuario #defines
.
A pesar de los muchos inconvenientes de su uso, el beneficio principal es que #defines
ocupa espacio a menos que se utilicen ...
#define STRING_HELLO "Hello"
#define STRING_WORLD "World"
Otra posibilidad podría ser usar una función, en lugar de una matriz:
const char *enumtostring(string_enum_type e) {
switch(e) {
case STRING_HELLO: return "hello";
case STRING_WORLD: return "world";
}
}
gcc, al menos, avisará si agrega un valor de enumeración pero olvida agregar la caja del interruptor correspondiente.
(Supongo que también podrías intentar hacer este tipo de función en inline
).
Anexo: la advertencia de gcc que mencioné se aplica solo si la declaración de switch
no tiene un caso default
. Por lo tanto, si desea imprimir algo para los valores fuera de los límites que de alguna manera se arrastran, puede hacerlo, no con un caso default
, sino con algo como esto:
const char *enumtostring(string_enum_type e) {
switch(e) {
case STRING_HELLO: return "hello";
case STRING_WORLD: return "world";
}
return "(unrecognized string_enum_type value)";
}
También es bueno incluir el valor fuera de límites:
static char tmpbuf[50];
snprintf(tmpbuf, sizeof(tmpbuf), "(unrecognized string_enum_type value %d)", e);
return tmpbuf;
(Este último fragmento tiene un par de limitaciones adicionales, pero este addendum ya se está haciendo largo, así que no voy a tratar el tema con ellos en este momento).