C 语言中的回调函数

很久没有写 blog 了,这次写一点与回调函数相关的内容...🧐

此文算是之前学习过程中,记录的笔记吧。

概念

首先了解一下回调的概念:

回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python、ECMAScript等更现代的编程语言中还可以使用仿函数或匿名函数。

以上解释来自百度百科 - 回调函数。仅从简单理解的角度来讲,个人认为以这样的方式说明回调函数是什么,很直观明了。所以,这篇文章中所提到的回调函数就是指被作为参数传递的函数
下面,再研究一下 C 中回调函数的使用。

应用

如上所述,C 中的回调函数只能用函数指针实现,具体如何,直接看下面的示例:

test1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

typedef void (*pfunc)();

void ppfunc() {
printf("Here is ppfunc!\n");
}

void func(pfunc p) {
p();
printf("Here is func!\n");
}

int main() {
func(&ppfunc);
return 0;
}

在上面的代码中,利用typedef关键字定义了一个叫做pfunc的函数指针,这个函数指针指向的函数有两个“特点”:

  1. 没有参数
  2. 返回值为void

另外,上述代码还定义了一个叫做func的函数,这个函数以pfunc类型的函数指针p作为参数,返回值为void,且在其函数体内还调用了p指向的函数,并打印输出了Here is func!
最终,这个程序的输出结果是:

1
2
Here is ppfunc!
Here is func!

可以发现,在main函数中通过传递函数指针ppfunc实际上,这里把函数的入口地址叫做函数指针不是很严谨的说法,仅仅是为了方便说明>),在func函数中成功调用了ppfunc函数。此时,ppfunc就叫做回调函数,func就叫做主调函数。
整个执行过程,也可以简单的概括为:在调用func函数的过程中,通过传递过来的函数指针p来调用ppfunc函数。

现在可以发现,要想掌握 C 中回调函数的用法,必须要具备:

  1. 理解函数指针概念
  2. 理解typedef关键字定义函数指针用法
  3. 理解函数的本质:内存中的代码段

同时,也会产生这样的疑问:如果只是为了调用ppfunc函数,为什么不直接在func函数里调用ppfunc函数呢?用回调函数的方式来调用,不是多此一举吗?

其实上面的代码仅仅是为了用作说明而写出来的,实际情况当然不会这么多此一举。
在 C 语言中比较常见使用回调函数的例子,就是标准库的qsort函数,比如下面的示例代码:

test2
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
#include <stdio.h>
#include <stdlib.h>

int cmp_int(const void *p, const void *q) {
return *(int *)p > *(int *)q;
}

int cmp_double(const void *p, const void *q) {
return *(double *)p < *(double *)q;
}

int main() {
int arr1[5] = {3, 2, 5, 1, 4};
qsort(arr1, 5, sizeof(*arr1), cmp_int);
puts("arr1:");
for(int i = 0; i < sizeof(arr1) / sizeof(*arr1); i++)
printf("%d ", arr1[i]);
putchar('\n');

double arr2[5] = {1.2, 2.3, 3.4, 4.5, 5.6};
qsort(arr2, 5, sizeof(*arr2), cmp_double);
puts("arr2:");
for(int i = 0; i < sizeof(arr2) / sizeof(*arr2); i++)
printf("%.1lf ", arr2[i]);
putchar('\n');
return 0;
}
/*
out:
arr1:
1 2 3 4 5
arr2:
5.6 4.5 3.4 2.3 1.2
*/

在上面的代码中,使用qsort函数对arr1arr2分别进行升序排序和降序排序,而arr1arr2分别是两个不同类型的数组。
此时,可以发现,如果不使用回调函数,就需要写出两个参数类型和排序方式都不同的排序函数,尽管这两个排序函数的算法是一样。所以,为了避免写重复的代码(如果从工程的角度来讲,原因更多),就可以借助回调函数的方式。

结语

在 C 语言中,回调函数的应用场景还是比较多的,但想要用好这个工具,必须要明白指针的概念。归根结底,回调函数还是在指针上玩花样。
另外,C++ 中的回调函数也很常见,比如 C++11 的 lambda 提供的匿名函数或者仿函数。
总而言之,回调函数是一种编程思想,是工程中的一种实践方法。


Buy me a coffee ? :)
0%