Есть несколько подходов, которые вы можете использовать, один из которых заключается в сохранении void*
в вашем ADT.
Я всегда считал, что это связано с некоторыми трудностями в связанном списке, поскольку вы должны управлять его размещением отдельно для самого списка. Другими словами, чтобы выделить узел, вам нужно выделить как узел, так и его полезную нагрузку отдельно (и не забудьте также очистить их при удалении).
Один из подходов, которые я использовал в прошлом, это иметь структуру «переменного размера», такую как:
typedef struct _tNode {
struct _tNode *prev;
struct _tNode *next;
char payload[1];
} tNode;
Теперь это не выглядит с переменным размером, но давайте выделим структуру таким образом:
typedef struct {
char Name[30];
char Addr[50];
char Phone[20];
} tPerson;
tNode *node = malloc (sizeof (tNode) - 1 + sizeof (tPerson));
Теперь у вас есть узел, который для всех намерений и целей выглядит следующим образом:
typedef struct _tNode {
struct _tNode *prev;
struct _tNode *next;
char Name[30];
char Addr[50];
char Phone[20];
} tNode;
или в графическом виде (где [n]
означает n
байт):
+------------+
| prev[4] |
+------------+
| next[4] |
+------------+ +-----------+
| payload[1] | | Name[30] | <- overlap
+------------+ +-----------+
| Addr[50] |
+-----------+
| Phone[20] |
+-----------+
То есть, если вы знаете, как правильно обращаться с полезной нагрузкой. Это можно сделать следующим образом:
node->prev = NULL;
node->next = NULL;
tPerson *person = &(node->payload); // cast for easy changes to payload.
strcpy (person->Name, "Richard Cranium");
strcpy (person->Addr, "10 Smith St");
strcpy (person->Phone, "555-5555");
Эта строка приведения просто преобразует адрес символа payload
(в типе tNode
) в адрес фактического tPerson
типа полезной нагрузки.
Используя этот метод, вы можете переносить любой тип полезной нагрузки в узле, даже различные типы полезной нагрузки в каждом узле , если сделать структуру более похожей на:
typedef struct _tNode {
struct _tNode *prev;
struct _tNode *next;
int payloadType; // Allows different payload type at each node.
char payload[1];
} tNode;
и используйте payloadType
для хранения индикатора относительно того, какова полезная нагрузка.
Это имеет преимущество перед объединением в том, что оно не тратит пространство, как это видно из следующего:
union {
int fourBytes;
char oneHundredBytes[100];
} u;
, где 96 байтов тратятся впустую каждый раз, когда вы сохраняете целочисленный тип в списке (для 4-байтового целого числа).
Тип полезной нагрузки в tNode
позволяет вам легко определить, какой тип полезной нагрузки несет этот узел, поэтому ваш код может решить, как его обработать. Вы можете использовать что-то вроде:
#define PAYLOAD_UNKNOWN 0
#define PAYLOAD_MANAGER 1
#define PAYLOAD_EMPLOYEE 2
#define PAYLOAD_CONTRACTOR 3
или (возможно, лучше):
typedef enum {
PAYLOAD_UNKNOWN,
PAYLOAD_MANAGER,
PAYLOAD_EMPLOYEE,
PAYLOAD_CONTRACTOR
} tPayLoad;
Единственное, на что вам нужно обратить внимание - это убедиться в правильности выравнивания полезной нагрузки. Поскольку и мой заполнитель полезной нагрузки, и полезная нагрузка имеют типы char
, это не проблема. Однако, если ваша полезная нагрузка состоит из типов с более строгими требованиями к выравниванию (например, что-то более строгое, чем указатели, вам может потребоваться настроить его).
Хотя я никогда не видел среды с выравниванием более строгим, чем указатели, возможно в соответствии со стандартом ISO C.
Обычно вы можете получить необходимое выравнивание, просто используя тип данных для заполнителя полезной нагрузки, который имеет самое строгое требование выравнивания, например:
long payload;
В ретроспективе мне приходит в голову, что вам, вероятно, не нужен массив в качестве заполнителя полезной нагрузки. Достаточно просто иметь то, что вы можете взять по адресу. Я подозреваю, что моя особая идиома относится ко временам, когда я просто сохранял массив символов (а не структуру) и ссылался на них напрямую. В этом случае вы можете использовать payload[]
самостоятельно, без приведения к другому типу.