在C中将枚举类型的变量用作字符串的简单方法?

时间:2020-03-06 14:51:28  来源:igfitidea点击:

这是我想做的事情:

typedef enum { ONE, TWO, THREE } Numbers;

我正在尝试编写一个函数,该函数将执行类似于以下的切换情况:

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

除了在每种情况下都定义之外,还有没有办法像我在上面尝试的那样使用enum变量来设置它?

解决方案

没有内置的解决方案。最简单的方法是使用一个char *数组,其中枚举的int值索引到包含该枚举的描述性名称的字符串。如果我们有一个稀疏的"枚举"(一个不以0开头或者在编号上有间隔),其中某些" int"映射足够高,使得基于数组的映射不切实际,那么我们可以使用哈希表反而。

如果枚举索引基于0,则可以将名称放入char *数组中,并使用枚举值对其进行索引。

尝试将C ++枚举转换为字符串。注释具有改进功能,可以解决枚举项具有任意值时的问题。

尽管我经常需要C或者C ++,但它不提供此功能。

尽管最适合非稀疏枚举,但以下代码也可以工作。

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

非稀疏,我的意思不是形式

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

因为那有很大的差距。

这种方法的优点是,它使枚举和字符串的定义彼此靠近。在函数中使用switch语句会使它们变长。这意味着我们不太可能更改一个而没有另一个。

在Mu Dynamics研究实验室博客档案中查看想法。我今年早些时候发现,我忘记了遇到它的确切上下文,并将其改编为此代码。我们可以争论在前面加上E的优点。它适用于已解决的特定问题,但不适用于一般解决方案。我将其保存在我的" vignettes"文件夹中,在其中保存了一些有趣的代码片段,以备日后使用。我不好意思说我当时没有记下这个想法的来源。

标题:paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

来源示例:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

C预处理程序不一定是世界上最干净的用法,但确实可以防止多次写出该材料。

// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever

使某些东西既是C标识符又是字符串

绝对有一种方法可以做到-使用X()宏。这些宏使用C预处理器从源数据列表构造枚举,数组和代码块。我们只需要向包含X()宏的#define中添加新项目即可。 switch语句将自动扩展。

示例可以编写如下:

// Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

有更有效的方法(即使用X宏创建字符串数组和枚举索引),但这是最简单的演示。

我知道我们有几个不错的可靠答案,但是我们知道C预处理器中的运算符吗?

它可以让我们做到这一点:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}

使某物同时成为C标识符和字符串的技术?可以在这里使用。

像使用此类预处理器一样,编写和理解预处理器部分可能很困难,包括将宏传递给其他宏,并涉及使用和#operators,但使用起来确实很容易。我发现这种样式对于长枚举非常有用,因为两次枚举相同的列表确实很麻烦。

工厂代码-仅键入一次,通常隐藏在标题中:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

工厂使用

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

可以轻松扩展该技术,以便XX宏接受更多参数,并且我们还可以准备更多的宏来代替XX以满足不同的需求,类似于本示例中提供的三个。

使用#include / #define / #undef与X-Macros的比较

尽管这与其他人提到的X-Macros类似,但我认为此解决方案更为优雅,因为它不需要#undefing任何内容,这使我们可以在工厂中隐藏更多复杂的东西头文件头文件是当我们需要定义一个新的枚举时,我们根本不需要接触任何东西,因此,新的枚举定义要短得多而且更简洁。

吻。我们将使用枚举来做各种各样的其他切换/案例操作,那么为什么打印应该有所不同?当我们考虑到还有大约100个其他地方可以忘记一个案例时,在打印例程中忘记一个案例并不重要。只需编译-Wall,它将警告非详尽的案例匹配。不要使用"默认",因为这将使开关穷举,并且我们不会收到警告。相反,让开关退出并像这样处理默认情况...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}