Вот опция, которая очень гибкая, но требует большой работы.
В своем узле списка сохраните указатель на данные как void *
:
struct node {
void *data;
struct node *next;
};
Затем вы создадите набор функций для каждого типа, которые будут обрабатывать такие задачи, как сравнение, назначение, дублирование и т. Д.:
// create a new instance of the data item and copy the value
// of the parameter to it.
void *copyInt(void *src)
{
int *p = malloc(sizeof *p);
if (p) *p = *(int *)src;
return p;
}
void assignInt(void *target, void *src)
{
// we create a new instance for the assignment
*(int *)target = copyInt(src);
}
// returns -1 if lhs < rhs, 0 if lhs == rhs, 1 if lhs > rhs
int testInt(void *lhs, void *rhs)
{
if (*(int *)lhs < *(int *)rhs) return -1;
else if (*(int *)lhs == *(int *)rhs) return 0;
else return 1;
}
char *intToString(void *data)
{
size_t digits = however_many_digits_in_an_int();
char *s = malloc(digits + 2); // sign + digits + terminator
sprintf(s, "%d", *(int *)data);
return s;
}
Затем вы можете создать тип списка с указателями на этифункции, такие как
struct list {
struct node *head;
void *(*cpy)(void *); // copy operation
int (*test)(void *, void *); // test operation
void (*asgn)(void *, void *); // assign operation
char *(*toStr)(void *); // get string representation
...
}
struct list myIntList;
struct list myDoubleList;
myIntList.cpy = copyInt;
myIntList.test = testInt;
myIntList.asgn = assignInt;
myIntList.toStr = intToString;
myDoubleList.cpy = copyDouble;
myDoubleList.test = testDouble;
myDoubleList.asgn = assignDouble;
myDoubleList.toStr = doubleToString;
...
Затем, когда вы передаете список операции вставки или поиска, вы вызываете функции из объекта списка:
void addToList(struct list *l, void *value)
{
struct node *new, *cur = l->head;
while (cur->next != NULL && l->test(cur->data, value) <= 0)
cur = cur->next;
new = malloc(sizeof *new);
if (!new)
{
// handle error here
}
else
{
new->data = l->cpy(value);
new->next = cur->next;
cur->next = new;
if (logging)
{
char *s = l->toStr(new->data);
fprintf(log, "Added value %s to list\n", s);
free(s);
}
}
}
...
i = 1;
addToList(&myIntList, &i);
f = 3.4;
addToList(&myDoubleList, &f);
ПередавОперации с учетом типов для разделения функций, вызываемых через указатели на функции, теперь у вас есть структура списка, которая может хранить значения любого типа.Чтобы добавить поддержку новых типов, вам нужно только реализовать новые функции копирования, назначения, toString и т. Д. Для этого нового типа.
Есть недостатки.Во-первых, вы не можете использовать константы в качестве параметров функции (например, вы не можете сделать что-то простое, например, addToList(&myIntList, 1);
) - вам нужно все присвоить переменной first и передать адреспеременной (именно поэтому вам нужно создавать новые экземпляры элемента данных, когда вы добавляете его в список; если вы просто назначаете адрес переменной, каждый элемент в списке будет указывать на один и тот же объект, которыйможет больше не существовать в зависимости от контекста).
Во-вторых, вы выполняете lot управления памятью;Вы не просто создаете новый экземпляр узла списка, но вы также должны создать новый экземпляр члена данных.Вы должны помнить, чтобы освободить элемент данных перед освобождением узла.Затем вы создаете новый экземпляр строки каждый раз, когда хотите отобразить данные, и вы должны помнить, чтобы освободить эту строку, когда закончите с ней.
Наконец, это решение выбрасывает безопасность типов прямо в окно и во встречное движение (после того, как оно загорелось).Функции делегатов рассчитывают на you , чтобы типы были прямыми;ничто не мешает вам передать адрес переменной double
одной из int
функций обработки.
Между управлением памятью и тем фактом, что вы должны выполнять вызов функции практически для каждой операции,производительность будет страдать.Это не быстрое решение.
Конечно, это предполагает, что каждый элемент в списке имеет одинаковый тип;если вы хотите хранить элементы разных типов в том же списке, то вам нужно будет сделать что-то другое, например связать функции с каждым узлом, ачем список в целом.