Apakah ada kemungkinan untuk mengubah nama enumerator menjadi string di C?
92
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.
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
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]; }
sumber
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.Tidak ada cara sederhana untuk mencapai ini secara langsung. Tetapi P99 memiliki makro yang memungkinkan Anda membuat jenis fungsi seperti itu secara otomatis:
di file header, dan
dalam satu unit kompilasi (file .c) kemudian harus melakukan trik, dalam contoh itu fungsi akan dipanggil
color_getname
.sumber
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 ); }
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:
sumber
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_str
array 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.
sumber
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);
)
sumber
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 };
sumber
Saya biasanya melakukan ini:
#define COLOR_STR(color) \ (RED == color ? "red" : \ (BLUE == color ? "blue" : \ (GREEN == color ? "green" : \ (YELLOW == color ? "yellow" : "unknown"))))
sumber