如何在 c 中将枚举名称转换为字符串

有没有可能在 C 中将枚举数名称转换为字符串?

153450 次浏览

在这种情况下:

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];
}

没有简单的方法可以直接实现这一点。但是 P99的宏允许您自动创建这种类型的函数:

 P99_DECLARE_ENUM(color, red, green, blue);

在头文件中,并且

 P99_DEFINE_ENUM(color);

在一个编译单元(。C 文件)应该可以完成这个任务,在这个例子中,函数将被称为 color_getname

One way, making the preprocessor do the work. It also ensures your enums and strings are in sync.

#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));

在这种情况下,两级宏似乎是多余的,但是,由于字符串化在 C 中的工作方式,在某些情况下它是必要的。例如,假设我们想在枚举中使用 # Definition:

#define foo apple


int main() {
printf("%s\n", str(foo));
printf("%s\n", xstr(foo));
}

产出将是:

foo
apple

This is because str will stringify the input foo rather than expand it to be apple. By using xstr the macro expansion is done first, then that result is stringified.

有关更多信息,请参见 束缚

像这样不验证枚举的函数有点危险。我建议使用 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;

使用 switch 语句创建一个函数,这应该返回一个 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 _ value 可以是资源值。

(如果使用 C + + ,那么所有的函数可以有相同的名称。

string EnumToString(fruit e);

)

我通常这样做:

#define COLOR_STR(color)                            \
(RED       == color ? "red"    :                \
(BLUE     == color ? "blue"   :                \
(GREEN   == color ? "green"  :                \
(YELLOW == color ? "yellow" : "unknown"))))

我发现了一个 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 );
}

颜色是红色。
有三种颜色。

Non-Sequential enums

因为我想将错误代码定义映射为数组字符串,这样我就可以将原始错误定义附加到错误代码(例如 "The error is 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];

在这个例子中,the C preprocessor will generate the following code:

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 _ error _ _ string [-1] = = > LC _ error _ _ string [ LC _ RESPONSE _ GENERIC _ ERROR ] = = > “ LC _ RESPONSE _ GENERIC _ ERROR”

对于日本政府的“非序列枚举”答案,有一个更简单的替代方法,它基于使用指示符来实例化字符串数组:

#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 };

您不需要依赖预处理器来确保枚举和字符串同步。对我来说,使用宏会使代码更难阅读。

使用枚举和字符串数组

enum fruit
{
APPLE = 0,
ORANGE,
GRAPE,
BANANA,
/* etc. */
FRUIT_MAX
};


const char * const fruit_str[] =
{
[BANANA] = "banana",
[ORANGE] = "orange",
[GRAPE]  = "grape",
[APPLE]  = "apple",
/* etc. */
};

Note: the strings in the fruit_str array don't have to be declared in the same order as the enum items.

如何使用

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);

如果枚举项的数量与数组中字符串的数量不匹配,则将在编译时报告错误。

I settled on making a function whose body is updated by copying the enum over and using a regex in Vim. I use a switch-case because my enum isn't compact so we have maximum flexibility. I keep the regex as a comment in the code so it's just a matter of copy-pasting it.

我的枚举(缩写为 enum,真正的枚举要大得多) :

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";
}

I paste the contents of the enum inside the switch brackets:

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";
}

然后在 Vim 中用 Shift-V 选择行,按 :,然后粘贴(Windows 上是 Ctrl-V)正则表达式 s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";,然后按 Enter:

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空间表格,所以您可能需要编辑样式的正则表达式。