先放一段简单的代码
/*******************************API********************************/
#include"callback.h"
CCALLBACK::CCALLBACK(){}
CCALLBACK::~CCALLBACK(){}
void CCALLBACK::regist_callback(callBack cb)
{
p_cb = cb;
}
void CCALLBACK::use_callBack(std::string str,int n)
{
//TODO
//回调函数
p_cb(str,n);
}
/*******************************main program*****************/
#include"callback.h"
#include
//回调函数
void callbackFun(std::string str,int n)
{
for(int i=0;i<n;i++)
std::cout<<str<<std::endl;
}
int main()
{
CCALLBACK m_callback;
std::string str = "hello world";
int n = 3;
m_callback.regist_callback(callbackFun);
m_callback.use_callBack(str,n);
return 0;
}
回调的概念:
第一部分API
是API库文件,第二部分main program
是项目的主程序,主程序调用API中的库函数实现某个功能
回调函数callbackFun
在主程序中定义
注册回调函数regist_callback
在API库文件中定义
什么是回调呢,
首先,主程序中使用注册回调函数regist_callback
把回调函数的地址告诉API
然后,主程序使用APIuse_callBack
函数实现某个功能,在这个过程中库函数use_callBack
调用了回调函数callbackFun
,而回调函数在执行时调用了库函数use_callBack
的两个参数string str和int n
这种相互调用关系被称为回调
上述大致就是回调函数的使用过程了,根据项目的实际需求,回调函数的实现、需要调用的参数会随之变化,对于回调函数的使用原理基本是不变的。
但是为什么要使用回调函数呢?在这个简单示例里,完全可以把回调函数中的实现放入到API中,在主程序中直接调用库函数就可以实现同样的功能效果,更不用注册回调函数这么麻烦。
/*******************************API********************************/
void use_callBack(string str,int n)
{
for (int i = 0; i < n; i++)
cout << str << endl;
}
/*******************************main program*****************/
int main()
{
string str="hello world";
int n = 3;
use_callBack(str,n);
system("pause");
return 0;
}
当使用回调函数这一机制时,无非是把传递的参数由普通的数据类型变成函数指针罢了。考虑到一个问题,有时在我们自己的程序中调用API中的函数时,有的API可能仅提供给了我们头文件和封装好的API库,这使得我们无法看到API函数中的实现,也不可以进行修改。当我们需求有变化时,上述所说的直接调用库函数来实现某些功能就无法实现了。
例如:
我们将这个例子做的复杂一些
use_callBack
函数的功能修改为获取当前的时间——时:分:秒,API只提供头文件和封装好的库文件callbackFun
来显示当前的时间,首先我们将显示方式设置为hour/min/sec,格式为24小时制/*******************************main program*****************/
#include"callback.h"
#include
#include
#include
#include
using namespace std;
void callbackFun(int hour,int min, int sec)
{
char time_now[20];
memset(time_now,0,20);
sprintf(time_now,"%02d/%02d/%02d",hour,min,sec);
std::cout<<time_now<<endl;
}
int main()
{
CCALLBACK m_callback;
m_callback.regist_callback(callbackFun);
while(1)
{
m_callback.use_callBack();
sleep(1);
}
return 0;
}
那么,如果我们的需求变了,想要12小时制显示,并且格式想将hour/min/sec
改为hour:min:sec
,这时只需要修改我们的回调函数就可以了。
void callbackFun(int hour,int min, int sec)
{
char time_now[20];
memset(time_now,0,20);
if(hour>12)hour-=12;//将24小时制改为12小时制
sprintf(time_now,"%02d/%02d/%02d",hour,min,sec);
std::cout<<time_now<<endl;
}
这个例子可能不是那么恰当,但是如果再继续将这个例子改变一下,我们创建两个类,其中第一个类仍然是之前用到的API中的CCALLBACK
,我们把主程序中的内容放到另一个类B
中去,类B
是我们自己设计的模块,这样由CCALLBACK
类来获取时间,由类B
显示时间,当需求再次变化时例如时间的可视化显示,可视化界面的设计等等,这样我们只需要设计我们这边的B
中的相关函数便可以了,无需修改API中的内容。
使用回调函数有时并非是一种好的策略,而大量使用回调函数更不是一个好的选择。回调函数本身是一种破坏系统结构的设计思路 [1],当我们的代码很庞大时,回调函数会绝对的变化系统的运行轨迹,执行顺序,调用顺序,让读到你的代码的人非常的懵头转向,给代码优化带来困扰。
在两个模块之间相互传递参数我们也可以在声明全局变量时添加extern
关键字,但是如果需要对这种全局变量进行修改,还需要引入lock
与unlock
对数据进行保护,如果锁比较多,还可能会造成死锁。
但是到底怎么去使用回调函数,我想可能要根据实际情况和自己累积的编程经验去判断了。
参考链接:[1]https://blog.csdn.net/llzhang_fly/article/details/104933969