在C语言中,接受未知类型参数的通用函数

5

我正在尝试创建一些函数,以处理未知类型的参数并通用地应用函数。

举个例子,我们可以创建一个函数,来将close(int fd)函数应用于数组的每个元素:

void for_each(void *array[], void (*func)(void *))
{
    for (size_t i = 0; array[i] != NULL; ++i)
        func(array[i]);
}

如果我想要使用这个函数,和close(int fd)一起,我需要创建一个类似于下面的封装函数:

void close_fd(void *fd)
{
    close(*(int *)fd);
}

我希望能够使用for_each函数处理字符串、浮点数以及其他所有类型的数据
  • 难道没有一种方法能够实现它,不需要进行包装吗?
  • 我知道C++有很多方法可以完成这个功能,如lambda表达式、模板等,但是在C语言中是否有好的方法呢?折衷方案

请查看以下链接:https://dev59.com/sWkw5IYBdhLWcg3wkLX2 - Bathsheba
这种方法通常会导致过于复杂、难以阅读的代码,你真的需要这样吗? - Anton Malyshev
1
如果你想要C++的功能,就使用C++。我认为你试图在C中实现你所概述的内容会带来很多痛苦。 - Jonathan Leffler
@JonathanLeffler 我正在编写一个学校项目,我不能选择语言(可惜)。 - BiasInput
4
一种 for_each 宏可以做你展示的事情,可能具有稍微更好的类型安全性。不过我不知道你为什么想要这样做,使用它很奇怪,而且最后那个防止空指针的保护条件如果有一天被忘记了会带来很多麻烦。 - Mat
2个回答

1

显然,您想要为不同的数据多次调用一个函数(相同的函数)。 但是这些数据是作为数组传递的,根据定义,数组是存储多个相同类型数据项的一种结构。因此,最终没有特殊的结构来实现这一点,只需在普通的C语言中进行即可:

typedef .... MY_TYPE;

/* define a pointer to represent the type of callback pointer you are using */
typedef void (*callback_ptr)(MY_TYPE param);

/* this is the real callback function you are going to use */
void my_func1(MY_TYPE param){
    /* process a single MY_TYPE */
}

void my_funcN(MY_TYPE param) {
    /* another process function */
}

/* function to apply the callback to an array of MY_TYPE */
void for_each( MY_TYPE array[], size_t array_sz, callback_ptr f )
{
    int i;
    for (i = 0; i < array_sz; i++)
        f(array[i]);
}

....
MY_TYPE my_array[100] = { ... };
size_t my_array_sz = sizeof my_array / sizeof my_array[0];

for_each(my_array, my_array_sz, my_func1); /* process all by my_func1 */
for_each(my_array, my_array_sz, my_funcN); /* process all by my_funcN */

尽可能避免在不严格需要时使用 void *。也许在早期设计阶段,您不知道它将要处理的实际数据类型,但很明显,一旦编写了实际调用语句,您就必须放置参数并使用返回值,因此您就会坚持实际类型....这给了您有关如何声明回调指针和处理函数的提示。在函数调用中放置实际类型使编译器检查正确性,并且这是帮助您避免犯错误的一种方式。
通用函数在C中不受支持,但如果您想要一些排列方式,允许您编写其中某个数据类型为通用类型的函数,则可以使用宏模拟(必须非常小心地编写宏以使其工作,但可以实现非常好的近似)。
您可以使用cpp宏为每种类型定义不同的函数,例如:

func_def.h

#define FUNC(TYPE)                        \
        void my_func_##TYPE( TYPE param ) \
        {                                 \
          /* body of function */          \
        }

然后在文件作用域中包含以下函数:

program.c

FUNC(int)
FUNC(double)

并且这将会扩展为:

        void my_func_int( int param )     \
        {                                 \
          /* body of function */          \
        }
    
        void my_func_double( double param ) \
        {                                   \
            /* body of function */          \
        }

你无论如何都会得到参数类型检查。如你所见,你必须在函数名中使用类型,因为C不支持函数重载(具有相同名称和不同参数列表的函数)。在这种情况下,my_func_*回调和for_each_*函数都必须定义,以便编译器进行完全的类型检查。

我没有理解“any type”部分...你的示例只适用于MY_TYPE类型吗?难道我不需要为每种不同的类型编写一个for_each函数和一个typedef回调吗?您能否更详细地说明一下这个过程?谢谢。 - BiasInput

0

如评论中所提到的,您可以使用宏来实现这一点。在 C 语言中,更习惯于将数组的大小传递给使用它的函数,而不是依赖哨兵值(字符串是例外)。

#define for_each(array, array_size, func) do { \
    for (size_t i = 0; i < (array_size); ++i) { \
        func((array)[i]); \
    } \
} while(0)

使用方法:

#define ARRAY_SIZE(array) (sizeof((array)) / sizeof(*(array)))

void int_func(int arg)
{

}

void string_func(const char *arg)
{

}

int int_array[5];
const char *string_array[5];

void calling_func(void)
{
    for_each(int_array, ARRAY_SIZE(int_array), string_func); // Warning: pointer from integer without a cast
    for_each(string_array, ARRAY_SIZE(string_array), int_func); // Warning: integer from pointer without a cast
}

这非常不错,但在我看来,警告是不可容忍的。 - BiasInput
@RémyMachado 这就是关键所在。我正在展示如果将字符串函数应用于整数数组会发生什么情况:你会得到一个警告。 - Samuel Peter
@RémyMachado 我展示这个是因为这个解决方案更好的地方,你可以在编译时进行一些类型检查。当使用void*时,如果混淆类型,你将不会收到任何警告,这是不好的。 - Samuel Peter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接