Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/68.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 实现链表的两种方法:哪种更好?_C_Linked List - Fatal编程技术网

C 实现链表的两种方法:哪种更好?

C 实现链表的两种方法:哪种更好?,c,linked-list,C,Linked List,我通常知道用C设计通用链表数据结构的两种方法,我想知道哪种更好。在提问之前,我将简要介绍这两种方法: 一种方法是围绕如下结构构建函数: struct list_element { struct list_element *prev; struct list_element *next; void *data; }; 显然,数据指针指向有效负载。列表元素结构在有效负载之外。例如,glib是如何设计其双链表功能的: 另一种方法是它在Linux内核中的实现方式:。列表元素结构

我通常知道用C设计通用链表数据结构的两种方法,我想知道哪种更好。在提问之前,我将简要介绍这两种方法:

一种方法是围绕如下结构构建函数:

struct list_element {
    struct list_element *prev;
    struct list_element *next;
    void *data;
};
显然,数据指针指向有效负载。列表元素结构在有效负载之外。例如,glib是如何设计其双链表功能的:

另一种方法是它在Linux内核中的实现方式:。列表元素结构中没有指向有效负载的空指针。相反,列表元素结构包含在有效负载结构中:

struct list_element {
    struct list_element *prev;
    struct list_element *next;
};

struct person {
    char name[20];
    unsigned int age;
    struct list_element list_entry;
};
一个特殊的宏用于获取一个指向有效负载结构的指针,给定一个指向list_条目的指针,该指针的名称包含有效负载结构和有效负载结构的类型(list_entry()宏)

最后,这是一个问题:构建链表的两种方法中的后一种有什么好处?有几次我听到人们说第二种比第一种更“通用”,但为什么呢?我甚至认为第一种方法更通用,因为有效负载结构与列表实现无关,而第二种方法则不是这样。
第二种方法的另一个缺点是,如果要将有效负载放在多个列表中,则应该为有效负载结构中的每个列表添加一个struct list_元素成员

编辑: 总结到目前为止,我看到了两个对我很重要的答案:

  • 对于第一种方法:从列表中删除有效负载涉及在整个列表中循环,直到找到指向有效负载的列表元素。您不需要使用第二种方法来执行此操作。(帕特里克的回答)
  • 对于第一个方法,必须为每个元素执行两个malloc():一个用于有效负载,另一个用于列表元素结构。对于第二个方法,一个malloc()就足够了。(罗迪的回答)

第一个更好,因为您可以拥有完全没有数据的列表节点


在第二个选项中,无论实际使用情况如何,您始终使用空格(例如,名称为20个字符)。

这是马的路线

第一种方法效率较低,因为它通常需要为每个列表元素提供两个
malloc()
s和
free()
s,以及一个额外的间接指针来访问它们,当然还有指针的存储空间

但是,它允许不同的列表元素具有不同大小的有效负载,这对于第二种方法来说可能更加尴尬

对于第二种方法,我将对结构进行重新排序,以使列表元素位于开始位置,这样就为不同的负载大小提供了一些灵活性

struct person {
    struct list_element list_entry;
    unsigned int age;
    char name[20];  // now could be variable length.
};

第二种方法是侵入式列表。您必须修改要存储在列表中的结构。通过这种方法,您可以获得一点性能,因为间接性更少。如果您需要更灵活的解决方案,而不是最后一点性能,则应使用第一种方法。

第二种方法是“侵入式”;它需要修改列表中的类型。列表中的类型必须知道它在列表中。您必须能够修改结构以将其放在列表中

第一种方法不是侵入性的。它不需要对结构进行修改。您可以在列表中添加任何类型。您甚至可以在单个列表中包含异构类型,尽管这会带来很多问题。但是,即使基本类型不可修改,也可以将其放在列表的第一个类型中。与此相反,它需要更多的空间


因此,如果您可以完全控制要放在列表中的数据类型(并且可以修改它以支持所需的列表),那么第二种类型比第一种类型有一些优势。在Linux内核的上下文中,满足了前提条件,这是有意义的。否则,第一种类型更灵活,但开销稍大。

我认为这更像是一个概念/分析问题。您正在处理的实体是否有列表或实例列表

换句话说,如果您在数据中管理的内容是独立存在的,那么第一个是有意义的,因为任何数据点都将被独立地操作。如果数据总是并且必然是列表的一部分,那么第二种方法可能更清晰


与大多数设计决策一样,最重要的标准应该是更清晰和最明显的标准。

我认为这是一个非常主观的问题,因为没有给出标准来比较两者

对于简单的列表,我倾向于使用两者的组合

struct list_node {
    struct list_node *  prev;
    struct list_node *  next;
};

struct some_struct {
    struct list_node  node;
    ...
};
尽管这看起来与第二个几乎相同,但请注意,链表节点是“some_struct”的第一个元素。这意味着,当您前进到列表中的下一个节点或后退到上一个节点时,指针位于结构的开头。否则,我将被迫执行一些指针数学来开始“some_struct”。就目前而言,我可以简单地施放

然而,这种方法确实有其局限性。例如,如果我想要一个具有多个链表的结构,则列出的每个方法都有一个缺陷,即它需要指针算法才能到达至少一个结构的开头。为了解决这个问题,一些实现(如BSD VFS代码中的实现)使用宏来创建链表元素。在这些情况下,链表始终指向结构的起点,但宏包含代码,以便在需要时自动应用结构中节点的偏移量(用于前进到下一个或倒带上一个)

希望这有帮助


编辑:修复了一些术语。

第一种方法似乎不太具侵入性,但在许多情况下并非如此(除非添加额外的数据结构)

想象你