将枚举与字符串相关联的正确方法
A proper way of associating enums with strings
假设我有一些在我的程序中经常使用的字符串(用于存储状态和类似的东西)。字符串操作可能很昂贵,所以每当处理它们时我都想使用枚举。到目前为止,我已经看到了几个解决方案:
typedef enum {
STRING_HELLO = 0,
STRING_WORLD
} string_enum_type;
// Must be in sync with string_enum_type
const char *string_enumerations[] = {
"Hello",
"World"
}
另一个我经常遇到的:
typedef enum {
STRING_HELLO,
STRING_WORLD
} string_enum_type;
const char *string_enumerations[] = {
[STRING_HELLO] = "Hello",
[STRING_WORLD] = "World"
}
这两种方法cons/pros是什么?还有更好的吗?
另一种可能性是用户#defines
。
尽管它的使用有很多缺点,但主要好处是 #defines
不占用 space 除非它们被使用...
#define STRING_HELLO "Hello"
#define STRING_WORLD "World"
前者的唯一优势是它 backwards-compatible 符合古老的 C 标准。
除此之外,后一种选择更为优越,因为即使枚举被修改或项目位置发生变化,它也能确保数据完整性。但是,应该通过检查来完成,以确保枚举中的项目数与 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");
以上是简单"enum - lookup table"耦合的最佳方法。另一种选择是使用结构,但对于更复杂的数据类型,这更合适table。
最后,更像是 side-note,第三个版本将使用 "X macros"。除非您对代码重复和维护有特殊要求,否则不建议这样做。为了完整起见,我将其包含在此处,但在一般情况下我不推荐它:
#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");
另一种可能是使用函数而不是数组:
const char *enumtostring(string_enum_type e) {
switch(e) {
case STRING_HELLO: return "hello";
case STRING_WORLD: return "world";
}
}
gcc 至少会在您添加枚举值但忘记添加匹配的开关大小写时发出警告。
(我想你也可以尝试制作这种功能 inline
。)
附录:我提到的 gcc 警告仅适用于 switch
语句 not 有 default
的情况。因此,如果你想为 out-of-bounds 值打印一些不知何故逐渐渗透的东西,你可以这样做,而不是 default
情况,而是像这样的事情:
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)";
}
包含 out-of-bounds 值也很好:
static char tmpbuf[50];
snprintf(tmpbuf, sizeof(tmpbuf), "(unrecognized string_enum_type value %d)", e);
return tmpbuf;
(这最后一个片段还有一些额外的限制,但这个附录已经很长了,所以我现在不会再强调这一点了。)
假设我有一些在我的程序中经常使用的字符串(用于存储状态和类似的东西)。字符串操作可能很昂贵,所以每当处理它们时我都想使用枚举。到目前为止,我已经看到了几个解决方案:
typedef enum {
STRING_HELLO = 0,
STRING_WORLD
} string_enum_type;
// Must be in sync with string_enum_type
const char *string_enumerations[] = {
"Hello",
"World"
}
另一个我经常遇到的:
typedef enum {
STRING_HELLO,
STRING_WORLD
} string_enum_type;
const char *string_enumerations[] = {
[STRING_HELLO] = "Hello",
[STRING_WORLD] = "World"
}
这两种方法cons/pros是什么?还有更好的吗?
另一种可能性是用户#defines
。
尽管它的使用有很多缺点,但主要好处是 #defines
不占用 space 除非它们被使用...
#define STRING_HELLO "Hello"
#define STRING_WORLD "World"
前者的唯一优势是它 backwards-compatible 符合古老的 C 标准。
除此之外,后一种选择更为优越,因为即使枚举被修改或项目位置发生变化,它也能确保数据完整性。但是,应该通过检查来完成,以确保枚举中的项目数与 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");
以上是简单"enum - lookup table"耦合的最佳方法。另一种选择是使用结构,但对于更复杂的数据类型,这更合适table。
最后,更像是 side-note,第三个版本将使用 "X macros"。除非您对代码重复和维护有特殊要求,否则不建议这样做。为了完整起见,我将其包含在此处,但在一般情况下我不推荐它:
#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");
另一种可能是使用函数而不是数组:
const char *enumtostring(string_enum_type e) {
switch(e) {
case STRING_HELLO: return "hello";
case STRING_WORLD: return "world";
}
}
gcc 至少会在您添加枚举值但忘记添加匹配的开关大小写时发出警告。
(我想你也可以尝试制作这种功能 inline
。)
附录:我提到的 gcc 警告仅适用于 switch
语句 not 有 default
的情况。因此,如果你想为 out-of-bounds 值打印一些不知何故逐渐渗透的东西,你可以这样做,而不是 default
情况,而是像这样的事情:
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)";
}
包含 out-of-bounds 值也很好:
static char tmpbuf[50];
snprintf(tmpbuf, sizeof(tmpbuf), "(unrecognized string_enum_type value %d)", e);
return tmpbuf;
(这最后一个片段还有一些额外的限制,但这个附录已经很长了,所以我现在不会再强调这一点了。)