C двойной связанный список с абстрактным типом данных - PullRequest
7 голосов
/ 18 июля 2010

Мне нужно в двойной связанный список в C, но это должно быть для разных типов. В C ++ мы используем шаблоны для этого. Где я могу найти пример в C для двойного связанного списка с элементами абстрактных типов.

Спасибо

Ответы [ 5 ]

12 голосов
/ 18 июля 2010

Есть несколько подходов, которые вы можете использовать, один из которых заключается в сохранении 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[] самостоятельно, без приведения к другому типу.

3 голосов
/ 18 июля 2010

Обработка произвольных данных в C обычно осуществляется с помощью указателей, в частности, void * в большинстве случаев.

2 голосов
/ 18 июля 2010

Очевидно, что ядро ​​linux использует связанные списки во многих, многих местах как в самом ядре, так и во многих модулях драйверов устройств. Почти все они реализованы с использованием одного и того же набора макросов, определенных в linux / list.h

См. http://isis.poly.edu/kulesh/stuff/src/klist/ или http://kernelnewbies.org/FAQ/LinkedLists для хорошего объяснения.

Сначала макросы выглядят немного странно, но их легко использовать, и вскоре они становятся второй натурой. Их можно легко адаптировать для использования в пространстве пользователя (см. list.h ).

1 голос
/ 18 июля 2010

Наиболее близким представлением C в базовом классе или шаблонных типах является указатель void. 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 некоторое пространство. Просто обязательно освободите память, когда закончите.

0 голосов
/ 18 июля 2010

Вы можете использовать макросы, как показано здесь (этот конкретный пример реализует общие хеш-таблицы).

...