回调函数预备知识
在讲解回调函数之前,我们需要了解函数指针。
函数指针
int *p1; // p1 是一个指向整数(int)类型的指针变量
char *p2; // p2 是一个指向字符(char)类型的指针变量
STRUCT *p3; // p3 是一个指向结构体类型 STRUCT 的指针变量
我们一般很少使用函数指针,通常直接使用函数调用。下面来了解一下函数指针的概念和使用方法。
什么是函数指针
函数指针也是个指针,但是和通常的指针不一样。通常的指针指向的是整型、字符型或数组等变量,而函数指针指向的是函数。
函数指针的语法
返回类型 (*指针变量名)(参数类型列表);
- 返回类型: 函数返回的数据类型(如 int, double, void 等)。
- 指针变量名: 你给这个函数指针起的名字。
- 参数类型列表: 函数接受的参数类型(如果没有参数,可以留空或写 void)。
这里需要注意的是:**(*指针变量名)**两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针,而是一个函数声明了,即声明了一个返回值类型为指针型的函数。
那么怎么判断一个指针变量是指向变量的还是指向函数的呢?
- 首先看变量名前面有没有'*',如果有说明是指针变量;
- 其次看变量名有没有带 (),如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。
最后需要注意的是:指向函数的指针变量没有 ++ 和 – 运算。
一般为了方便使用,我们会选择用 typedef 进行函数指针类型的别名定义。
// 定义一个函数指针类型 别名为:Fun1,它指向返回 int 类型、接受一个 int 参数的函数
typedef int (*Fun1)(int);
// 定义一个函数指针类型 别名为:Fun2,它指向返回 int 类型、接受两个参数(int 和 int)的函数
typedef int (*Fun2)(int, int);
// 定义一个函数指针类型 别名为:Fun3,它指向返回 void 类型、无参数的函数
typedef void (*Fun3)(void);
// 定义一个函数指针类型 别名为:Fun4,它指向返回 void* 类型、接受一个 void* 参数的函数
typedef void* (*Fun4)(void*);
如何用函数指针调用函数
int Func(int x); /* 声明一个函数 */
int (*p)(int x); /* 定义一个函数指针 */
p = Func; /* 将 Func 函数的首地址赋给指针变量 p */
p = &Func; /* 将 Func 函数的首地址赋给指针变量 p */
赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了。
特别注意的是,因为函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以用&取函数的地址。
函数指针作为函数的参数
#include <stdio.h>
// 定义一个函数类型:别名为 operation,返回类型是 int,参数类型是 int 和 int
typedef int (*operation)(int, int);
// 一个加法函数
int add(int a, int b) {
return a + b;
}
// 一个减法函数
int sub(int a, int b) {
return a - b;
}
// 函数 modifyValue 接受一个函数指针作为参数,并调用它
void modifyValue(int *ptr, operation op) {
*ptr = op(*ptr, 5); // 使用传入的函数指针 op 来修改 ptr 指向的值
}
int main() {
int num = 10;
printf("Before: %d\n", num);
// 传递加法函数的指针
modifyValue(&num, add);
printf("After add: %d\n", num);
// 传递减法函数的指针
modifyValue(&num, sub);
printf("After sub: %d\n", num);
return 0;
}
在 C 语言中,函数名(如 add 或 sub)在没有括号时会自动转换为指向该函数的指针。这与 operation 类型的期望使用一个函数指针相匹配。
函数指针作为函数返回类型
有了上面的基础,要写出返回类型为函数指针的函数应该不难了。
void (*func5(int, int, float))(int, int) { ... }
在这里,func5 以 (int, int, float) 为参数,其返回类型为 void (*)(int, int)。
函数指针数组
既然函数指针也是指针,那我们就可以用数组来存放函数指针。
/* 方法 1 */
void (*func_array_1[5])(int, int, float);
/* 方法 2 */
typedef void (*p_func_array)(int, int, float);
p_func_array func_array_2[5];
回调函数
什么是回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
假设我们要使用一个排序函数来对数组进行排序,那么在主程序中,我们先通过库,选择一个库排序函数。但排序算法有很多,有冒泡排序,选择排序,快速排序,归并排序。同时,我们也可能需要对特殊的对象进行排序,比如特定的结构体等。库函数会根据我们的需要选择一种排序算法,然后调用实现该算法的函数来完成排序工作。这个被调用的排序函数就是回调函数。
结合上面解释,我们可以发现,要实现回调函数,最关键的一点就是将函数的指针传递给一个函数,然后这个函数就可以通过这个指针来调用回调函数了。注意,回调函数并不是 C 语言特有的,几乎任何语言都有回调函数。在 C 语言中,我们通过使用函数指针来实现回调函数。
总结:把一段可执行的代码 (函数) 像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫回调。如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调。
为什么要用回调函数
因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。
简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活地使用不同的方法。
回调似乎只是函数间的调用,和普通函数调用没啥区别。但仔细看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。
这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样就很灵活。并且当库函数很复杂或者不可见的时候利用回调函数就显得十分优秀。
怎么使用回调函数
#include <iostream>
using namespace std;
int Callback_1(int a) {
cout << "Hello, this is Callback_1. a=" << a << endl;
return 0;
}
int Callback_2(int b) {
cout << "Hello, this is Callback_2. b=" << b << endl;
return 0;
}
int Callback_3(int c) {
cout << "Hello, this is Callback_3. c=" << c << endl;
return 0;
}
int Handle(int x, int (*Callback)(int x)) {
Callback(x);
return 0;
}
int main() {
Handle(10, Callback_1);
Handle(20, Callback_2);
Handle(30, Callback_3);
return 0;
}
本质上是把 Callback_1, Callback_2, Callback_3 函数的地址传给 Handle 函数。Handle 函数里的 Callback(x) 存储的是具体回调函数的内存地址,比如说你传入的是 Handle(10, Callback_1),那 Callback 存储的是 Callback_1 的地址,存储的 x 变量是 10,它就会找到 Callback_1 并且把 10 传给它。
下面是一个四则运算的简单回调函数例子:
#include <iostream>
using namespace std;
typedef float (*Operation)(float, float);
float ADD(float a, float b) {
cout << "a+b=" << a + b << endl;
return a + b;
}
float SUB(float a, float b) {
cout << "a-b=" << a - b << endl;
return a - b;
}
float MUL(float a, float b) {
cout << "a*b=" << a * b << endl;
return a + b;
}
float DIV(float a, float b) {
if (b == 0) {
printf("Error: Division by zero!\n");
return 0;
}
cout << "a/b=" << a / b << endl;
return a / b;
}
float add_sub_mul_div(float a, float b, Operation op) {
op(a, b);
;
}
{
(, , ADD);
(, , SUB);
(, , MUL);
(, , DIV);
;
}
总结
本文介绍了函数指针的定义、语法及 typedef 用法,包括作为参数和返回值的场景。随后阐述了回调函数的概念,即通过函数指针传递代码地址,使调用者与被调用者解耦。文中包含示例展示了如何利用回调实现灵活的功能扩展。


