打印文本而不是C枚举值


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

Answers:


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开始。
卡萨布兰卡

1
如果我依靠使用枚举作为索引,则实际上我希望显式地对每个数字进行编号。根据标准,这不是必需的,但是根据我的经验,作为一个整体,编译器并不能完全遵循标准。
jdmichal

3
C标准说:“如果第一个枚举数没有=,则其枚举常数的值为0”。但是,将其明确显示并没有任何伤害。
Michael Burr

17
不要忘记使用C99可以做到const char* dayNames[] = {[Sunday] = "Sunday", [Monday] = "Monday", [Tuesday] = "Tuesday", /* ... etc ... */ };。您知道,如果重新安排了星期几,或者您决定将星期一作为一周的第一天。
蒂姆·谢弗

2
@ user3467349(预处理程序字符串化)只是将#之后的符号转换为字符串。因此,是的,#Monday将变为“ Monday”,但Days TheDay = Monday; printf("%s", #TheDay);将打印“ TheDay”。
Tyler McHenry 2015年

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.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停止帮助ICE,2010年

1
您可能可以通过将return语句更改为return _(#element)或类似方法来使其国际化。
Vargas 2010年

当C预处理器有用但丑陋时,我通常将其替换为简单的代码生成器或脚本语言中的自定义预处理器。实际上,我已经在多个项目中使用了一个Python脚本来实现此目的。但是,我现在不经常使用它-在许多用例中,您可以只使用字符串而不必费心枚举(在C ++或ObjC中更是如此)。
abarnert,2012年

6

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

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

4

enumC语言中的s并没有真正按照您期望的那样工作。您可以认为它们有点像光荣的常量(还有一些与收集此类常量有关的其他好处),并且您为“ Sunday”编写的文本在编译过程中确实解析为数字,最终被丢弃。

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


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,因此,c99 strict如果将其插入ideone(在线编译器),请使用


一定喜欢“干净的”宏如何:-)。
mk12

3

我知道我晚会晚了,但是呢?

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

这样,您不必手动使enumchar*阵列保持同步。如果您像我一样,则有可能稍后再更改enum,并且char*数组将打印无效的字符串。这可能不是普遍支持的功能。但是afaik,大多数现代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

枚举的区别是,如果重复数字,则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);
}

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

枚举的功能应该在它们自己的翻译单元中,但是我在这里将它们结合起来使其更加简单。

优点是该解决方案很灵活,遵循DRY原理,可以将信息与每个元素一起存储,可以在运行时创建新的枚举,还可以在运行时添加新的元素。缺点是这很复杂,需要动态分配内存,不能在switch-中使用case,需要更多的内存并且速度较慢。问题是,在需要时是否不应该使用高级语言。


-3

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

printf("%s", TheDay);

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.