我需要一个C中的双向链表,但它必须用于不同的类型。在C++中,我们使用模板。我在哪里可以找到一个例子 * 在C* 的双向链表与抽象数据类型的项目?
dgjrabp21#
您可以采取几种方法,其中一种方法涉及在ADT中存储void*。我一直觉得这是一个有点痛苦的链表,因为你必须管理它的分配单独的列表本身。换句话说,要分配一个节点,您需要分别分配节点及其有效负载(并记住在删除时也要清理它们)。我过去使用的一种方法是使用一个“可变大小”的结构,如:
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字节):
[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有效负载类型的地址。使用此方法,您可以在节点中携带任何您想要的有效负载类型,甚至 * 每个节点中的不同有效负载类型 *,如果您使结构更像:
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来存储关于有效载荷实际上是什么的指示符。与联合相比,它的优点在于不会浪费空间,如下所示:
payloadType
union { int fourBytes; char oneHundredBytes[100]; } u;
型其中,每次在列表中存储整数类型(对于4字节整数)时,都会浪费96个字节。tNode中的payload类型允许您轻松检测此节点携带的有效负载类型,因此您的代码可以决定如何处理它。您可以使用以下内容:
#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标准,这是可能的。您通常可以简单地通过为有效负载占位符使用具有最严格对齐要求的数据类型来获得所需的对齐,例如:
char
long payload;
型回想起来,我突然想到,你可能不需要一个数组作为有效负载占位符。它很简单,只要有一些你可以采取的地址。我怀疑我的这种特殊习惯用法可以追溯到我只存储字符数组(而不是结构)并直接引用它们的日子。在这种情况下,您可以单独使用payload[],而无需转换为其他类型。
payload[]
chhqkbe12#
在C中处理任意数据通常是通过使用指针来完成的-特别是在大多数情况下void *。
void *
pn9klfpd3#
显然,linux内核在很多地方使用了链表,包括内核本身和许多设备驱动模块。几乎所有这些都是使用在linux/list. h中定义的同一组宏实现的。参见http://isis.poly.edu/kulesh/stuff/src/klist/或http://kernelnewbies.org/FAQ/LinkedLists以获得更好的解释。宏一开始看起来有点奇怪,但很容易使用,很快就成了第二天性。它们可以简单地适应用户空间(参见list.h)。
9jyewag04#
在C语言中,最接近于“对象”基类或模板类型的是void指针。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一些空间。完成后一定要释放内存。
int
float
malloc
vsdwdz235#
您可以使用宏,如here所示(此特定示例实现了通用散列表)。
5条答案
按热度按时间dgjrabp21#
您可以采取几种方法,其中一种方法涉及在ADT中存储
void*
。我一直觉得这是一个有点痛苦的链表,因为你必须管理它的分配单独的列表本身。换句话说,要分配一个节点,您需要分别分配节点及其有效负载(并记住在删除时也要清理它们)。
我过去使用的一种方法是使用一个“可变大小”的结构,如:
字符串
现在,这看起来并不是变量大小,但让我们这样分配一个结构:
型
现在你有一个节点,对于所有的意图和目的,看起来像这样:
型
或者,以图形形式表示(其中
[n]
表示n
字节):型
也就是说,假设您知道如何正确寻址有效负载。这可以通过以下方式完成:
型
该强制转换行只是将
payload
字符(tNode
类型)的地址强制转换为实际tPerson
有效负载类型的地址。使用此方法,您可以在节点中携带任何您想要的有效负载类型,甚至 * 每个节点中的不同有效负载类型 *,如果您使结构更像:
型
并使用
payloadType
来存储关于有效载荷实际上是什么的指示符。与联合相比,它的优点在于不会浪费空间,如下所示:
型
其中,每次在列表中存储整数类型(对于4字节整数)时,都会浪费96个字节。
tNode
中的payload类型允许您轻松检测此节点携带的有效负载类型,因此您的代码可以决定如何处理它。您可以使用以下内容:型
或者(可能更好):
型
你唯一需要注意的是确保有效载荷的对齐是正确的。因为我的有效负载占位符和有效负载都是
char
类型,所以这不是问题。但是,如果您的有效负载包含具有更严格对齐要求的类型(例如比指针更严格的类型,则可能需要进行调整)。虽然我从来没有见过一个环境的对齐比指针更严格,但根据ISO C标准,这是可能的。
您通常可以简单地通过为有效负载占位符使用具有最严格对齐要求的数据类型来获得所需的对齐,例如:
型
回想起来,我突然想到,你可能不需要一个数组作为有效负载占位符。它很简单,只要有一些你可以采取的地址。我怀疑我的这种特殊习惯用法可以追溯到我只存储字符数组(而不是结构)并直接引用它们的日子。在这种情况下,您可以单独使用
payload[]
,而无需转换为其他类型。chhqkbe12#
在C中处理任意数据通常是通过使用指针来完成的-特别是在大多数情况下
void *
。pn9klfpd3#
显然,linux内核在很多地方使用了链表,包括内核本身和许多设备驱动模块。几乎所有这些都是使用在linux/list. h中定义的同一组宏实现的。
参见http://isis.poly.edu/kulesh/stuff/src/klist/或http://kernelnewbies.org/FAQ/LinkedLists以获得更好的解释。
宏一开始看起来有点奇怪,但很容易使用,很快就成了第二天性。它们可以简单地适应用户空间(参见list.h)。
9jyewag04#
在C语言中,最接近于“对象”基类或模板类型的是
void
指针。void *
表示指向某个对象的指针,但它不指定指向的数据类型。如果要访问数据,则需要使用强制转换。双向链接列表节点可能如下所示:
字符串
例如,要为节点分配字符串,可以执行以下操作:
型
若要从节点取回字串,请使用转型:
型
若要储存非指标,您必须从数据建立指标。对于结构,如果示例的生存期足够长,则可以简单地取消引用该示例(类似于上面的示例)。如果不是,或者您正在处理一个基元类型(例如,
int
或float
),则需要malloc
一些空间。完成后一定要释放内存。vsdwdz235#
您可以使用宏,如here所示(此特定示例实现了通用散列表)。