Bagaimana mengubah nama enum menjadi string di c

92

Apakah ada kemungkinan untuk mengubah nama enumerator menjadi string di C?

Misha
sumber

Jawaban:

187

Satu cara, membuat preprocessor melakukan pekerjaannya. Ini juga memastikan enum dan string Anda selaras.

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

Setelah preprocessor selesai, Anda akan memiliki:

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

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

Kemudian Anda dapat melakukan sesuatu seperti:

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

Jika kasus penggunaan benar-benar hanya mencetak nama enum, tambahkan makro berikut:

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

Kemudian lakukan:

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

Dalam kasus ini, makro dua tingkat mungkin tampak berlebihan, namun, karena cara kerja stringifikasi di C, ini diperlukan dalam beberapa kasus. Misalnya, kita ingin menggunakan #define dengan enum:

#define foo apple

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

Outputnya adalah:

foo
apple

Ini karena str akan merangkai input foo daripada mengembangkannya menjadi apple. Dengan menggunakan xstr ekspansi makro dilakukan terlebih dahulu, kemudian hasil tersebut dirangkai.

Lihat Stringification untuk informasi lebih lanjut.

Terrence M
sumber
1
Ini sempurna, tetapi saya tidak dapat memahami apa yang sebenarnya terjadi. : O
p0lAris
Juga bagaimana seseorang mengubah string menjadi enum dalam kasus di atas?
p0lAris
Ada beberapa cara yang bisa dilakukan, tergantung pada apa yang ingin Anda capai?
Terrence M
5
Jika Anda tidak ingin mencemari namespace dengan apel dan jeruk ... Anda dapat #define GENERATE_ENUM(ENUM) PREFIX##ENUM,
mengawalnya
1
Bagi mereka yang menemukan posting ini, metode menggunakan daftar makro untuk menghitung berbagai item dalam program secara informal disebut "makro X".
Lundin
27

Dalam situasi di mana Anda memiliki ini:

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

Saya suka meletakkan ini di file header di mana enum didefinisikan:

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

    return strings[f];
}
Richard J. Ross III
sumber
4
Untuk kehidupan saya, saya tidak dapat melihat bagaimana ini membantu. Bisakah Anda mengembangkan sedikit untuk membuatnya lebih jelas.
David Heffernan
2
Oke, bagaimana itu bisa membantu? Apakah Anda mengatakan lebih mudah mengetik enumToString(apple)daripada mengetik "apple"? Ini tidak seperti ada jenis keamanan di mana pun. Kecuali saya melewatkan sesuatu yang Anda sarankan di sini tidak ada gunanya dan hanya berhasil mengaburkan kode.
David Heffernan
2
Oke, saya mengerti sekarang. Makro itu palsu dalam pandangan saya dan saya sarankan Anda menghapusnya.
David Heffernan
2
komentar berbicara tentang makro. Dimana itu?
mk ..
2
Ini juga merepotkan untuk dipertahankan. Jika saya memasukkan enum baru, saya harus ingat untuk menggandakannya juga dalam array, pada posisi yang benar.
Fabio
14

Tidak ada cara sederhana untuk mencapai ini secara langsung. Tetapi P99 memiliki makro yang memungkinkan Anda membuat jenis fungsi seperti itu secara otomatis:

 P99_DECLARE_ENUM(color, red, green, blue);

di file header, dan

 P99_DEFINE_ENUM(color);

dalam satu unit kompilasi (file .c) kemudian harus melakukan trik, dalam contoh itu fungsi akan dipanggil color_getname.

Jens Gustedt
sumber
Bagaimana cara menarik lib ini?
JohnyTex
14

Saya menemukan trik preprocessor C yang melakukan pekerjaan yang sama tanpa mendeklarasikan string array khusus (Sumber: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en ).

Enum berurutan

Setelah penemuan Stefan Ram, enum berurutan (tanpa secara eksplisit menyatakan indeksnya, misalnya enum {foo=-1, foo1 = 1}) dapat direalisasikan seperti trik jenius ini:

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

Ini memberikan hasil sebagai berikut:

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

Warnanya MERAH.
Ada 3 warna.

Enum Non-Sequential

Karena saya ingin memetakan definisi kode kesalahan menjadi string array, sehingga saya dapat menambahkan definisi kesalahan mentah ke kode kesalahan (misalnya "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), saya memperpanjang kode sedemikian rupa sehingga Anda dapat dengan mudah menentukan indeks yang diperlukan untuk masing-masing nilai enum :

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

Dalam contoh ini, praprosesor C akan menghasilkan kode berikut :

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", };

Ini menghasilkan kapabilitas implementasi berikut:

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

Maschina
sumber
Bagus. Inilah yang saya cari dan gunakan untuk itu. Kesalahan yang sama :)
mrbean
6

Anda tidak perlu bergantung pada preprocessor untuk memastikan enum dan string Anda sinkron. Bagi saya, menggunakan makro cenderung membuat kode lebih sulit dibaca.

Menggunakan Enum Dan Array Of Strings

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

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

Catatan: string dalam fruit_strarray tidak harus dideklarasikan dalam urutan yang sama seperti item enum.

Bagaimana cara menggunakannya

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

Menambahkan Pemeriksaan Waktu Kompilasi

Jika Anda takut melupakan satu senar, Anda dapat menambahkan tanda centang berikut:

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

Kesalahan akan dilaporkan pada waktu kompilasi jika jumlah item enum tidak cocok dengan jumlah string dalam larik.

jyvet
sumber
2

Fungsi seperti itu tanpa memvalidasi enum adalah sedikit berbahaya. Saya sarankan menggunakan pernyataan sakelar. Keuntungan lain adalah ini dapat digunakan untuk enum yang memiliki nilai yang ditentukan, misalnya untuk bendera dengan nilai 1,2,4,8,16 dll.

Juga gabungkan semua string enum Anda dalam satu larik: -

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

tentukan indeks di file header: -

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

Melakukan hal ini mempermudah untuk menghasilkan versi yang berbeda, misalnya jika Anda ingin membuat versi internasional program Anda dengan bahasa lain.

Menggunakan makro, juga di file header: -

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

Buat fungsi dengan pernyataan switch, ini harus mengembalikan const char *karena string statis const: -

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

Jika memprogram dengan Windows maka nilai ID_ dapat berupa nilai sumber daya.

(Jika menggunakan C ++ maka semua fungsi bisa memiliki nama yang sama.

string EnumToString(fruit e);

)

QuentinUK
sumber
2

Alternatif yang lebih sederhana untuk jawaban Hokyo "Non-Sequential enums", berdasarkan penggunaan penanda untuk membuat instance larik string:

#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 };
Lars
sumber
-2

Saya biasanya melakukan ini:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   
gigilibala
sumber
Ini bukanlah jawaban yang buruk. Jelas, sederhana dan mudah dimengerti. Jika Anda bekerja pada sistem di mana orang lain perlu membaca dan memahami kode Anda dengan cepat, kejelasan sangat penting. Saya tidak akan merekomendasikan menggunakan trik preprocessor kecuali mereka benar-benar dikomentari atau dijelaskan dalam standar pengkodean.
nielsen