Cách chuyển đổi tên enum thành chuỗi trong c


92

Có khả năng chuyển đổi tên điều tra viên thành chuỗi trong C không?

Câu trả lời:


185

Một cách, làm cho bộ tiền xử lý thực hiện công việc. Nó cũng đảm bảo enums và chuỗi của bạn được đồng bộ.

#define FOREACH_FRUIT(FRUIT) \
        FRUIT(apple)   \
        FRUIT(orange)  \
        FRUIT(grape)   \
        FRUIT(banana)  \

#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,

enum FRUIT_ENUM {
    FOREACH_FRUIT(GENERATE_ENUM)
};

static const char *FRUIT_STRING[] = {
    FOREACH_FRUIT(GENERATE_STRING)
};

Sau khi bộ tiền xử lý hoàn tất, bạn sẽ có:

enum FRUIT_ENUM {
    apple, orange, grape, banana,
};

static const char *FRUIT_STRING[] = {
    "apple", "orange", "grape", "banana",
};

Sau đó, bạn có thể làm điều gì đó như:

printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);

Nếu trường hợp sử dụng thực sự chỉ là in tên enum, hãy thêm các macro sau:

#define str(x) #x
#define xstr(x) str(x)

Sau đó làm:

printf("enum apple as a string: %s\n", xstr(apple));

Trong trường hợp này, có vẻ như macro hai cấp là không cần thiết, tuy nhiên, do cách chuỗi hoạt động trong C, nó là cần thiết trong một số trường hợp. Ví dụ: giả sử chúng ta muốn sử dụng #define với enum:

#define foo apple

int main() {
    printf("%s\n", str(foo));
    printf("%s\n", xstr(foo));
}

Đầu ra sẽ là:

foo
apple

Điều này là do str sẽ xâu chuỗi foo đầu vào thay vì mở rộng nó thành apple. Bằng cách sử dụng xstr, việc mở rộng macro được thực hiện trước, sau đó kết quả đó được chuỗi.

Xem Stringification để biết thêm thông tin.


1
Điều này thật hoàn hảo, nhưng tôi không thể hiểu chuyện gì đang thực sự xảy ra. : O
p0lAris

Ngoài ra, làm thế nào để chuyển đổi một chuỗi thành một enum trong trường hợp trên?
p0lAris

Có một số cách có thể được thực hiện, tùy thuộc vào những gì bạn đang cố gắng đạt được?
Terrence M

5
Nếu bạn không muốn làm ô nhiễm không gian tên với táo và cam ... bạn có thể thêm tiền tố nó với#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
jsaak

1
Đối với những người xem qua bài đăng này, phương pháp sử dụng danh sách macro để liệt kê các mục khác nhau trong một chương trình được gọi là "X macro".
Lundin

27

Trong tình huống mà bạn có điều này:

enum fruit {
    apple, 
    orange, 
    grape,
    banana,
    // etc.
};

Tôi muốn đặt cái này trong tệp tiêu đề nơi enum được định nghĩa:

static inline char *stringFromFruit(enum fruit f)
{
    static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };

    return strings[f];
}

4
Đối với cuộc sống của tôi, tôi không thể thấy điều này có ích gì. Bạn có thể mở rộng một chút để làm cho nó rõ ràng hơn.
David Heffernan

2
OK, điều đó giúp ích như thế nào? Bạn đang nói rằng nó dễ dàng enumToString(apple)hơn để gõ "apple"? Không phải ở đâu cũng có kiểu an toàn. Trừ khi tôi thiếu thứ gì đó, những gì bạn đề xuất ở đây là vô nghĩa và chỉ thành công trong việc xáo trộn mã.
David Heffernan

2
OK tôi nhìn thấy bây giờ. Macro không có thật trong quan điểm của tôi và tôi khuyên bạn nên xóa nó.
David Heffernan

2
bình luận nói về vĩ mô. Nó đâu rồi?
mk ..

2
Điều này cũng không thuận tiện để bảo trì. Nếu tôi chèn một enum mới, tôi phải nhớ để sao chép enum đó cũng trong mảng, ở đúng vị trí.
Fabio

14

Không có cách nào đơn giản để đạt được điều này một cách trực tiếp. Nhưng P99 có các macro cho phép bạn tự động tạo loại chức năng như vậy:

 P99_DECLARE_ENUM(color, red, green, blue);

trong tệp tiêu đề và

 P99_DEFINE_ENUM(color);

trong một đơn vị biên dịch (tệp .c) sau đó sẽ thực hiện thủ thuật, trong ví dụ đó, hàm sau đó sẽ được gọi color_getname.


Làm cách nào để lấy lib này vào?
JohnyTex

14

Tôi đã tìm thấy một thủ thuật tiền xử lý C đang thực hiện công việc tương tự mà không cần khai báo chuỗi mảng chuyên dụng (Nguồn: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_application_en ).

Enums tuần tự

Sau phát minh của Stefan Ram, các enum tuần tự (chẳng hạn như không nêu rõ chỉ số enum {foo=-1, foo1 = 1}) có thể được thực hiện giống như thủ thuật thiên tài này:

#include <stdio.h>

#define NAMES C(RED)C(GREEN)C(BLUE)
#define C(x) x,
enum color { NAMES TOP };
#undef C

#define C(x) #x,    
const char * const color_name[] = { NAMES };

Điều này cho kết quả sau:

int main( void )  { 
    printf( "The color is %s.\n", color_name[ RED ]);  
    printf( "There are %d colors.\n", TOP ); 
}

Màu sắc là ĐỎ.
Có 3 màu.

Enums không tuần tự

Vì tôi muốn ánh xạ các định nghĩa mã lỗi thành chuỗi mảng, để tôi có thể nối định nghĩa lỗi thô vào mã lỗi (ví dụ "The error is 3 (LC_FT_DEVICE_NOT_OPENED).":), tôi đã mở rộng mã theo cách đó bạn có thể dễ dàng xác định chỉ mục cần thiết cho các giá trị enum tương ứng :

#define LOOPN(n,a) LOOP##n(a)
#define LOOPF ,
#define LOOP2(a) a LOOPF a LOOPF
#define LOOP3(a) a LOOPF a LOOPF a LOOPF
#define LOOP4(a) a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP5(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP6(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP7(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP8(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP9(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF


#define LC_ERRORS_NAMES \
    Cn(LC_RESPONSE_PLUGIN_OK, -10) \
    Cw(8) \
    Cn(LC_RESPONSE_GENERIC_ERROR, -1) \
    Cn(LC_FT_OK, 0) \
    Ci(LC_FT_INVALID_HANDLE) \
    Ci(LC_FT_DEVICE_NOT_FOUND) \
    Ci(LC_FT_DEVICE_NOT_OPENED) \
    Ci(LC_FT_IO_ERROR) \
    Ci(LC_FT_INSUFFICIENT_RESOURCES) \
    Ci(LC_FT_INVALID_PARAMETER) \
    Ci(LC_FT_INVALID_BAUD_RATE) \
    Ci(LC_FT_DEVICE_NOT_OPENED_FOR_ERASE) \
    Ci(LC_FT_DEVICE_NOT_OPENED_FOR_WRITE) \
    Ci(LC_FT_FAILED_TO_WRITE_DEVICE) \
    Ci(LC_FT_EEPROM_READ_FAILED) \
    Ci(LC_FT_EEPROM_WRITE_FAILED) \
    Ci(LC_FT_EEPROM_ERASE_FAILED) \
    Ci(LC_FT_EEPROM_NOT_PRESENT) \
    Ci(LC_FT_EEPROM_NOT_PROGRAMMED) \
    Ci(LC_FT_INVALID_ARGS) \
    Ci(LC_FT_NOT_SUPPORTED) \
    Ci(LC_FT_OTHER_ERROR) \
    Ci(LC_FT_DEVICE_LIST_NOT_READY)


#define Cn(x,y) x=y,
#define Ci(x) x,
#define Cw(x)
enum LC_errors { LC_ERRORS_NAMES TOP };
#undef Cn
#undef Ci
#undef Cw
#define Cn(x,y) #x,
#define Ci(x) #x,
#define Cw(x) LOOPN(x,"")
static const char* __LC_errors__strings[] = { LC_ERRORS_NAMES };
static const char** LC_errors__strings = &__LC_errors__strings[10];

Trong ví dụ này, bộ tiền xử lý C sẽ tạo ra mã sau :

enum LC_errors { LC_RESPONSE_PLUGIN_OK=-10,  LC_RESPONSE_GENERIC_ERROR=-1, LC_FT_OK=0, LC_FT_INVALID_HANDLE, LC_FT_DEVICE_NOT_FOUND, LC_FT_DEVICE_NOT_OPENED, LC_FT_IO_ERROR, LC_FT_INSUFFICIENT_RESOURCES, LC_FT_INVALID_PARAMETER, LC_FT_INVALID_BAUD_RATE, LC_FT_DEVICE_NOT_OPENED_FOR_ERASE, LC_FT_DEVICE_NOT_OPENED_FOR_WRITE, LC_FT_FAILED_TO_WRITE_DEVICE, LC_FT_EEPROM_READ_FAILED, LC_FT_EEPROM_WRITE_FAILED, LC_FT_EEPROM_ERASE_FAILED, LC_FT_EEPROM_NOT_PRESENT, LC_FT_EEPROM_NOT_PROGRAMMED, LC_FT_INVALID_ARGS, LC_FT_NOT_SUPPORTED, LC_FT_OTHER_ERROR, LC_FT_DEVICE_LIST_NOT_READY, TOP };

static const char* __LC_errors__strings[] = { "LC_RESPONSE_PLUGIN_OK", "" , "" , "" , "" , "" , "" , "" , "" "LC_RESPONSE_GENERIC_ERROR", "LC_FT_OK", "LC_FT_INVALID_HANDLE", "LC_FT_DEVICE_NOT_FOUND", "LC_FT_DEVICE_NOT_OPENED", "LC_FT_IO_ERROR", "LC_FT_INSUFFICIENT_RESOURCES", "LC_FT_INVALID_PARAMETER", "LC_FT_INVALID_BAUD_RATE", "LC_FT_DEVICE_NOT_OPENED_FOR_ERASE", "LC_FT_DEVICE_NOT_OPENED_FOR_WRITE", "LC_FT_FAILED_TO_WRITE_DEVICE", "LC_FT_EEPROM_READ_FAILED", "LC_FT_EEPROM_WRITE_FAILED", "LC_FT_EEPROM_ERASE_FAILED", "LC_FT_EEPROM_NOT_PRESENT", "LC_FT_EEPROM_NOT_PROGRAMMED", "LC_FT_INVALID_ARGS", "LC_FT_NOT_SUPPORTED", "LC_FT_OTHER_ERROR", "LC_FT_DEVICE_LIST_NOT_READY", };

Điều này dẫn đến các khả năng triển khai sau:

LC_errors__strings [-1] ==> LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"


Đẹp. Đây chính xác là những gì tôi đang tìm kiếm và sử dụng nó. Các lỗi tương tự :)
mrbean

5

Bạn không cần phải dựa vào bộ xử lý trước để đảm bảo rằng enums và chuỗi của bạn được đồng bộ hóa. Đối với tôi, việc sử dụng macro có xu hướng làm cho mã khó đọc hơn.

Sử dụng Enum và một mảng chuỗi

enum fruit                                                                   
{
    APPLE = 0, 
    ORANGE, 
    GRAPE,
    BANANA,
    /* etc. */
    FRUIT_MAX                                                                                                                
};   

const char * const fruit_str[] =
{
    [BANANA] = "banana",
    [ORANGE] = "orange",
    [GRAPE]  = "grape",
    [APPLE]  = "apple",
    /* etc. */  
};

Lưu ý: các chuỗi trong fruit_strmảng không cần phải được khai báo theo thứ tự như các mục enum.

Làm thế nào để sử dụng nó

printf("enum apple as a string: %s\n", fruit_str[APPLE]);

Thêm kiểm tra thời gian biên dịch

Nếu bạn sợ quên một chuỗi, bạn có thể thêm kiểm tra sau:

#define ASSERT_ENUM_TO_STR(sarray, max) \                                       
  typedef char assert_sizeof_##max[(sizeof(sarray)/sizeof(sarray[0]) == (max)) ? 1 : -1]

ASSERT_ENUM_TO_STR(fruit_str, FRUIT_MAX);

Một lỗi sẽ được báo cáo tại thời điểm biên dịch nếu số lượng các mục enum không khớp với số lượng chuỗi trong mảng.


2

Một hàm như thế mà không xác thực enum là một điều nguy hiểm. Tôi đề nghị sử dụng một câu lệnh chuyển đổi. Một ưu điểm khác là điều này có thể được sử dụng cho các enum có các giá trị được xác định, ví dụ cho các cờ có giá trị là 1,2,4,8,16, v.v.

Cũng đặt tất cả các chuỗi enum của bạn lại với nhau trong một mảng: -

static const char * allEnums[] = {
    "Undefined",
    "apple",
    "orange"
    /* etc */
};

xác định các chỉ số trong tệp tiêu đề: -

#define ID_undefined       0
#define ID_fruit_apple     1
#define ID_fruit_orange    2
/* etc */

Làm điều này giúp bạn tạo các phiên bản khác nhau dễ dàng hơn, chẳng hạn như nếu bạn muốn tạo các phiên bản quốc tế của chương trình bằng các ngôn ngữ khác.

Sử dụng macro, cũng trong tệp tiêu đề: -

#define CASE(type,val) case val: index = ID_##type##_##val; break;

Tạo một hàm với câu lệnh switch, câu lệnh này sẽ trả về a const char *bởi vì các chuỗi static khuyết điểm: -

const char * FruitString(enum fruit e){

    unsigned int index;

    switch(e){
        CASE(fruit, apple)
        CASE(fruit, orange)
        CASE(fruit, banana)
        /* etc */
        default: index = ID_undefined;
    }
    return allEnums[index];
}

Nếu lập trình với Windows thì giá trị ID_ có thể là giá trị tài nguyên.

(Nếu sử dụng C ++ thì tất cả các hàm có thể có cùng tên.

string EnumToString(fruit e);

)


2

Một giải pháp thay thế đơn giản hơn cho câu trả lời "enums không theo trình tự" của Hokyo, dựa trên việc sử dụng các ký hiệu chỉ định để khởi tạo mảng chuỗi:

#define NAMES C(RED, 10)C(GREEN, 20)C(BLUE, 30)
#define C(k, v) k = v,
enum color { NAMES };
#undef C

#define C(k, v) [v] = #k,    
const char * const color_name[] = { NAMES };

-2

Tôi thường làm điều này:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   

1
Đây là cách đơn giản ridicolous
Massimo Callegari

Đây không phải là một câu trả lời tồi. Nó rõ ràng, đơn giản và dễ hiểu. Nếu bạn đang làm việc trên các hệ thống mà người khác cần đọc và hiểu mã của bạn một cách nhanh chóng, thì sự rõ ràng là rất quan trọng. Tôi không khuyên bạn nên sử dụng các thủ thuật tiền xử lý trừ khi chúng được nhận xét hoặc mô tả kỹ lưỡng trong một tiêu chuẩn mã hóa.
nielsen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.