我正在编写一个链表实现,并想创建一个foreach
函数。我想要这个函数有两个版本:一个供内部使用,它在节点本身上进行迭代,一个供最终用户对节点中所保存的数据进行迭代。由于每个代码几乎相同,因此我想根据第一个定义第二个版本。现在,对于每个版本中用于迭代的函数,我有两个typedef
和foreach的内部版本:
// The internal-use version that takes a node
typedef void(*Foreach_Node_Function)(size_t index, Node* node);
// The end-user version that takes raw data
typedef void(*Foreach_Function)(size_t index, void* data);
void static foreach_node(Linked_List* ll, Foreach_Node_Function f) {
Node* current = ll->head;
for (size_t i = 0; i < length(ll); i++) {
(*f)(i, current);
current = current->next;
}
}
我需要能够编写将Foreach_Node_Function
变成Foreach_Function
的适配器。如果我可以使用匿名功能,那将是微不足道的。我可以在Foreach_Function
上关闭一个函数,并在传递给定数据之前对其进行修改。诸如此类(假装lambda
就是这样)
void foreach(Linked_List* ll, Foreach_Function f) {
Foreach_Node_Function adapted = lambda(size_t i, Node* node) {
// Only pass on the data to f
(*f)(i, node->data);
};
foreach_node(ll, adapted);
}
C似乎并不支持匿名函数,除非您愿意诉诸compiler specific hacks,而且我不知道如果没有匿名函数,该怎么实现。
这可以用C实现吗?如果可以,怎么办?
答案 0 :(得分:5)
通常,当您具有这样的回调函数时,可以添加一个额外的void *
参数(通常称为 callback data 或 user data ),该参数可以使调用者将额外的数据传递给回调函数。因此,您将拥有:
typedef void(*Foreach_Node_Function)(size_t index, Node* node, void *userdata);
typedef void(*Foreach_Function)(size_t index, Node* node, void *userdata);
void static foreach_node(Linked_List* ll, Foreach_Node_Function f, void *f_userdata);
然后,您可以将要改编的回调及其其 void *
参数传递给适配器回调:
struct foreach_node_adapter_userdata {
Foreach_Function f;
void *f_userdata;
};
static void foreach_node_adapter(size_t index, Node *node, void *userdata) {
struct foreach_node_adapter_userdata *adapter_userdata = userdata;
(*adapter_userdata->f)(index, node->data, adapter_userdata->f_userdata);
}
void foreach(Linked_List* ll, Foreach_Function f, void *f_userdata) {
struct foreach_node_adapter_userdata adapter_userdata = {f, f_userdata};
foreach_node(ll, foreach_node_adapter, &adapter_userdata);
}
答案 1 :(得分:1)
自从您标记了此C11以来,您可以为此使用_Generic
。 _Generic
很好,因为它是类型安全的,而不像老学校void*
那样非常危险。它还解决了不能与函数指针类型一起使用的void*
泛型的问题。
您可以使用“ functor”函数来对容器中的每个项目进行调用,例如:
typedef void node_operation_t (size_t index, node_t* node);
// functions following this form:
void node_add (size_t index, node_t* node);
void node_delete (size_t index, node_t* node);
...
然后您要这样称呼
linked_list_t list;
traverse(&list, node_delete);
(命名:“遍历”是C语言中容器通用迭代的常用术语,而“ foreach”则是其他各种语言中的循环关键字。)
您现在可以为此创建一个_Generic
宏,接受链接列表或其他容器:
#define traverse(list, func) \
_Generic((func), \
node_operation_t*: traverse_linked_list, \
foo_operation_t*: traverse_foo \
)(list, func)
这通过检查功能指针类型来选择适当的功能。仅当不同的函数具有不同的类型时才需要这样做,否则您也可以使traverse
成为函数。
类似地,可以使用相同的容器类型对不同的参数输入进行多个操作。许多可能性。
traverse_linked_list
类似于:
void traverse_linked_list (linked_list_t* ll, node_operation_t* op)
这只是循环遍历列表,并为每个节点调用op
。如果要将参数从调用方一直传递到每个单独的操作(例如“将所有项目设置为值5”),则可以扩展它以获取更多参数。