Let's say I have a number of strings I use often throughout my program (to store state and things like that). String operations can be expensive, so whenever addressing them I'd like to use an enumeration. I've seen a couple solutions so far:
typedef enum {
STRING_HELLO = 0,
STRING_WORLD
} string_enum_type;
// Must be in sync with string_enum_type
const char *string_enumerations[] = {
"Hello",
"World"
}
The other one I encounter quite often:
typedef enum {
STRING_HELLO,
STRING_WORLD
} string_enum_type;
const char *string_enumerations[] = {
[STRING_HELLO] = "Hello",
[STRING_WORLD] = "World"
}
What are cons/pros of these two methods? Is there a better one?
The only advantage with the former is that it's backwards-compatible with ancient C standards.
Apart from that, the latter alternative is superior, as it ensures data integrity even if the enum is modified or items change places. However, it should be completed with a check to ensure that the number of items in the enum corresponds with the number of items in the look-up table:
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");
The above is the best method for a simple "enum - lookup table" coupling. Another option would be to use structs, but that's more suitable for more complex data types.
And finally, more as a side-note, the 3rd version would be to use "X macros". This is not recommended unless you have specialized requirements regarding code repetition and maintenance. I'll include it here for completeness, but I don't recommend it in the general case:
#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");
Another possibility might be to use a function, instead of an array:
const char *enumtostring(string_enum_type e) {
switch(e) {
case STRING_HELLO: return "hello";
case STRING_WORLD: return "world";
}
}
gcc, at least, will warn if you add an enum value but forget to add the matching switch case.
(I suppose you could try making this sort of function inline
, as well.)
Addendum: The gcc warning I mentioned applies only if the switch
statement does not have a default
case. So if you want to print something for out-of-bounds values that somehow creep through, you could do that, not with a default
case, but with something like this:
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)";
}
It's also nice to include the out-of-bounds value:
static char tmpbuf[50];
snprintf(tmpbuf, sizeof(tmpbuf), "(unrecognized string_enum_type value %d)", e);
return tmpbuf;
(This last fragment has a couple of additional limitations, but this addendum is getting long already, so I won't belabor the point with them just now.)
Another possibility is to user #defines
.
In spite of the many cons of its use, the main benefit is that #defines
take up no space unless they are used...
#define STRING_HELLO "Hello"
#define STRING_WORLD "World"
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.