4

对于我即将到来的大学 C 项目,我被要求拥有 C 允许的模块化代码。基本上,对于某些数据结构,我将拥有 .c 文件和相应的 .h 文件,例如链表、二叉树、哈希表等等……

以链表为例,我有这个:

typedef struct sLinkedList {
    int value;
    struct sLinkedList *next;
} List;

但这强制value是类型int,使用这个链表库的用户将被迫直接更改库的源代码。我想避免这种情况,我想避免更改库的需要,使代码尽可能模块化。

我的项目可能需要对整数列表使用链表,或者可能需要使用某种结构的列表。但我不会复制库文件/代码并相应地更改代码。

我该如何解决这个问题?

4

8 回答 8

4

不幸的是,没有简单的方法可以解决这个问题。对于这种情况,最常见的纯 C 方法是使用 a void*,并将值复制到您分配的内存中到指针中。但是,这使得使用变得棘手,并且非常容易出错。

于 2010-03-02T18:41:31.257 回答
1

list.h在 Linux 内核的通用链表实现中可以找到另一个尚未提及的替代方案。原理是这样的:

/* generic definition */
struct list {
  strict list *next, *prev;
};

// some more code

/* specific version */
struct intlist {
  struct list list;
  int i;
};

如果您创建struct intlist*指针,则可以安全地将它们(在 C 中)转换为struct list*指针,从而允许您编写可以操作的泛型函数,struct list*并且无论数据类型如何,它们都可以工作。

list.h实现使用一些宏技巧来支持struct list在您的特定列表中任意放置,但我更喜欢自己依赖 struct-cast-to-first-member 技巧。它使调用代码更易于阅读。当然,它禁用了“多重继承”(假设您认为这是某种继承),但next(mylist)看起来比next(mylist, list). 另外,如果你能避免钻研offsetofhackery,你最终可能会变得更好。

于 2010-03-02T18:52:10.297 回答
0

由于这是一个大学项目,我们不能只给你答案。相反,我会邀请您思考两个 C 功能:void 指针(您以前可能遇到过)和标记粘贴运算符(您可能没有)。

于 2010-03-02T18:42:08.300 回答
0

您可以通过将 value 定义为 来避免这种情况void* value;。您可以通过这种方式将指针分配给任何类型的数据,但调用代码需要将指针强制转换和取消引用到正确的类型。跟踪这一点的一种方法是向 中添加一个短char数组以struct记录类型名称。

于 2010-03-02T18:42:24.127 回答
0

这个问题正是为 C++ 开发模板的原因。我在 C 中使用过一次或两次的方法是让 value 字段为 void*,并在插入时将值转换为该值,并在检索时将它们转换回。当然,这远非类型安全。为了获得额外的模块化,我可能会为您使用它的每种类型编写 insert_int()、get_mystruct() 等函数,并在那里进行转换。

于 2010-03-02T18:44:45.180 回答
0

您可以使用 Void* 代替 int。这允许数据是任何类型的。但是用户应该知道数据的类型。

为此,您可以选择拥有另一个代表 Type 的成员。这是枚举 {INT,CHAR,float...}

于 2010-03-02T18:45:18.647 回答
0

与可以使用的 C++ 不同templatevoid *它是事实上的 C 解决方案。

此外,您可以将链表的元素放在单独的结构中,例如:

typedef struct sLinkedListElem {
    int value; /* or "void * value" */
} ListElem;

typedef struct sLinkedList {
    ListElem data;
    struct sLinkedList *next;
} List;

以便可以在不影响链接代码的情况下更改元素。

于 2010-03-02T18:54:30.467 回答
0

以下是 C 中链表实用程序的示例:

struct Single_List_Node
{
    struct Single_List * p_next;
    void *               p_data;
};

struct Double_List_Node
{
    struct Double_List *    p_next;
    struct Double_List *    p_prev; // pointer to previous node
    void *                  p_data;
};

struct Single_List_Data_Type
{
    size_t                         size; // Number of elements in list
    struct Single_List_Node *      p_first_node;
    struct Single_List_Node *      p_last_node; // To make appending faster.
};

一些通用函数:

void    Single_List_Create(struct Single_List_Data_Type * p_list)
{
    if (p_list)
    {
        p_list->size = 0;
        p_list->first_node = 0;
        p_list->last_node = p_list->first_node;
    }
    return;
}


void    Single_List_Append(struct Single_List_Data_Type *   p_list,
                           void *                           p_data)
{
    if (p_list)
    {
        struct Single_List_Node * p_new_node = malloc(sizeof(struct Single_List_Node));
        if (p_new_node)
        {
            p_new_node->p_data = p_data;
            p_new_node->p_next = 0;
            if (p_list->last_node)
            {
                p_list->last_node->p_next = p_new_node;
            }
            else
            {
                if (p_list->first_node == 0)
                {
                    p_list->first_node = p_new_node;
                    p_list->last_node = p_new_node;
                }
                else
                {
                    struct Single_List_Node * p_last_node = 0;
                    p_last_node = p_list->first_node;
                    while (p_last_node->p_next)
                    {
                        p_last_node = p_last_node->p_next;
                    }
                    p_list->last_node->p_next = p_new_node;
                    p_list->last_node = p_new_node;
                }
            }
            ++(p_list->size);
        }
    }
    return;
}

您可以将所有这些函数放入单个源文件中,并将函数声明放入头文件中。这将允许您将这些功能与其他程序一起使用,而不必一直重新编译。void *for 指向数据的指针将允许您使用具有许多不同数据类型的列表。

(以上代码按原样提供,未经任何编译器测试。错误修复的责任取决于示例的用户。)

于 2010-03-03T01:02:46.293 回答