Общее перечисление для поиска текста в C

[Обновить]

  1. данный код действительно работает. Я ошибся, когда подумал, что это не так. Виноват; Сожалеем ++. Если вы можете улучшить код, пожалуйста, сделайте это по адресу https://codereview.stackexchange.com/questions/150480/generic-enum-to-text-lookup-in-c
  2. Мы должны объявить наши строки во время компиляции. Мы кодируем встроенные системы и не можем malloc(), Извините, что не так ясно.

[Update ++] Я, вероятно, приму один из ответов ниже. Я забыл заявить, однако, что наши перечисления не смежны и имеют широкий диапазон, который может иметь значение


Интертубы и этот сайт переполнены вопросами, требующими получения текста из перечисления.

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

Во всем нашем коде у нас есть несколько массивов структур, содержащих пары перечислений и соответствующие строки.

Проблема состоит в том, что строки имеют разную длину, поэтому мы кодируем функцию поиска для каждой из них, которая выполняет циклы по массиву структур, пытаясь сопоставить перечисление и возвращая соответствующий текст, если совпадение найдено.

Давайте возьмем следующие два надуманных примера:

// +=+=+=+=+=+=+=+=+=+=+=+=+=+=
typedef enum
{
    north,
    south,
    east,
    west
} E_directions;

struct direction_datum
{
    E_directions direction;
    char         direction_name[6];
};

struct direction_datum direction_data[] =
{
    {north, "north"},
    {south, "south"},
    {east,  "east"},
    {west,  "west"},
};

// +=+=+=+=+=+=+=+=+=+=+=+=+=+=
typedef enum
{
    hearts,
    spades,
    diamonds,
    clubs,
} E_suits;

struct suit_datum
{
    E_suits suit;
    char    suit_name[9];
};

struct suit_datum suit_data[] =
{
    {hearts,   "hearts"},
    {spades,   "spades"},
    {diamonds, "diamonds",},
    {clubs,    "clubs"},
};

Помимо длины строки, они похожи / идентичны, поэтому, теоретически, мы должны иметь возможность кодировать универсальную функцию для циклического direction_data или же suit_dataс учетом индекса и возврата соответствующего текста.

Я думаю, что-то вроде этого - но это не работает (значение перечисления в структуре всегда кажется нулем, поэтому, очевидно, моя арифметика указателя выключена).

Что я делаю неправильно?

char *Get_text_from_enum(int enum_value, 
                         void *array, 
                         unsigned int array_length, 
                         unsigned int size_of_array_entry)
{
    unsigned int i;
    unsigned int offset;

    for (i = 0; i < array_length; i++)
    {
        offset = i * size_of_array_entry;

        if ((int) * ((int *) (array+ offset)) == enum_value)
            return (char *)  (array + offset + sizeof(int));
    }

    return NULL;
}


printf("Expect south,    got %s\n", 
          Get_text_from_enum(south,    
                             direction_data, 
                             ARRAY_LENGTH(direction_data),  
                             sizeof(direction_data[0])));

printf("Expect diamonds, got %s\n", 
          Get_text_from_enum(diamonds, 
                             suit_data,      
                             ARRAY_LENGTH(suit_data),       
                             sizeof(suit_data[0])));

2 ответа

Решение

Я всегда использую подход, который описан ниже. Обратите внимание, что структура данных и функция одинаковы для всех перечислений.

struct enum_datum  
{
    int    enum_val;
    char  *enum_name;
};

char *GetEnumName(enum_datum *table, int value)
{
   while (table->enum_name != NULL)
   {
      if (table->enum_val == value)
         return enum_name;
      table++;
   }
   return NULL;
}

После этого для каждого конкретного перечисления вам нужно определить:

typedef enum {
    north, south, east, west
} E_directions;

enum_datum E_directions_datum[] = 
{
    { north, "north" },
    { south, "south" },
    { east,  "east"  },
    { west,  "west"  },
    { some_value_not_important, NULL }, // The end of the array marker.
};

char *GetDirectionName(E_directions dir)
{
    return GetEnumName(E_directions_datum, dir);
}

Обратите внимание, что строка может не совпадать с именем перечислителя. В моих собственных проектах иногда у меня есть несколько enum_datum массивы для того же перечисления. Это позволяет получать более и менее подробные сообщения без серьезного усложнения общего дизайна.

И в значительной степени это все. Основным преимуществом является простота.

Есть два "канонических" способа сделать это. Тот, который читается, а другой избегает повторения кода.


Читаемый способ

"Читаемый способ" - это то, что я бы порекомендовал. Он создает перечисление с соответствующей справочной таблицей, где константы перечисления соответствуют индексам справочной таблицы:

typedef enum
{
    north,
    south,
    east,
    west,
    directions_n // only used to keep track of the amount of enum constants
} direction_t;

const char* STR_DIRECTION [] =  // let array size be based on number of items
{
  "north",
  "south",
  "east",
  "west"
};


#define ARRAY_ITEMS(array) (sizeof(array) / sizeof(*array))
...
// verify integrity of enum and look-up table both:
_Static_assert(directions_n == ARRAY_ITEMS(STR_DIRECTION), 
               "direction_t does not match STR_DIRECTION");

Вы все еще можете иметь структуру, основанную на этом, если хотите:

typedef struct
{
  direction_t dir;
  const char* str;
} dir_struct_t;

const dir_struct_t DIRS [directions_n] = 
{ // use designated initializers to guarantee data integrity even if item order is changed:
  [north] = {north, STR_DIRECTION[north]},
  [south] = {south, STR_DIRECTION[south]},
  [east]  = {east,  STR_DIRECTION[east]},
  [west]  = {west,  STR_DIRECTION[west]}
};

Нет способа повторения кода

Другой альтернативой является использование так называемых "X-макросов", которые на самом деле не рекомендуются, кроме как в крайнем случае, поскольку они, как правило, делают код крайне нечитаемым, особенно для тех, кто не используется в таких макросах.

Этот код эквивалентен моему примеру выше:

#define DIRECTION_LIST \
  X(north), \
  X(south), \
  X(east),  \
  X(west),         // trailing commma important here! (and ok in enums since C99)

typedef enum
{
  #define X(dir) dir
    DIRECTION_LIST
  #undef X
  directions_n // only used to keep track of the amount of enum constants
} direction_t;

typedef struct
{
  direction_t dir;
  const char* str;
} dir_struct_t;

const dir_struct_t DIRS [directions_n] = 
{
  #define X(dir) {dir, #dir}
    DIRECTION_LIST
  #undef X
};

Эта версия макроса избавляется от таблицы поиска явных строк.

Другие вопросы по тегам