2011-06-25 77 views
0

C中,我可以使用預處理器將enum轉換爲其字符串等效項。將char *轉換爲枚舉值的高效方法

但是,是否有任何巧妙的把戲將char*轉換爲enum

我可以用一個if語句和每個字符串strcmp和返回等同enum但有一個更優雅的方式?

+0

,我不認爲有任何聰明的技巧,你可以在這裏做,這將是爲有效爲你編寫一個解析器枚舉。 –

+0

@Jeff Mercado:我的答案呢?這幾乎是一樣的手寫解析器有效,但它可以與'CREATEENUM'所做的任何枚舉使用。 – orlp

+0

@night:這當然有用,沒有爭論。但就時間和空間效率而言,它並沒有擊敗量身定製的枚舉解析器。首先,您需要空間來存儲值的字符串表示形式(並非如此糟糕)。然後,您需要將輸入字符串與所有字符串表示形式進行比較,以獲取所有枚舉值(可能很糟糕)。解析器(DFA)可以避免所有這些開銷。 –

回答

2

請不要做這樣的嘲弄。幾乎可以肯定你有一個設計缺陷。


編輯:如果你真的必須由於某種原因,我砍死在一起,這個做到這一點。這個例子也應該表現出它的用法:

#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

你是什麼意思? –

+0

@約翰Zwinck:我的意思是,如果你不得不做這樣的事情你的代碼很可能不具有良好的結構。 – orlp

+0

我不知道,這是Ç畢竟,它不是像C的枚舉是非常豐富的或有用的開箱。如果有的話,OP的問題似乎很普遍。 –

2

你可以創建一個枚舉與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; 
} 
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; 
} 
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; 
} 

這樣做的更有效的方法是可行的。在開始處理它們之前,請記住測量此映射實際上對運行時的影響。

  • 讓第二個數組保持字符串排序,並使用 二分查找來查找字符串。第一個表仍然存在,因此將整數 映射到字符串仍然很快。
  • 對第二個數組使用散列表。
  • 完全拋棄第一個數組,並使用散列值作爲整數,如果 可以找到一個沒有特定數據衝突的散列函數。這應該是 是完全可行的,如果你打算使用枚舉,那麼 值集合是在編譯時固定的。 (您仍然需要反向 映射的散列表:從整數到字符串。)

例如,如果您的值代表工作日,則使用前兩個字符作爲散列函數就足夠了。見http://en.wikipedia.org/wiki/Perfect_hashing爲指針,在一般情況下構建這樣的散列函數的一些相關信息。

0

不是非常聰明,但如果你的字符串很少,而且很短(4個字節或東西,在一個整數類型適合),你可以重鑄他們作爲整數和開關等使用。黑客肯定,但有時這可能會給你你想要的。

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