详解回调函数
摘要
回调函数的理论基础是函数名是函数的入口地址,其入口地址可以赋值给一个函数指针变量,用此函数指针变量来调用函数。也可以让函数指针变量作为形参,用一个函数的入口地址(函数名)作为实参传入到这个函数指针变量中,完成了函数指针变量的赋值过程;函数的入口地址(函数名)指向的函数就是回调函数;
总而言之,回调函数的理论基础是函数指针;而函数指针的出现的原因是函数名称是函数的入口地址;函数作为一个编程语言的基本单元,必须理解透彻!更深层次的要清楚内存四驱模型和函数调用模型!这两个模型的深入理解是理解回调函数的终极目标!
1 函数名称是回调函数的本质
首先要很清楚函数:
函数三个要素:函数名称,函数参数,函数返回值。
比如 double add(int a,int b);
add:函数名称;
a和b:函数参数;
double:函数返回值;
对函数的三要素的作用要很明白:
函数参数:是输入,是函数要处理的数据,也就是函数做饭要用的原料。
函数返回值:就是函数处理完数据后的输出,也就是函数做好的菜;
函数名称:函数的入口地址,===》地址。这个一定要记住。这个函数名相当于菜名,知道了菜名,你才能知道去哪里点找个菜;
如下图:是函数名称是一个地址的证明,这个一定要理解:这个是回调函数的根源所在。
在编程语言中 小括号()就是函数调用符;函数名称+()就是调用这个函数;
然后可以通过下面这句代码可以调用func函数
4199952();
2.函数指针—是回调函数的理论基础
函数也可以是一种数据类型。
和int,double等数据类型一样可以定义对应的变量。
用函数这种自定义数据类型定义的变量,叫做函数类型变量。
用自定义函数类型定义的指针变量就是函数指针。
函数指针:指向函数入口地址的指针。
用个案例来说明这个问题:
- 需求:定义一个函数指针,让这个函数指针指向func函数。func函数如下:
void func(int a ,char b)
{
printf("hello world\n");
}
(1)首先定义一个函数类型。函数类型的定义属于自定义类型 的定义。和结构体的这种自定义数据类型一样,都是自定义数据类型。要遵循定义的规则,当然函数类型的定义的规则是根据typedef得出来的,感兴趣的可以研究,不感兴趣的记住就可以了!不影响继续学习!其实就是为了得到一个int而已;
//函数类型的定义:
typedef void(FUNC_TYPE)(int, char);
说明:
FUNC_TYPE就是定义 的函数类型。可以通过这个函数类型来定义相应的指针变量
(2)定义函数指针
用FUNC_TYPE函数类型来定义函数指针.(相当于int* a)
FUNC_TYPE* pFunc;
(3)将pFunc指针指向函数func:(其实就是一个赋值的代码)func函数的函数名称是func,func就是函数的入口地址。不懂请回到第一部分讲解;
pFunc=func;
(4)用函数调用函数
pFunc(10, 'a');
完整代码:
void func(int a ,char b)
{
printf("hello world\n");
}
void test01()
{
//先定义出函数的类型,再通过类型定义函数指针变量
typedef void(FUNC_TYPE)(int, char); // 定义出一个函数类型,返回值是void,形参列表(int,char);
FUNC_TYPE * pFunc = func;
pFunc(10, 'a');
}
运行结果:
可以看到通过咱们自定义的函数类型定义的指针变量pFunc通过函数调用符()实现了对func函数的调用;咱们这里是通过普通的变量赋值的方式实现了这个过程;
还可以通过实参传给形参的方式,将func赋值给pFunc变量;第二种赋值方式就是回调函数产生的过程,而func函数就是回调函数:下面的部分会继续详细讲解这块!
3.推荐使用的函数指针定义的方式
void(*p)(int, char) = func;
说明方式:后面的学习都是用这种方式;STL标准库,linux的Libevent都是用这种方式;
这种方式将上面的方式的步骤和二为一;下面是两种方式的对比;为了 便于理解:
//1.直接定义
void(*p)(int, char) = func;
p=func;
//2.间接定义
typedef void(FUNC_TYPE)(int, char);
FUNC_TYPE* pFunc;
pFunc=func;
4.回调函数—>函数指针变量做函数的参数
首先要知道以下一条原则:
函数参数除了是普通变量,还可以是函数指针变量
然后要明白c语言定义函数指针变量的两个用途:
1.作为函数的参数,实现函数地址的传递
2.实现函数指针的数组,实现多个函数的遍历
用途1就是回调函数的真正来源所在。
让函数指针变量作为形参,其函数的入口地址作为实参传入到另外一个函数中,这个过程就是回调函数。
一般定义为:涉及到的数据类型定义为void类型的;这样才可以发挥回调函数的威力!
void(*mp)(void *);
5.回调函数的案例
回调函数是一个非常重要的知识点,一定要搞明白,不然会影响后面的学习。
下面的案例根据前面四部分的理论:以下为完整的一端代码:好好研究一下
这个函数的需求是:实现一个函数,这个函数可以实现打印任意类型数据
要实现这个需求必须要用回调函数;因为只有自己才知道自己要打印的数据类型,所以函数的一个参数是函数指针;程序员通过写这个函数指针相对应的回调函数来实现任意数据类型的打印;下面的代码实现了
void printInt(void * data)
void printDouble(void * data)
void printPerson(void * data)
三个回调函数,来实现了通过myPrint函数对三种数据类型的打印;
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//提供一个函数,可以打印任意类型数据
void myPrint(void * data , void(*mp)(void *) )
{
//double * num = data;
//printf("%f\n", num);
mp(data);
}
struct Person
{
char name[64];
int age;
};
void printInt(void * data)
{
int * num = data;
printf("%d\n", *num);
}
void printDouble(void * data)
{
double * num = data;
printf("%f\n", *num);
}
void printPerson(void * data)
{
struct Person * num = data;
printf("姓名:%s 年龄:%d\n",num->name,num->age);
}
void test01()
{
int a = 10;
myPrint(&a, printInt);
double b = 3.14;
myPrint(&b, printDouble);
struct Person p1 = { "aaa", 18 };
myPrint(&p1, printPerson);
}
int main(){
test01();
system("pause");
return EXIT_SUCCESS;
}
6.回调函数的使用场景
程序员只负责实现回调函数,不用去主动执行回调函数。
也就是只负责将写好的回调函数的函数名称当作实参传给相对应的形参(这个形参是函数指针),就可以了!
我们在今后的项目中发现函数库中有个函数的形参是函数指针,我们要做的事情就是两个步骤:
(1)写一个与形参的返回值和和参数相一致的回调函数;
(2)然后将回调函数的函数名称作为实参传入函数库中的这个函数的函数指针所在的参数;
如第五部分的案例:我们调用下面这个函数
void myPrint(void * data , void(*mp)(void *) )
其中void(*mp)(void *)是个函数指针做形参;我们只需要实现一个函数的返回值和函数参数一样的函数就可以了:
比如我们实现了:下面这个就是咱们需要自己写的回调函数;
void printInt(void * data)
{
int * num = data;
printf("%d\n", *num);
}
将回调函数的函数名称传入myPrint(void * data , void(*mp)(void *) )函数的第二个函数指针参数,就是咱们在项目中要做的事情;
myPrint(&a, printInt);
myPrint(&b, printDouble);
7 布置两个回调函数的作业
(1)实现一个函数,可以打印任意数组;
(2)实现一个函数,可以对任意类型数组排序;
答案:
看我的另外一篇文章,这个文章有点长了,为了让大家更好的阅读,另起一篇文章。
https://blog.csdn.net/qq_41286360/article/details/90177276#2__135