1:什么是回调函数
2:为什么需要回调函数
3:有哪些函数可以做回调函数
4:小结
回调函数本质上也是普通函数,只是调用机制有所区别,----首先通过传参的形式将该函数的地址传递给其他函数,然后在其他函数中通过函数指针调用该函数,那么在其他函数中通过函数指针调用该函数的过程就称为:回调。而作为被调用的该函数则被称为回调函数。下面我们一步一步解释,为什么需要回调函数。
1: 这就不得不说联合开发带来的后果,-----接口兼容性问题,举个例子:程序员小A和小B联合开发一个项目,要求小A开发的函数必须为小B开发的函数提供灵活的接口。
// test.h
#pragma once
#include
using namespace std;
// 声明一个接口,提供给小B,开发
int add(int, int);
int Add(int a, int b) {
cout << add(a, b) << endl;
}
// main.cpp
#include "test.h"
#include
#include
using namespace std;
// 小B定义接口。是不是和JAVA里面接口回调很相似。
int add(int a, int b) {
return a + b;
}
void main() {
Add(1, 2);
}
2: 现在为了并行开发:开发内容没有变化,但是需要小B单独在另一个文件中开发,来提高工作效率。
// test.h
#pragma once
#include
using namespace std;
// 声明一个接口,提供给小B,开发
int add(int, int);
inline void Add(int a, int b) {
cout << add(a, b) << endl;
}
// test.cpp
#include "test.h"
#include
#include
using namespace std;
// 小B定义接口。是不是和JAVA里面接口回调很相似。
int add(int a, int b) {
return a + b;
}
// main.cpp
#include "test.h"
#include
#include
using namespace std;
void main() {
Add(1, 2);
}
3: 但是这-似乎也没什么,不就是在一个函数中调用另一个函数吗?(简单来说就是:小A声明一个接口,小B定义这个接口,然后在合适时机小B定义的这个函数被调用。)
1:小A不再需要每次调用小B定义的函数之前,都要进行声明。
2:小A只需要提供一个函数指针来接收小B传过来的函数地址,而不用在考虑函数名
3:小A只需要用这个指针可以直接调用小B定义的函数。
4:小B可以自己起函数名。
5:通过函数指针实现回调函数
// test.h
#pragma once
#include
using namespace std;
// 定义一个匿名的函数指针
inline void Add(int(*callbackFun)(int, int), int a, int b) {
cout << callbackFun(a, b) << endl;
}
// test.cpp
#include "test.h"
#include
#include
using namespace std;
// 小B定义接口。是不是和JAVA里面接口回调很相似。
inline int add(int a, int b) {
return a + b;
}
// main.cpp
#include "test.h"
#include "test.cpp"
#include
#include
using namespace std;
void main() {
Add(add,1, 2);
}
显然从上面的例子可以看出:回调函数必须通过指针进行传递和调用,为了简化代码,一般会将函数指针起各别名,格式为:
typedef 返回值类型 (*指针名) (参数列表)
回调函数规避了在调用函数前声明的弊端,而且能够让用户直观的感受到自动定义的函数被调用,小A需要在声明函数指针时规定参数列表,小B在定义回调函数时需要与小A声明的函数指针保持相同的参数列表。
当然如果小A提前为回调函数形参设置了默认值,那么小B也可以决定不使用。
// test.h
#pragma once
#include
using namespace std;
// 定义函数指针类型
typedef int(*callbackFun)(int, int, int);
inline void Add(callbackFun callback, int a, int b,int c = 10) {
cout << callback(a, b,c) << endl;
}
// test.cpp
#include "test.h"
#include
#include
using namespace std;
// 小B定义接口。是不是和JAVA里面接口回调很相似。
inline int add(int a, int b,int c) {
return a + b +c;
}
// main.cpp
#include "test.h"
#include "test.cpp"
#include
#include
using namespace std;
void main() {
Add(add,1, 2);
}
可以做回调函数的函数在C++中目前有两种情况,第一种C语言风格函数,第二种静态成员函数。第一种就是上面的格式,这个我们已经做 了介绍,下面详细说说静态成员函数。
众所周知:类的非静态成员函数的参数列表中隐含了一个this指针,当用类的对象访问类的非静态成员函数时,编译器慧将this指针指向该对象,从而保证了函数体中操纵的成员变量是该对象的成员变量,即使你没有写this指针,编译器在编译的时候,还是会自动添加this指针。
那么这就会造成一个问题:非静态成员函数的参数列表和函数指针参数列表个数无法匹配。
如下所示:函数指针callbackFun有两个参数,非静态成员函数隐含this指针,从而有三个参数,显然不匹配。
#include
#include
using namespace std;
class AddClass {
private:
int a, b;
public:
int add(int a, int b);
};
int AddClass::add(int aa, int bb) {
this->a = aa;
this->b = bb;
return a + b;
}
void Add(int(*callbackFun)(int, int), int c =0, int d = 0) {
cout << callbackFun(c, d) << endl;
}
void main() {
//Add(add,1, 2);
Add(AddClass().add, 1, 2); // error : 指向绑定函数的指针只能用于调用函数
// 显然非静态成员函数参数列表有 三个参数
// 而 函数指针:int(*callbackFun)(int ,int) 只有两个参数
}
但是你如果执着,使用非静态成员函数,就只能通过全局函数中转,如下例所示:
#include
#include
using namespace std;
class AddClass {
private:
int a, b;
public:
int add(int a, int b);
};
int AddClass::add(int aa, int bb) {
a = aa;
b = bb;
return a + b;
}
void Add(int(*callbackFun)(int, int), int c =0, int d = 0) {
cout << callbackFun(c, d) << endl;
}
int MyAdd(int a, int b) {
AddClass addClass;
return addClass.add(a, b);
}
void main() {
//Add(add,1, 2);
Add(MyAdd, 1, 2);
}
由于:类的静态成员函数属于类,为所有对象共享,它没有this指针,因此这里我们采用静态成员函数作为回调函数,但是这样我们遇到另一一个问题:静态成员函数无法方位类的非静态成员。
因此为了解决上述这个问题,我们需要在静态成员函数参数列表中做个修改:
在形参列表中加入 万能指针 *void 作为形参,然后在函数体中做类型强转。
// test.h
#pragma once
#include
using namespace std;
// 定义函数指针类型
typedef int(*callbackFun)(int, int, void*);
inline void Add(callbackFun callback, int a, int b,void* p) {
cout << callback(a, b, p) << endl;
}
// test.cpp
#include "test.h"
#include
#include
using namespace std;
class AddClass {
private:
int a, b;
public:
inline static int add(int a, int b,void* temp);
};
int AddClass::add(int aa, int bb,void* p) {
AddClass* temp = (AddClass*)p;
if (temp)
{
temp->a = aa;
temp->b = bb;
}
return temp->a + temp->b;
}
// main.cpp
#include "test.cpp"
#include
#include
using namespace std;
void main() {
//Add(add,1, 2);
AddClass* addclass = new AddClass;
Add(AddClass::add, 1, 2, addclass);
delete addclass;
}
1:回调函数本质其实是对函数指针的一种应用,上面的例子都比较简单,还没有完成体现回调函数的威力。
2:回调函数是一种设计系统的思想,能够解决系统架构中的部分问题,但是系统中不宜过多使用回调函数,因为回调函数会改变整个系统运行轨迹和执行顺序,耗费资源,而且会使代码变得臃肿
3:C++ STL中大量使用了回调函数的例子,比如遍历函数 for_each()中的 lambda表达式就是一个回调函数。
#include
#include
#include
using namespace std;
int main(){
vector v{1,2,3,4,5};
for_each (v.begin(), v.end(), [=](int val){
cout<< val<< " "<< endl;
});
}
#include
#include
#include
using namespace std;
class A
{
std::function callback_;
public:
A(const std::function& f) :callback_(f) {
cout << "构造函数" << endl;
};
void notify(void)
{
cout << "notify" << endl;
callback_();
}
};
class Foo {
public:
void operator()(void)
{
// __FUNCTION__是对应的函数名
std::cout << __FUNCTION__ << std::endl;
}
};
int main(void)
{
Foo foo;
// 仿函数调用
foo();
// 仿函数对象表示:匿名的 function
A aa(foo);
aa.notify();
}