在C语言中,是否有将枚举器名称转换为字符串的可能性?
一种方法是让预处理器来完成工作。这还可以确保您的枚举和字符串保持同步。
#define FOREACH_FRUIT(FRUIT) \
FRUIT(apple) \
FRUIT(orange) \
FRUIT(grape) \
FRUIT(banana) \
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
enum FRUIT_ENUM {
FOREACH_FRUIT(GENERATE_ENUM)
};
static const char *FRUIT_STRING[] = {
FOREACH_FRUIT(GENERATE_STRING)
};
预处理器完成后,您将得到:
enum FRUIT_ENUM {
apple, orange, grape, banana,
};
static const char *FRUIT_STRING[] = {
"apple", "orange", "grape", "banana",
};
printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);
#define str(x) #x
#define xstr(x) str(x)
printf("enum apple as a string: %s\n", xstr(apple));
#define foo apple
int main() {
printf("%s\n", str(foo));
printf("%s\n", xstr(foo));
}
foo
apple
您拥有以下内容:
enum fruit {
apple,
orange,
grape,
banana,
// etc.
};
我喜欢将这个放在枚举定义的头文件中:
static inline char *stringFromFruit(enum fruit f)
{
static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };
return strings[f];
}
enumToString(apple)
比输入 "apple"
更简单吗?好像现在并没有任何类型安全保障。除非我漏掉了什么,否则你在这里的建议就是毫无意义的,只会使代码变得难以理解。 - David Heffernan你不需要依靠预处理器来确保枚举和字符串同步。在我看来,使用宏往往会使代码更难阅读。
enum fruit
{
APPLE = 0,
ORANGE,
GRAPE,
BANANA,
/* etc. */
FRUIT_MAX
};
const char * const fruit_str[] =
{
[BANANA] = "banana",
[ORANGE] = "orange",
[GRAPE] = "grape",
[APPLE] = "apple",
/* etc. */
};
注意:fruit_str数组中的字符串不必按枚举项的顺序声明。
printf("enum apple as a string: %s\n", fruit_str[APPLE]);
如果您担心忘记一个字符串,可以添加以下检查:
#define ASSERT_ENUM_TO_STR(sarray, max) \
typedef char assert_sizeof_##max[(sizeof(sarray)/sizeof(sarray[0]) == (max)) ? 1 : -1]
ASSERT_ENUM_TO_STR(fruit_str, FRUIT_MAX);
如果枚举项的数量与数组中字符串的数量不匹配,编译时将报告错误。
我找到了一个 C 预处理器技巧,可以做同样的工作,无需声明专门的字符串数组(来源:http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en)。
在 Stefan Ram 发明了连续枚举量之后(不需要显式声明索引,例如 enum {foo=-1, foo1 = 1}
),可以通过这种精巧的技巧实现:
#include <stdio.h>
#define NAMES C(RED)C(GREEN)C(BLUE)
#define C(x) x,
enum color { NAMES TOP };
#undef C
#define C(x) #x,
const char * const color_name[] = { NAMES };
这将产生以下结果:
int main( void ) {
printf( "The color is %s.\n", color_name[ RED ]);
printf( "There are %d colors.\n", TOP );
}
颜色是红色。
有3种颜色。
由于我想将错误代码定义映射到字符串数组中,以便我可以将原始错误定义附加到错误代码中(例如"错误为3 (LC_FT_DEVICE_NOT_OPENED)"
),因此我扩展了代码,使您可以轻松确定相应枚举值所需的索引:
#define LOOPN(n,a) LOOP##n(a)
#define LOOPF ,
#define LOOP2(a) a LOOPF a LOOPF
#define LOOP3(a) a LOOPF a LOOPF a LOOPF
#define LOOP4(a) a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP5(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP6(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP7(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP8(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP9(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LC_ERRORS_NAMES \
Cn(LC_RESPONSE_PLUGIN_OK, -10) \
Cw(8) \
Cn(LC_RESPONSE_GENERIC_ERROR, -1) \
Cn(LC_FT_OK, 0) \
Ci(LC_FT_INVALID_HANDLE) \
Ci(LC_FT_DEVICE_NOT_FOUND) \
Ci(LC_FT_DEVICE_NOT_OPENED) \
Ci(LC_FT_IO_ERROR) \
Ci(LC_FT_INSUFFICIENT_RESOURCES) \
Ci(LC_FT_INVALID_PARAMETER) \
Ci(LC_FT_INVALID_BAUD_RATE) \
Ci(LC_FT_DEVICE_NOT_OPENED_FOR_ERASE) \
Ci(LC_FT_DEVICE_NOT_OPENED_FOR_WRITE) \
Ci(LC_FT_FAILED_TO_WRITE_DEVICE) \
Ci(LC_FT_EEPROM_READ_FAILED) \
Ci(LC_FT_EEPROM_WRITE_FAILED) \
Ci(LC_FT_EEPROM_ERASE_FAILED) \
Ci(LC_FT_EEPROM_NOT_PRESENT) \
Ci(LC_FT_EEPROM_NOT_PROGRAMMED) \
Ci(LC_FT_INVALID_ARGS) \
Ci(LC_FT_NOT_SUPPORTED) \
Ci(LC_FT_OTHER_ERROR) \
Ci(LC_FT_DEVICE_LIST_NOT_READY)
#define Cn(x,y) x=y,
#define Ci(x) x,
#define Cw(x)
enum LC_errors { LC_ERRORS_NAMES TOP };
#undef Cn
#undef Ci
#undef Cw
#define Cn(x,y) #x,
#define Ci(x) #x,
#define Cw(x) LOOPN(x,"")
static const char* __LC_errors__strings[] = { LC_ERRORS_NAMES };
static const char** LC_errors__strings = &__LC_errors__strings[10];
在这个例子中,C 预处理器将生成以下代码::enum LC_errors { LC_RESPONSE_PLUGIN_OK=-10, LC_RESPONSE_GENERIC_ERROR=-1, LC_FT_OK=0, LC_FT_INVALID_HANDLE, LC_FT_DEVICE_NOT_FOUND, LC_FT_DEVICE_NOT_OPENED, LC_FT_IO_ERROR, LC_FT_INSUFFICIENT_RESOURCES, LC_FT_INVALID_PARAMETER, LC_FT_INVALID_BAUD_RATE, LC_FT_DEVICE_NOT_OPENED_FOR_ERASE, LC_FT_DEVICE_NOT_OPENED_FOR_WRITE, LC_FT_FAILED_TO_WRITE_DEVICE, LC_FT_EEPROM_READ_FAILED, LC_FT_EEPROM_WRITE_FAILED, LC_FT_EEPROM_ERASE_FAILED, LC_FT_EEPROM_NOT_PRESENT, LC_FT_EEPROM_NOT_PROGRAMMED, LC_FT_INVALID_ARGS, LC_FT_NOT_SUPPORTED, LC_FT_OTHER_ERROR, LC_FT_DEVICE_LIST_NOT_READY, TOP };
static const char* __LC_errors__strings[] = { "LC_RESPONSE_PLUGIN_OK", "" , "" , "" , "" , "" , "" , "" , "" "LC_RESPONSE_GENERIC_ERROR", "LC_FT_OK", "LC_FT_INVALID_HANDLE", "LC_FT_DEVICE_NOT_FOUND", "LC_FT_DEVICE_NOT_OPENED", "LC_FT_IO_ERROR", "LC_FT_INSUFFICIENT_RESOURCES", "LC_FT_INVALID_PARAMETER", "LC_FT_INVALID_BAUD_RATE", "LC_FT_DEVICE_NOT_OPENED_FOR_ERASE", "LC_FT_DEVICE_NOT_OPENED_FOR_WRITE", "LC_FT_FAILED_TO_WRITE_DEVICE", "LC_FT_EEPROM_READ_FAILED", "LC_FT_EEPROM_WRITE_FAILED", "LC_FT_EEPROM_ERASE_FAILED", "LC_FT_EEPROM_NOT_PRESENT", "LC_FT_EEPROM_NOT_PROGRAMMED", "LC_FT_INVALID_ARGS", "LC_FT_NOT_SUPPORTED", "LC_FT_OTHER_ERROR", "LC_FT_DEVICE_LIST_NOT_READY", };
这导致以下实现能力:
LC_errors__strings[-1] ==> LC_errors__strings[LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"
color_name
在头文件中,请不要忘记将其定义为静态。 - DimP没有直接实现这个功能的简单方法。但是 P99 有宏允许您自动创建此类型的函数:
P99_DECLARE_ENUM(color, red, green, blue);
在头文件中
P99_DEFINE_ENUM(color);
在一个编译单元(.c文件)中可以解决问题,在这个例子中,函数将会被称为color_getname
。
#define NAMES C(RED, 10)C(GREEN, 20)C(BLUE, 30)
#define C(k, v) k = v,
enum color { NAMES };
#undef C
#define C(k, v) [v] = #k,
const char * const color_name[] = { NAMES };
我通常这样做:
#define COLOR_STR(color) \
(RED == color ? "red" : \
(BLUE == color ? "blue" : \
(GREEN == color ? "green" : \
(YELLOW == color ? "yellow" : "unknown"))))
没有对枚举进行验证的这种函数有些危险。建议使用switch语句。另一个优点是,这可以用于具有定义值的枚举,例如对于标志,其中值为1、2、4、8、16等。
还要将所有枚举字符串放在一个数组中:
static const char * allEnums[] = {
"Undefined",
"apple",
"orange"
/* etc */
};
在头文件中定义索引:
#define ID_undefined 0
#define ID_fruit_apple 1
#define ID_fruit_orange 2
/* etc */
这样做可以更容易地生成不同版本,例如,如果您想用其他语言制作国际版程序。
在头文件中使用宏:
#define CASE(type,val) case val: index = ID_##type##_##val; break;
const char *
,因为这些字符串是静态常量:const char * FruitString(enum fruit e){
unsigned int index;
switch(e){
CASE(fruit, apple)
CASE(fruit, orange)
CASE(fruit, banana)
/* etc */
default: index = ID_undefined;
}
return allEnums[index];
}
如果在Windows系统上进行编程,则ID_值可以是资源值。
(如果使用C++,则所有函数都可以具有相同的名称。
string EnumToString(fruit e);
)
我决定编写一个函数,通过复制枚举并在 Vim 中使用正则表达式来更新其主体。我使用 switch-case,因为我的枚举不是紧凑的,所以我们有最大的灵活性。我将正则表达式作为注释保留在代码中,因此只需复制粘贴即可。
我的枚举(缩短版,真实的枚举要大得多):
enum opcode
{
op_1word_ops = 1024,
op_end,
op_2word_ops = 2048,
op_ret_v,
op_jmp,
op_3word_ops = 3072,
op_load_v,
op_load_i,
op_5word_ops = 5120,
op_func2_vvv,
};
在复制枚举之前的函数:
const char *get_op_name(enum opcode op)
{
// To update copy the enum and apply this regex:
// s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";
switch (op)
{
}
return "Unknown op";
}
const char *get_op_name(enum opcode op)
{
// To update copy the enum and apply this regex:
// s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";
switch (op)
{
op_1word_ops = 1024,
op_end,
op_2word_ops = 2048,
op_ret_v,
op_jmp,
op_3word_ops = 3072,
op_load_v,
op_load_i,
op_5word_ops = 5120,
op_func2_vvv,
}
return "Unknown op";
}
:
,然后粘贴(在Windows上使用Ctrl-V)正则表达式s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";
,然后按回车键。const char *get_op_name(enum opcode op)
{
// To update copy the enum and apply this regex:
// s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";
switch (op)
{
case op_1word_ops: return "op_1word_ops";
case op_end: return "op_end";
case op_2word_ops: return "op_2word_ops";
case op_ret_v: return "op_ret_v";
case op_jmp: return "op_jmp";
case op_3word_ops: return "op_3word_ops";
case op_load_v: return "op_load_v";
case op_load_i: return "op_load_i";
case op_5word_ops: return "op_5word_ops";
case op_func2_vvv: return "op_func2_vvv";
}
return "Unknown op";
}
\t
字符,然后将其后的每个既不是,
也不是
的字符放入\1
中,并匹配行的其余部分以删除所有内容。然后,使用\1
作为枚举标签,以case <label>: return "<label>";
格式重新制作行。请注意,它在此帖子中看起来排列不良,仅因为StackOverflow使用4个空格缩进,而我在Vim中使用8个空格缩进,因此您可能需要编辑样式的正则表达式。
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
作为前缀。 - jsaak