回调函数Callback到底是什么

记得刚开始工作时,要给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还是什么其他的东西,总之他们之间是独立的并且存在着明确的执行顺序以及需求,我们假设有以下的条件:

  1. A和B各自封装好了,可以独立执行,并且互相看不到对方的函数。
  2. A和B有通信的需求。
  3. A想要在执行过程中用到B的功能。
  4. A想在B执行某些功能后做出相应的反应。

通过以上的假设,我们可以知道A和B要进行以下调整:

  1. A要先执行,这样才能在执行过程调用B的功能。
  2. B需要让A知道他的某些函数,这样A才能调用B。
  3. A也需要让B知道他的某些函数,这样B才能在自己的功能执行过程中调用A,让A执行相应的反应。

由此引出两个概念来指明上述的关系:

  1. 接口函数(Interface): 指B暴露给A的那些函数,用于让A调用B的功能。
  2. 回调函数(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 XiaoGang

RegisterInterfacetoA
SetCallbacktoB
I want to make friends with:
MaYun //输入一个附近没有的人
Cannot find MaYun

Ok,用这样的小例子我们说明了回调函数的“回”字如何体现,尽管这个封装十分简单,没有很严格地封装,但简单一些便于理解。大家明白了吗?

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值