什么是回调函数
在写这篇博文之前,我对于回调函数的概念都是十分模糊的。这篇博文我将通过一些实际例子的分析来说明一下回调函数是什么
简单粗暴的总结
回调函数,就是一个函数的参数是另外一个函数
下面先用C语言写个最简单的例子
#include <stdio.h>
/*回调函数1*/
int callBack1(int x)
{
return x*2;
}
/*回调函数2*/
int callBack2(int x)
{
return x/2;
}
/*中间函数*/
int calc(int b,int (* func)(int)) //func 把函数当作参数来进行传递
{
return 100+func(b);
}
int main()
{
int a=100;
/*调用回调函数1*/
int q=calc(20,callBack1);
printf("%d\n",q);
/*调用回调函数2*/
int p=calc(40,callBack2);
printf("%d\n",p);
return 0;
}
运行结果
简单概括一下上面的程序:
在这里我定义了两个回调函数,分别是回调函数1,回调函数2。在main函数里面通过传递参数来将回调函数传参到一个中间函数里(注意:这里要用函数指针的方式来接收)。通过传入的回调函数不一样,从而拥有不一样的结果。
这里就可以简单地用我理解的方式来概括一下回调函数是什么:回调函数就是把它本身以函数指针的形式传参到一个中间函数里面,当我们调用这个中间函数的同时,将会执行这个回调函数。也就是说,我们什么时候调用这个中间函数,就会执行传参进去的回调函数。
例如我们常用的创建线程函数(pthread_create),就是我们上面的中间函数。我们传参进去的函数指针,也就是传说中的的回调函数。当我们在执行pthread_create这个函数时,在pthread_create函数内部会执行传参进去的函数,具体怎么执行的,这里不过多讨论。
回调函数的经典用法
回调函数当然不仅仅是用来装b的,它自然有它独到的用法。下面介绍一下我在公司学到的回调函数的用法。
场景模拟
在我们做嵌入式开发时,数据(例如摄像头获取的图像,雷达获取的坐标)一般是由底层传输到上层,如果我们在同一个.cpp文件里面去同时做数据采集和数据处理,那么代码未免也太过复杂了,也不方便维护。所以我们必须将底层的硬件配置与上层的数据处理相分离开。知道了我们的目的,下面我写了一个简化版的程序框架。
main.cpp
#include <iostream>
#include "msgCommon.h"
using namespace std;
/* 底层上来的数据在这里获取 */
void msgCallBack(int pUserData)
{
cout<<"from msgCommon.cpp get message:"<<pUserData<<endl;
}
int main()
{
int nRet;
/* 创建实例化对象 */
callBackTest test;
/* 设置msgCallBack为回调函数 */
test.SetMsgCallBack(msgCallBack, 0);
/* 模拟从底层获取数据 */
nRet = test.getData();
if(false == nRet)
{
cout<<"get Data failure"<<endl;
}
return 0;
}
msgCommon.cpp
#include <iostream>
#include "msgCommon.h"
using namespace std;
/* 将main.cpp里面传进来的数据与类里面的一一对应 */
bool callBackTest::SetMsgCallBack(pMsgCallBack pCallBack, int pUserData)
{
m_pCallBack = pCallBack;
m_pUserData = pUserData;
return true;
}
/* 用来将数据传递给上层 */
bool callBackTest::notify(int c)
{
m_pUserData = c;
if(m_pCallBack)
{
m_pCallBack(m_pUserData);
return true;
}
return false;
}
/* 用于接收数据,这里简单用键盘输入一个整型数 */
bool callBackTest::getData()
{
bool nRet;
int c;
/* 输入数据 */
cout<<"please input your data:";
cin>>c;
cout<<"input success,your data is:"<<c<<endl;
/* 传递数据给上层 */
nRet = notify(c);
if(false == nRet)
{
cout<<"failure"<<endl;
}
return true;
}
msgCommon.h
#pragma once //避免重复包含头文件
/* 声明回调函数的类型 */
typedef void (*pMsgCallBack)(int pUserData);
class callBackTest
{
public:
/* 设置回调函数的接口 */
bool SetMsgCallBack(pMsgCallBack pCallBack, int pUserData);
/* 用于将数据传递给上层 */
bool notify(int c);
bool getData(void);
private:
/* 回调函数 */
pMsgCallBack m_pCallBack;
/* 用户数据 */
int m_pUserData;
};
Makefile
test: main.o msgCommon.o
g++ -o $@ $^
%.o : %.cpp
g++ -c -o $@ $<
clean:
rm -f *.o test
运行结果
简单概括一下以上的程序:
这里在main.cpp(这里可以理解成我们的上层应用)里面定义了一个回调函数(msgCallBack),等待获取来自底层的数据,底层获取到数据后,就会执行main.cpp里面的回调函数,我们可以在这个函数里对来自底层的数据进行处理,想怎么玩怎么玩,怎么改都不会影响底层的程序。这就是我们的目的。
下面简单梳理一下这个框架的逻辑:
我们main函数里面的回调函数叫msgCallBack
通过传参传递到msgCommon.cpp里面
这里在callBackTest类里面声明了一个相同类型的回调函数(m_pCallBack)去将main函数里面的回调函数接住,因为传送的是函数指针,是地址,所以之后,我们操作(m_pCallBack)这个函数也就相当于操作main函数里面的(msgCallBack)。
之后我们执行了callBackTest类里面的getData()函数
用键盘输入一个整型数来模拟我们从底层获取到数据,获取到了数据后,调用notify这个函数
将我们获取到的数据传输到m_pCallBack这个函数里面,也就是调用了msgCallBack(因为两者是同一个东西),因为调用了msgCallBack,数据自然就来到了我们的应用程序。
这里就出现了我们刚刚所说的模型。notify这个函数就相当于我们刚刚C语言程序中的中间函数,m_pCallBack/msgCallBack就相当于回调函数。
总结
回调函数可以很好地实现上层和底层之间的数据交互,实现了将大型工程代码分层的思想。这里只是举了个简单例子,夹带了很多个人的主观看法。因为本人能力有限,如有纰漏,希望大家不吝指正。