Друкувати текст замість значення з 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;
}

Очікуваний результат - надрукувати рядок "Неділя" тощо?
GalacticCowboy

Відповіді:


104

Перерахування на С - це цифри, які мають зручні імена всередині вашого коду. Вони не є рядками, і імена, присвоєні їм у вихідному коді, не компілюються у вашу програму, тому вони не доступні під час виконання.

Єдиний спосіб отримати бажане - це написати функцію самостійно, яка переводить значення перерахування у рядок. Наприклад (при умові, що ви переносите декларацію 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в перерахуванні, щоб бути безпечним ... Я не впевнений, чи вимагає стандарт С компілятори починати перелічення з 0, хоча більшість це робить (я впевнений, що хтось прокоментує, щоб підтвердити або заперечити це ).


3
Ох, ви побили мене до рішення масиву. : P Але так, перелічення завжди починаються з 0, якщо ви не вказали інше значення.
casablanca

1
Якби я покладався на використання переліків як індексів, я б насправді віддав перевагу явному нумеруванню кожного з них. За непотрібністю згідно зі стандартами, але як груповий компілятори на моєму досвіді не найкраще дотримуються стандартів.
jdmichal

3
Стандарт C говорить: "Якщо у першого перелічувача немає =, значення його константи перерахунку дорівнює 0". Але це нічого не заважає, якщо це явно.
Michael Burr

17
Не забувайте, що з C99 ви можете це зробити const char* dayNames[] = {[Sunday] = "Sunday", [Monday] = "Monday", [Tuesday] = "Tuesday", /* ... etc ... */ };. Ви знаєте, на випадок, якщо дні тижня змінені впорядковано, або ви вирішите, що понеділок - перший день тижня.
Тім Шеффер

2
@ user3467349 Це (попередження обробкою рядків) просто перетворює символ, що стоїть за #, у рядок. Так, так, # понеділок перетворився б на "понеділок", але Days TheDay = Monday; printf("%s", #TheDay);надрукував би "TheDay".
Тайлер Макгенрі

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, день 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
Це негарно читати, але у вас немає дублювання даних. (На відміну від усіх інших.) Мене розриває питання, чи подобається це мені.
Кім Ріс,

1
+1 за надзвичайно креативне рішення без дублювання даних і, мабуть, найкращої ремонтопридатності / гнучкості, але так! Я думаю, що я все-таки вважаю за краще просто піти шляхом const char * [].
маніфест

4
Так, ремонтопридатність надзвичайна! Оновити це дуже просто, коли дні тижня змінюються! </sarcasm> До речі, це навіть не корисно для цілей локалізації, оскільки відображення між англійськими рядками та іменами в програмі тепер жорстко закодоване вашою спробою уникнути дублювання. Принаймні з іншими підходами можна перекладати рядки, не змінюючи кожного входження у вихідні файли.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

1
Можливо, ви можете інтернаціоналізувати його, змінивши оператори повернення на щось return _(#element)подібне.
Варгас

Коли препроцесор C такий корисний, але такий потворний, я зазвичай замінюю його простим генератором коду або спеціальним препроцесором мовою сценаріїв. І насправді, у мене є сценарій Python, який я використовував саме для цієї мети у багатьох проектах. Але я використовую його сьогодні не так часто - для багатьох випадків використання можна уникнути, просто використовуючи рядки і не турбуючись про перерахування (і тим більше в C ++ або ObjC).
abarnert

6

Я зазвичай це роблю, зберігаючи подання рядків в окремому масиві в тому ж порядку, а потім індексуючи масив зі значенням переліку:

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

4

enums в C насправді не працюють так, як ви очікуєте від них. Ви можете думати про них як про прославлені константи (з деякими додатковими перевагами, пов’язаними з тим, щоб бути колекцією таких констант), і текст, який ви написали для "неділі", дійсно отримує число під час компіляції, текст в кінцевому рахунку відкинутий.

Коротше кажучи: для того, щоб зробити те, що ви насправді хочете, вам потрібно буде зберегти масив рядків або створити функцію для зіставлення зі значення переліку в текст, який ви хочете надрукувати.


4

Перерахування на С в основному є синтаксичним цукром для іменованих списків цілочисельних значень, що автоматично розподіляються. Тобто, коли у вас є цей код:

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, тому використовуйте, c99 strictякщо ви підключите його до (онлайн-компілятора) ideone .


Потрібно любити, наскільки “чисті” макроси :-).
mk12

3

Я знаю, що запізнився на вечірку, але як щодо цього?

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

Таким чином, вам не доведеться синхронізувати масив enumта char*масив вручну . Якщо ви схожі на мене, швидше за все, ви згодом зміните enumі char*масив надрукує недійсні рядки. Можливо, ця функція не є універсально підтримуваною. Але, на жаль, більшість компіляторів сучасного дня C підтримують цей позначений стиль ініціалізації.

Ви можете прочитати більше про призначені ініціалізатори тут .


1

Питання в тому, що ви хочете написати ім’я лише один раз.
У мене такий ідер:

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

він використовує структуру для вставки переліку, щоб принтер у рядок міг слідувати за кожним значенням переліку.

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і деяка функція для створення нового переліку, яка зберігає елементи в а, 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;
}

Функції перерахування повинні бути у власній одиниці перекладу, але я їх поєднав тут, щоб спростити.

Перевага полягає в тому, що це рішення є гнучким, дотримується принципу СУХОСТІ, ви можете зберігати інформацію разом з кожним елементом, ви можете створювати нові переліки під час виконання та можете додавати нові елементи під час виконання. Недоліком є ​​те, що він складний, потребує динамічного розподілу пам’яті, не може бути використаний у switch- case, потребує більше пам’яті та повільніший. Питання в тому, чи не слід використовувати мову вищого рівня у випадках, коли це вам потрібно.


-3

TheDay відображає деякий цілий тип. Тому:

printf("%s", TheDay);

Спроби проаналізувати TheDay як рядок, або роздрукують сміття, або вийдуть з ладу.

printf не є безпечним для типу і довіряє вам передати йому правильне значення. Щоб роздрукувати ім'я значення, вам потрібно було б створити якийсь метод для зіставлення значення переліку в рядок - або таблицю підстановки, гігантський оператор перемикання тощо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.