2013-02-11 108 views
7

如果我有一个以上的enum多个,例如:C中的类型安全枚举?

enum Greetings{ hello, bye, how }; 

enum Testing { one, two, three }; 

我怎么能执行正确的enum的使用情况如何? 例如,我不希望有人使用hello,因为他们应该使用one以获得更好的调试和可读性。

回答

9

在C中,你可以使用样板代码来伪装它。

typedef enum { HELLO_E, GOODBYE_E } greetings_t; 
struct greetings { greetings_t greetings; }; 
#define HELLO ((struct greetings){HELLO_E}) 
#define GOODBYE ((struct greetings){GOODBYE_E}) 

typedef enum { ONE_E, TWO_E } number_t; 
struct number { number_t number; }; 
#define ONE ((struct number){ONE_E}) 
#define TWO ((struct number){TWO_E}) 

void takes_greeting(struct greetings g); 
void takes_number(struct number n); 

void test() 
{ 
    takes_greeting(HELLO); 
    takes_number(ONE); 

    takes_greeting(TWO); 
    takes_number(GOODBYE); 
} 

这是不应该收取任何费用,并产生错误,而不是警告:那我不使用GNU的扩展

 
$ gcc -c -std=c99 -Wall -Wextra test2.c 
test2.c: In function ‘test’: 
test2.c:19: error: incompatible type for argument 1 of ‘takes_greeting’ 
test2.c:20: error: incompatible type for argument 1 of ‘takes_number’ 

通知,并且不会产生假警告。只有错误。另外请注意,我使用了一个版本的GCC的那一样古老污垢,

 
$ gcc --version 
powerpc-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5493) 
Copyright (C) 2005 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

这应该与C99的复合文字支持任何编译工作。

+1

这很简洁,但是在switch语句中使用enums有点难看。 – Toby 2015-03-20 00:22:42

-2

您可以键入您的枚举,然后声明这些类型的变量和函数参数。

+0

在海湾合作委员会这是无用的。 – LtWorf 2013-02-11 22:59:48

8

Clang会产生以下警告,这是您可以做的最好的警告(尽管用户可以将警告升级为错误)。

enum Greetings { hello, bye, how }; 
enum Count { one, two, three }; 

void takes_greeting(enum Greetings x) {} 
void takes_count(enum Count x) {} 

int main() { 
    takes_greeting(one); 
    takes_count(hello); 
} 

编译器输出:

cc  foo.c -o foo 
foo.c:8:17: warning: implicit conversion from enumeration type 'enum Count' to different enumeration type 'enum Greetings' [-Wenum-conversion] 
     takes_greeting(one); 
     ~~~~~~~~~~~~~~ ^~~ 
foo.c:9:14: warning: implicit conversion from enumeration type 'enum Greetings' to different enumeration type 'enum Count' [-Wenum-conversion] 
     takes_count(hello); 
     ~~~~~~~~~~~ ^~~~~ 

如果用户打算从编译器忽略错误和警告,那么有没有什么可以做,以帮助他们。

+2

不幸的是,gcc在C模式下不会产生类似的警告。我不知道msvc是否存在。有一些评论[这里](http://stackoverflow.com/questions/4669454/how-to-make-gcc-warn-about-passing-wrong-enum-to-a-function)关于如何欺骗gcc进入以可读性为代价产生警告。 – 2013-02-11 22:52:19

2

这是你不想听到的答案。在C中,你不可能真的。现在,如果您的C代码位于C++的“Clean C”子集中,那么您可以使用C++编译器进行编译,以获取使用错误的枚举/ int值的所有错误等。

+0

即使在C++中也不是直截了当的:你需要C++ 11的“枚举类”功能,然后在任何地方指定类名 - 例如'foo = Greetings :: hello'; – Roddy 2013-02-11 23:05:43

2

不幸的是enum是一个薄弱环节C的类型系统enum类型的变量类型为enum类型,但是您声明的enum的常量类型为int

所以,在你的榜样

enum Greetings{ hello, bye, how }; 
enum Testing { one, two, three }; 

enum Greetings const holla = hello; 
enum Testing const eins = one; 

helloone是相同的值,即0两个人的名字,并与同类型int

hollaeins再次具有值0,但具有它们各自的类型。

如果要强制为“真正的”常量的“官方”的类型安全,那就是有你想要的类型和价值实体,你必须使用一些更复杂的结构:

#define GREETING(VAL) ((enum Greetings){ 0 } = (VAL)) 
#define HELLO GREETING(hello) 

GREETING宏中的赋值可确保结果是一个“右值”,因此它不能被修改,并且编译器将仅为其类型和值进行修改。

1

如果你还想确保有效范围,那么有一种技术会伴随着获取整数值的指针解引用的小开销 - 以及大量的样板类型输入。它可能仍然很有用,因为它利用了你在编写范围检查代码时的必要性。

greetings.h:

#ifndef GREETINGS_H 
#define GREETINGS_H 

struct greetings; 
typedef struct greetings Greetings; 

extern const Greetings * const Greetings_hello; 
extern const Greetings * const Greetings_bye; 
extern const Greetings * const Greetings_how; 

const char *Greetings_str(const Greetings *g); 
int Greetings_int(const Greetings *g); 

#endif 

greetings.c:

#include "greetings.h" 

struct greetings { 
    const int val; 
}; 

static const Greetings hello = { 0 }; 
static const Greetings bye = { 1 }; 
static const Greetings how = { 2 }; 

const Greetings * const Greetings_hello = &hello; 
const Greetings * const Greetings_bye = &bye; 
const Greetings * const Greetings_how = &how; 

static const char * const Greetings_names[] = { 
    "hello", 
    "bye", 
    "how" 
}; 

const char * 
Greetings_str(const Greetings *g) 
{ 
    return Greetings_names[g->val]; 
} 

int 
Greetings_int(const Greetings *g) 
{ 
    return g->val; 
} 

例如main.c中:

#include <stdio.h> 
#include "greetings.h" 

void 
printTest(const Greetings *greeting) 
{ 
    if (greeting == Greetings_how) return; 
    puts(Greetings_str(greeting)); 
} 

int 
main() 
{ 
    const Greetings *g = Greetings_hello; 
    printTest(g); 
} 

是的,很多类型,但你得到完整的类型和范围安全。没有其他编译单元能够实例化struct greetings,所以你是完全安全的。


编辑2015-07-04:为了防止NULL,有两种可能性。使用NULL作为默认值(#define Greetings_hello 0而不是现在使用的指针)。这非常方便,但为默认枚举值降低了类型安全性,NULL可用于任何枚举。或者宣布无效的它,要么在存取方法检查它,返回一个错误,或者使用类似GCC的__attribute__((nonnull()))抓住它在编译的时候,例如,在greetings.h:

const char *Greetings_str(const Greetings *g) 
     __attribute__((nonnull(1))); 
+0

如果用户传入'null',最好尽快崩溃并让他们排除。或者,如你所说,使用特定于编译器的技巧。我可以建议的一个小改进就是将问候字符串移动到“Greetings”结构中,因为无论如何您都将它们视为实例成员。所以如果你有例如'static const Greetings hello = {0,“hello”};'等等,你可以在实现中返回g-> label'。 – Yawar 2016-12-11 22:29:43