мне нужно в двойном связанном списке в C, но это должно быть для различных типов. В C++ мы используем шаблоны для него. Где я могу найти пример в C для двойного связанного списка с объектами абстрактных типов.
Спасибо
Есть несколько подходов, которые вы можете использовать, один из которых предполагает сохранение 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");
Эта строка преобразования просто преобразует адрес символа полезной нагрузки
(в типе 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 []
отдельно без преобразования к другому типу.
Работа с произвольными данными в C обычно осуществляется с помощью указателей - в частности, void *
в большинстве случаев.
Вы можете использовать макросы, как показано здесь (этот конкретный пример реализует общие хеш-таблицы).
Наиболее близким представлением C к "объектному" базовому классу или шаблонным типам является указатель void
. A void *
представляет собой указатель на что-то, но не указывает, на какой тип данных указывает. Если вы хотите получить доступ к данным, вам нужно использовать приведение.
Узел двусвязного списка может выглядеть так:
struct DoubleLinkedListNode {
struct DoubleLinkedListNode *previous, *next;
void *data;
};
Чтобы назначить узлу строку, например, вы можете сделать:
char myString[80] = "hello, world";
struct DoubleLinkedListNode myNode;
myNode.data = myString;
Чтобы вернуть строку из узла, вы используете приведение:
char *string = (char *)myNode.data;
puts(string);
Чтобы сохранить не-указатель, вам нужно сделать указатель из данных. Для структур вы можете просто разыменовать экземпляр, если его время жизни достаточно велико (аналогично приведенному выше примеру). Если нет, или если вы имеете дело с примитивным типом (например, int
или float
), вам нужно malloc
немного места. Просто не забудьте освободить память, когда закончите.
Очевидно, что ядро Linux использует связанные списки во многих, многих местах как в самом ядре, так и во многих модулях драйверов устройств. Почти все они реализованы с использованием одного и того же набора макросов, определенных в linux / list.h
См. http://isis.poly.edu/kulesh/stuff/src/klist/ или http://kernelnewbies.org/FAQ/LinkedLists для хорошего объяснения.
Макросы сначала выглядят немного странно, но просты в использовании и вскоре стали привычными. Их можно легко адаптировать для использования в пользовательском пространстве (см. list.h ).