callback 回调函数

一、调用和回调的定义

回调函数有点绕,可用简单的一个案例说明:

  • A给B打电话,问:1+1等于几? B说,我也忘了,我得想一会,想好了再告诉你。这时比较好的方式是断开电话链接,等B想好了再告诉A。于是他们挂断了电话,A又去忙别的事情,B算好了答案,给A打电话,告诉A结果等于2。
  • 上述,开始A给B打电话,后来,B想好了答案主动给A打电话的过程就是回调。

1.1 调用

  • 同步调用, 是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;
  • 异步调用, 是一种类似消息或事件的机制,不过它的调用方向刚好相反, 接口的服务在收到某种讯息或发生某种事件时,会主动通知客户端(即调用客户端的接口)。

1.2 回调

  • 回调, 是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;
  • 回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。
  • 同步调用是三者当中最简单的,而回调又常常是异步调用的基础,

1.3 架构回调实现

下面我们集中比较具有代表性的语言(C)和架构(C++)来分析回调的实现方式、具体作用等。

1.3.1 函数指针

1.函数和函数指针:

//1111111111111111111
void Func(char *s)// 函数原型
void (*pFunc) (char *);//函数指针

//222222222222222222
int* func(int params, ...); //这就是指针函数

2.可以看出,函数的定义和函数指针的定义非常类似。 一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下

//1111111111111111111
typedef void(*pcb)(char *);

//222222222222222222
// 为回调函数命名,类型命名为 CallBackFun,参数为char *p 。
typedef int (*CallBackFun)(char *p); 

3.回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。
被回调函数的案例:

//11111111111111111
void GetCallBack(pcb callback)
{
    /*do something*/
}

//2222222222222222
//下面的这些是调用者:有几种方式,看你喜欢哪种
int call(CallBackFun pCallBack, char *p)
 {    // 执行回调函数,方式一:通过命名方式
printf("call 直接打印出字符%s!\n", p);
pCallBack(p);
return 0;
}
int call2(char *p, int (*ptr)())
{     // 执行回调函数,方式二:直接通过方法指针
printf("==============\n", p); 
(*ptr)(p); 
}
int call3(char *p, CallBackFun pCallBack)

{      // 执行回调函数,方式一:通过命名方式
printf("--------------\n", p);
pCallBack(p); 
}

4.用户在调用上面的函数时,需要自己实现一个pcb类型的回调函数:

//111111111111111
void fCallback(char *s) 
{
    /* do something */
} 

//222222222222222
int CallBackFun(char * p){
//TODO
}

5.然后,就可以直接把fCallback当作一个变量传递给GetCallBack,

//1111111111111
GetCallBack(fCallback);

//2222222222
//关联

6.如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。
赋值可以发生在运行时,这样使你能实现动态绑定。

1.3.2 参数传递规则

  • 到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。
  • 如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。
  • C++ Builder也支持_fastcall调用规范。
  • 调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
  • 将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。

例如:

// 被调用函数是以int为参数,以int为返回值
__stdcall int callee(int); 

// 调用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int)); 

// 在p中企图存储被调用函数地址的非法操作
__cdecl int(*p)(int) = callee; // 出错, 前者是__cdecl ,后者是__stdcall

指针p和callee()的类型不兼容,因为它们有不同的调用规范。
因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列

1.4 回调函数的优缺点

1.4.1 优点

  • 很多时候回调函数可以用来执行条件驱动的任务。即当该回调函数关心的那个条件被触发时,回调函数将被执行。条件触发可以是某一时间到了或者某一事件发生或者某一中断触发。

  • 回调函数不是由该函数的实现方直接调用,而是在特定的条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

  • 使程序设计更灵活。允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。而且这一设计允许了底层代码调用在高层定义的子程序。

  • 回调函数最大的优势应用就是异步回调。

  • 程序变成异步的了,也就是你不必再调用这个函数的时候一直等待这个时间的到达、事件的发生或中断的发生(万一一直不发生,你的程序会怎么样?)

  • 在此期间你可以做别的事情,或者四处逛逛。当回调函数被执行时,你的程序重新得到执行的机会,此时你可以继续做必要的事情了。

1.4.2 缺点

  • 学习成本会比普通函数高,需要有一定的抽象思维能力,需要对应用场景的理解。
  • 回调函数很多情况下会附带有跨线程操作甚至于跨进程的操作,这些都是异步带来的成本。

2.案例

// 类A
class A{
public:
//A 的构造函数
A(){}
//函数指针类型自定义一下
typedef std::function<void(const std::shared_ptr<Submap2D>&)> UpdateFinishSubmapCallback;

//Test 函数,某种情况下会调用回调函数
void Test(){
std::shared_ptr<Submap2D> submaps;
if(submaps)
	SetUpdateSubmapCallback(submaps);
}
// 
void SetUpdateSubmapCallback(UpdateFinishSubmapCallback callback) {
    updateFinishSubmapCallback_ = callback;
  }
private:
//定义回调函数对象
UpdateFinishSubmapCallback updateFinishSubmapCallback_;

}

class B{
public:
A a_;
B(){
// B 构造时,将回调函数关联起来 UpdateFinishSubmapCallback  std::placeholders::_1 指一个参数  ,多个参数 std::placeholders::_2...
    a_.SetUpdateSubmapCallback(std::bind(&A::UpdateFinishSubmapCallback, this, std::placeholders::_1));
}
void spin(){
void (
// TODO
a_.Test();
}
private:
//声明和定义回调函数
bool UpdateFinishSubmapCallback(const std::shared_ptr<Submap2D>& submap){
	//TODO
}
}

B b;
b.spin();
  1. 当B类调用 函数spin()时,会调用A中的 Test() 函数
  2. A中 Test() 一定条件会调用A的函数SetUpdateSubmapCallback()
  3. A的函数SetUpdateSubmapCallback() 调用回调函数 UpdateFinishSubmapCallback;
  4. A的UpdateFinishSubmapCallback 回调函数在B中调用,会调用B中bool
  5. UpdateFinishSubmapCallback(const std::shared_ptr& submap){} 函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值