«Как я могу сделать точку данных в области, которую я могу использовать?»
Я не уверен, если вы имеете в виду то, что я объясню ниже (и это будет недолго: P), но если вы спрашиваете, как вы можете различать тип данных, хранящихся в Node->data
, то с этой реализацией вы не сможете.
Вы оставляете это до конца программиста, чтобы помнить, какие данные он хранил в списке (что не так плохо, кстати, наоборот, это норма). Другими словами, вы доверяете конечному программисту, что он/она будет применять правильные отбрасывания, когда скажет печать Node->data
.
Если по какой-либо причине вы хотите предоставить более управляемый API для своего списка, вы можете добавить еще одно поле в структуру , чтобы идентифицировать тип данных, хранящихся в вашем списке.
Например ...
enum DataType {
DT_INVALID = 0,
DT_PTR
DT_CHAR,
DT_INT,
DT_FLOAT,
DT_DOUBLE,
...
/* not a data-type, just their total count */
DT_MAX
};
#define DT_IS_VALID(dt) ((dt) > DT_INVALID && (dt) < DT_MAX)
typedef struct List List;
struct List {
enum DataType dt;
Node *head;
};
Конечно вы можете поддерживать меньше или больше типов данных, чем те, которые я перечислил в enum
, выше (даже пользовательские те, скажем, для строк, или что-то вы считаете нужным в соответствии с вашим проектом).
Итак, сначала вы будете нуждаться в конструкторе (или инициализаторе) для списка, что-то вроде этого ...
List *new_list(enum DataType dt)
{
List *ret = NULL;
if (!DT_IS_VALID(dt))
return NULL;
ret = malloc(sizeof(List));
if (NULL == ret)
return NULL;
ret->dt = dt;
ret->head = NULL;
return ret;
}
и создать его экземпляр, скажем так ...
int main(void)
{
List *listInt = new_list(DT_INT);
if (NULL == list) {
/* handle failure here */
}
Теперь, когда вы уже сохранили заданный тип данных в метаданных списка, вы можете свободно выбирать, как реализовать конструкторы Node
. Например, общий один может выглядеть примерно так ...
int list_add_node(List *list, const void *data)
{
Node *node = NULL;
size_t datasz = 0;
/* sanity checks */
if (!list || !data || !DT_IS_VALID(list->dt))
return 0; /* false */
node = malloc(sizeof(Node));
if (NULL == node)
return 0; /* false */
/* when data points to mem already reserved say for an array (TRICKY) */
if (DT_PTR == list->dt) {
node->data = data;
}
/* when data points to mem reserved for a primitive data-type */
else {
datasz = dt_size(list->dt); /* implement dt_size() according to your supported data-types */
node->data = malloc(datasz);
if (NULL == node->data) {
free(node);
return 0; /* false */
}
memcpy(node->data, data, datasz);
}
/* add here the code dealing with adding node into list->head */
...
return 1; /* true */
}
Для DT_PTR
(который я помечен как Tricky в данном примере), что является более безопасным для реализации другого Node
конструктора, возможно, принимая 2 дополнительные аргументы, давайте скажем elemsz
и nelems
, поэтому функция может выделить elemsz * nelems
байтов для копирования содержимого data
в них, в случае, если data
указывает на массив, структуру или любой другой непримитивный тип.Или вы можете предоставить дополнительные значения DT_ARR
enum специально для массивов. Вы можете делать все, что вам подходит.
В любом случае, для DT_PTR
приведенный выше пример зависит от вызвавшей list_add_node
, чтобы должным образом выделены переданную data
, и в общем контексте это не хорошая вещь вообще.
Код более сложный, но вы знаете тип данных, хранящийся в вашем списке. Поэтому, по крайней мере, для примитивных типов данных, которые вы можете добавить, можно сказать, что процедура печати автоматически выдает свой вывод в соответствии с list->dt
(для не-примитивных типов данных вы должны обеспечивать поддержку пользовательских процедур печати, обычно через функции обратного вызова).
Вы даже можете дотянуться до крайности, и переместите поле dt
от List
до Node
. В этом случае вы реализуете список гетерогенных данных в узлах, но он становится намного сложнее, а также редко бывает полезным (если вообще когда-либо).
Все эти предметы ADT с помощью (void *) указателей имеют серьезные проблемы с производительностью, поэтому критические реализации скорости используют вместо этого предварительный процессор (или злоупотребляют, если хотите), для такого рода вещей.
Указатель * нуждается в *, чтобы указать на определенное хранилище, которое вам разрешено использовать. Он может быть автоматическим, статическим, угрожающим, локальным или динамическим, но вы * не можете * использовать любой бит памяти, на который это указывает! – dmckee
Но разве это не значит, что мои вызовы в List-> current-> data не должны возвращать список для случая, когда были установлены данные = newList()? Похоже, они это делают. –
Нет, потому что в этом случае вы * установите * 'data'. В другом случае вы просто принимаете какое бы то ни было значение в памяти, которая была передана вам за ваш «узел» (который может быть буквально * любым значением, выражаемым в 'sizeof (void *)' bytes). – dmckee