如果我有一個以上的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