代码之家  ›  专栏  ›  技术社区  ›  PoVa

将枚举与字符串关联的正确方法

  •  20
  • PoVa  · 技术社区  · 7 年前

    假设我在整个程序中经常使用许多字符串(用于存储状态等)。字符串操作可能很昂贵,所以每当寻址它们时,我都希望使用枚举。到目前为止,我已经看到了几种解决方案:

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

    这两种方法的优缺点是什么?有更好的吗?

    3 回复  |  直到 7 年前
        1
  •  14
  •   T.J. Crowder    7 年前

    前者的唯一优点是它向后兼容古老的C标准。

    除此之外,后一种替代方案更为优越,因为它可以确保数据完整性,即使修改了枚举或项目更改了位置。但是,应通过检查来完成,以确保枚举中的项数与查找表中的项数相对应:

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

    以上是实现简单“枚举-查找表”耦合的最佳方法。另一种选择是使用结构,但这更适合于更复杂的数据类型。


    最后,作为补充说明,第三个版本将使用“X宏”。除非您有关于代码重复和维护的专门要求,否则不建议这样做。为了完整起见,我会将其包括在这里,但我不建议在一般情况下使用:

    #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");
    
        2
  •  3
  •   Steve Summit    7 年前

    另一种可能是使用函数,而不是数组:

    const char *enumtostring(string_enum_type e) {
        switch(e) {
            case STRING_HELLO: return "hello";
            case STRING_WORLD: return "world";
        }
    }
    

    如果添加枚举值但忘记添加匹配的开关大小写,gcc至少会发出警告。

    (我想你可以试试做这种功能 inline ,以及。)


    附录:我提到的gcc警告仅适用于 switch 语句执行 有一个 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)";
    }
    

    还可以包含越界值:

        static char tmpbuf[50];
        snprintf(tmpbuf, sizeof(tmpbuf), "(unrecognized string_enum_type value %d)", e);
        return tmpbuf;
    

    (最后一个片段还有一些额外的限制,但这个附录已经很长了,所以我现在不想再赘述了。)

        3
  •  0
  •   Alejandro Blasco    7 年前

    另一种可能性是用户 #defines .

    尽管使用它有许多缺点,但主要的好处是 #定义 除非被使用,否则不要占用空间。。。

    #define STRING_HELLO "Hello"
    #define STRING_WORLD "World"