1

我正在使用经典的 C 进行此类分配,并且遇到了关于采用可变参数计数和类型的回调函数的问题。

基本上,我正在研究一个散列树(每个节点都是一个散列树的树),并且我有一个特定的遍历策略,将多次用于不同的目的,所以我将它实现为ht_walk(HashTree tree, (*callback)(Element e)),这样称为回调的函数将以任何必要的方式处理元素。

问题是,在我的问题中的大多数情况下,回调函数必须采用不同的参数。我知道如何使用“可变参数”函数(使用 stdarg、printf-way)设计一个带有可变参数列表的函数,但我不知道如何将这些参数“重新传递”给回调函数。

让我提供一个具体的例子:假设我有一个名为 的回调函数addToList(Element e, List list),并且我的 ht_walk 声明是 now ht_walk(HashTree tree, (*callback)(Element e), ...)。考虑我想在以下代码段中使用 ht_walk :

HashTree my_tree = ht_create();
/* run some algorithm that populates the tree somehow */
List my_list = list_create();
ht_walk(my_tree, addToList, my_list);

有没有办法做到这一点?提前致谢!

4

5 回答 5

3

您可以使用以下两种方法之一来解决此问题。

最常见、易于理解和干净的方法是使用“用户”结构:

void ht_walk(HashTree tree, void (*callback)(Element e, void *user), void *user);

void addToList(Element e, void *arg)
{
    STATIC_ASSERT(sizeof(void *) >= sizeof(List));

    List list = arg;

    /* ... */
}

HashTree my_tree = ht_create();
/* run some algorithm that populates the tree somehow */
List my_list = list_create();
ht_walk(my_tree, addToList, my_list);

另一种方法是接受va_list

#include <stdarg.h>

void ht_walk(HashTree tree, void (*callback)(Element e, va_list args), ...)
{
    for(..)
    {
        va_list args;
        va_start(args, callback);
        callback(element, args);
        va_end(args);
    }
}

void addToList(Element e, va_list args)
{
    List list = va_arg(args, List);

    /* ... */
}

HashTree my_tree = ht_create();
/* run some algorithm that populates the tree somehow */
List my_list = list_create();
ht_walk(my_tree, addToList, my_list);
于 2009-03-20T01:17:15.127 回答
1

有一系列函数和宏用于处理可变参数(例如 va_start) GNU 有一个很好的指南here

话虽如此,您似乎在描述一种访问者模式。我不确定我是否喜欢在这里使用可变参数列表。如果您通常无法确定您的回调将为所有节点一致地获取哪些参数,那么您一开始就有麻烦了。

于 2009-03-20T01:11:33.113 回答
1

我不认为你想要做的是传递任意数量的参数。我认为您在这里遇到的情况是您有多种类型的回调。在您的示例中,您传递了一个列表,但您有时也会提到您将传递一个元素。所以在我看来,你真正想要做的是声明你的回调采用 void* 和一个标志来指示它是什么。就像是:

void callback(void* arg, int type) {
    switch (type) {
    case ARG_TYPE_LIST:
        List* list = (List*)arg;
        ...
        break;
    case ARG_TYPE_ELEMENT:
        Element* ele = (Element*)arg;
        ...
        break;
    ...
}

如果您将事物作为列表传递,则列表应该知道计数,因此您不必担心传递它。

或者,您可以定义多种类型的回调,并始终根据您需要执行的处理类型来回调正确的类型。

如果您不拘泥于纯 C(即您可以使用 C++),您可以使用boost::bind进行调查,因为它对这类事情非常有用。

于 2009-03-20T01:17:47.247 回答
0

我相信你使用va_list宏。请参阅stdarg 的手册页

虽然我已经有一段时间没有尝试过了......

于 2009-03-20T01:10:32.007 回答
0

我会传入一个 void *,因为我认为您不能在回调函数中使用可变数量的参数。不过我可能是错的。

typedef void (*tree_walk_callback)(Element e, void *data);

add_element(Element e, void *data)
{
    List *list = (List *)data;
    add_element_to_list(list, e);
}

...in your function...
List my_list;
ht_walk(my_tree, &add_element, (void *)&my_list);

如果用户想要传递多个参数,那么他们将传递一个包含他们需要的任何内容的结构。

于 2009-03-20T01:11:09.107 回答