Эффективный способ перевести char * в значение enum - PullRequest
0 голосов
/ 25 июня 2011

В C я могу использовать препроцессор для преобразования enum в его строковый эквивалент.

Но есть ли хитрый трюк для преобразования char* в enum.

Я могу использовать оператор if и strcmp для каждой строки и возвращать эквивалент enum, но есть ли более элегантный способ?

Ответы [ 6 ]

2 голосов
/ 25 июня 2011

Вы можете создать структуру со значениями enum и char * и искать их по каждой конверсии.Может быть удобно, если у вас много типов enum.На основании этой темы .

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 голосов
/ 25 июня 2011

Пожалуйста, не делайте таких хакерских атак.У вас почти наверняка недостаток дизайна.


РЕДАКТИРОВАТЬ: Если вы действительно должны сделать это по какой-то причине, я взломал это вместе.Пример также должен показать его использование:

#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 голос
/ 25 июня 2011

Я подозреваю, что вы действительно хотите не перечисления, а способ отображения целого числа в строку и обратно.Например, вы можете представлять месяцы и использовать целые числа для упрощения сравнения («эта дата раньше этой даты»), а строку - для более удобного для человека представления.

(мне пришлось угадать это, поскольку ваш вопрос не объяснил, что вы на самом деле пытаетесь решить.)

Если вы откажетесь от перечислений, вы можете написать небольшую вспомогательную библиотеку, которая решает эту проблему в общем виде.Действительно простым способом реализации сопоставления было бы использование простого массива строк и использование индекса в массиве в качестве целого числа:

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

Возможны более эффективные способы сделать это.Прежде чем начать работать с ними, не забудьте измерить, какое влияние это отображение на самом деле оказывает на вашу среду выполнения.

  • Имейте второй массив, в котором хранятся отсортированные строки, и ищите строки с помощью бинарного поиска.Первая таблица остается, поэтому по-прежнему можно быстро отобразить целое число в строку.
  • Использовать хеш-таблицу для второго массива.
  • Полностью отказаться от первого массива и использовать значения хеш-функциикак целые числа, если вы можете найти хеш-функцию, которая не имеет коллизий для ваших конкретных данных.Это должно быть вполне осуществимо, если вы все равно собираетесь использовать перечисления, так как тогда набор значений будет фиксирован во время компиляции.(Хеш-таблица все еще нужна для обратного сопоставления: от целого числа к строке.)

Например, если ваши значения представляют дни недели, использование первых двух символов в качестве хеш-функции вполне достаточно.См. http://en.wikipedia.org/wiki/Perfect_hashing для указаний на некоторую связанную информацию о построении таких хеш-функций в общем случае.

0 голосов
/ 25 июня 2011

Нет никаких "умных" способов сделать это эффективно 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;
}
0 голосов
/ 25 июня 2011

Не очень умно, но если ваши строки немногочисленны и очень короткие (4 байта или что-то, что соответствует целочисленному типу), вы можете преобразовать их в целые числа и использовать в switch и т.п.Хакерство конечно, но иногда это может дать вам то, что вы хотите.

0 голосов
/ 25 июня 2011

Если вы можете доверять 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;
}
...