Skip to content

LV025-回调函数

一、回调函数简介

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.为什么使用回调函数?

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

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

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

c
#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)定义实现回调函数的"调用函数"

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

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

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

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

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

c
/* 定义回调函数 */
void printfTest() 
{
    printf("Hello World!\n");
}

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

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

二、自定义回调函数

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

1. 简单示例

c
#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;
}

int func1(char *p)
{
	printf("This is func1! p = %s\n", p);
	return 0;
}

int callFunc1(int (*callFuncBack)(char *p), char *p)
{
	printf("This is callFunc1! p = %s\n", p);
	callFuncBack(p);
	return 0;
}

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

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

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

shell
This is callFunc1! p = fanhua!
This is func1! p = fanhua!

2. typedef 简化

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

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

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

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

修改后的实例如下:

c
#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;
}

int func1(char *p)
{
	printf("This is func1! p = %s\n", p);
	return 0;
}

int callFunc1(callFuncBack pCallFuncBack, char *p)
{
	printf("This is callFunc1! p = %s\n", p);
	pCallFuncBack(p);
	return 0;
}

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

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

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

shell
This is callFunc1! p = fanhua!
This is func1! p = fanhua!