回调函数
回调函数
一、什么是回调函数
先看看来自 Stack Overflow 某位大神简洁明了的表述:A "callback" is any function that is called by another function which takes the first function as a parameter(回调函数是由接收一个函数作为参数的另一个函数调用的任何函数)。是不是很抽象?
简单来说,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
下面通过一个小栗子深入理解一下什么事回到函数。
1.1 一个简单的栗子
现在有三个函数 func1()
、func2(void *funcAddr)
、func3()
,他们之间的关系是:func1 调用 func2,并将 func3 的地址作为参数传递给 fun2。那么在函数 func2 执行的过程中,通过指针调用了函数 func3,这个动作就叫做回调(callback)。而先被当做指针传入、后又被调用的函数 func3 就是回调函数。
#include <stdio.h>
void func3()
{
printf("func3\n");
}
void func2(void *funcAddr)
{
// 类型转换
void (*func)();
func = funcAddr;
func();// 通过指针 func 调用 func3(),该动作就是回调
}
void func1()
{
func2(func3);// 将func3地址传递给func2
}
int main()
{
func1();
return 0;
}
二、为什么要用回调函数
在上边的例子中,就算不传递 func3 的地址,也可以在 func2 直接调用,为何要多此一举呢?
要回答这个问题,我们先来了解一下回调函数的好处和作用,那就是解耦。就是因为这个特点,普通函数代替不了回调函数。
下面来看看维基百科上的一张图片:
下面以一段不完整的 C 语言代码来呈现上图的意思:
#include <stdio.h>
#include "softwareLib.h" // 包含 Library 的头文件
int Callback() // Callback Function
{
// TODO
return 0;
}
// Main program
int main()
{
Library(Callback);
return 0;
}
乍一看,回调似乎只是函数间的调用,和普通函数调用没啥区别,但仔细一看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,并且丝毫不需要修改库函数的实现,这就是解耦。
再仔细看看,主函数和回调函数是在同一层的,而库函数在另外一层,想一想,如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数了,这也就是在日常工作中常见的情况。现在再把main()
、Library()
和Callback()
函数套回前面 func1、func2 和 func3 函数里面,是不是就更明白了。
三、函数指针
通过上面的讲解,我们很容易知道,在 C 语言中,只能通过函数指针来实现回调函数,接下来我将对函数指针做个简单的总结。
3.1 什么是函数指针
首先它是一个指针,只是这个指针指向的是一个函数。指针变量可以指向变量的地址、数组、字符串、动态分配地址等等,同时也可指向一个函数。每个函数在编译的时候,系统会分配给该函数一个入口地址,而函数名就表示这个入口地址。那么指向函数的指针变量称为函数指针变量。
3.2 怎么用
指向函数的指针包含了函数的地址,可以通过它来调用函数。
声明格式:类型说明符 (*函数名)(形参列表),其实这里不能称为函数名,应该叫做指针的变量名。指针的声明必须和它指向函数的声明保持一致。
小试牛刀
函数声明 | 函数指针 | |
---|---|---|
① | void func(); | void (*funcAddr)(); |
② | char *func(); | char *(*funcAddr)(); |
③ | int funcMax(int a, int b); | int (*funcAddr)(int, int); |
①.c
#include <stdio.h>
void func()
{
printf("func\n");
}
void test(void (*funcAddr)())
{
funcAddr();
}
int main()
{
test(func);
return 0;
}
②.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *func()
{
char *buf = (char *)malloc(100);
strncpy(buf, "hello world", 100 - 1);
return buf;
}
void test(char *(*funcAddr)())
{
char *buf = funcAddr();
printf("buf[%s]\n", buf);
}
int main()
{
test(func);
return 0;
}
③.c
#include <stdio.h>
int funcMax(int a, int b)
{
return a > b ? a : b;
}
void test(int (*funcAddr)(int, int))
{
int a = 10;
int b = 20;
int res = funcAddr(a, b);
printf("max(%d, %d) = %d\n", a, b, res);
}
int main()
{
test(funcMax);
return 0;
}
对于函数指针的声明,我们还可以通过 typedef 简化一下,以 ①.c 为例,修改如下:
#include <stdio.h>
typedef void (*pFunc)();
void func()
{
printf("func\n");
}
void test(pFunc funcAddr)
{
funcAddr();
}
int main()
{
test(func);
return 0;
}
如果想要深入学习,可参考如下文章: