【问题标题】:C double linked list with abstract data type具有抽象数据类型的 C 双链表
【发布时间】:2010-07-18 05:45:27
【问题描述】:

我需要 C 中的双链表,但它必须用于不同的类型。在 C++ 中,我们使用模板。我在哪里可以找到 C 中带有抽象类型项的双链表的示例。

谢谢

【问题讨论】:

    标签: c list data-structures


    【解决方案1】:

    您可以采用几种方法,其中一种方法是在您的 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而已。
    • 您解释说您需要转换有效负载,但您只是在示例中取消引用它。
    • 例如,x86 上的 128 位向量类型(由 SSE 指令集使用)需要 16 字节对齐。
    • @strager,我很确定数组大小为 0 在 C99 中是非法的(至少对于常量大小的数组)。将有效负载的地址分配给tPerson 类型(person)就是我所说的演员表。从那里,您可以使用person 访问成员。
    • @paxdiablo,是的;不过,我认为 0 大小的数组有一个 gcc 扩展。
    【解决方案2】:

    在 C 中处理任意数据通常使用指针来完成 - 在大多数情况下特别是 void *

    【讨论】:

    • 我也瘦了。然后,如果我将在我的结构中使用 void* item,我该如何为这个结构分配内存。使用 malloc 和 sizeof(mystructname) ?
    • @sterh,是的,完全正确。然后将列表结构的void * 成员指向您要使用列表跟踪的数据。
    • @sterh:分配结构本身不会改变。事实上,所有指针(int *void *等)的大小都是一样的。只是他们指向的实际数据会有所不同。
    • 另外请注意,您可以在 void* 和 "small" 类型之间进行强制转换,因此您可以通过将值直接存储到 void* 使用的位置来避免动态内存分配
    • @zvrba:这是个坏主意,因为对于少数 64 位平台,指针大小和小数据大小是不同的。
    【解决方案3】:

    显然,Linux 内核在内核本身和许多设备驱动程序模块中的许多地方都使用了链表。几乎所有这些都是使用 linux/list.h 中定义的相同的一组宏来实现的

    请参阅 http://isis.poly.edu/kulesh/stuff/src/klist/http://kernelnewbies.org/FAQ/LinkedLists 以获得很好的解释。

    宏起初看起来有点奇怪,但易于使用并很快成为第二天性。它们可以很容易地适用于用户空间(请参阅list.h)。

    【讨论】:

    • 这些很微妙而且很聪明。它们是通常方法的反转:不是在列表节点类型中存储(指向)您的数据类型,而是在您的数据类型中存储列表节点类型的实例。
    【解决方案4】:

    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);
    

    要存储非指针,您需要从数据中创建一个指针。对于结构,如果实例的生命周期足够长(类似于上面的示例),您可以简单地取消引用该实例。如果不是,或者如果您正在处理原始类型(例如 intfloat),则需要 malloc 一些空间。完成后请务必释放内存。

    【讨论】:

      【解决方案5】:

      您可以使用 here 演示的宏(此特定示例实现了通用哈希表)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-25
        • 1970-01-01
        • 1970-01-01
        • 2021-11-13
        相关资源
        最近更新 更多