2013-03-05 18 views
14

Tôi thường thấy mình viết phương pháp trình gỡ lỗi trợ giúp trả về chuỗi có thể in, được cung cấp một số giá trị enum. Lý do cho điều này là khi bạn thường đăng nhập một enum, tất cả các bạn nhận được là một số thực sự. Tôi ghét phải quay trở lại nguồn của mình để tìm ra cái enum đó là gì. Vì vậy, tôi sẽ làm một cái gì đó nhưCó bất kỳ thủ thuật biên dịch/tiền xử lý nào để gỡ lỗi in tên của enum không?

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)); 

Vì vậy, câu hỏi của tôi là, có cách nào để tránh viết tất cả mã trợ giúp này, chỉ để xem giá trị nhãn của enum?

Cảm ơn

+0

Theo như tôi biết không có cách nào để làm điều này, viết tắt phương pháp của riêng bạn, như bạn đã làm. –

+0

Thật không may là không thể vì tên enum không có sẵn trong quá trình biên dịch. – iDev

+0

có thể trùng lặp của [Convert target-c typedef thành chuỗi tương đương] (http://stackoverflow.com/questions/1094984/convert-objective-c-typedef-to-its-string-equivalent). Bạn có thể sử dụng một trong các phương pháp được đề xuất ở đó. – iDev

Trả lời

22

Nếu bạn sẵn sàng viết mã "nghịch ngợm" khiến các nhà phát triển khác khóc, thì có. Hãy thử điều này:

#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 
     ) 

Bạn rõ ràng cần một macro riêng để thực hiện công việc này. Dưới đây là một đơn giản:

#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__) 

Nó nên được rõ ràng làm thế nào để kéo dài mà vòng lặp để có một giới hạn trên dài hơn, nhưng ... cân nhắc không gian cho câu trả lời này. Vòng lặp có thể có khả năng miễn là bạn sẵn sàng sao chép và dán thêm các lần lặp vào bit này.

Đối với bản dựng không gỡ lỗi, hãy #ifdef chọn phiên bản ENUM không có dòng thứ hai.


EDIT: Để ăn cắp initialisers ý tưởng thiết kế từ teppic, đây là một phiên bản thậm chí khủng khiếp hơn mà cũng làm việc với những người không theo lệnh initialiser giá trị:

#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) 
     ) 

tôi không thể đảm bảo an toàn cá nhân của bạn nếu bạn sử dụng loại điều này trong mã mà người khác phải duy trì.

+2

lạm dụng các macro tiền xử lý luôn nhận được một tối đa –

+1

Eww. bạn giành được +1 internets cho bài đăng đó mặc dù :) –

+0

Tôi đã viết một ví dụ làm thế nào để macro-ize các bộ phận chức năng ở đây: http://stackoverflow.com/a/14931957/1162141 – technosaurus

7

Một phương pháp đơn giản hơn là thiết lập một mảng của xâu là bản sao giao nhãn theo vị trí của họ trong mảng, ví dụ

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

Ở đây bạn cần điền vào khoảng trống để làm cho giá trị enum khớp với vị trí mảng.

Hoặc, tốt hơn, cho C99 với initialisers định:

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

Trong trường hợp này, miễn là việc kê khai enum đến trước, bạn có thể trao đổi các chỉ số mảng trực tiếp cho các giá trị enum để làm cho nó rõ ràng hơn nhiều , ví dụ { [kOne] = "kOne", ... }.

sau đó cho MyEnum e bạn chỉ có thể sử dụng printf("%s\n", enumlabels[e]); hoặc một số loại như vậy.

Tôi đã viết một chút code để chứng minh điều này tốt hơn nhiều:

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

Nếu bạn có hằng enum lớn (ví dụ 32767) điều này rõ ràng không phải là một giải pháp lý tưởng, do kích thước của mảng yêu cầu. Nếu không có initialisers được chỉ định, bạn có thể gán trực tiếp các giá trị mảng, nếu cẩn thận hơn, với enum_colours[white] = "white"; và cứ thế, nhưng chỉ trong một hàm.

+0

Tôi thích giải pháp này ngoại trừ việc tôi lo lắng về những gì xảy ra khi ai đó thêm phần tử khác vào enum nhưng quên mở rộng mảng mô tả ... –

+0

Đặt chúng trong một cấu trúc với nhau để cả hai cần được cập nhật cùng nhau. –

+1

Điều này làm cho một giả định rằng giá trị enum bắt đầu ở số không mà có thể không phải là trường hợp luôn luôn. Nó có thể được xác định để bắt đầu ở bất kỳ số nào. – iDev

0

Tên mình sẽ không có sẵn, nhưng nó thường là đủ để cung cấp cho họ một số rõ ràng:

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

Dấu phẩy sau cũng hợp pháp là C++. – Ferruccio

+0

Có vẻ như nó ở trong C++ 11, rất hay để biết ... –

2

Bạn không thể có được với tên enum tại thời gian chạy từ những biểu tượng qua lâu rồi bởi sau đó, nhưng bạn có thể sử dụng hằng số nhiều ký tự để tạo ra nhiều giá trị có ý nghĩa hơn cho enums của bạn.

#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; 
} 

sẽ in

 
kOne = One. 
kTwo = Two. 
kThree = Thre 

trên console.

Để giữ debugString từ việc tạo rác, mỗi enum phải dài chính xác bốn ký tự (trên OSX). Đây là trình biên dịch cực kỳ phụ thuộc và nền tảng. Nó rất tốt để gỡ lỗi, nhưng không có gì khác.

Tất nhiên điều này sẽ không hoạt động nếu bạn cần enums có giá trị hoặc giá trị cụ thể có liên quan với nhau.

+0

Đó là một ý tưởng hay, tôi không biết tại sao bạn có dữ liệu rác, trừ khi bạn không có bất kỳ thành viên nào lớn hơn 16 bit ... –