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) |
点击查看本文参考资料
参考方向 | 参考原文 |
回调函数 | C语言中的回调函数(Callback Function) |
回调函数 | 维基百科——回调函数 |
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 |
|
从表面上看,回调似乎只是函数间的调用,和普通函数调用没什么太大的区别。但是,仔细看,我们会发现在回调中,主程序把回调函数像参数一样传入库函数。这样有什么好处?只要我们改变传进库函数的参数,就可以根据传入的参数实现不同的功能,这样是很灵活的,而且不需要修改库函数的实现,这就是解耦。
另外,主函数和回调函数是在同一层的,而库函数在另外一层,这是什么意思?如果库函数对我们不可见,也就是我们无法看到库函数的实现的话,我们也修改不了库函数的实现,所以也就无法通过修改库函数,让库函数调用普通函数那样实现,那我们想要通过这个库函数执行不同的函数吗,实现不同的功能,但是我们又无法修改库函数,这怎么办?如此这般,那我们就只能通过传入不同的回调函数了。
3.回调函数实现机制
- (1)定义一个回调函数,并声明;
这与我们平时定义函数的方式没有什么不同之处,因为回调函数也是一个函数而已,只不过它并不是直接被主程序调用,而是通过一个函数指针来实现该函数的调用。
- (2)定义实现回调函数的”调用函数”
当我们定义了回调函数的时候,肯定还需要一个函数指针指向这个回调函数才行,一般这个函数指针会存在于另一个函数的形参列表中,这个所谓的另一个函数就是实现回调函数的调用函数,一般格式如下:
1 | <数据类型> <函数名称>(<形参列表>) |
其中的数据类型、函数名称和形参列表与普通函数定义是一样,只是形参列表稍有不同,形参列表中需要有一个函数指针,用于指向一个回调函数(经过后边的学习,这里可以直接是一个函数指针,若传入的形参是结构体,那这个函数指针也可以存在于形参结构体的某个成员中,例如后边学到的 sigaction 函数)。
- (3)当特定的事件或者条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
这一般是在需要调用回调函数实现功能的时候,就会调用一个带有函数函数指针参数的函数,通过该函数去调用回调函数。
点击查看实例
1 | /* 定义回调函数 */ |
4.自定义回调函数
上边我们已经了解过回调函数的实现机制,下边就来看一看怎么自定义一个回调函数吧。
点击查看实例
1 |
|
在终端执行以下命令编译程序:
1 | gcc test.c -Wall # 生成可执行文件 a.out |
然后,终端会有以下信息显示:
1 | This is callFunc1! p = fanhua! |
关于 int (*callFuncBack)(char *p) 的解释,前边函数一节的笔记分析的很详细了,若是借 typedef 来帮个忙,将函数中的函数指针定义成一个类型的话,后边可能会更加方便些:
1 | typedef int (*callFuncBack)(char *p); |
如此,上边用于调用回调函数的函数声明就可以修改为下边这样:
1 | int callFunc1(callFuncBack pCallFuncBack, char *p); |
点击查看修改后实例
1 |
|
在终端执行以下命令编译程序:
1 | gcc test.c -Wall # 生成可执行文件 a.out |
然后,终端会有以下信息显示:
1 | This is callFunc1! p = fanhua! |