In văn bản thay vì giá trị từ 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;
}

Đầu ra mong đợi của bạn là để nó in chuỗi "Chủ nhật", v.v.?
GalacticCowboy

Câu trả lời:


104

Các bảng liệt kê trong C là các số có tên thuận tiện bên trong mã của bạn. Chúng không phải là chuỗi và các tên được gán cho chúng trong mã nguồn không được biên dịch vào chương trình của bạn và vì vậy chúng không thể truy cập được trong thời gian chạy.

Cách duy nhất để có được những gì bạn muốn là tự viết một hàm để dịch giá trị liệt kê thành một chuỗi. Ví dụ: (giả sử ở đây bạn di chuyển khai báo ra enum Daysbên ngoài 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));

Ngoài ra, bạn có thể sử dụng một mảng làm bản đồ, ví dụ:

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

/* ... */

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

Nhưng ở đây bạn có thể muốn chỉ định Sunday = 0trong liệt kê để an toàn ... Tôi không chắc liệu tiêu chuẩn C có yêu cầu trình biên dịch bắt đầu liệt kê từ 0 hay không, mặc dù hầu hết đều làm như vậy (tôi chắc rằng ai đó sẽ nhận xét để xác nhận hoặc phủ nhận điều này ).


3
Aw, bạn đã đánh bại tôi với giải pháp mảng. : P Nhưng có, enums luôn bắt đầu bằng 0 trừ khi bạn chỉ định một giá trị khác.
casablanca

1
Nếu tôi đang dựa vào việc sử dụng các bảng liệt kê làm chỉ mục, tôi thực sự muốn đánh số rõ ràng từng cái một. Theo kinh nghiệm của tôi, không cần thiết theo các tiêu chuẩn, nhưng với tư cách là một nhóm biên dịch viên không phải là người giỏi nhất trong việc tuân theo các tiêu chuẩn.
jdmichal

3
Tiêu chuẩn C nói, "Nếu điều tra viên đầu tiên không có =, giá trị của hằng số liệt kê của nó là 0". Nhưng nó không làm hại bất cứ điều gì để có nó rõ ràng.
Michael Burr

17
Đừng quên rằng với C99 bạn có thể làm được const char* dayNames[] = {[Sunday] = "Sunday", [Monday] = "Monday", [Tuesday] = "Tuesday", /* ... etc ... */ };. Bạn biết đấy, trong trường hợp các ngày trong tuần được sắp xếp lại, hoặc bạn quyết định rằng Thứ Hai là ngày đầu tiên trong tuần.
Tim Schaeffer

2
@ user3467349 Điều đó (chuỗi tiền xử lý) chỉ biến biểu tượng theo sau dấu # thành một chuỗi. Vì vậy, có, #Monday sẽ chuyển thành "Thứ Hai" nhưng Days TheDay = Monday; printf("%s", #TheDay);sẽ in "TheDay".
Tyler McHenry

29

Tôi sử dụng một cái gì đó như thế này:

trong tệp "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

thì trong bất kỳ tệp tiêu đề nào, bạn thực hiện khai báo 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)

sau đó trong một tệp có tên EnumToString.c:

#include "enum.h"

#define GENERATE_ENUM_STRINGS  // Start string generation

#include "enum.h"             

#undef GENERATE_ENUM_STRINGS   // Stop string generation

thì trong 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;
}

điều này sẽ tạo ra các chuỗi "tự động" cho bất kỳ enum nào được khai báo theo cách này và được đưa vào "EnumToString.c"


4
Thật là xấu khi đọc qua, nhưng bạn không bị trùng lặp dữ liệu. (Không giống như những người khác.) Tôi không biết có nên thích điều này không.
Kim Reece

1
+1 cho giải pháp sáng tạo đáng kinh ngạc không có dữ liệu trùng lặp và có thể là khả năng bảo trì / linh hoạt tốt nhất, nhưng yech! Tôi nghĩ tôi vẫn muốn chỉ đi theo tuyến const char * [].
kê khai

4
Vâng, khả năng bảo trì thật tuyệt vời! Thực sự dễ dàng cập nhật khi các ngày trong tuần thay đổi! </sarcasm> Nhân tiện, điều này thậm chí không hữu ích cho mục đích bản địa hóa vì ánh xạ giữa các chuỗi tiếng Anh và tên trong chương trình hiện được mã hóa cứng bởi nỗ lực của bạn để tránh trùng lặp. Ít nhất với các cách tiếp cận khác, có thể dịch các chuỗi mà không thay đổi mọi lần xuất hiện trong các tệp nguồn.
R .. GitHub DỪNG TRỢ GIÚP NGAY LÚC NÀY

1
Bạn có thể quốc tế hóa nó bằng cách (với một cái gì đó như gettext) thay đổi các câu lệnh return thành return _(#element)và tương tự.
Vargas

Khi bộ tiền xử lý C hữu ích nhưng xấu xí này, tôi thường thay thế nó bằng một bộ tạo mã đơn giản hoặc bộ tiền xử lý tùy chỉnh bằng ngôn ngữ kịch bản. Và trên thực tế, tôi đã có một tập lệnh Python mà tôi đã sử dụng cho chính xác mục đích này trong nhiều dự án. Nhưng tôi không sử dụng nó thường xuyên hiện nay — đối với nhiều trường hợp sử dụng, bạn có thể thoát khỏi chỉ bằng cách sử dụng chuỗi và không bận tâm đến enum (và thậm chí hơn thế nữa trong C ++ hoặc ObjC).
abarnert

6

Cách tôi thường làm điều này là lưu trữ các biểu diễn chuỗi trong một mảng riêng biệt theo cùng một thứ tự, sau đó lập chỉ mục mảng với giá trị enum:

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

4

enums trong C không thực sự hoạt động theo cách bạn mong đợi. Bạn có thể nghĩ về chúng giống như các hằng số được tôn vinh (với một số lợi ích bổ sung liên quan đến việc trở thành một tập hợp các hằng số như vậy) và văn bản bạn đã viết cho "Chủ nhật" thực sự được giải quyết thành một số trong khi biên dịch, văn bản là cuối cùng bị loại bỏ.

Tóm lại: để làm những gì bạn thực sự muốn, bạn cần giữ một mảng các chuỗi hoặc tạo một hàm để ánh xạ từ giá trị của enum đến văn bản bạn muốn in.


4

Các phép liệt kê trong C về cơ bản là đường cú pháp cho danh sách có tên các giá trị số nguyên được sắp xếp tự động. Đó là, khi bạn có mã này:

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

    Days TheDay = Monday;
}

Trình biên dịch của bạn thực sự giải ra điều này:

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

Do đó, xuất một bảng liệt kê C dưới dạng một chuỗi không phải là một hoạt động có ý nghĩa đối với trình biên dịch. Nếu bạn muốn có những chuỗi mà con người có thể đọc được cho những chuỗi này, bạn sẽ cần xác định các hàm để chuyển đổi từ kiểu liệt kê sang chuỗi.


4

Đây là một cách dễ dàng hơn để làm điều đó với macro:

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

Tôi không chắc rằng đây là bộ tiền xử lý b / w hoàn toàn di động, nhưng nó hoạt động với gcc.

Đây là c99 btw, vì vậy hãy sử dụng c99 strictnếu bạn cắm nó vào (trình biên dịch trực tuyến) Ideone .


Gotta yêu cách macro "sạch" :-).
mk12,

3

Tôi biết tôi đến muộn bữa tiệc, nhưng thế này thì sao?

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

Bằng cách này, bạn không phải giữ cho mảng enumchar*mảng đồng bộ theo cách thủ công . Nếu bạn giống tôi, rất có thể sau này bạn sẽ thay đổi enum, vàchar* mảng sẽ in ra các chuỗi không hợp lệ. Đây có thể không phải là một tính năng được hỗ trợ trên toàn cầu. Tuy nhiên, hầu hết các trình biên dịch C ngày nay đều hỗ trợ kiểu khởi tạo được chỉ định này.

Bạn có thể đọc thêm về các trình khởi tạo được chỉ định tại đây .


1

Câu hỏi là bạn muốn viết tên chỉ một lần.
Tôi có một ider như thế này:

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

nó sử dụng một cấu trúc để chèn enum, để máy in thành chuỗi có thể tuân theo mỗi định nghĩa giá trị 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

Sự khác biệt với enum là người xây dựng không thể báo lỗi nếu các số được lặp lại. nếu bạn không thích viết số, __LINE__có thể thay thế nó:

#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

tôi mới làm quen với điều này nhưng một câu lệnh chuyển đổi sẽ hoạt động chắc chắn

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

Định dạng thích hợp (đọc: thụt đầu dòng) sẽ là bắt buộc đối với mã đúng nguyên văn trong câu trả lời ...
p4010

0

Tôi thích điều này để có enum trong dayNames. Để giảm việc gõ, chúng ta có thể làm như sau:

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

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

0

Có một giải pháp khác: Tạo lớp liệt kê động của riêng bạn. Có nghĩa là bạn có một structvà một số hàm để tạo một bảng liệt kê mới, lưu trữ các phần tử trong a structvà mỗi phần tử có một chuỗi cho tên. Bạn cũng cần một số loại để lưu trữ một phần tử riêng lẻ, các chức năng để so sánh chúng, v.v. Đây là một ví dụ:

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

Các chức năng của phép liệt kê nên nằm trong đơn vị dịch của riêng chúng, nhưng tôi đã kết hợp chúng ở đây để làm cho nó đơn giản hơn.

Ưu điểm là giải pháp này linh hoạt, tuân theo nguyên tắc DRY, bạn có thể lưu trữ thông tin cùng với từng phần tử, bạn có thể tạo bảng liệt kê mới trong thời gian chạy và bạn có thể thêm các phần tử mới trong thời gian chạy. Điểm bất lợi là điều này phức tạp, cần cấp phát bộ nhớ động, không thể sử dụng trong switch- case, cần nhiều bộ nhớ hơn và chậm hơn. Câu hỏi là nếu bạn không nên sử dụng ngôn ngữ cấp cao hơn trong trường hợp bạn cần điều này.


-3

TheDay ánh xạ trở lại một số kiểu số nguyên. Vì thế:

printf("%s", TheDay);

Cố gắng phân tích cú pháp TheDay thành một chuỗi và sẽ in ra rác hoặc lỗi.

printf không phải là loại an toàn và tin tưởng bạn chuyển giá trị phù hợp cho nó. Để in ra tên của giá trị, bạn cần tạo một số phương thức để ánh xạ giá trị enum thành một chuỗi - bảng tra cứu, câu lệnh switch khổng lồ, v.v.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.