2013-03-05 38 views
14

我經常發現自己正在編寫幫助調試器方法,該方法返回一個可打印的字符串,給定一些枚舉值。原因是當你通常記錄一個枚舉時,你所得到的只是一個數字。我討厭回到我的源頭去弄清楚這個枚舉是什麼。所以我會做類似是否有任何編譯器/預處理器技巧來調試打印枚舉的名稱?

typedef enum 
{ 
    kOne = 1, 
    kTwo, 
    kThree, 
} 
MyEnum; 

NSString *debugStringForEnum(MyEnum e) 
{ 
    switch (e) 
     case kOne: 
      return @"One"; 
     case kTwo: 
      return @"Two"; 
     .... 
} 

.... 
NSLog(@"My debug log: %@", debugStringForEnum(someVariable)); 

所以我的問題是,有沒有辦法避免編寫所有這些輔助代碼,正好看到枚舉的標籤值?

由於

+0

據我所知,沒有辦法做到這一點,就像你所做的一樣,沒有寫自己的方法。 – 2013-03-05 22:11:57

+0

不幸的是,這是不可能的,因爲枚舉名稱在編譯之後不可用。 – iDev 2013-03-05 22:22:09

+0

[將objective-c typedef轉換爲其字符串等效項]的可能重複(http://stackoverflow.com/questions/1094984/convert-objective-c-typedef-to-its-string-equivalent)。您可以使用其中一種建議的方法。 – iDev 2013-03-05 22:22:36

回答

22

如果你願意寫出讓其他開發者哭泣的「調皮」代碼,那麼是的。試試這個:

#define ENUM(name, ...) typedef enum { M_FOR_EACH(ENUM_IDENTITY, __VA_ARGS__) } name; \ 
char * name ## _DEBUGSTRINGS [] = { M_FOR_EACH(ENUM_STRINGIZE, __VA_ARGS__) }; 

#define ENUM_IDENTITY(A) A, 
#define ENUM_STRINGIZE(A) #A, 

ENUM(MyEnum, 
     foo, bar, baz, boo 
     ) 

你顯然需要一個for-each宏來完成這個工作。這裏有一個簡單的一個:

#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N 

#define M_CONC(A, B) M_CONC_(A, B) 
#define M_CONC_(A, B) A##B 

#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__) 

#define M_FOR_EACH_0(ACTN, E) E 
#define M_FOR_EACH_1(ACTN, E) ACTN(E) 
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__) 
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__) 
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__) 
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__) 
#define M_FOR_EACH_6(ACTN, E, ...) ACTN(E) M_FOR_EACH_5(ACTN, __VA_ARGS__) 
#define M_FOR_EACH_7(ACTN, E, ...) ACTN(E) M_FOR_EACH_6(ACTN, __VA_ARGS__) 
#define M_FOR_EACH_8(ACTN, E, ...) ACTN(E) M_FOR_EACH_7(ACTN, __VA_ARGS__) 
#define M_FOR_EACH_9(ACTN, E, ...) ACTN(E) M_FOR_EACH_8(ACTN, __VA_ARGS__) 
#define M_FOR_EACH_10(ACTN, E, ...) ACTN(E) M_FOR_EACH_9(ACTN, __VA_ARGS__) 

這應該是顯而易見如何擴展該循環有更長的上限,但是......這個答案空間的考慮。只要您願意將額外的迭代複製並粘貼到該位,循環可能會持續下去。

對於非調試構建,請使用#ifdef選擇ENUM版本而不使用第二行。


編輯:從teppic偷指定initialisers想法,這裏是一個更可怕的版本,也有非有序初始化器值的工作原理:

#define ENUM(name, ...) typedef enum { M_FOR_EACH(ENUM_ENAME, __VA_ARGS__) } name; \ 
char * name ## _DEBUGSTRINGS [] = { M_FOR_EACH(ENUM_ELEM, __VA_ARGS__) }; 

#define ENUM_ENAME(A) M_IF(M_2ITEMS(M_ID A), (M_FIRST A = M_SECOND A), (A)), 
#define ENUM_ELEM(A) M_IF(M_2ITEMS(M_ID A), ([M_FIRST A] = M_STR(M_FIRST A)), ([A] = M_STR(A))), 

#define M_STR(A) M_STR_(A) 
#define M_STR_(A) #A 
#define M_IF(P, T, E) M_CONC(M_IF_, P)(T, E) 
#define M_IF_0(T, E) M_ID E 
#define M_IF_1(T, E) M_ID T 
#define M_2ITEMS(...) M_2I_(__VA_ARGS__, 1, 0) 
#define M_2I_(_2, _1, N, ...) N 
#define M_FIRST(A, ...) A 
#define M_SECOND(A, B, ...) B 
#define M_ID(...) __VA_ARGS__ 

ENUM(MyEnum, 
     foo, bar, baz, boo 
     ) 

ENUM(NotherEnum, 
     A, B, (C, 12), D, (E, 8) 
     ) 

我不能保證你的人身安全,如果你在其他人必須維護的代碼中使用這種類型的東西。

+2

濫用預處理器宏總是得到upvote – 2013-03-05 22:25:57

+1

Eww。你爲該帖子贏得+1互聯網:) – 2013-03-05 22:27:43

+0

我寫了一個示例如何在這裏宏觀化功能部分:http://stackoverflow.com/a/14931957/1162141 – technosaurus 2013-03-05 22:36:14

7

更簡單的方法是設置字符串常量的陣列,根據其在陣列中的位置,例如複製標籤

char *enumlabels[] = { NULL, "KOne", "KTwo", "KThree"}; 

在這裏,您需要填寫空白以使枚舉值與陣列位置匹配。

或者,更好,對C99與指定initialisers:

char *enumlabels[] = { [1] = "KOne", [2] = "KTwo", [3] = "KThree"}; 

在這種情況下,只要枚舉聲明至上,您可以直接交換數組下標爲枚舉值,使之更加清晰,例如{ [kOne] = "kOne", ... }。你可以用printf("%s\n", enumlabels[e]);或者其他的。

我寫一些代碼來證明這更好:

typedef enum { 
    white = 1, 
    red = 2, 
    green = 4, 
    blue = 8, 
    black = 16 
} Colour; // be sure to update array below! 

char *enum_colours[] = { [white] = "white", [red] = "red", [green] = "green", 
         [blue] = "blue", [black] = "black" }; 

Colour c = blue; 
printf("The label for %d is %s\n", c, enum_colours[c]); 

Output: The label for 8 is blue

如果你有巨大的枚舉常數(如32767),這顯然不是一個理想的解決方案,由於所需數組的大小。如果沒有指定的初始化程序,可以直接指定數組值,如果更費力,則可以使用enum_colours[white] = "white";等等,但僅限於函數中。

+0

我喜歡這個解決方案,但我擔心有人在枚舉中添加另一個元素時會發生什麼,但忘記擴展描述數組... – 2013-03-05 22:20:55

+0

將它們放在一個結構中,以便它們都需要一起更新。 – 2013-03-05 22:25:26

+1

這使得枚舉值從零開始,這可能並非總是如此。它可以被定義爲以任意數量開始。 – iDev 2013-03-05 22:25:51

0

名稱本身不會是可用的,但它通常是不夠的,給他們一個明確的數字:

enum{ 
    dog = 100, 
    cat = 200, 
    problem = dog+cat, //trailing comma legal in C and C++11 
}; 
+0

尾部逗號也是合法的,也是C++。 – Ferruccio 2013-03-06 00:06:48

+0

看起來像是在c + + 11,很高興知道... – 2013-03-06 01:07:11

2

你不能在運行時枚舉名字,因爲這些符號早已過去了那麼,但是您可以使用多字符常量爲您的枚舉創建更有意義的值。

#import <Foundation/Foundation.h> 

NSString* debugString(int en) { 
    return [NSString stringWithFormat:@"%c%c%c%c", 
      (en>>24) & 0xff, 
      (en>>16) & 0xff, 
      (en>>8) & 0xff, 
      en & 0xff]; 
} 

typedef enum { 
    kOne = 'One.', 
    kTwo = 'Two.', 
    kThree = 'Thre', 
} MyEnum; 

int main(int argc, const char * argv[]) 
{ 
    @autoreleasepool { 
     NSLog(@"kOne = %@", debugString(kOne)); 
     NSLog(@"kTwo = %@", debugString(kTwo)); 
     NSLog(@"kThree = %@", debugString(kThree)); 
    } 
    return 0; 
} 

將打印到控制檯

 
kOne = One. 
kTwo = Two. 
kThree = Thre 

要保持debugString不生成垃圾,每個枚舉必須正好四個字符長(在OSX上)。這是非常依賴編譯器和平臺的。這對調試很有幫助,但不是其他的。

當然,如果您需要枚舉具有相互關聯的特定值或值,這將不起作用。

+0

這是一個好主意,我不知道爲什麼你會有垃圾數據,除非你沒有任何成員大於16位... – 2013-03-06 02:01:45