将char *转换为枚举值的有效方法

时间:2011-06-25 16:04:45

标签: c c-preprocessor

C 中,我可以使用预处理器将enum转换为其等效的字符串。

但将char*转换为enum是否有任何聪明的技巧。

我可以为每个字符串使用if语句和strcmp并返回等效的enum但是有更优雅的方法吗?

6 个答案:

答案 0 :(得分:2)

请不要做这样的hackery。你有几乎肯定设计缺陷。


编辑:如果你真的必须出于某种原因这样做,我一起攻击了这个。该示例还应显示其用法:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/*** BEGIN HACK ***/

#define CREATEENUM(name, first, ...) \
    typedef enum { first = 0, __VA_ARGS__ } name; \
    char name##_s[] = #first ", " #__VA_ARGS__;
#define TOSTR(x) #x
#define TOENUM(name, x) ((name) _toenum(name##_s, x))

long _toenum(char *enum_s, const char *x) {
    long i = 0;
    size_t len = strlen(enum_s);

    char *copy = (char*) malloc(sizeof(char) * (len + 1));
    strncpy(copy, enum_s, len);
    copy[len] = '\0';

    char *saveptr = NULL;
    char *s = strtok_r(copy, ", ", &saveptr);
    do {
        if (strcmp(s, x) == 0) {
            free(copy);
            return i;
        }
        i++;
    } while((s = strtok_r(NULL, ", ", &saveptr)) != NULL);

    free(copy);
    return -1;
}

/*** END HACK ***/

// create enum with the name "super"
CREATEENUM(super,
    COOL,
    AWESOME,
    UBER,
    JON_SKEET
)

int main(int argc, char *argv[]) {
    printf("%d\n", TOENUM(super, "JON_SKEET")); // 3
    printf("%d\n", TOENUM(super, "EXTREME")); // -1 (not found)
    printf("%d\n", TOENUM(super, "COOL")); // 0

    printf("%s\n", TOSTR(AWESOME)); // AWESOME
    return 0;
}

答案 1 :(得分:2)

您可以使用enum和char *值创建一个结构,并为每次转换搜索它们。如果你有很多枚举类型,可能会派上用场。基于this thread

enum colors {
  unknown = 0,
  red,
  blue,
  black,
  yellow
};

struct enumtypes
{
   colors color;
   char* str;
};

struct enumtypes array[] = {
  {red,"red"},
  {blue,"blue"}
  // etc for each enum type  
};

// function to convert string to enum type
colors cvt(const char* str)
{
   const int sz = sizeof(array) / sizeof(array[0]);

   for(int i = 0; i < sz; i++)
   {
       if(strcmp(array[i].str, str) == 0)
          return array[i].color;
   }
   return unknown;
}

答案 2 :(得分:1)

我怀疑你真正想要的不是枚举,而是一种将整数映射到字符串并返回的方法。例如,您可能表示几个月,并使用整数来进行简单的比较(“这是该日期之前的日期”),以及允许更友好的表示的字符串。

(我不得不猜测,因为你的问题没有解释你实际上要解决的问题。)

如果放弃枚举,可以编写一个小助手库,以通用的方式解决这个问题。实现映射的一种非常简单的方法是使用简单的字符串数组,并使用数组中的索引作为整数:

#include <string.h>

/* Find string in mapping. Return -1 if not found. */
int map_string_to_int(char *map[], int count, char *string)
{
    for (int i = 0; i < count; ++i) {
        if (strcmp(map[i], string) == 0)
            return i;
    }
    return -1;
}

/* Map int to string. Return NULL if not found. */
char *map_int_to_string(char *map[], int count, int i)
{
    if (i < 0 || i >= count)
        return NULL;
    return map[i];
}

/* Add new string to mapping. Return its unique id. Return existing id if
   string was in mapping already. Caller is responsible to make sure 
   there's room. Caller is responsible for making sure string does not
   get de-allocated. */
int map_add(char *map[], int *count, char *string)
{
    int i;

    i = map_string_to_int(map, *count, string);
    if (i == -1) {
        i = *count;
        map[i] = string;
        ++(*count);
    }
    return i;
}


/* A main program for testing, not actually part of the library. */

#include <stdio.h>

#define N 1024
int main(void)
{
    char *map[N];
    int count;

    map_add(map, &count, "Monday");
    map_add(map, &count, "Tuesday");
    map_add(map, &count, "Thursday");
    map_add(map, &count, "Wednesday");
    map_add(map, &count, "Friday");
    map_add(map, &count, "Sunday");
    map_add(map, &count, "Saturday");

    for (int i = 0; i < count; ++i)
        printf("[%d] = %s\n", i, map_int_to_string(map, count, i));

    printf("Monday is %d\n", map_string_to_int(map, count, "Monday"));

    return 0;
}

更有效的方法是可行的。在开始处理它们之前,请记住测量此映射实际对运行时产生的影响。

  • 有第二个数组,它保持字符串排序,并使用查找字符串 二分搜索。第一个表仍然存在,因此映射整数仍然很快 到一个字符串。
  • 对第二个数组使用哈希表。
  • 完全抛弃第一个数组,并使用哈希值作为整数,如果你 可以找到一个没有特定数据冲突的哈希函数。这应该 如果你打算使用枚举,那么完全可行,从那以后 值集在编译时固定。 (你仍然需要反向哈希表 mapping:从整数到字符串。)

例如,如果您的值代表工作日,则使用前两个字符作为哈希函数就足够了。有关在一般情况下构造此类哈希函数的一些相关信息的指示,请参阅http://en.wikipedia.org/wiki/Perfect_hashing

答案 3 :(得分:0)

如果您可以信任char*,并且它是否适合进行简单计算,请尝试以下方法:

#include <stdio.h>

enum Weekdays {
      Sun = 'S' + 'u' + 'n',
      Mon = 'M' + 'o' + 'n',
      Tue = 'T' + 'u' + 'e',
      Wed = 'W' + 'e' + 'd',
      Thu = 'T' + 'h' + 'u',
      Fri = 'F' + 'r' + 'i',
      Sat = 'S' + 'a' + 't'
};

int main(void) {
  char tmp[10];
  printf("Enter day of the week: ");
  fflush(stdout);
  if (fgets(tmp, sizeof tmp, stdin)) {

    enum Weekdays enumvalue =
          tmp[0] + tmp[1] + tmp[2];

    switch (enumvalue) {
      default:    printf("Ohoh\n"); break;
      case Sun:   printf("Sun: %d\n", Sun); break;
      case Mon:   printf("Mon: %d\n", Mon); break;
      case Tue:   printf("Tue: %d\n", Tue); break;
      case Wed:   printf("Wed: %d\n", Wed); break;
      case Thu:   printf("Thu: %d\n", Thu); break;
      case Fri:   printf("Fri: %d\n", Fri); break;
      case Sat:   printf("Sat: %d\n", Sat); break;
    }
  }
  return 0;
}

答案 4 :(得分:0)

不是非常聪明但是如果你的字符串很少而且非常短(4个字节或适合整数类型的东西),你可以将它们重新整理为整数并用于开关等。 Hackery肯定,但有时可能会给你你想要的东西。

答案 5 :(得分:0)

没有任何“聪明”的方法来有效地 AFAIK。只需为您的特定枚举编写一个简单的解析器并完成它。当然有限制,这只适用于单个枚举,并且不适用于所有枚举。与其他语言一样,C没有这样做的机制,它的级别太低了。

对于它来说,这是一个手写的“经典”DFA解析这个MyEnum的例子:

typedef enum
{
    MyEnum_foo,
    MyEnum_bar,
    MyEnum_baz,
} MyEnum;

/**
 * M -> y -> E -> n -> u -> m -> _ -> f -> o -> o
 *                                 -> b -> a -> r
 *                                           -> z
 */

MyEnum parse_MyEnum(const char *str)
{
    int state = 0;
    MyEnum result;
    if (str == 0) { /* handle null pointer error */ }
    for ( ; ; )
    {
        char c = *str++;
        switch (state)  /* case sensitive parse */
        {
        case 0:
            /* we could jump to state 7 with the
               appropriate check here but I won't :) */
            switch (c)
            {
            case 'M': state = 1; break;
            default: goto error_state;
            }
            break;
        case 1:     /* M */
            switch (c)
            {
            case 'y': state = 2; break;
            default: goto error_state;
            }
            break;
        case 2:     /* My */
            switch (c)
            {
            case 'E': state = 3; break;
            default: goto error_state;
            }
            break;
        case 3:     /* MyE */
            switch (c)
            {
            case 'n': state = 4; break;
            default: goto error_state;
            }
            break;
        case 4:     /* MyEn */
            switch (c)
            {
            case 'u': state = 5; break;
            default: goto error_state;
            }
            break;
        case 5:     /* MyEnu */
            switch (c)
            {
            case 'm': state = 6; break;
            default: goto error_state;
            }
            break;
        case 6:     /* MyEnum */
            switch (c)
            {
            case '_': state = 7; break;
            default: goto error_state;
            }
            break;
        case 7:     /* MyEnum_ */
            switch (c)
            {
            case 'f': state = 8; break;
            case 'b': state = 11; break;
            default: goto error_state;
            }
            break;
        case 8:     /* MyEnum_f */
            switch (c)
            {
            case 'o': state = 9; break;
            default: goto error_state;
            }
            break;
        case 9:     /* MyEnum_fo */
            switch (c)
            {
            case 'o': state = 10; break;
            default: goto error_state;
            }
            break;
        case 10:    /* MyEnum_foo */
            switch (c)
            {
            case '\0': result = MyEnum_foo; goto accept_state;
            default: goto error_state;
            }
            break;
        case 11:    /* MyEnum_b */
            switch (c)
            {
            case 'a': state = 12; break;
            default: goto error_state;
            }
            break;
        case 12:    /* MyEnum_ba */
            switch (c)
            {
            case 'r': state = 13; break;
            case 'z': state = 14; break;
            default: goto error_state;
            }
            break;
        case 13:    /* MyEnum_bar */
            switch (c)
            {
            case '\0': result = MyEnum_bar; goto accept_state;
            default: goto error_state;
            }
            break;
        case 14:    /* MyEnum_baz */
            switch (c)
            {
            case '\0': result = MyEnum_baz; goto accept_state;
            default: goto error_state;
            }
            break;
        default:
            /* we shouldn't be here */
            assert(0);
        }
    }
error_state:
    /* handle error */
    result = (MyEnum)-1;
accept_state:
    return result;
}