如果我有一个以上的enum
多个,例如:C中的类型安全枚举?
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
我怎么能执行正确的enum
的使用情况如何? 例如,我不希望有人使用hello
,因为他们应该使用one
以获得更好的调试和可读性。
如果我有一个以上的enum
多个,例如:C中的类型安全枚举?
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
我怎么能执行正确的enum
的使用情况如何? 例如,我不希望有人使用hello
,因为他们应该使用one
以获得更好的调试和可读性。
在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的复合文字支持任何编译工作。
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);
~~~~~~~~~~~ ^~~~~
如果用户打算从编译器忽略错误和警告,那么有没有什么可以做,以帮助他们。
不幸的是,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
这是你不想听到的答案。在C中,你不可能真的。现在,如果您的C代码位于C++的“Clean C”子集中,那么您可以使用C++编译器进行编译,以获取使用错误的枚举/ int值的所有错误等。
即使在C++中也不是直截了当的:你需要C++ 11的“枚举类”功能,然后在任何地方指定类名 - 例如'foo = Greetings :: hello'; – Roddy 2013-02-11 23:05:43
不幸的是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;
hello
和one
是相同的值,即0
两个人的名字,并与同类型int
。
holla
和eins
再次具有值0
,但具有它们各自的类型。
如果要强制为“真正的”常量的“官方”的类型安全,那就是有你想要的类型和价值实体,你必须使用一些更复杂的结构:
#define GREETING(VAL) ((enum Greetings){ 0 } = (VAL))
#define HELLO GREETING(hello)
GREETING
宏中的赋值可确保结果是一个“右值”,因此它不能被修改,并且编译器将仅为其类型和值进行修改。
如果你还想确保有效范围,那么有一种技术会伴随着获取整数值的指针解引用的小开销 - 以及大量的样板类型输入。它可能仍然很有用,因为它利用了你在编写范围检查代码时的必要性。
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)));
如果用户传入'null',最好尽快崩溃并让他们排除。或者,如你所说,使用特定于编译器的技巧。我可以建议的一个小改进就是将问候字符串移动到“Greetings”结构中,因为无论如何您都将它们视为实例成员。所以如果你有例如'static const Greetings hello = {0,“hello”};'等等,你可以在实现中返回g-> label'。 – Yawar 2016-12-11 22:29:43
这很简洁,但是在switch语句中使用enums有点难看。 – Toby 2015-03-20 00:22:42