虽然可以利用Gcc或者Clang的特性在C语言中使用闭包,但是在Windows下这些编译器都不好用。所以如果要写跨平台的C程序,有时候还是只能自己人肉实现闭包了(当一个语言的表达能力不够强的时候,往往需要程序员自己模拟编译器实现一些语法结构)。闭包其实是函数代码和其定义时的环境数据的一个结合体。C语言原生只支持函数指针,这时候就需要程序员自己手动传递环境数据。在大部分C代码中,这个环境数据往往用void *表示。对于前几篇文章里提到的目录遍历应用,使用void*指针定义环境数据的接口如下:
- typedef void (*pDirentVisitor)(struct dirent * dp, char *dir_name, void *context);
- extern void each(pDirentVisitor visitor, char *dir_name, void *context);
和前两篇文章中支持闭包的接口相比
- typedef void (*pDirentVisitor)(struct dirent * dp);
-
-
extern void each(pDirentVisitor visitor, char *dir_name);
Visitor函数多了两个参数。迭代函数each函数多了一个参数context,它其实只是简单将context传递给Visitor函数。
- void each(pDirentVisitor visitor, char *dir_name, void *context){
-
struct dirent * dp;
-
DIR * dirp = opendir(dir_name);
-
if(dirp==NULL) return;
-
while ((dp = readdir(dirp)) != NULL){
-
(*visitor)(dp, dir_name, context);
-
}
-
closedir(dirp);
-
}
如果visitor函数只需要从上下文环境中获得多个参数,那么必须提前定义一个结构体,然后把需要用到的参数拷贝到结构体中,然后将结构体的指针赋值给context。以文件名和大小查找为例,要先定义一个结构体:
- typedef struct find_context{
-
char *find_str;
-
int min_byte;
-
} FindContext, *pFindContext;
然后在调用时人肉给结构体赋值,
- void find(char *dir_name, char *find_str, int min_byte){
-
FindContext ctx;
-
ctx.find_str=find_str;
-
ctx.min_byte=min_byte;
-
each(compare_name_and_size, dir_name, &ctx);
-
}
这是C语言通行的做法。
但是每次都提前定义一个结构体太麻烦了,所以笔者想了一个可变参数的接口。其接口定义如下:
- typedef void (*pDirentVisitor)(struct dirent * dp,char *dir_name, va_list args);
-
-
extern void each(pDirentVisitor visitor, char *dir_name, ...);
这样的话,调用each来遍历目录的时候,有几个参数就传递几个,也不用提前定义结构体,会方便一些。但是visitor需要自己将参数从va_list中解析出来。比如上篇文章中的compare_name_and_size函数就要增加两行参数解析的代码:
- void compare_name_and_size(struct dirent * dp, char *dir_name, va_list args){
-
char *find_str =va_arg(args, char *);
-
int min_byte =va_arg(args, int);
-
......
-
}
采用可变参数的话,写each函数的工作量就要大一些了,代码如下:
- void each(pDirentVisitor visitor, char *dir_name, ...){
-
va_list args,args2;
-
struct dirent * dp;
-
DIR * dirp = opendir(dir_name);
-
if(dirp==NULL) return;
-
va_start (args, dir_name);
-
while ((dp = readdir(dirp)) != NULL){
-
va_copy(args2,args);
-
(*visitor)(dp, dir_name, args2);
-
va_end(args2);
-
}
-
closedir(dirp);
-
va_end(args);
-
}
其中一个值得注意的地方是在循环中一定要调用va_copy,否则参数解析会出错。此外,windows下没有va_copy宏,直接用=给va_list赋值即可。
用void *传递数据的完整代码例子在:
用可变参数传递数据的完整代码例子在:
阅读(1831) | 评论(0) | 转发(0) |