callback,函数的回调,从 ANSI C 开始,一直被广为使用。无论是 Windows API 的所谓消息机制、动态链接库的调用,还是 SQLite 的命令、GCC 下的 pthread、qsort,callback 都在其中起着难以替代的作用。
那么,callback 为何如此受到青睐呢?因为它可以以一个函数为参数,放到参数列表交给 API,让 API 调用。
比如有个文件夹下有一千万个文件,我们希望对每个文件进行一些处理,比如查看文件长度是否超过 512 字节。而遍历这个文件夹的程序是个 API。如果不使用 callback,API 可能有两种办法。
一种是申请一个超巨大的空间,然后把找到的所有文件名都放进去,然后返回这个空间(类似的还有把这组文件名写到文件中,一个意思),这样速度的确很快,而且简单方便,但问题是,一千万个文件,占用的空间是如此的庞大,空间复杂度为 O(n),而我们只是要对每个文件进行一点小小的处理而已,实在是杀鸡焉用宰牛刀。
另一个方法是搞个迭代器,每次只返回一个文件名,下次调用再返回下个。为了线程安全,每次传参都得来个指针之类的记录自己的进度。如果做的细心,也可以做的很快,也不占用空间。但问题是,只是个简单的遍历文件夹的 API,这么折腾好累哦。而如果使用 callback,事情就简单很多了。主程序调用 API,把处理文件的函数用参数的方法传递给 API,API 每找到一个文件名,就调用这个函数,当场处理不用存储任何文件名。这样空间复杂度是 O(1),而且方便有效,何乐而不为呢?
callback 具体怎么实现的呢?我在本文中做一个小小的 demo。
Demo 的实现的功能是这样的:
bar.c 调用 foo.c 的一个函数,请求给一个 magic number。但 foo.c 的函数并不是直接返回,而是调用 bar.c 传递过来的函数 use_magic_num,把 magic number 作为参数使用进去。
首先,定义最后要被调用的函数 use_magic_num 如下:
void use_magic_num(int magic_num);
void use_magic_num(int magic_num)
{
printf("bar.c : magic number is %d \n", magic_num);
}也就是传进去一个参数,然后函数会打印出这个 magic number。它被放置在 bar.c 下。
然后 foo.h 中定义 API get_magic_num 的原型:
void get_magic_num(void (*callback)(int magic));它的参数列表和普通的有点区别,传入的是一个函数指针,这个函数必须是原型为有一个 int 类型为参数,返回为 void。其他的就和普通的函数一样了。
对 get_magic_num 进行实现。
typedef void (*foo_run_magic)(int);
void get_magic_num(foo_run_magic foo)
{
int magic = 9;
foo(magic);
}我们可以看到,在这儿,我做了点小花招,我 typedef 了这个函数指针,这样我们可以直接使用 foo_run_magic 来代替麻烦的各种括号的函数指针了。
然后调用 foo,其实就是调用传入的参数。在本工程中,也就是前面写的 use_magic_num 了。
虽然 foo.c 完全看不见 use_magic_num,但完全不妨碍函数的使用。
最后定义 bar.c 的主程序:
int main(int argc,char *argv[])
{
get_magic_num(use_magic_num);
return 0;
}也就是调用 foo 并传入 use_magic_num 的函数了。
编译和执行结果大致是如下的样子:
[yu@argcv callback]$ make
gcc -c -Wall -std=c99 bar.c -o bar.o
gcc -c -Wall -std=c99 foo.c -o foo.o
gcc -Wall -std=c99 -o bar bar.o foo.o
[yu@argcv callback]$ ./bar
bar.c : magic number is 9
[yu@argcv callback]$ 完整的工程请参考 此处。
Comments · 19