วิธีการแปลงชื่อ enum เป็นสตริงใน c


92

มีความเป็นไปได้ที่จะแปลงชื่อตัวแจงนับเป็นสตริงใน C หรือไม่?

คำตอบ:


188

วิธีหนึ่งที่ทำให้พรีโปรเซสเซอร์ทำงานได้ นอกจากนี้ยังช่วยให้แน่ใจว่า enums และสตริงของคุณซิงค์กัน

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

หลังจากประมวลผลล่วงหน้าเสร็จสิ้นคุณจะมี:

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

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

จากนั้นคุณสามารถทำสิ่งต่างๆเช่น:

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

หากกรณีการใช้งานเป็นเพียงการพิมพ์ชื่อ enum ให้เพิ่มมาโครต่อไปนี้:

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

จากนั้นทำ:

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

ในกรณีนี้อาจดูเหมือนว่ามาโครสองระดับนั้นไม่จำเป็นอย่างไรก็ตามเนื่องจากวิธีการทำงานของสตริงใน C จึงมีความจำเป็นในบางกรณี ตัวอย่างเช่นสมมติว่าเราต้องการใช้ #define กับ enum:

#define foo apple

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

ผลลัพธ์จะเป็น:

foo
apple

เนื่องจาก str จะสตริงอินพุต foo แทนที่จะขยายเป็นแอปเปิ้ล โดยการใช้ xstr การขยายมาโครจะเสร็จสิ้นก่อนจากนั้นผลลัพธ์นั้นจะถูกทำให้เป็นสตริง

ดูStringificationสำหรับข้อมูลเพิ่มเติม


1
สมบูรณ์แบบ แต่ฉันไม่เข้าใจว่าเกิดอะไรขึ้นจริงๆ : O
p0lAris

วิธีหนึ่งจะแปลงสตริงเป็น enum ในกรณีข้างต้นได้อย่างไร?
p0lAris

มีสองสามวิธีที่สามารถทำได้ขึ้นอยู่กับสิ่งที่คุณพยายามบรรลุ?
Terrence M

5
หากคุณไม่ต้องการสร้างเนมสเปซให้เป็นมลพิษกับแอปเปิ้ลและส้ม ... คุณสามารถนำหน้าด้วย#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
jsaak

1
สำหรับผู้ที่เจอโพสต์นี้วิธีการใช้รายการมาโครเพื่อแจกแจงรายการต่างๆในโปรแกรมเรียกอย่างไม่เป็นทางการว่า "X macros"
Lundin

27

ในสถานการณ์ที่คุณมีสิ่งนี้:

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

ฉันต้องการใส่สิ่งนี้ในไฟล์ส่วนหัวที่กำหนด enum:

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

    return strings[f];
}

4
สำหรับชีวิตของฉันฉันไม่เห็นว่าสิ่งนี้จะช่วยได้อย่างไร ช่วยขยายความเล็กน้อยเพื่อให้ชัดเจนขึ้น
David Heffernan

2
ตกลงมันช่วยได้อย่างไร? คุณบอกว่าพิมพ์ง่ายenumToString(apple)กว่าพิมพ์"apple"หรือเปล่า? มันไม่เหมือนกับว่ามีความปลอดภัยทุกประเภท เว้นแต่ฉันจะพลาดสิ่งที่คุณแนะนำที่นี่ก็ไม่มีจุดหมายและประสบความสำเร็จในการสร้างรหัสที่ทำให้สับสน
David Heffernan

2
ตกลงฉันเห็นแล้ว มาโครหลอกลวงในมุมมองของฉันและฉันขอแนะนำให้คุณลบมัน
David Heffernan

2
ความคิดเห็นพูดคุยเกี่ยวกับมาโคร มันอยู่ที่ไหน?
..

2
นอกจากนี้ยังไม่สะดวกในการดูแลรักษา ถ้าฉันใส่ enum ใหม่ฉันต้องจำให้ทำซ้ำสิ่งนั้นในอาร์เรย์ด้วยในตำแหน่งที่ถูกต้อง
Fabio

14

ไม่มีวิธีง่ายๆในการบรรลุสิ่งนี้โดยตรง แต่P99มีมาโครที่ให้คุณสร้างฟังก์ชันประเภทนี้โดยอัตโนมัติ:

 P99_DECLARE_ENUM(color, red, green, blue);

ในไฟล์ส่วนหัวและ

 P99_DEFINE_ENUM(color);

ในหน่วยคอมไพล์หนึ่งหน่วย (ไฟล์. c) ควรทำเคล็ดลับในตัวอย่างนั้นฟังก์ชันจะถูกเรียกcolor_getnameใช้


ฉันจะดึง lib นี้เข้ามาได้อย่างไร
JohnyTex

14

ฉันพบเคล็ดลับตัวประมวลผลก่อน C ที่ทำงานเดียวกันโดยไม่ต้องประกาศสตริงอาร์เรย์เฉพาะ (ที่มา: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en )

enums ตามลำดับ

หลังจากการประดิษฐ์ของ Stefan Ram แล้ว enums ตามลำดับ (โดยไม่ระบุดัชนีอย่างชัดเจนเช่นenum {foo=-1, foo1 = 1}) สามารถรับรู้ได้เช่นเคล็ดลับอัจฉริยะนี้

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

สิ่งนี้ให้ผลลัพธ์ดังต่อไปนี้:

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

สีแดง
มี 3 สี

enums ที่ไม่ใช่ลำดับ

เนื่องจากฉันต้องการแมปนิยามรหัสข้อผิดพลาดเป็นสตริงอาร์เรย์ดังนั้นฉันจึงสามารถผนวกนิยามข้อผิดพลาดดิบเข้ากับรหัสข้อผิดพลาด (เช่น"The error is 3 (LC_FT_DEVICE_NOT_OPENED).") ฉันจึงขยายรหัสในลักษณะนั้นเพื่อให้คุณสามารถกำหนดดัชนีที่ต้องการสำหรับค่า 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];

ในตัวอย่างนี้ตัวประมวลผลก่อน C จะสร้างรหัสต่อไปนี้ :

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

ผลลัพธ์นี้เป็นความสามารถในการใช้งานต่อไปนี้:

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


ดี. นี่คือสิ่งที่ฉันกำลังมองหาและใช้มัน ข้อผิดพลาดเดียวกัน :)
mrbean

6

คุณไม่จำเป็นต้องพึ่งพาตัวประมวลผลล่วงหน้าเพื่อให้แน่ใจว่า enums และสตริงของคุณซิงค์กัน สำหรับฉันการใช้มาโครมักจะทำให้โค้ดอ่านยากขึ้น

การใช้ Enum และ 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. */  
};

หมายเหตุ: fruit_strไม่จำเป็นต้องประกาศสตริงในอาร์เรย์ในลำดับเดียวกับรายการ enum

วิธีการใช้งาน

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

การเพิ่มการตรวจสอบเวลาคอมไพล์

หากคุณกลัวที่จะลืมหนึ่งสตริงคุณสามารถเพิ่มการตรวจสอบต่อไปนี้:

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

ระบบจะรายงานข้อผิดพลาดในเวลาคอมไพล์หากจำนวนรายการ enum ไม่ตรงกับจำนวนสตริงในอาร์เรย์


2

ฟังก์ชั่นเช่นนั้นโดยไม่ต้องตรวจสอบ enum เป็นเรื่องเล็ก ฉันขอแนะนำให้ใช้คำสั่งสวิตช์ ข้อดีอีกประการหนึ่งคือสามารถใช้สำหรับ enums ที่กำหนดค่าไว้ตัวอย่างเช่นสำหรับแฟล็กที่ค่าเป็น 1,2,4,8,16 เป็นต้น

ใส่สตริง enum ทั้งหมดของคุณเข้าด้วยกันในอาร์เรย์เดียว: -

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

กำหนดดัชนีในไฟล์ส่วนหัว: -

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

การทำเช่นนี้ทำให้การสร้างเวอร์ชันต่างๆง่ายขึ้นเช่นหากคุณต้องการสร้างโปรแกรมเวอร์ชันสากลด้วยภาษาอื่น ๆ

การใช้มาโครในไฟล์ส่วนหัว: -

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

สร้างฟังก์ชันด้วยคำสั่ง switch ซึ่งควรส่งคืนไฟล์ const char *เนื่องจากสตริงคงที่ consts: -

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

หากเขียนโปรแกรมด้วย Windows ค่า ID_ อาจเป็นค่าทรัพยากร

(หากใช้ C ++ ฟังก์ชันทั้งหมดสามารถมีชื่อเดียวกันได้

string EnumToString(fruit e);

)


2

ทางเลือกที่ง่ายกว่าสำหรับคำตอบ "Non-Sequential enums" ของ Hokyo โดยอาศัยการใช้ตัวออกแบบเพื่อสร้างอินสแตนซ์อาร์เรย์สตริง:

#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

ฉันมักจะทำสิ่งนี้:

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

นี่ไม่ใช่คำตอบที่ไม่ดี มีความชัดเจนเรียบง่ายและเข้าใจง่าย หากคุณกำลังทำงานในระบบที่ผู้อื่นต้องการอ่านและทำความเข้าใจโค้ดของคุณอย่างรวดเร็วความชัดเจนเป็นสิ่งสำคัญมาก ฉันไม่แนะนำให้ใช้กลอุบายของตัวประมวลผลล่วงหน้าเว้นแต่จะได้รับการแสดงความคิดเห็นอย่างละเอียดหรืออธิบายไว้ในมาตรฐานการเข้ารหัส
นีลเซ่น
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.