目录
前言
什么是回调函数?我想大家应该都有所耳闻,但是不知道它具体的概念,用法以及为啥用,今天,我就在这篇文章中好好介绍一下,也通过一些栗子来加深大家的理解,首先呢,要说明白回调函数,就要先说一下函数指针,跟我来看看吧。
函数指针介绍
1.概念
整型指针存放整型地址的指针变量,字符指针存放字符地址的指针变量,所以,函数指针,顾名思义,存放函数地址的指针变量。语法:函数返回类型 (*指针变量名)(函数参数列表)
2.用法举例
前提:
有如下两个函数Add、Sub
int Add(int x, int y)
{
return x + y;
}
初始化:
pf为变量名,除去之后int (*)(int, int) 为函数指针类型,代表pf为函数指针变量,函数名或者&函数名都可以用来初始化函数指针
int (*pf)(int, int) = Add;
//或
int (*pf)(int, int) = &Add;
使用:
pf为变量名,带不带解引用符都可以调用函数
int tmp = pf(2, 3);
//或
int tmp = (*pf)(2, 3);
那就有人问了,好端端的一个函数,明明可以直接调用它,为啥要通过存入函数指针呢?这就狭隘了,存在即合理,下面即将介绍的回调函数就是它的典型应用之一。
回调函数概念介绍
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。---百度百科
举个例子简单理解一下,有函数int Add(int x,int y),void test(int (*pf)(int ,int)),当Add函数通过test函数的函数指针参数传入test函数内,此时Add函数就叫做回调函数。
回调函数用途举例
-
用途举例一:计算器程序实现
1.普通实现
对于计算器程序的实现,大家应该都可以得心应手,但是在认识回调函数之前,可能只能写出下面的小白写法,这里只实现计算器的基本的加减乘除功能以说明问题。
代码:
int Add(int x, int y) //加法
{
return x + y;
}
int Sub(int x, int y) //减法
{
return x - y;
}
int Mul(int x, int y) //乘法
{
return x * y;
}
int Div(int x, int y) //除法
{
return x / y;
}
void menu() //菜单
{
printf("**************************\n");
printf("**** 1.add 2.sub ****\n");
printf("**** 3.mul 4.div ****\n");
printf("**** 0.exit ****\n");
printf("**************************\n");
}
int main()
{
int input = 0;
int x = 0, y = 0;
int ret = 0;
do
{
menu();
printf("请输入选项:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("结果为%d\n", ret);
break;
case 2:
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("结果为%d\n", ret);
break;
case 3:
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("结果为%d\n", ret);
break;
case 4:
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("结果为%d\n", ret);
break;
case 0:
printf("退出成功\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
2.借助回调函数实现
在上方的小白写法可以看出,部分代码出现多次,很冗余,那么有什么方法可以减少这种冗余呢?从switch语句中的不同case可以看出:大部分case的代码是相同的,只有要实现的函数不同。
所以,通过函数指针的学习,我们可以去定义这样一个函数(calc),参数设为函数指针类型,通过调用不同功能的函数实现相应功能,以减少代码冗余。
代码:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*****************************\n");
printf("***** 1.加法 2.减法 *****\n");
printf("***** 3.乘法 4.除法 *****\n");
printf("***** 0.退出 *****\n");
printf("*****************************\n");
}
//接受不同函数以实现对应功能的函数,此时上面的Add,Sub等函数称为回调函数
void calc(int (*pf)(int,int))
{
int x = 0;
int y = 0;
int tmp = 0;
printf("请输入2个操作数:>");
scanf("%d%d", &x, &y);
tmp = pf(x, y);
printf("结果为%d\n", tmp);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出成功\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
3.对比与总结
先看普通写法,代码重复很多,很冗余,而且这里我们实现的功能少,所以还不算特别多,但如果加上计算器的其他功能,代码冗余量可想而知,反观借助回调函数实现的程序,通过一个函数的参数使用函数指针来调用不同函数,这大大的减少了代码的冗余量。
这里使用回调函数是一个什么情况呢?总的来说,就是当主函数需要实现多个不同但相似的功能函数时,只要找出这些函数的共性,去实现一个函数,参数中定义一个函数指针,可以调用这些函数,此时就不用去单个实现这些函数了。
-
用途举例二:库函数qsort
1.qsort介绍
我们学过有冒泡排序,堆排序,快速排序,简单选择排序等排序方法,这里举冒泡排序算法,算法如下:
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz-1-i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
但是,我们发现这些排序算法只能排序整型变量,对于浮点型,字符串,结构体等一些数据类型无法排序,所以有无一种函数适用于所有数据类型的排序呢?有的
qsort就满足了这种需求,它是一种基于快速排序的适用所有数据类型排序的库函数,先来看看有关它的定义。
语法与头文件:
qsort参数列表 :
参数中的函数指针所指向的函数类型:
cmp是个函数指针类型,就是存放需适应多种数据类型比较的函数地址,以调用需比较的数据类型的函数,比如,需要比较整型,那就传入适用于比较整型大小的函数,见用法举例①,需要比较结构体,就传入适用于比较结构体大小的函数,进一步按照结构体的哪一个成员比较就看实际情况了,见用法举例②。
void* 可以接受任意数据类型的指针,但无法直接解引用,需要强转,这就可以适应任何数据类型的传入。
2.qsort用法举例
①比较整型
//自定义函数,用来排序整型数组,默认升序,若需要降序,只需要改为e2-e1
int cmp_int(const void* e1,const void* e2)
{
return *((int*)e1) - *((int*)e2); //通过强转来获取所比较数据类型大小的权限
}
int main()
{
int i = 0;
int arr[] = { 5,8,9,6,7,3,1,4,2,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
//打印数组
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
测试:
②:比较结构体
//自定义函数,比较结构体类型,按照成员名字来比较,当然也可以设置比较成员年龄或分数来比较
int cmp_struct_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
//先强转成要比较的结构体类型,再访问其成员
}
int main()
{
int i = 0;
struct Stu arr[] = { {"Mary",18,85.5},{"Xiao",21,95.0},{"Army",20,90.0} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_struct_name);
//打印数组
for (i = 0; i < sz; i++)
{
printf("%s ",arr[i].name);
}
return 0;
}
测试:
3.模拟实现qsort
qsort基于快排实现的库函数,我们基于上面的冒泡排序模拟实现同样功能的bsort函数。
思路过程:
bsort函数大部分与冒泡排序函数相同,只有交换两个元素部分不同,重点讲解。
已知bsort函数传入首地址void* 类型,元素个数num,以及元素大小width,我们无法访问所需要比较的数据类型大小的完整数据,但我们是可以将void*强转成char*类型,一个字节一个字节访问,一个字节一个字节交换,而width就是我们交换的次数,循环一轮就相当于交换了两个元素,见下图,比如交换9,8,小端字节存储
代码:
void bsort(void* base, int num, int width, int(*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < num - 1; i++)
{
int j = 0;
for (j = 0; j < num - 1 - i; j++)
{
//交换两个元素
char* pc1 = (char*)base + j * width; //元素首字节地址
char* pc2 = (char*)base + (j + 1) * width; //另一元素首字节地址
if (cmp(pc1,pc2)>0 ) //传入自定义函数中比较大小
{
int t = 0;
for (t = 0; t < width; t++)
{
char tmp = *pc1;
*pc1 = *pc2;
*pc2 = tmp;
pc1++;
pc2++;
}
}
}
}
}
int main()
{
int i = 0;
int arr[] = { 5,8,9,6,7,3,1,4,2,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bsort(arr, sz, sizeof(arr[0]), cmp_int);
//打印数组
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
测试: