**
回调函数
**
上节文章,我们简单的讲述了一下关于回调函数,这篇文章我们来深度解析一下回调函数,并且会用一个经典例子来理解!!
回调函数的概念:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应!
可能概念有点太抽象,我们画图来理解一下,如下:
简单理解就是:
我们在主程序中把函数的指针(地址)当作一个参数,然后再传递给另一个函数,当这个指针被用来调用其指向的函数时,我们就称为回调函数。
下面,我们再用一个简单的例子来解释一下:
#include<stdio.h>
void test1()//<回调函数
{
printf("hehe\n");
}
void test2(void (*p)())//<这个指针p被用来调用其指向的函数test1,所以test1叫做回调函数
{
(*p)();//解引用打印
}
int main()
{
test2(test1);//在test2中传递了test1的地址
return 0;
}
通过,这个简单例子,大家对于回调函数,应该有了一定的理解,这时候相信大家一定有个这样的问题,就是这个回调函数与普通的函数调用有什么区别呢?一样都是函数调用,为啥要使用回调函数,还那么复杂,下面我给大家讲解一下,我们的回调函数是有多么厉害,并且后续会使用到典例去给大家解释。、
回调函数时优点:
在回调中,主程序把回调函数当作一个参数一样传入库函数。这样一来,只要我们改变传进库函数的,就可以实现不同的功能,这样有没有觉得非常灵活?并且当库函数很复杂或者不可见的时候利用回调函数就显得十分优秀,并且这可以让我们代码变得十分简便,没有那么繁琐!
典例进行讲解一下:
典例一:
我们如果要写一个简单的计算器实现加减乘除,我们一般的思路可能不会想到回调函数,可能只想到利用switch语句和do while()语句进行实现,这种思路当然是可行的,只不过存在一个问题就是,我们会发现代码非常冗余,并且重复代码会比较多,因此我们使用回调函数就能够很好的解决这个问题,思路:(如下代码+图分析)
void menu()
{
printf("*******************************\n");
printf("****** 1. add 2. sub *****\n");
printf("****** 3. mul 4. div *****\n");
printf("****** 0. exit *****\n");
printf("*******************************\n");
}
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 calc(int (*pf)(int, int))//通过函数指针调用其指向的函数称为回调函数
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
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;
}
这个就是利用回调函数进行解决一个比较繁琐的问题,这个代码可能不够非常明显体现出回调函数的特点,下面我们用画图来分析分析:
我们可以发现在这个代码中,我们的calc相当于一个媒介一样,我们通过它传递函数地址,然后再用函数指针调用其指向的函数,这个函数就称为回调函数!
这个例子不知道大家有没有理解,如果没有理解,没事,我们再来第二个例子!!
典例二:(库函数qsort()例子)
下面,我们通过qsort库函数例子进行深入解析一下回调函数的作用。
qsort()函数是用来快速排列字符串的,它的优点是可以排列任何字符串(字符串内部的元素类型相同)我们就可以不用每创建一个不同类型的字符串,就不用重新写一个函数来排列了。
qsort函数基于快速排序算法(也称为快速排序)。
我们通过cplusplus来仔细仔细了解一下qsort函数:
我们可看到qsort的功能是对数组的元素进行排序,同时我们观察qsort的参数:
void qsort (void* base, size_t num, size_t size,
int (compar)(const void,const void*));
我们来简单解释一下这个参数的含义:
void qsort(void* base, //待排序的数组的起始地址
size_t num, //数组中元素的个数
size_t size,//每个元素的所占的字节数
int (*compar)(const void* , const void* ));
//比较两个函数,可以是int型,可以是结构体...
这里我还要再解释一下为什么是void* base,这是因为我们在传这个数组的起始地址我们并不知道,它是整形还是字符串型,所以我们就用void*接收,那么所有的类型的数组我们都可以进行接收,相当于将其当作一个垃圾桶,我用代码解释一下:
int main()
{
int a = 10;
float f = 5.5f;
int* p = &a;
//p = &f;这里p的返回类型是int*,所以不能接收float型
//pp -> 垃圾桶
void* pp = &f;
pp = &a; //返回类型是void*型,因此所有的都可以进行接收
//pp++; 但是不能将其改变
//printf("%f\n", *pp); 也不能将其进行打印
return 0;
}
还有qsort函数中的参数size_t,其实就相当于unsigned int无符号整形,我们在vs里转到定义中查看:
还有qsort()函数中的参数 int (compar)(const void , const void* )),相信经过上一篇文章的讲解大家一定不陌生了(这里大家有疑问的可以返回我的上一篇文章去getget一下),这个就是函数指针。
这里就简单解释一下上面的函数指针,和compar结合作为一个指针,指针指向一个函数,这个函数的参数是(const void ,const void*),函数的返回类型是int型。(这里的const是指它只具有读取的权限但不能被修改)
int (compar)(const void , const void* )):这个作用是比较两个函数,但是具体的两个元素相比是要qsort()函数使用者自己来实现(也就是我们自己来实现的),因为qsort()函数作者也不知道你要比较的元素类型,所有它的参数是void*。
下面是比较函数的返回值,qsort()函数默认是升序,如果我们要实现降序的话,就将两个元素的相减顺序改变一下就行,后续会有代码说明一下这个升序降序。
好啦,以上这个库函数qsort讲解完了,下面我们通过典例利用qsort()函数并利用回调函数:
首先,我们通过库函数qsort对数组进行排升序降序:
#include<stdio.h>
int cmp_int(const void* e1, const void* e2)//回调函数
{
//由于不知道e1,e2是什么类型,所以先强制转换(int*)再解引用
return *(int*)e1 - *(int*)e2;//此时是升序
//return *(int*)e2 - *(int*)e1; 降序
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]), cmp_int);
//数组的起始地址,元素个数,元素字节大小,比较两个函数
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
库函数qsort()会根据我们的需要选择一种排序算法,然后调用实现该算法的函数来完成排序工作。这个被调用的排序函数就是回调函数。(如上面代码中cmp_int)
这是库函数qsort对整型数组进行排序的,我们说过qsort函数是可以排序任何字符串的,下面我们用结构体变量实现对其进行排序:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//使用qsort排序结构体
struct Stu
{
char name[20];
int age;
};
//按照年龄顺序排序
int cmp_stu_by_age(const void* e1, const void* e2)//回调函数
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//此时是升序
}
//按照名字来排序
//int cmp_stu_by_name(const void* e1, const void* e2)//回调函数
//{
// return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);//此时是升序
//}
int main()
{
struct Stu s[3] = { {"zhangsan",18},{"lisi",30},{"wangwu",20} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s,sz,sizeof(s[0]), cmp_stu_by_age);
//qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
return 0;
}
在这个代码中,我们的cmp_stu_by_age,cmp_stu_by_name属于回调函数。这同时也进一步了解了我们的库函数qsort()不仅仅是可以排序整形,还可以排序结构体成员。
模拟实现qsort()函数
模拟实现qsort()函数,我们自己写一个bubble_sort函数排序整型数组 和 排序结构体数组,接下来我们用代码去实现:
struct Stu
{
char name[20];
int age;
};
//通过bubble_sort函数排序整形数组
int cmp_int(const void* e1, const void* e2)//回调函数
{
//由于不知道e1,e2是说明类型,所以先强制转换(int*)再解引用
return *(int*)e1 - *(int*)e2; //此时是升序
//return *(int*)e2 - *(int*)e1; 降序
}
//使用bubble_sort函数按照结构体中年龄顺序排序
int cmp_stu_by_age(const void* e1, const void* e2)//回调函数
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//此时是升序
}
//使用bubble_sort函数按照结构体中名字来排序
int cmp_stu_by_name(const void* e1, const void* e2)//回调函数
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//此时是升序
}
void test1()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//整形数组
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//使用我们自己写的bubble_sort函数排序结构体数组
void test2()
{
struct Stu s[3] = { {"zhangsan",18},{"lisi",30},{"wangwu",20} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test1();
test2();
return 0;
}
改造冒泡排序
接下来,我们通过我们自己写的bubble_sort()函数改造冒泡排序函数,使得这个函数可以排序任意指定的数组,不仅仅是整形数组,还可以是结构体数组,下面我们上代码理解:
#include<stdio.h>
int cmp(const void* e1, const void* e2)//回调函数
{
return *(int*)e1 - *(int*)e2;
}
void swap(char* buf1, char* buf2, int width)//交换
{
for (int i = 0; i < width; i++)//这里是一个字节一个字节进行交换的
{
char tmp = *buf2;
*buf2 = *buf1;
*buf1 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* e1, const void* e2))
{
//总趟数
for (int i = 0; i < sz; i++)
{
//每一趟排序的过程
for (int j = 0; j < sz - 1 - i; j++)
{
//判断是否需要交换顺序,通过回调函数cmp判断
if (cmp((char*)base + j * width ,(char*)base + (j + 1) * width) > 0)
{
//交换两个函数
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test();
return 0;
}
在这个的代码中,我们充分利用到了回调函数和关于库函数qsort()的知识点,在这个代码中需要注意的是swap()函数在进行交换时,它是一个字节一个字节进行交换的,因此我们利用了一个for循环(for (int i = 0; i < width; i++)//这里是一个字节一个字节进行交换的)width是每一个元素的字节大小,这里整形是四个字节,因此我们一个字节一个字节进行交换的话(访问的最小字节是一个字节),所以需要交换四次。这部分改造冒泡排序的代码希望大家能够自己去实践实践,一定会有不一样的收获!!!
那么这部分知识点就到这啦,大家有什么见解和疑问欢迎各位大佬留言!!!
小火车继续前行~~~~