在理解“回调函数”之前,首先讨论下函数指针的概念。
函数指针概述
指针是一个变量,是用来指向内存地址的。一个程序运行时,所有和运行相关的物件都是需要加载到内存中,这就决定了程序运行时的任何物件都可以用指针来指向它。函数是存放在内存代码区域内的,它们同样有地址,因此同样可以用指针来存取函数,把这种指向函数入口地址的指针称为函数指针。
下面是个使用函数指针的例子:
#include <iostream>
// 也可以用宏定义的方式来声明函数指针
// typedef int (*fp)(int, int);
int testFun(int a, int b) {
return a + b;
}
int main() {
// 定义一个函数指针fp,初始化指向testFun函数
int (*fp)(int, int) = testFun;
// 通过函数指针fp,调用testFun函数
int sum = fp(1, 2);
std::cout << sum << std::endl; // 输出:3
return 0;
}
由上知道:*函数指针与函数的声明之间唯一区别就是,用指针名(fp)代替了函数名 testFun,这样这声明了一个函数指针,然后进行赋值fp=testFun就可以进行函数指针的调用了。下面可以讨论需要用到函数指针的回调函数了。
回调函数概述
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
回调函数的机制如下:
- 定义一个回调函数;
- 提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
- 当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
回调函数的意义
因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。简而言之,回调函数就是允许用户把需要调用的函数的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。下面看几个应用:
- 回调可用于通知机制。比如要写一个多线程下载器,需要显示下载进度,在另外的下载线程类中下载好了文件时,此时不方便也不允许直接调用进度条界面的刷新进度值函数,这时候就需要将刷新进度值函数设置为回调函数了,作为下载线程中调用者函数的参数,执行调用者函数就相当于执行回调函数,就可以刷新界面的进度值了。
- 另一个使用回调机制的 API 函数是 EnumWindow(),它枚举屏幕上所有的顶层窗口,每个窗口都可以通过它调用另一个程序提供的函数,并传递窗口的处理程序。例如:如果被调用者返回一个值,就继续进行迭代;否则,退出。EnumWindow() 并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。
- 还有图形界面客户端常用 事件循环 (event loop) 有条不紊的处理用户输入/计时器/系统处理/跨进程通信 等事件,一般采用回调响应事件。
注意:不管怎么说,回调函数是继承自C语言的。在 C++ 中,应只在与C代码建立接口或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在 C++ 中应使用虚拟方法或仿函数(functor),而不是回调函数。
用函数指针实现回调函数
不带参回调函数的示例如下:
#include <iostream>
// 定义不带参回调函数
void callbackFun() {
std::cout << "This is callbackFun";
}
// 定义参数为回调函数的调用者(一般在其它系统或子线程中)
void callbackExec(void (*fp)()) {
return fp();
}
int main() {
// 当特定的事件或条件发生的时候,调用者调用回调函数callbackFun
callbackExec(callbackFun); // 输出:"This is callbackFun"
return 0;
}
带参回调函数:
#include <iostream>
// 定义带参回调函数
int callbackFun(int a, int b) {
return a + b;
}
// 定义参数为回调函数的"调用函数"
int callbackExec(int (*fp)(int, int), int a, int b) {
return fp