Общее перечисление для поиска текста в C
[Обновить]
- данный код действительно работает. Я ошибся, когда подумал, что это не так. Виноват; Сожалеем ++. Если вы можете улучшить код, пожалуйста, сделайте это по адресу https://codereview.stackexchange.com/questions/150480/generic-enum-to-text-lookup-in-c
- Мы должны объявить наши строки во время компиляции. Мы кодируем встроенные системы и не можем
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
};
Эта версия макроса избавляется от таблицы поиска явных строк.