回调函数Callback到底是什么
- 1. 回调函数 的 书本定义
- 2. 回调的体现
- 2.1 Callback以及Interface的概念
- 2.2 写个Callback小例子
- A.h:
- A.cpp:
- B.h:
- B.cpp
- Shared.h:
- main.cpp
记得刚开始工作时,要给HAL层添加Framework层能使用的功能,问大佬怎么让这两层通信,大佬只说了一句:看看有没有现成的接口,没有的话要从上至下添加回调接口实现你的功能。这个回调一词直接给我整懵了。什么是回调?谁调的谁?谁回的谁?回调函数实现在哪?脑子一团浆糊。
1. 回调函数 的 书本定义
书上或者百科上都明确地写了:
回调函数(Callback)是通过函数指针调用的函数。
通过函数指针调用的函数这个定义还是比较好理解的。可以通过下面的例子理解。
int FuncExample(void* param) //我先定义一个函数
{
int a = (int)(param);
cout << "Param is:" << param << endl;
return 0;
}
typedef int (*funcPtr)(void* param); //然后定义一个函数指针的类型
int main() {
funcPtr fp = nullptr; //创建funcPtr类型的函数指针
fp = &FuncExample; //指针指向函数
int a = 5;
fp((void*)a); //通过函数指针调用函数。
return 0;
}
例子的 main() 函数中 FuncExample() 函数的调用是通过调用指针 fp来实现的。
那么 FuncExample() 就叫回调函数吗?显然不是的,这只能叫通过函数指针调用的函数。
但是回调函数确实是通过函数指针来调用的函数。所以这两个概念其实是有包含关系的。画个图来说明:
-------------------------
| 通过函数指针调用的函数 |
| ----------- |
| | 回调函数 | |
| ----------- |
-------------------------
那么到底什么才是回调函数呢?
2. 回调的体现
回调函数在通过指针调用这一个特征的基础上,还需要体现出“回“这个概念。Callback,要把back这个概念体现出来才叫Callback嘛。
要理解这个回字,我们不妨先假设一个抽象的例子,用例子来说明回调以及相关的概念。
2.1 Callback以及Interface的概念
假设现在我们有两个独立的封装好的代码块A和B,不管他们是动态库/sdk/api还是什么其他的东西,总之他们之间是独立的并且存在着明确的执行顺序以及需求,我们假设有以下的条件:
- A和B各自封装好了,可以独立执行,并且互相看不到对方的函数。
- A和B有通信的需求。
- A想要在执行过程中用到B的功能。
- A想在B执行某些功能后做出相应的反应。
通过以上的假设,我们可以知道A和B要进行以下调整:
- A要先执行,这样才能在执行过程调用B的功能。
- B需要让A知道他的某些函数,这样A才能调用B。
- A也需要让B知道他的某些函数,这样B才能在自己的功能执行过程中调用A,让A执行相应的反应。
由此引出两个概念来指明上述的关系:
- 接口函数(Interface): 指B暴露给A的那些函数,用于让A调用B的功能。
- 回调函数(Callback): 指A传递给B的那些自己的函数,用于让B能够反向调用A,让A做出相应的反应。
当然因为A和B都封装好了,如果相互包含头文件来实现调用则破坏了封装性,因此这种需求一般是通过函数指针的调用来实现的。
A B 调用B暴露的Interface 回调A提供的Callback A B
这样说,回调的”回“字就能相对明确的解释清楚了。所以回调函数其实是指,当需要让调用者对后执行的被调用者的功能做出反应时,被调用者通过函数指针反向调用的调用者提供的函数。哈哈哈哈,这句话是不是给你说懵圈了?提取句子主谓宾就是:B反向调用的A的函数。明白了吧。
明白以上关系,那困扰我个人的问题就解决了(谁回调谁?函数实现在哪?)。答案非常明确了,用上图来说就是B回调A的回调函数,回调函数的实现是在A中,B通过函数指针调用了A的回调函数。
2.2 写个Callback小例子
我们设定一个具体场景:
A是一个人,想要和别人交朋友。
B是一个查询器,可以查找附近的人。
那么我们根据上述场景分别实现A和B的功能,首先从A开始:
A.h:
//File name: A.h
//Function: A的头文件
#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
typedef void (*INTERFACE)(void* param); //定义接口函数指针类型
void RegisterInterfacetoA(void* itf); //定义接口注册的函数
void MakeFriends(); // A 发起交朋友的动作
void SayHi(void* name); //和别人 Say Hi
A.cpp:
//File name: A.pp
//Function: A的源文件
#include "A.h"
INTERFACE g_interface = nullptr; //定一个全局的接口指针
void RegisterInterfacetoA(void* itf) {
g_interface = (INTERFACE)itf; //注册接口时,把来自B的接口传递给g_interface保存
cout << __func__ << endl;
}
void MakeFriends() {
char name[10];
cout << "I want to make friends with:" << endl;
cin >> name;
if (g_interface) {
g_interface((void*)name); //交朋友时,调用B(查询器)的找人功能,去找朋友
}
else {
cout << "Enable to find this people" << endl;
}
}
void SayHi(void* name) { //此作为回调函数,当B找到人时,A就可以响应Say Hi了
if (name) {
cout << "Hi " << (char*)name << endl;
}
}
A中实现了两个函数,一个是MakeFriends(),用于发起交朋友的动作。另一个是SayHi(),这个函数将作为回调函数传递给B,让B在查询到某人的时候,反向调用SayHi()以让A对人家说你好。
接下来我们看看B的实现:
B.h:
//File name: B.h
//Function: B的头文件
#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
typedef void (*CALLBACK)(void* param); //定义回调函数的函数指针
void SetCallbacktoB(void* callback); //定义设置回调的函数
void FindPeople(void* name); // B 找人的功能
B.cpp
//File name: B.pp
//Function: B的源文件
#include "B.h"
CALLBACK g_callback; //定义一个全局回调指针
char PEOPLE[][10]{ //定义现在附近有的人
"Ming",
"Hong",
"XiaoGang",
"Lucy",
"Ben",
"MAX_NUM"
};
void SetCallbacktoB(void* callback) {
g_callback = (CALLBACK)callback; //设置回调时,把 A 提供的回调函数传递给g_callback保存
cout << __func__ << endl;
}
void FindPeople(void* name) // B 的功能,查找附近的人
{
int i = 0;
char* people = PEOPLE[0];
bool isPeopleFound = false;
while (strcmp(people, "MAX_NUM")) {
if (!strcmp((char*)name, people))
{
isPeopleFound = true;
g_callback(name); //找到时调用回调函数,让A做出相应反应
break;
}
i++;
people = PEOPLE[i];
}
if (!isPeopleFound) {
cout << "Cannot find " << (char*)name << endl;
}
}
B的实现中只有一个功能,那就是FindPeople()找人,当找到人之后,B需要回调A,让A反应。根据之前A的实现我们可以知道,这会回调A的SayHi()函数,实现让A对找到的人说你好。
Shared.h:
为了不破坏A和B的封装,我们只需要让他们相互暴露接口注册以及设置回调的函数。为此我们将这部分暴露的函数单独写一个文件
//File name: Shared.h
//Function: 这是一个包含了相互暴露的函数的头文件
#pragma once
#include "B.h"
#include "A.h"
extern void SetCallback(void* callback) {
SetCallbacktoB(callback);
}
extern void RegisterInterface(void* itf) {
RegisterInterfacetoA(itf);
}
在这个相互暴露的,共享的文件中,我们定义了两个extern标识的函数,让编译器在其他调用到他们的地方知道他们的实现是在外部的,不会编译报错。
main.cpp
最后通过一个主函数完成上述的功能。注意调用的顺序,我们先让A发起交朋友的动作,再调用到B提供的查询附近的人的接口,最后如果找到了人就回调A的回调函数完成SayHi。
//File name: main.cpp
//Function: 这是主函数所在的源文件
#include "Shared.h" //我们需要include共享头文件
using namespace std;
typedef void(*funcPointer)(void);
int main()
{
//先注册接口,设置回调
RegisterInterface(funcPointer(FindPeople));
SetCallbacktoB(funcPointer(SayHi));
//然后调用A的功能
MakeFriends();
return 0;
}
让我们来看看“找到了”和“找不到”的打印结果
RegisterInterfacetoA
SetCallbacktoB
I want to make friends with:
XiaoGang //输入一个附近存在的人名
Hi XiaoGangRegisterInterfacetoA
SetCallbacktoB
I want to make friends with:
MaYun //输入一个附近没有的人
Cannot find MaYun
Ok,用这样的小例子我们说明了回调函数的“回”字如何体现,尽管这个封装十分简单,没有很严格地封装,但简单一些便于理解。大家明白了吗?