【问题标题】:How to convert enum names to string in c如何在c中将枚举名称转换为字符串
【发布时间】:2012-04-12 00:35:24
【问题描述】:

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

【问题讨论】:

    标签: c string enums


    【解决方案1】:

    一种方法,让预处理器完成工作。它还可以确保您的枚举和字符串是同步的。

    #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 中的工作方式,在某些情况下是必要的。例如,假设我们想使用带有枚举的#define:

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

    输出将是:

    foo
    apple
    

    这是因为 str 会将输入的 foo 字符串化,而不是将其扩展为 apple。通过使用 xstr,首先完成宏扩展,然后将结果字符串化。

    更多信息请参见Stringification

    【讨论】:

    • 这很完美,但我无法理解到底发生了什么。 :O
    • 在上述情况下如何将字符串转换为枚举?
    • 有几种方法可以完成,具体取决于您要实现的目标?
    • 如果你不想用苹果和橙子污染命名空间......你可以在它前面加上#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
    • 看到这篇文章的人,这种使用宏列表枚举程序中各种项目的方法被非正式地称为“X宏”。
    【解决方案2】:

    在你有这种情况的情况下:

    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" 更容易吗?这不像任何地方都有任何类型安全。除非我遗漏了您在此处提出的建议,否则毫无意义,并且只是成功地混淆了代码。
    • 好的,我现在明白了。在我看来这个宏是假的,我建议你删除它。
    • cmets 谈论宏。它在哪里?
    • 这样维护也不方便。如果我插入一个新的枚举,我必须记住在数组中的正确位置复制它。
    【解决方案3】:

    我发现了一个 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种颜色。

    非顺序枚举

    由于我想将错误代码定义映射为数组字符串,以便我可以将原始错误定义附加到错误代码(例如"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];
    

    在本例中,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"

    【讨论】:

    • 不错。这正是我正在寻找和使用它的目的。同样的错误:)
    • LOOP3...LOOP9 可以分别定义为 LOOP2 - LOOP8。
    • @Michael:说得好。尽管它更难维护,但它会产生更短的代码?
    【解决方案4】:

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

    使用枚举和字符串数组

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

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

    【讨论】:

      【解决方案5】:

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

       P99_DECLARE_ENUM(color, red, green, blue);
      

      在头文件中,并且

       P99_DEFINE_ENUM(color);
      

      然后应该在一个编译单元(.c 文件)中解决问题,在该示例中,该函数将被称为 color_getname

      【讨论】:

      • 我如何拉入这个库?
      【解决方案6】:

      Hokyo 的“非顺序枚举”答案的更简单替代方案,基于使用指示符来实例化字符串数组:

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

      【讨论】:

      • 这在索引为负数时不起作用(在错误枚举中很常见)
      【解决方案7】:

      这样的函数不验证枚举是有点危险的。我建议使用 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_ 值可以是资源值。

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

      string EnumToString(fruit e);
      

      )

      【讨论】:

        【解决方案8】:

        我通常这样做:

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

        【讨论】:

        • 这不是一个糟糕的答案。它清晰、简单且易于理解。如果您在其他人需要快速阅读和理解您的代码的系统上工作,那么清晰非常重要。我不建议使用预处理器技巧,除非它们在编码标准中得到了彻底的注释或描述。
        【解决方案9】:

        我决定创建一个函数,它通过复制枚举并在 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";
        }
        

        然后在 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 &lt;label&gt;: return "&lt;label&gt;"; 格式重新制作行。请注意,它在这篇文章中看起来不太对齐,只是因为 StackOverflow 使用 4 空格制表,而在 Vim 中我使用 8 空格制表,因此您可能需要编辑样式的正则表达式。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-08-07
          • 1970-01-01
          • 2010-09-06
          • 2015-06-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-30
          相关资源
          最近更新 更多