2013-02-11 124 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