如何在C语言中模拟强类型枚举?

19

在C++03中,可以通过将强类型枚举放入类(或命名空间)中来模拟强类型枚举

struct MyEnum
{
  enum enumName
  {
    VALUE_1 = 1,
    VALUE_2,
  };
};

并且要使用它:

MyEnum::enumName v = MyEnum::VALUE_1;

在C语言中是否有类似的方法?如果有,该如何实现?


我尝试了这样做,但当然不起作用:

struct A
{
  enum aa
  {
    V1 = 5
  };
};

int main()
{
  A::aa a1 = A::V1;
  enum A::aa a2 = A::V1;
  struct A::aa a3 = A::V1;

  return 0;
}

1
在C语言中没有命名空间和类,因此您无法... - tuxtimo
@Morwenn 的 C99 解决方案很好。 - BЈовић
@AJG85:C结构体怎么能够足够好地满足OP的需求呢?你能给出答案吗?--我认为不能,没有作用域运算符,你怎么访问结构体内枚举类型的值呢?(你无法)而且枚举值将被推送到公共标识符空间,这显然不符合问题的强类型部分--这实际上是问题所在。对于这个特定目的,C结构体与C++结构体完全不同,因为它们不定义枚举的作用域 - David Rodríguez - dribeas
@RichardJ.RossIII 当然可以。即使有警告,你仍然可以编译。 - BЈовић
1
@tuxtimo:实际上,C语言为标识符提供了四个不同的命名空间。其中一个用于标签,一个用于结构体、联合体和枚举类型标记,一个用于结构体和联合体成员,最后一个用于所有其他标识符,包括枚举常量。 - John Bode
显示剩余10条评论
3个回答

7
这是我的解决方案。相对于 @Eric 的设计,它有以下优点:
  • 支持相等性测试(例如,A_VALUE_0 == value
  • 不依赖于 C99 的复合字面量
  • 可以转换为将不当的值分配给枚举。
缺点:
  • 标志不起作用(例如,A_VALUE_0 | A_VALUE_1
  • 不能使用 switch
  • 在测试相等性时,可能会让 IDE 混淆错误行在哪里(例如,A_VALUE_0 == B_VALUE_1
注释:
  • 绝对不要解引用此类型的指针。会比兰博基尼更快地导致崩溃
以下是实现代码(使用 -Werror-pedantic 编译):
typedef struct A { char empty[1]; } *A; // we use 'empty' so that we don't get a warning that empty structs are a GNU extension
#define A_VALUE_0 ((A) 0x1)
#define A_VALUE_1 ((A) 0x2)
#define A_VALUE_2 ((A) 0x4)

typedef struct B { char empty[1]; } *B;

#define B_VALUE_0 ((B) 0x0)
#define B_VALUE_1 ((B) 0x1)
#define B_VALUE_2 ((B) 0x2)

int main()
{
    A a = A_VALUE_0;

    int equal = (a == A_VALUE_1); // works!
    int euqal = (a == B_VALUE_1) // doesn't work

    A flags = A_VALUE_0 | A_VALUE_1; // doesn't work!

    switch (a) { // doesn't work
        case A_VALUE_0:
            puts("value 0");
            break;
        case A_VALUE_1:
            puts("value 1");
            break;
        case A_VALUE_2:
            puts("value 2");
            break;
        default:
            puts("unknown value");
            break;
    } // doesn't work

    // casting works for assignment:
    A b = (A) (B_VALUE_2);

    return 0;
}

+1,一些评论:我仍然会选择在基础上使用“枚举”来声明值。如果添加值,则更容易维护。为了避免一些旧编译器的整数到指针警告,您应该通过uintptr_t进行操作,类似于((B)(uintptr_t) 0x2)。要声明对“struct”的指针,您甚至不必声明结构本身,“typedef struct spooky_struct* A”就足够了。 - Jens Gustedt
这种方法依赖于一种合作实现定义的行为:“整数可以转换为任何指针类型。...结果是实现定义的,可能不正确对齐,可能不指向所引用类型的实体,并且可能是陷阱表示。” C17dr § 6.3.2.3 5 在边缘编码。 - chux - Reinstate Monica

3
你可以这样做:
// Declare A to use for an enumeration, and declare some values for it.
typedef struct { int i; } A;
#define A0  ((A) { 0 })
#define A1  ((A) { 1 })

// Declare B to use for an enumeration, and declare some values for it.
typedef struct { int i; } B;
#define B0  ((B) { 0 })
#define B1  ((B) { 1 })


void foo(void)
{
    // Initialize A.
    A a = A0;

    // Assign to A.
    a = A1;

    // Assign a value from B to A.
    a = B0; // Gets an error.
}

这样做可以让你有一些输入,但根据你想要执行的枚举和其值的其他操作而定,这可能会很麻烦。


该任务使用了C99的一个特性(复合字面量),但其余部分也适用于C89。 - rwos
@Shahbaz,这对我来说使用gcc -ansi(使用gcc 4.1.2)是有效的。 - Eric Finn
@rwos,啊是的。我现在记得了,在我的某些代码中我不能编写它,因为我正在使用g++编译器,而不是C11。我的错。 - Shahbaz
8
问题在于测试相等性不起作用,即A a = A0; A b = A0; bool foo = (a == b);会编译错误。此外,无法在switch语句中使用类型为A的变量。 - JeremyP
gcc -std=c99 来拯救。 - R. Martinho Fernandes
显示剩余2条评论

3

由于C语言不支持命名空间,你可以使用前缀来代替。

enum MyEnum {
    MyEnumA = 1,
    MyEnumB,
    MyEnumC
};

enum OtherEnum {
    OtherEnumA = 1,
    OtherEnumB
};

然后,在变量声明中为了简洁起见,您可以像这样为枚举类型声明类型:
typedef enum MyEnum MyEnum;
typedef enum OtherEnum OtherEnum;

如果你不想允许将OtherEnumB隐式转换为MyEnum类型,Clang提供了-Wenum-conversion标志(不幸的是,我认为GCC没有类似的标志)。

/tmp/test.c:24:20: warning: implicit conversion from enumeration type 'enum OtherEnum' to different enumeration type 'MyEnum' (aka 'enum MyEnum') [-Wenum-conversion]
    MyEnum value = OtherEnumB;
           ~~~~~   ^~~~~~~~~~
1 warning generated.

这种方法的优点是简单易懂,并且可以很好地配合您(至少是我的)集成开发环境的自动完成功能。


我知道现在已经过去了3年,所以如果没有回复也不用担心。你当时是使用g++吗?我用gcc就无法使其正常工作。我尝试了4.6和4.7版本,并且还添加了-Wextra、-Wall和-Wenum-compare参数。 - domen
我使用了Clang编译器,我认为GCC在enum类型之间的隐式转换时也会发出警告,但我可能是错误的。看起来当使用-Wenum-compare选项进行比较时,GCC只会发出警告。 - Nicolas Bachschmidt
啊,好的。使用clang时它按照你描述的方式工作。我也尝试了一个更近期的gcc,但仍然没有警告。也有一些错误报告和讨论,但似乎gcc开发人员对现状感到满意,因为“标准规定所有这些都是有效的”。 - domen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接