열거 형 이름을 C에서 문자열로 변환하는 방법


92

C에서 열거 자 이름을 문자열로 변환 할 수 있습니까?

답변:


185

한 가지 방법은 전처리 기가 작업을 수행하도록하는 것입니다. 또한 열거 형과 문자열이 동기화되어 있는지 확인합니다.

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

사용 사례가 말 그대로 열거 형 이름 만 인쇄하는 경우 다음 매크로를 추가합니다.

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

다음을 수행하십시오.

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

이 경우 2 단계 매크로가 불필요한 것처럼 보일 수 있지만 C에서 문자열 화가 작동하는 방식으로 인해 경우에 따라 필요합니다. 예를 들어, 열거 형과 함께 #define을 사용하고 싶다고 가정 해 보겠습니다.

#define foo apple

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

출력은 다음과 같습니다.

foo
apple

이것은 str이 입력 foo를 apple로 확장하는 대신 문자열 화하기 때문입니다. xstr을 사용하면 매크로 확장이 먼저 수행되고 그 결과가 문자열 화됩니다.

자세한 내용은 문자열 화 를 참조하십시오.


1
이것은 완벽하지만 실제로 무슨 일이 일어나고 있는지 이해할 수 없습니다. : O
— p0lAris

또한 위의 경우 문자열을 열거 형으로 어떻게 변환합니까?
— p0lAris 2015

달성하려는 목표에 따라 몇 가지 방법이 있습니까?
— Terrence M

5
당신은 사과와 오렌지 네임 스페이스를 오염하지 않으려면 ... 당신은 그것을 앞에 수#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
— jsaak

1
이 게시물을 본 사람들에게 프로그램의 다양한 항목을 열거하기 위해 매크로 목록을 사용하는이 방법을 비공식적으로 "X 매크로"라고합니다.
— Lundin

27

다음과 같은 상황에서 :

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

열거 형이 정의 된 헤더 파일에 이것을 넣는 것을 좋아합니다.

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 2012 년

2
좋아요, 어떻게 도움이 되나요? 입력하는 enumToString(apple)것보다 입력하는 것이 더 쉽다는 "apple"말입니까? 어디에나 형식 안전성이있는 것과는 다릅니다. 내가 여기서 당신이 제안하는 것이 무의미하고 코드를 난독 화하는 데 성공하지 않는 한.
— David Heffernan 2012 년

2
이제 알겠습니다. 내 관점에서 매크로는 가짜이며 삭제하는 것이 좋습니다.
— David Heffernan 2012 년

2
댓글은 매크로에 대해 이야기합니다. 어디 있어요?
— mk .. apr.

2
이것은 또한 유지하기 불편합니다. 새 열거 형을 삽입하면 올바른 위치에있는 배열에도 복제하기 위해 기억해야합니다.
— 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 ).

순차 열거 형

Stefan Ram의 발명에 따라 순차적 인 열거 형 (예 : 인덱스를 명시 적으로 명시하지 않음 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 가지 색상이 있습니다.

비 순차적 열거 형

오류 코드 정의를 배열 문자열로 매핑하고 싶었 기 때문에 원시 오류 정의를 오류 코드 (예 :)에 추가 "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."할 수 있도록 코드를 확장하여 각 열거 형 값에 필요한 인덱스를 쉽게 결정할 수 있습니다. :

#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

5

열거 형과 문자열이 동기화되어 있는지 확인하기 위해 전처리기에 의존 할 필요가 없습니다. 나에게 매크로를 사용하면 코드를 읽기가 더 어려워지는 경향이 있습니다.

열거 형 및 문자열 배열 사용

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배열 의 문자열 은 열거 형 항목과 동일한 순서로 선언 할 필요가 없습니다.

사용 방법

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

열거 형 항목의 양이 배열의 문자열 양과 일치하지 않으면 컴파일 타임에 오류가보고됩니다.


2

열거 형을 검증하지 않는 것과 같은 함수는 사소한 위험입니다. 나는 switch 문을 사용하는 것이 좋습니다. 또 다른 장점은 값이 정의 된 열거 형 (예 : 값이 1,2,4,8,16 등) 인 플래그에 사용할 수 있다는 것입니다.

또한 모든 열거 형 문자열을 하나의 배열에 함께 넣으십시오.

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이기 때문에 a를 반환해야합니다 .

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

지정자를 사용하여 문자열 배열을 인스턴스화하는 것을 기반으로하는 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"))))   

1
이것은 단순히 ridicolous입니다
— 마시모 Callegari

이것은 나쁜 대답이 아닙니다. 명확하고 간단하며 이해하기 쉽습니다. 다른 사람들이 코드를 빠르게 읽고 이해해야하는 시스템에서 작업하는 경우 명확성이 매우 중요합니다. 코딩 표준에 완전히 설명하거나 설명하지 않는 한 전 처리기 트릭을 사용하지 않는 것이 좋습니다.
— nielsen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.