对于即将到来的大学C项目,我被要求拥有模块化代码,因为C允许它。基本上,我将.c文件和相应的.h文件用于某些数据结构,如链表,二叉树,哈希表,等等...
使用链接列表作为示例,我有:
typedef struct sLinkedList {
int value;
struct sLinkedList *next;
} List;
但这会强制value
为int
类型,并且使用此链表库的用户将被迫直接更改库的源代码。我想避免这种情况,我想避免更改库,以使代码尽可能模块化。
我的项目可能需要使用链表来获取整数列表,或者可能需要使用某个结构列表。但我不会复制库文件/代码并相应地更改代码。
我该如何解决这个问题?
答案 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 *
将允许您使用具有许多不同数据类型的列表。
(上面的代码按原样进行,并且未经过任何编译器的测试。错误修复的责任取决于示例的用户。)