LV01-09-C语言-回调函数

本文主要是C语言基础——回调函数相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
点击查看相关文件下载
--- ---

1.什么是回调函数?

回调函数就是一个通过函数指针调用的函数。其实就是我们自己定义一个函数,自己实现了相应的函数功能,然后把这个函数(入口地址)作为参数传入其他使用者(或系统)的函数中,由其他使用者(或系统)的函数在运行时来调用的函数。函数是我们自己实现的,但由其他使用者(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。

在维基百科中,它是这样定义的:在计算机程序设计中,回调函数,或简称回调( Callback 即 call then back 被主函数调用运算后会返回主函数),是指通过参数将函式传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。

好像还是不怎么理解,后来在 Stack Overflow 看到了某位大神简洁明了的表述: A “callback” is any function that is called by another function which takes the first function as a parameter 。 意思大概就是,函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数 F2 调用了函数 F3 ,这个动作就叫做回调( Callback ),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。

需要注意的是回调函数并不是 C 语言特有的,几乎任何语言都有回调函数。在 C 语言中,我们通过使用函数指针来实现回调函数

2.为什么使用回调函数?

 为什么不像普通函数调用那样,在回调的地方直接写函数的名字?这样不可以吗?我们在网上会看到解析回调函数的很多例子,其实完全可以用普通函数调用来实现的。那回调函数究竟有什么优点,能够让我们写程序的时候选择这种函数呢?它的优点就是可以解耦,它是一种去耦合的技巧,解耦又是什么意思?我们先来看一张图,这张图来自于维基百科:

回调通常与原始调用者处于相同的抽象层

为了便于理解,我们写一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
#include<softwareLib.h> /* 包含 Library Function 声明所在 Software library 库的头文件 */

/* 回调函数 Callback Function */
int Callback()
{
// TODO
return 0;
}
/* 主函数 Main program */
int main()
{
// TODO
Library(Callback);
// TODO
return 0;
}

从表面上看,回调似乎只是函数间的调用,和普通函数调用没什么太大的区别。但是,仔细看,我们会发现在回调中,主程序把回调函数像参数一样传入库函数。这样有什么好处?只要我们改变传进库函数的参数,就可以根据传入的参数实现不同的功能,这样是很灵活的,而且不需要修改库函数的实现,这就是解耦

另外,主函数和回调函数是在同一层的,而库函数在另外一层,这是什么意思?如果库函数对我们不可见,也就是我们无法看到库函数的实现的话,我们也修改不了库函数的实现,所以也就无法通过修改库函数,让库函数调用普通函数那样实现,那我们想要通过这个库函数执行不同的函数吗,实现不同的功能,但是我们又无法修改库函数,这怎么办?如此这般,那我们就只能通过传入不同的回调函数了。

3.回调函数实现机制

  • (1)定义一个回调函数,并声明;

这与我们平时定义函数的方式没有什么不同之处,因为回调函数也是一个函数而已,只不过它并不是直接被主程序调用,而是通过一个函数指针来实现该函数的调用。

  • (2)定义实现回调函数的”调用函数”

当我们定义了回调函数的时候,肯定还需要一个函数指针指向这个回调函数才行,一般这个函数指针会存在于另一个函数的形参列表中,这个所谓的另一个函数就是实现回调函数的调用函数,一般格式如下:

1
2
3
4
<数据类型> <函数名称>(<形参列表>)
{
语句块;
}

其中的数据类型、函数名称和形参列表与普通函数定义是一样,只是形参列表稍有不同,形参列表中需要有一个函数指针,用于指向一个回调函数(经过后边的学习,这里可以直接是一个函数指针,若传入的形参是结构体,那这个函数指针也可以存在于形参结构体的某个成员中,例如后边学到的 sigaction 函数)。

  • (3)当特定的事件或者条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

这一般是在需要调用回调函数实现功能的时候,就会调用一个带有函数函数指针参数的函数,通过该函数去调用回调函数。

点击查看实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 定义回调函数 */
void printfTest()
{
printf("Hello World!\n");
}

/* 定义实现回调函数的"调用函数" */
void callPrintfTest(void (*callFunc)())
{
callFunc();
}

/* 实现函数回调 */
int main(int argc,char* argv[])
{
callPrintfTest(PrintfTest);
return 0;
}

4.自定义回调函数

上边我们已经了解过回调函数的实现机制,下边就来看一看怎么自定义一个回调函数吧。

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>

int func1(char *p);
int callFunc1(int (*callFuncBack)(char *p), char *p);

int main(int arc, char *argv[])
{
char *p = "fanhua!";
callFunc1(func1, p);

return 0;
}

/**
* @Function: func1
* @Description: 回调函数
* @param p : 传入给回调函数的参数
* @return : 0
*/
int func1(char *p)
{
printf("This is func1! p = %s\n", p);
return 0;
}

/**
* @Function: callFunc1
* @Description: 调用回调函数的函数
* @param callFuncBack : 回调函数的名称
* @param p : 传入给回调函数的参数
* @return : 0
*/
int callFunc1(int (*callFuncBack)(char *p), char *p)
{
printf("This is callFunc1! p = %s\n", p);
callFuncBack(p);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
This is callFunc1! p = fanhua!
This is func1! p = fanhua!

关于 int (*callFuncBack)(char *p) 的解释,前边函数一节的笔记分析的很详细了,若是借 typedef 来帮个忙,将函数中的函数指针定义成一个类型的话,后边可能会更加方便些:

1
typedef int (*callFuncBack)(char *p);

如此,上边用于调用回调函数的函数声明就可以修改为下边这样:

1
int callFunc1(callFuncBack pCallFuncBack, char *p);
点击查看修改后实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>

typedef int (*callFuncBack)(char *p);

int func1(char *p);
int callFunc1(callFuncBack pCallFuncBack, char *p);

int main(int arc, char *argv[])
{
char *p = "fanhua!";
callFunc1(func1, p);

return 0;
}

/**
* @Function: func1
* @Description: 回调函数
* @param p : 传入给回调函数的参数
* @return : 0
*/
int func1(char *p)
{
printf("This is func1! p = %s\n", p);
return 0;
}

/**
* @Function: callFunc1
* @Description: 调用回调函数的函数
* @param pCallFuncBack : 回调函数的名称
* @param p : 传入给回调函数的参数
* @return : 0
*/
int callFunc1(callFuncBack pCallFuncBack, char *p)
{
printf("This is callFunc1! p = %s\n", p);
pCallFuncBack(p);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
This is callFunc1! p = fanhua!
This is func1! p = fanhua!