Chinaunix首页 | 论坛 | 博客
  • 博客访问: 60344
  • 博文数量: 15
  • 博客积分: 353
  • 博客等级: 一等列兵
  • 技术积分: 175
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-12 16:15
文章分类
文章存档

2011年(15)

我的朋友

分类: C/C++

2011-12-13 22:48:45

    虽然可以利用Gcc或者Clang的特性在C语言中使用闭包,但是在Windows下这些编译器都不好用。所以如果要写跨平台的C程序,有时候还是只能自己人肉实现闭包了(当一个语言的表达能力不够强的时候,往往需要程序员自己模拟编译器实现一些语法结构)。闭包其实是函数代码和其定义时的环境数据的一个结合体。C语言原生只支持函数指针,这时候就需要程序员自己手动传递环境数据。在大部分C代码中,这个环境数据往往用void *表示。对于前几篇文章里提到的目录遍历应用,使用void*指针定义环境数据的接口如下:

  1. typedef void (*pDirentVisitor)(struct dirent * dp, char *dir_name, void *context);

  2. extern void each(pDirentVisitor visitor, char *dir_name, void *context);
和前两篇文章中支持闭包的接口相比
  1. typedef void (*pDirentVisitor)(struct dirent * dp);

  2. extern void each(pDirentVisitor visitor, char *dir_name);
Visitor函数多了两个参数。迭代函数each函数多了一个参数context,它其实只是简单将context传递给Visitor函数。
  1. void each(pDirentVisitor visitor, char *dir_name, void *context){
  2.     struct dirent * dp;
  3.     DIR * dirp = opendir(dir_name);
  4.     if(dirp==NULL) return;
  5.     while ((dp = readdir(dirp)) != NULL){
  6.         (*visitor)(dp, dir_name, context);
  7.     }
  8.     closedir(dirp);
  9. }
如果visitor函数只需要从上下文环境中获得多个参数,那么必须提前定义一个结构体,然后把需要用到的参数拷贝到结构体中,然后将结构体的指针赋值给context。以文件名和大小查找为例,要先定义一个结构体:
  1. typedef struct find_context{
  2.     char *find_str;
  3.     int min_byte;
  4. } FindContext, *pFindContext;
然后在调用时人肉给结构体赋值,
  1. void find(char *dir_name, char *find_str, int min_byte){
  2.     FindContext ctx;
  3.     ctx.find_str=find_str;
  4.     ctx.min_byte=min_byte;
  5.     each(compare_name_and_size, dir_name, &ctx);
  6. }
这是C语言通行的做法。
但是每次都提前定义一个结构体太麻烦了,所以笔者想了一个可变参数的接口。其接口定义如下:

  1. typedef void (*pDirentVisitor)(struct dirent * dp,char *dir_name, va_list args);

  2. extern void each(pDirentVisitor visitor, char *dir_name, ...);
这样的话,调用each来遍历目录的时候,有几个参数就传递几个,也不用提前定义结构体,会方便一些。但是visitor需要自己将参数从va_list中解析出来。比如上篇文章中的compare_name_and_size函数就要增加两行参数解析的代码:
  1. void compare_name_and_size(struct dirent * dp, char *dir_name, va_list args){
  2.     char *find_str =va_arg(args, char *);
  3.     int min_byte =va_arg(args, int);
  4. ......
  5. }
采用可变参数的话,写each函数的工作量就要大一些了,代码如下:

  1. void each(pDirentVisitor visitor, char *dir_name, ...){
  2.     va_list args,args2;
  3.     struct dirent * dp;
  4.     DIR * dirp = opendir(dir_name);
  5.     if(dirp==NULL) return;
  6.     va_start (args, dir_name);
  7.     while ((dp = readdir(dirp)) != NULL){
  8.         va_copy(args2,args);
  9.         (*visitor)(dp, dir_name, args2);
  10.         va_end(args2);
  11.     }
  12.     closedir(dirp);
  13.     va_end(args);
  14. }
其中一个值得注意的地方是在循环中一定要调用va_copy,否则参数解析会出错。此外,windows下没有va_copy宏,直接用=给va_list赋值即可。

用void *传递数据的完整代码例子在:

用可变参数传递数据的完整代码例子在:




阅读(1831) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~