C enum의 값 대신 텍스트 인쇄


87
int main()
{

  enum Days{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday};

  Days TheDay;

  int j = 0;

  printf("Please enter the day of the week (0 to 6)\n");

  scanf("%d",&j);

  TheDay = Days(j);

  //how to PRINT THE VALUES stored in TheDay

  printf("%s",TheDay);  //   isnt working

  return 0;
}

예상되는 출력은 문자열 "Sunday"등을 인쇄하는 것입니까?
GalacticCowboy

답변:


104

C의 열거 형은 코드 내에 편리한 이름을 가진 숫자입니다. 이들은 문자열이 아니며 소스 코드에서 할당 된 이름은 프로그램으로 컴파일되지 않으므로 런타임에 액세스 할 수 없습니다.

원하는 것을 얻는 유일한 방법은 열거 형 값을 문자열로 변환하는 함수를 직접 작성하는 것입니다. 예 (여기에서의 enum Days외부 선언을 이동한다고 가정 main) :

const char* getDayName(enum Days day) 
{
   switch (day) 
   {
      case Sunday: return "Sunday";
      case Monday: return "Monday";
      /* etc... */
   }
}

/* Then, later in main: */
printf("%s", getDayName(TheDay));

또는 배열을 맵으로 사용할 수 있습니다.

const char* dayNames[] = {"Sunday", "Monday", "Tuesday", /* ... etc ... */ };

/* ... */

printf("%s", dayNames[TheDay]);

하지만 여기서는 Sunday = 0안전을 위해 열거 형 에 할당하고 싶을 것입니다 ... C 표준이 컴파일러가 0부터 열거 형을 시작하도록 요구하는지 확실하지 않습니다. 대부분의 경우에도 그렇습니다. ).


3
아, 당신은 저를 어레이 솔루션으로 이겼습니다. : P 그러나 예, 열거 형은 다른 값을 지정하지 않는 한 항상 0에서 시작합니다.
casablanca

1
열거 형을 인덱스로 사용하는 데 의존하는 경우 실제로 각각에 명시 적으로 번호를 매기는 것이 좋습니다. 표준에 따르면 불필요하지만 그룹 컴파일러는 내 경험상 표준을 따르는 데 정확히 최고가 아닙니다.
jdmichal 2010

3
C 표준은 "첫 번째 열거 자에 =가 없으면 열거 상수의 값은 0"이라고 말합니다. 그러나 그것을 명시 적으로 갖는 것은 아무것도 해치지 않습니다.
Michael Burr

17
C99로 할 수 있다는 것을 잊지 마십시오 const char* dayNames[] = {[Sunday] = "Sunday", [Monday] = "Monday", [Tuesday] = "Tuesday", /* ... etc ... */ };. 요일이 다시 정렬되거나 월요일이 요일로 결정되는 경우를 대비하여 알 수 있습니다.
Tim Schaeffer

2
@ user3467349 그 (전 처리기 문자열 화)는 # 다음에 나오는 기호를 문자열로 바꿉니다. 예, #Monday는 "Monday"로 바뀌지 만 Days TheDay = Monday; printf("%s", #TheDay);"TheDay"를 인쇄합니다.
Tyler McHenry

29

다음과 같이 사용합니다.

"EnumToString.h"파일에서 :

#undef DECL_ENUM_ELEMENT
#undef DECL_ENUM_ELEMENT_VAL
#undef DECL_ENUM_ELEMENT_STR
#undef DECL_ENUM_ELEMENT_VAL_STR
#undef BEGIN_ENUM
#undef END_ENUM

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element,
    #define DECL_ENUM_ELEMENT_VAL( element, value ) element = value,
    #define DECL_ENUM_ELEMENT_STR( element, descr ) DECL_ENUM_ELEMENT( element )
    #define DECL_ENUM_ELEMENT_VAL_STR( element, value, descr ) DECL_ENUM_ELEMENT_VAL( element, value )
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            const char* GetString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define BEGIN_ENUM( ENUM_NAME) const char * GetString##ENUM_NAME( enum tag##ENUM_NAME index ) {\
        switch( index ) { 
    #define DECL_ENUM_ELEMENT( element ) case element: return #element; break;
    #define DECL_ENUM_ELEMENT_VAL( element, value ) DECL_ENUM_ELEMENT( element )
    #define DECL_ENUM_ELEMENT_STR( element, descr ) case element: return descr; break;
    #define DECL_ENUM_ELEMENT_VAL_STR( element, value, descr ) DECL_ENUM_ELEMENT_STR( element, descr )

    #define END_ENUM( ENUM_NAME ) default: return "Unknown value"; } } ;

#endif

그런 다음 헤더 파일에서 enum 선언, day enum.h를 만듭니다.

#include "EnumToString.h"

BEGIN_ENUM(Days)
{
    DECL_ENUM_ELEMENT(Sunday) //will render "Sunday"
    DECL_ENUM_ELEMENT(Monday) //will render "Monday"
    DECL_ENUM_ELEMENT_STR(Tuesday, "Tuesday string") //will render "Tuesday string"
    DECL_ENUM_ELEMENT(Wednesday) //will render "Wednesday"
    DECL_ENUM_ELEMENT_VAL_STR(Thursday, 500, "Thursday string") // will render "Thursday string" and the enum will have 500 as value
    /* ... and so on */
}
END_ENUM(MyEnum)

그런 다음 EnumToString.c라는 파일에서 :

#include "enum.h"

#define GENERATE_ENUM_STRINGS  // Start string generation

#include "enum.h"             

#undef GENERATE_ENUM_STRINGS   // Stop string generation

그런 다음 main.c에서 :

int main(int argc, char* argv[])
{
    Days TheDay = Monday;
    printf( "%d - %s\n", TheDay, GetStringDay(TheDay) ); //will print "1 - Monday"

    TheDay = Thursday;
    printf( "%d - %s\n", TheDay, GetStringDay(TheDay) ); //will print "500 - Thursday string"

    return 0;
}

이렇게하면 이렇게 선언되고 "EnumToString.c"에 포함 된 모든 열거 형에 대한 문자열이 "자동으로"생성됩니다.


4
읽는 것은 추악하지만 데이터 중복이 없습니다. (다른 사람들과는 다릅니다.) 나는 이것을 좋아할지 여부가 찢어졌습니다.
Kim Reece

1
데이터 중복이없고 아마도 최고의 유지 보수성 / 유연성이있는 놀랍도록 창의적인 솔루션에 +1,하지만 yech! 나는 여전히 const char * [] 경로로 가고 싶다고 생각한다.
매니페스트

4
예, 유지 관리가 굉장합니다! 요일이 바뀔 때 업데이트하는 것은 정말 쉽습니다! </ sarcasm> 그건 그렇고, 영어 문자열과 프로그램의 이름 사이의 매핑이 이제 중복을 방지하려는 시도로 인해 하드 코딩 되었기 때문에 지역화 목적으로는 유용하지 않습니다. 적어도 다른 접근 방식을 사용하면 소스 파일의 모든 항목을 변경하지 않고 문자열을 번역 할 수 있습니다.
R .. GitHub의 STOP 돕기 ICE

1
return 문을로 변경하여 (gettext와 같은 것을 사용하여) 국제화 할 수 있습니다 return _(#element).
Vargas

C 전처리 기가 이렇게 유용하지만 추악 할 때, 저는 보통 스크립팅 언어의 간단한 코드 생성기 나 사용자 정의 전처리기로 대체합니다. 사실, 여러 프로젝트에서 정확히이 목적으로 사용한 Python 스크립트가 있습니다. 하지만 요즘에는 그렇게 자주 사용하지 않습니다. 많은 사용 사례에서 문자열 만 사용하고 열거 형을 사용하지 않아도됩니다 (C ++ 또는 ObjC에서는 더욱 그렇습니다).
abarnert

6

일반적으로 이렇게하는 방법은 문자열 표현을 동일한 순서로 별도의 배열에 저장 한 다음 열거 형 값으로 배열을 인덱싱하는 것입니다.

const char *DayNames[] = { "Sunday", "Monday", "Tuesday", /* etc */ };
printf("%s", DayNames[Sunday]); // prints "Sunday"

4

enumC의 s는 예상대로 작동하지 않습니다. 여러분은 그것들을 영광스러운 상수와 비슷하다고 생각할 수 있습니다 ( 이러한 상수 의 모음 과 관련된 몇 가지 추가 이점이 있음 ). "일요일"에 대해 작성한 텍스트는 실제로 컴파일하는 동안 숫자로 해결됩니다. 텍스트는 다음과 같습니다. 궁극적으로 폐기됩니다.

간단히 말해서, 정말로 원하는 것을 수행하려면 문자열 배열을 유지하거나 열거 형 값에서 인쇄하려는 텍스트로 매핑하는 함수를 만들어야합니다.


4

C의 열거 형은 기본적으로 자동으로 순서가 지정된 정수 값의 명명 된 목록에 대한 구문 설탕입니다. 즉,이 코드가있는 경우 :

int main()
{
    enum Days{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday};

    Days TheDay = Monday;
}

컴파일러는 실제로 다음과 같이 뱉어냅니다.

int main()
{
    int TheDay = 1; // Monday is the second enumeration, hence 1. Sunday would be 0.
}

따라서 C 열거 형을 문자열로 출력하는 것은 컴파일러에게 의미있는 작업이 아닙니다. 사람이 읽을 수있는 문자열을 사용하려면 열거 형에서 문자열로 변환하는 함수를 정의해야합니다.


4

다음은 매크로를 사용하여 수행하는 더 깨끗한 방법입니다.

#include <stdio.h>
#include <stdlib.h>

#define DOW(X, S)                                                         \
    X(Sunday) S X(Monday) S X(Tuesday) S X(Wednesday) S X(Thursday) S X(Friday) S X(Saturday)

#define COMMA ,

/* declare the enum */
#define DOW_ENUM(DOW) DOW
enum dow {
    DOW(DOW_ENUM, COMMA)
};

/* create an array of strings with the enum names... */
#define DOW_ARR(DOW ) [DOW] = #DOW
const char * const dow_str[] = {
    DOW(DOW_ARR, COMMA)
};

/* ...or create a switchy function. */
static const char * dowstr(int i)
{
#define DOW_CASE(D) case D: return #D

    switch(i) {
        DOW(DOW_CASE, ;);
    default: return NULL;
    }
}


int main(void)
{
    for(int i = 0; i < 7; i++)
        printf("[%d] = «%s»\n", i, dow_str[i]);
    printf("\n");
    for(int i = 0; i < 7; i++)
        printf("[%d] = «%s»\n", i, dowstr(i));
    return 0;
}

이것이 완전히 이식 가능한 흑백 전처리 기인지는 모르겠지만 gcc와 함께 작동합니다.

이것은 c99 btw이므로 (온라인 컴파일러) ideone에c99 strict 연결하면 사용 하십시오 .


"깨끗한"매크로가 얼마나 좋은지 좋아해야합니다. :-).
mk12 2011

3

내가 파티에 늦었다는 건 알지만 이건 어때?

const char* dayNames[] = { [Sunday] = "Sunday", [Monday] = "Monday", /*and so on*/ };
printf("%s", dayNames[Sunday]); // prints "Sunday"

이렇게하면 enumchar*어레이 를 수동으로 동기화 할 필요가 없습니다 . 당신이 나와 같다면 나중에를 변경 enum하고 char*배열이 잘못된 문자열을 인쇄 할 가능성이 있습니다. 이것은 보편적으로 지원되는 기능이 아닐 수 있습니다. 그러나 대부분의 mordern day C 컴파일러는이 지정된 초기 스타일을 지원합니다.

여기에서 지정된 이니셜 라이저에 대해 자세히 읽을 수 있습니다 .


1

문제는 이름을 한 번만 쓰고 싶다는 것입니다.
나는 다음과 같은 ider가 있습니다.

#define __ENUM(situation,num) \
    int situation = num;        const char * __##situation##_name = #situation;

    const struct {
        __ENUM(get_other_string, -203);//using a __ENUM Mirco make it ease to write, 
        __ENUM(get_negative_to_unsigned, -204);
        __ENUM(overflow,-205);
//The following two line showing the expanding for __ENUM
        int get_no_num = -201;      const char * __get_no_num_name = "get_no_num";
        int get_float_to_int = -202;        const char * get_float_to_int_name = "float_to_int_name";

    }eRevJson;
#undef __ENUM
    struct sIntCharPtr { int value; const char * p_name; };
//This function transform it to string.
    inline const char * enumRevJsonGetString(int num) {
        sIntCharPtr * ptr = (sIntCharPtr *)(&eRevJson);
        for (int i = 0;i < sizeof(eRevJson) / sizeof(sIntCharPtr);i++) {
            if (ptr[i].value == num) {
                return ptr[i].p_name;
            }
        }
        return "bad_enum_value";
    }

구조체를 사용하여 enum을 삽입하므로 프린터에서 문자열로 각 enum 값 정의를 따를 수 있습니다.

int main(int argc, char *argv[]) {  
    int enum_test = eRevJson.get_other_string;
    printf("error is %s, number is %d\n", enumRevJsonGetString(enum_test), enum_test);

>error is get_other_string, number is -203

열거 형과의 차이점은 숫자가 반복되면 빌더가 오류를보고 할 수 없다는 것입니다. 숫자 쓰기가 마음에 들지 않으면 __LINE__대체 할 수 있습니다.

#define ____LINE__ __LINE__
#define __ENUM(situation) \
    int situation = (____LINE__ - __BASELINE -2);       const char * __##situation##_name = #situation;
constexpr int __BASELINE = __LINE__;
constexpr struct {
    __ENUM(Sunday);
    __ENUM(Monday);
    __ENUM(Tuesday);
    __ENUM(Wednesday);
    __ENUM(Thursday);
    __ENUM(Friday);
    __ENUM(Saturday);
}eDays;
#undef __ENUM
inline const char * enumDaysGetString(int num) {
    sIntCharPtr * ptr = (sIntCharPtr *)(&eDays);
    for (int i = 0;i < sizeof(eDays) / sizeof(sIntCharPtr);i++) {
        if (ptr[i].value == num) {
            return ptr[i].p_name;
        }
    }
    return "bad_enum_value";
}
int main(int argc, char *argv[]) {  
    int d = eDays.Wednesday;
    printf("day %s, number is %d\n", enumDaysGetString(d), d);
    d = 1;
    printf("day %s, number is %d\n", enumDaysGetString(d), d);
}

>day Wednesday, number is 3 >day Monday, number is 1


0

나는 이것에 익숙하지 않지만 switch 문은 방어 적으로 작동합니다.

#include <stdio.h>

enum mycolor;

int main(int argc, const char * argv[])

{
enum Days{Sunday=1,Monday=2,Tuesday=3,Wednesday=4,Thursday=5,Friday=6,Saturday=7};

enum Days TheDay;


printf("Please enter the day of the week (0 to 6)\n");

scanf("%d",&TheDay);

switch (TheDay)
 {

case Sunday:
        printf("the selected day is sunday");
        break;
    case Monday:
        printf("the selected day is monday");
        break;
    case Tuesday:
        printf("the selected day is Tuesday");
        break;
    case Wednesday:
        printf("the selected day is Wednesday");
        break;
    case Thursday:
        printf("the selected day is thursday");
        break;
    case Friday:
        printf("the selected day is friday");
        break;
    case Saturday:
        printf("the selected day is Saturaday");
        break;
    default:
        break;
}

return 0;
}

올바른 형식 (읽기 : 들여 쓰기)은 답변의 축어 코드에 필수입니다 ...
p4010

0

나는 이것이 dayNames에 열거 형을 갖는 것을 좋아합니다. 타이핑을 줄이기 위해 다음을 수행 할 수 있습니다.

#define EP(x) [x] = #x  /* ENUM PRINT */

const char* dayNames[] = { EP(Sunday), EP(Monday)};

0

또 다른 해결책이 있습니다. 고유 한 동적 열거 클래스를 만듭니다. struct요소를 a에 저장 struct하고 각 요소에 이름에 대한 문자열이있는 새 열거를 만드는 기능과 일부 기능이 있음을 의미합니다 . 또한 개별 요소를 저장하기위한 몇 가지 유형,이를 비교하는 기능 등이 필요합니다. 다음은 예입니다.

#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


struct Enumeration_element_T
{
  size_t index;
  struct Enumeration_T *parrent;
  char *name;
};

struct Enumeration_T
{
  size_t len;
  struct Enumeration_element_T elements[];
};
  


void enumeration_delete(struct Enumeration_T *self)
{
  if(self)
  {
    while(self->len--)
    {
      free(self->elements[self->len].name);
    }
    free(self);
  }
}

struct Enumeration_T *enumeration_create(size_t len,...)
{
  //We do not check for size_t overflows, but we should.
  struct Enumeration_T *self=malloc(sizeof(self)+sizeof(self->elements[0])*len);
  if(!self)
  {
    return NULL;
  }
  self->len=0;
  va_list l; 
  va_start(l,len);
  for(size_t i=0;i<len;i++)
  {
    const char *name=va_arg(l,const char *);
    self->elements[i].name=malloc(strlen(name)+1);
    if(!self->elements[i].name)
    {
      enumeration_delete(self);
      return NULL;
    }
    strcpy(self->elements[i].name,name);
    self->len++;
  }
  return self;
}


bool enumeration_isEqual(struct Enumeration_element_T *a,struct Enumeration_element_T *b)
{
  return a->parrent==b->parrent && a->index==b->index;
}

bool enumeration_isName(struct Enumeration_element_T *a, const char *name)
{
  return !strcmp(a->name,name);
}

const char *enumeration_getName(struct Enumeration_element_T *a)
{
  return a->name;
}

struct Enumeration_element_T *enumeration_getFromName(struct Enumeration_T *self, const char *name)
{
  for(size_t i=0;i<self->len;i++)
  {
    if(enumeration_isName(&self->elements[i],name))
    {
      return &self->elements[i];
    }
  }
  return NULL;
}
  
struct Enumeration_element_T *enumeration_get(struct Enumeration_T *self, size_t index)
{
  return &self->elements[index];
}

size_t enumeration_getCount(struct Enumeration_T *self)
{
  return self->len;
}

bool enumeration_isInRange(struct Enumeration_T *self, size_t index)
{
  return index<self->len;
}



int main(void)
{
  struct Enumeration_T *weekdays=enumeration_create(7,"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday");
  if(!weekdays)
  {
    return 1;
  }
    
  printf("Please enter the day of the week (0 to 6)\n");
  size_t j = 0;
  if(scanf("%zu",&j)!=1)
  {
    enumeration_delete(weekdays);
    return 1;
  }
  // j=j%enumeration_getCount(weekdays); //alternative way to make sure j is in range
  if(!enumeration_isInRange(weekdays,j))
  {
    enumeration_delete(weekdays);
    return 1;
  }

  struct Enumeration_element_T *day=enumeration_get(weekdays,j);
  

  printf("%s\n",enumeration_getName(day));
  
  enumeration_delete(weekdays);

  return 0;
}

열거의 기능은 자체 번역 단위에 있어야하지만 여기에 결합하여 더 간단하게 만들었습니다.

장점은이 솔루션이 유연하고 DRY 원칙을 따르며 각 요소와 함께 정보를 저장할 수 있으며 런타임 중에 새 열거를 만들 수 있으며 런타임 중에 새 요소를 추가 할 수 있다는 것입니다. 단점은 이것이 복잡하고 동적 메모리 할당이 필요하며 사용할 수 없다는 것입니다 switch.case 필요 에서 더 많은 메모리가 필요하고 느리다는 것입니다. 문제는 이것이 필요한 경우에 더 높은 수준의 언어를 사용해서는 안된다는 것입니다.


-3

TheDay는 정수 유형으로 다시 매핑됩니다. 그래서:

printf("%s", TheDay);

TheDay를 문자열로 구문 분석하려고 시도하고 가비지 또는 충돌을 출력합니다.

printf는 형식이 안전하지 않으며 올바른 값을 전달할 것을 신뢰합니다. 값의 이름을 인쇄하려면 열거 형 값을 문자열에 매핑하는 몇 가지 방법 (조회 테이블, 거대한 스위치 문 등)을 만들어야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.