在工作中不断的遇到和听到回调函数,但自己对回调函数一直并未尝试使用,感觉理解也不够清晰。下面摘了一些其他看起来通俗易懂的说明,先记下来,备忘。
1、基础知识
所谓回调,就是模块A要通过模块B的某个函数b()完成一定的功能,但是函数b()自己无法实现全部功能,需要反过头来调用模块A中的某个函数a()来完成,这个a()就是回调函数。如下图
①约定接口规范。在模块B必须约定接口规范,也就是定义回调函数a()的函数原型
这里回调函数原型的定义最好遵循typedef void (*SCT_XXX)(LPVOID lp, const CBParamStruct& cbNode); SCT_XXX是回调函数名称,lp是回调上下文,CBParamStruct是回调参数,一般由于要回调的参数不止一个,所以定义一个结构体比较方便。
②回调函数的注册。为了让模块B知道自己将要使用的回调函数,必须有一个函数或语句来注册回调函数
注册回调函数的定义遵循void RCF_XXX(SCT_XXX pfn, LPVOID lp); RCF_XXX是注册函数名,pfn是回调函数名称(是指针),lp是回调上下文。一般在A模块初始化完B模块后调用,将A模块中定义的回调函数地址赋值给pfn,lp赋值为this。
③在模块A中要做的事情:
首先将回调函数声明成静态的,static void CF_XXX(LPVOID lp, const CBParamStruct& cbNode); 函数的参数必须与B模块中回调函数原型的参数保持一致。
初始化B模块时,调用注册函数将模块A中声明的回调函数CF_XXX的地址传给pfn,即pfn=CF_XXX;(函数名称CF_XXX其实是个指针,指向回调函数的地址) 。
2、举例
回调函数的yi个应用场景:网络编程。 在网络编程中,为了体现模块化,一般把通讯和数据处理划分开来,即通讯模块负责协议定义、数据收发,而数据处理模块只负责对收发的数据进行解析和打包,假如通讯模块开启了一个线程在持续地接收数据,这个时候问题来了,它通过什么手段把数据交到数据处理模块手中呢?每次收到数据,拿到数据处理模块的指针完成相关操作,这样有犯了两个类指针互相指的错误,也破坏了两个模块的独立性。使用回调函数这些问题都迎刃而解了,下面给出部分伪代码:
通讯模块
typedef void (*DataReceiveCBFunc)(ReceiveParam & recvParam); // 回调函数原型定义
// 开始接收,数据处理模块调用,相对于注册回调函数
static BOOL StartReceive(DataReceiveCBFunc pfnData, LPVOID lpContext,……);
// 接收数据的线程,一收到数据就通知回调
static UINT TH_Receive(LPVOID lp);
数据处理模块
// 开始接收数据,开启监听线程,调用上面的StartReceive函数
int StartReceiveInfo(int nListenPort, std::string strLocalIP);
// 数据接收回调函数,被CUdpEx::TH_Receive()回调
static void RecvInfoCallback(ReceiveParam &recvParam);
3、关于常见的几个函数构成的回调函数
下面来说说回调函数的几个要点:
[ 被调用的函数A ] 可以认为是最后核心的被执行的函数;这个就是回调函数。
[ 对被调用函数进行调用的函数B ] 可以认为是绑定这个被调函数A的地方,让它以一种什么样的形式来使用(例如在循环体中执行N次,对别的变量赋值等等);
[ 触发主动调用函数的函数C ] 真正开始触发各个函数被调用的地方。
在上面的这三个函数A、B、C中,函数A和C通常是在一个应用层级来写的,而函数B则可能在较低的层级实现。
第一块所实现的一些应用逻辑的功能函数,就是函数A或者函数C,具体看是哪一个被传入函数B的,哪一个是主动调B的,被当做参数传入给B的,就是回调函数A,主动调B的,是真正触发各个函数被调用的函数C。
#include
#include
//简单点,让我们命名的方式简单点
/* 定义一种输出为int型,输入参数为两个int型的这一类的函数,并给它一个统一的类别名字,callBackfunName*/
typedef int(*funName)(int x1, int x2);
/* 下述 add() 和 mix() 是两个符合funName这种形式的函数,通常它们处在应用层的下一层,即库函数或者外部接口等*/
int add(int x, int y) //函数A,回调函数
{
printf("add() is called.\n");
return (x+y);
}
int mix(int x, int y)
{
printf("mix() is called.\n");
return (x*y);
}
void call_by_funName(funName f,int x)//函数B,调用调用函数A的函数 callBackfunName此时就被当做int char等一样的形式来用!
{
int ret = -100;
printf("call_by_funName is begain.\n");
ret = f(x,x);
printf("call_by_funName is finished.\n");
printf("[ %d ]\n",ret);
}
int main()//函数C,调用B的函数,首先引起各个调用的地方
{
callBackfunName f1 = mix;
callBackfunName f2 = add;
call_by_funName(f1,10);
call_by_funName(f2,20);
system("pause");
return 0;
}
四、回调函数的通俗解释:
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。