【问题标题】:Print text instead of value from C enum打印文本而不是 C 枚举中的值
【发布时间】:2011-03-11 05:52:48
【问题描述】:
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”等?

标签: c enums


【解决方案1】:

使用宏和字符串化操作符(#)我们可以实现这一点......

#include <stdio.h>
typedef enum
{
    MON=0,
    TUE
}week;
int main()
{
    #define printt(data)        printf("%s",#data);
    printt(MON);
    return 0;
}

【讨论】:

    【解决方案2】:

    还有另一种解决方案:创建自己的动态枚举类。意味着你有一个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 *parent;
      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->parent==b->parent && 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】:

      问题是你只想把名字写一次。
      我有一个这样的想法:

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

      &gt;error is get_other_string, number is -203

      与 enum 不同的是,如果数字重复,builder 不会报错。 如果你不喜欢写数字,__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);
      }
      

      &gt;day Wednesday, number is 3&gt;day Monday, number is 1

      【讨论】:

        【解决方案4】:

        我喜欢在 dayNames 中有枚举。 为了减少打字,我们可以这样做:

        #define EP(x) [x] = #x  /* ENUM PRINT */
        
        const char* dayNames[] = { EP(Sunday), EP(Monday)};
        

        【讨论】:

          【解决方案5】:

          我知道我迟到了,但是这个怎么样?

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

          这样,您不必手动保持enumchar* 数组同步。如果你和我一样,很可能你稍后会更改enum,而char* 数组将打印无效字符串。 这可能不是普遍支持的功能。但是,afaik,大多数现代 C 编译器都支持这种指定的初始样式。

          您可以阅读有关指定初始化程序here 的更多信息。

          【讨论】:

            【解决方案6】:

            我是新手,但 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;
            }
            

            【讨论】:

            • 正确的格式(阅读:缩进)是答案中逐字代码的必要条件......
            【解决方案7】:

            下面是使用宏的更简洁的方法:

            #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,所以如果您将其插入 (the online compiler) ideone,请使用 c99 strict

            【讨论】:

            • 一定会喜欢“干净”的宏:-)。
            【解决方案8】:

            我使用这样的东西:

            在文件“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
            

            然后在任何头文件中进行枚举声明,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”中的任何枚举“自动”生成字符串

            【讨论】:

            • 读起来很难看,但你没有数据重复。 (不像其他人。)我很纠结是否要喜欢这个。
            • +1 是非常有创意的解决方案,没有数据重复,可能是最好的可维护性/灵活性,但是是的!我想我还是宁愿走 const char*[] 路线。
            • 是的,可维护性很棒!当星期几发生变化时,更新真的很容易! 顺便说一句,这对于本地化目的甚至没有用处,因为英文字符串和程序中的名称之间的映射现在是硬编码的,因为您试图避免重复。至少使用其他方法,可以在不更改源文件中每次出现的情况下翻译字符串。
            • 您可以通过(使用 gettext 之类的方法)将返回语句更改为 return _(#element) 等来将其国际化。
            • 为什么大家都抱怨数据重复,而函数原型却没有问题?
            【解决方案9】:

            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 枚举作为字符串输出对于编译器来说不是有意义的操作。如果您希望这些字符串具有人类可读的字符串,则需要定义函数以将枚举转换为字符串。

            【讨论】:

              【解决方案10】:

              我通常这样做的方法是将字符串表示形式以相同的顺序存储在一个单独的数组中,然后用枚举值索引该数组:

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

              【讨论】:

                【解决方案11】:

                TheDay 映射回某个整数类型。所以:

                printf("%s", TheDay);
                

                尝试将 TheDay 解析为字符串,并且会打印出垃圾或崩溃。

                printf 不是类型安全的,它相信您将正确的值传递给它。要打印出值的名称,您需要创建一些将枚举值映射到字符串的方法 - 查找表、巨型 switch 语句等。

                【讨论】:

                  【解决方案12】:

                  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 开始枚举,尽管大多数都这样做(我相信有人会评论以确认或否认这一点)。

                  【讨论】:

                  • 哦,你在阵列解决方案上打败了我。 :P 但是,枚举总是从 0 开始,除非您指定不同的值。
                  • 如果我依赖使用枚举作为索引,我实际上更愿意明确地为每个枚举编号。根据标准是不必要的,但作为一个群体,根据我的经验,编译器在遵循标准方面并不是最好的。
                  • C 标准说,“如果第一个枚举数没有 =,则其枚举常量的值为 0”。但明确表示并没有什么坏处。
                  • 不要忘记使用 C99 你可以做到const char* dayNames[] = {[Sunday] = "Sunday", [Monday] = "Monday", [Tuesday] = "Tuesday", /* ... etc ... */ };。您知道,如果一周中的日子被重新排序,或者您决定星期一是一周的第一天。
                  • @user3467349 (预处理器字符串化)只是将 # 后面的符号转换为字符串。所以,是的,#Monday 会变成“Monday”,但 Days TheDay = Monday; printf("%s", #TheDay); 会打印“TheDay”。
                  【解决方案13】:

                  C 中的enums 并没有真正按照您期望的方式工作。您可以将它们想象成一种美化的常量(与成为此类常量的集合相关的一些额外好处),并且您为“星期日”编写的文本确实被解析为在编译过程中,文本最终被丢弃。

                  简而言之:要执行您真正想要的操作,您需要保留一个字符串数组或创建一个函数以将枚举值映射到您要打印的文本。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-04-17
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多