提问者:小点点

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


假设我在整个程序中经常使用一些字符串(用来存储状态之类的)。字符串操作可能很昂贵,所以无论何时处理它们,我都喜欢使用枚举。到目前为止,我已经看到了一些解决方案:

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个答案

匿名用户

前者的唯一优点是它向后兼容古老的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");

匿名用户

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

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

如果您添加了一个enum值,但忘记添加匹配的switch case,gcc至少会发出警告。

(我想您也可以尝试使这种函数inline。)

附录:我提到的gcc警告仅适用于< code>switch语句没有< code>default case的情况。因此,如果您想打印一些不知何故悄悄通过的越界值,您可以这样做,不是用< code>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;

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

匿名用户

另一种可能是用户#定义

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

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