C语言中的模块化数据结构,具有动态数据类型

时间:2010-03-02 18:37:43

标签: c data-structures modular

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

使用链接列表作为示例,我有:

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

但这会强制valueint类型,并且使用此链表库的用户将被迫直接更改库的源代码。我想避免这种情况,我想避免更改库,以使代码尽可能模块化。

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

我该如何解决这个问题?

8 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:1)

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

/* 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)更好。另外,如果你可以避免钻研offsetof hackery,你可能最终会变得更好。

答案 2 :(得分:0)

由于这是一个大学项目,我们不能只给你答案。相反,我邀请你冥想两个C特征:空指针(你以前可能遇到过)和token pasting operator(你可能没有)。

答案 3 :(得分:0)

您可以通过将值定义为void* value;来避免这种情况。您可以通过这种方式为任何类型的数据指定指针,但是需要调用代码来将指针强制转换为正确的类型。跟踪此问题的一种方法是向char添加一个简短的struct数组,以记下类型名称。

答案 4 :(得分:0)

这个问题恰恰是为C ++开发模板的原因。我在C中使用过一次或两次的方法是将value字段设为void *,并在插入时将值转换为值,然后在检索时将其强制转换。当然,这远非类型安全。为了获得额外的模块性,我可以为你使用它的每种类型编写insert_int(),get_mystruct()等函数,并在那里进行转换。

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

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

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

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

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

答案 7 :(得分: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 *将允许您使用具有许多不同数据类型的列表。

(上面的代码按原样进行,并且未经过任何编译器的测试。错误修复的责任取决于示例的用户。)