Итак, подведем итог:
У вас есть функция crawlOne(List *plist)
, которая находит некоторые объекты типа struct point
и что-то делает с их col1
членами.
Вы хотели бы иметь функцию crawlSome(List *plist, int colnum)
, которая:
- при вызове
crawlSome(list, 1)
работает с col1
- при вызове
crawlSome(list, 2)
работает на col2
- и так далее.
Безусловно, самый чистый подход, как предложил Джон Боллинджер в комментарии, - это перепроектировать struct point
, чтобы он содержал массив вместо трех отдельных членов:
struct point {
double col[3];
};
void crawlSome(List *plist, int colnum) {
// ...
pnode->tick.col[colnum] = ...;
}
Это означает, что вам нужно изменить весь существующий код, который использует struct point
, что, хотя и просто, может быть утомительным, но в долгосрочной перспективе вы получите более чистый и эффективный дизайн. Я бы сделал это, если это вообще возможно.
Если вы действительно не можете изменить определение struct point
(например, он используется сторонним кодом, который вы не можете изменить), тогда вы не можете избежать каким-то образом жестко запрограммировать имена членов, поскольку имена не существуют во время выполнения. KamilCuk предложил несколько вариантов для этого. Другой вариант, о котором я мог бы подумать, - это вытащить жесткое кодирование в функцию «селектора столбцов», чтобы это можно было сделать только в одном месте вашей программы. Он не должен быть слишком неэффективным, если его можно встроить.
inline double *select_column(struct point *pt, int colnum) {
switch (colnum) {
case 1: return &pt->col1;
case 2: return &pt->col2;
case 3: return &pt->col3;
default: abort(); // or perhaps return NULL;
}
}
void crawlSome(List *plist, int colnum) {
// ...
*select_column(&pnode->tick, colnum) = ...
}
Это позволяет избежать необходимости дублировать код crawlOne
вообще, с макросом или без него.
Если вы хотите уменьшить количество повторений при определении select_column
, вы можете использовать макрос с вставкой токена:
inline double *select_column(struct point *pt, int colnum) {
switch (colnum) {
#define DO(N) case N: return &pt->col ## N ;
DO(1)
DO(2)
DO(3)
#undef DO
default: abort(); // or perhaps return NULL;
}
}
Если вы хотите сделать селектор немного удобнее, вы можете обернуть его в макрос:
#define COL(p, n) (*select_column(&(p), (n)))
void crawlSome(List *plist, int colnum) {
// ...
COL(pnode->tick, colnum) = ...;
}
В качестве альтернативы аналогичный подход может быть реализован с помощью offsetof
, хотя и с тем же отсутствием проверки типов, что указывает KamilCuk:
#include <stddef.h>
const size_t col_offsets[3] = {
offsetof(struct point, col1),
offsetof(struct point, col2),
offsetof(struct point, col3)
};
#define COL(p, n) (*(double *)((char *)&(p) + col_offsets[(n)]))