【发布时间】:2010-07-18 05:45:27
【问题描述】:
我需要 C 中的双链表,但它必须用于不同的类型。在 C++ 中,我们使用模板。我在哪里可以找到 C 中带有抽象类型项的双链表的示例。
谢谢
【问题讨论】:
标签: c list data-structures
我需要 C 中的双链表,但它必须用于不同的类型。在 C++ 中,我们使用模板。我在哪里可以找到 C 中带有抽象类型项的双链表的示例。
谢谢
【问题讨论】:
标签: c list data-structures
您可以采用几种方法,其中一种方法是在您的 ADT 中存储 void*。
我一直觉得这对链表来说有点麻烦,因为您必须单独管理它对链表本身的分配。换句话说,要分配节点,您需要分别分配节点及其负载(并记住在删除时也要清理它们)。
我过去使用的一种方法是使用“可变大小”结构,例如:
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[] 而无需转换为其他类型。
【讨论】:
char payload[0],所以sizeof代表header而已。
tPerson 类型(person)就是我所说的演员表。从那里,您可以使用person 访问成员。
在 C 中处理任意数据通常使用指针来完成 - 在大多数情况下特别是 void *。
【讨论】:
void * 成员指向您要使用列表跟踪的数据。
int *、void *等)的大小都是一样的。只是他们指向的实际数据会有所不同。
显然,Linux 内核在内核本身和许多设备驱动程序模块中的许多地方都使用了链表。几乎所有这些都是使用 linux/list.h 中定义的相同的一组宏来实现的
请参阅 http://isis.poly.edu/kulesh/stuff/src/klist/ 或 http://kernelnewbies.org/FAQ/LinkedLists 以获得很好的解释。
宏起初看起来有点奇怪,但易于使用并很快成为第二天性。它们可以很容易地适用于用户空间(请参阅list.h)。
【讨论】:
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 一些空间。完成后请务必释放内存。
【讨论】:
您可以使用 here 演示的宏(此特定示例实现了通用哈希表)。
【讨论】: