由于对这个问题并不重要的几个原因,是否可以为 ANSI 中的通用结构实现一个列表 - 一个容器?这个相同的列表可以包含不同类型的结构吗?
编辑:经过几个建议,我正在考虑一些选项。但我仍然不清楚为什么没有标准的方法来做到这一点。在 ANSI C 中不需要吗?唯一的方法是使用另一种语言?
干杯
由于对这个问题并不重要的几个原因,是否可以为 ANSI 中的通用结构实现一个列表 - 一个容器?这个相同的列表可以包含不同类型的结构吗?
编辑:经过几个建议,我正在考虑一些选项。但我仍然不清楚为什么没有标准的方法来做到这一点。在 ANSI C 中不需要吗?唯一的方法是使用另一种语言?
干杯
取决于你想做什么,这是可能的。这是一个很大的痛苦,但这是可能的。
您可以使用void
指针来创建通用列表,例如
struct glist {
void *data;
stuct glist *next;
};
该data
成员可以指向任何类型的对象。问题是,一旦分配了指针,就会丢失类型信息;您需要单独跟踪类型。您可以使用整数或枚举值来标记类型,例如
enum dtype {type_A, type_B, type_C, ...};
struct glist {
void *data;
enum dtype data_type;
struct glist *next;
};
或者
#define TYPE_A 1
#define TYPE_B 2
...
struct glist {
void *data;
int data_type;
struct glist *next;
};
当您从列表中检索元素时,您将检查data_type
成员以确定要调用哪些函数或根据数据类型执行哪些过程:
switch (elem->data_type)
{
case TYPE_A: // process for TYPE A
break;
case TYPE_B: // process for TYPE B
break;
...
}
或者,您可以存储一个指向一个或多个对特定数据类型进行操作的函数的指针,并完全跳过 switch 或 if-else 语句:
struct glist {
void *data;
void (*process)(const void *);
void (*copy)(const void *);
void (*delete)(const void *); // not shown
struct glist *next;
};
对于每种数据类型,您创建一个流程函数的实例并将指向该函数的指针与节点联系起来。该函数必须将 avoid *
作为输入,但它会转换为适当的类型以进行处理:
void process_typeA (const void *data)
{
typeA *obj = (typeA *) data; // cast away const
// process obj as necessary
}
void *copy_typeA (const void *data)
{
typeA *obj = (typeA *) data;
typeA *newObj = malloc(sizeof *newObj);
if (newObj)
// copy from obj to newObj
return newObj;
}
void addItem (struct glist *list, const void *obj,
void (*process)(const void *),
void *(*copy)(consg void *),
void (*delete)(const void *))
{
struct glist *newNode = malloc(sizeof *newNode);
newNode->copy = copy;
newNode->delete = delete;
newNode->process = process;
newNode->data = (*copy)(obj);
newNode->next = NULL;
// add newNode to list
}
void foo(void)
{
struct glist *myList = new_list(); // not shown here
...
addItem(myList, myTypeAObj, process_typeA, copy_typeA, delete_typeA);
addItem(myList, myTypeBObj, process_typeB, copy_typeB, delete_typeB);
...
}
然后,当您浏览列表时,您可以调用process
适用于该特定节点的函数:
void processList(struct glist *list)
{
struct glist *node = list;
while (node)
{
node->process(node->data);
node = node->next;
}
...
}
这种方法的美妙之处在于您将该节点的行为与节点本身相关联;您不需要继续向 switch 或 if-else 链添加案例。缺点是您使用大量指针,您必须为要使用的每种数据类型实现一个单独的进程/复制/删除功能,您正在进行大量的内存管理(您需要复制和删除方法,原因一旦考虑就很明显了),并且您将任何类型安全的概念抛到了窗外并进入迎面而来的交通;您将失去任何编译器级别的保护,以防止将错误的进程/复制/删除函数与错误的数据类型相关联。
您可能想阅读 Linux 内核链接列表。(http://isis.poly.edu/kulesh/stuff/src/klist/ 有一些信息)
它可以存储任意结构,但我相信给定列表中只有一种类型。我已经有一段时间没有使用它了,所以我对细节有点生疏。正如理查德所暗示的,它使用了一些预处理器巫术,但不是太多。
你看过sglib吗?
Jacob Navia 为“ C 容器库项目”制定了这个计划。