Я подозреваю, что вам (в идеале) нужен словарь:
theMeas[i]["signal_path"] = atoi(&chunk[2]);
Конечно, приведенный выше синтаксис никогда не произойдет в C, но это не очень важно здесь. Проблема в том, что вам придется писать весь код, реализующий тип данных словаря, и я подозреваю, что это излишне.
Так что я подозреваю, что вы (действительно) хотите иметь способ иметь имена, которые можно использовать в цикле:
foreach(signal_path, trace_length, sample_rate)
И я здесь, чтобы сказать вам, что вы можете сделать это (вроде)! Самый простой способ - с enum
:
enum fields {
signal_path,
trace_length,
sample_rate,
END_fields,
UNKNOWN_fields,
BEGIN_fields = 0,
};
Вместо struct
членов вы используете массив:
int theMeas[size][END_fields];
Чтобы проиндексировать «член», используйте это:
theMeas[i][signal_path];
Вы можете перебрать все «члены», вы можете использовать это:
for(enum fields j = BEGIN_fields; j != END_fields; j++)
theMeas[i][j];
Это немного ломается, когда вы хотите получить сравнение на основе символов, но мы можем сделать немного:
const char *to_str(enum fields f)
{
#define FIELD(x) case x: return #x
switch(f)
{
FIELD(signal_path);
FIELD(trace_length);
FIELD(sample_rate);
default: return "<unknown>";
}
#undef FIELD
}
enum fields from_str(const char *c)
{
#define FIELD(x) if(!strcmp(c, #x)) return x
FIELD(signal_path);
FIELD(trace_length);
FIELD(sample_rate);
default: return UNKNOWN_fields;
#undef FIELD
}
enum fields from_abv(char *c)
{
for(enum fields i = BEGIN_fields; i < END_fields; i++)
{
char *field = field_str(i);
if(tolower(c[0]) == field[0] && tolower(c[1]) == strchr(field, '_')[1])
return i;
}
return UNKNOWN_fields;
}
Ваши if
заявления могут быть заменены на:
theMeas[i][from_abv(chunk)] = atoi(&chunk[2]);
Или, более безопасно:
enum fields j = from_abv(chunk);
if(j != UNKNOWN_fields) theMeas[i][j] = atoi(&chunk[2]);
else /* erroneous user input */;
Это как можно ближе.
Обратите внимание, что я намеренно использовал схему именования, чтобы упростить создание макросов, которые автоматизируют большую часть этого. Давайте попробуем:
#define member(name, ...) \
enum name { __VA_ARGS__, \
M_END_##name, \
M_UNKNOWN_##name, \
M_BEGIN_##name = 0 }
#define miter(name, var) \
enum name var = M_BEGIN_##name; var != M_END_##name; var++
#define msize(name) M_END_##name
Использование:
// define our fields
member(fields, signal_path, trace_length, sample_rate);
// declare object with fields
int theMeas[N][msize(fields)];
for(size_t i = 0; i < N; i++)
// iterate over fields
for(miter(fields, j))
// match against fields
if(j == from_abv(chunk))
theMeas[i][j] = atoi(&chunk[2]);
Этот последний бит не кажется таким уж плохим. Он по-прежнему позволяет вам что-то близкое к struct
-подобному доступу через theMeas[i][signal_path]
, но позволяет перебирать «члены» и скрывает большую часть тяжелой работы за макросами.
Функции to_str
и from_str
требуют немного больше макросов для автоматизации. Вам, вероятно, придется посмотреть на P99 для этого. Функцию from_abv
я бы не рекомендовал для общего случая, поскольку у нас нет способа гарантировать, что в следующий раз, когда вы создадите итерируемые поля, вы будете использовать имена с подчеркиванием. (Конечно, вы можете отказаться от функции from_abv
и дать своим членам такие непостижимые имена, как SP
, TL
и SR
, что позволит вам напрямую сравнивать их со строковыми данными, но вам придется изменить от strcmp
до memcmp
с аргументом размера (sizeof(#x) - 1)
. Тогда все места, которые у вас есть from_abv
, вы просто используете from_str
, который может быть автоматически сгенерирован для вас.)
Тем не менее, from_abv
не сложно определить, и вы можете честно просто скопировать и вставить в него свои блоки if
сверху - это будет немного более эффективно, хотя, если вы добавите «член», вы придется обновить функцию (как написано, она обновится сама, если вы добавите участника.)