열거 형 이름을 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.