四、回调函数
1.回调函数的概念(用函数指针调用的函数)
概念:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
我们直接大白话给他翻译成通俗易懂的语言。怎么样就是回调函数呢?就比如你现在有一个可以实现两数之和功能的函数Add,你明明可以在main函数里面直接调用这个函数,给他传上两个整数的参数,让他返回和的值。但是,什么叫回调函数呢?其实就是你稍微拐了个弯儿,你把这个函数作为参数传递给一个Calc函数,然后Clac函数的参数被设计成为一个指向Add函数的函数指针,然后我们在Calc函数中,用接收Add函数的函数指针p(假设指针的名字是p)重新调用Add函数,这时Add函数就被称为回调函数
2.回调函数的使用场景
2.1使用场景一:
我们先用上面那个代码,来应用一下回调函数的使用
如果我们想要实现加减乘除这些函数功能的实现,除了上方写一个函数指针数组来实现,也还可以用switch的语句来实现,例如下面的代码:
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; } int main() { int input = 0; int x = 0; int y = 0; do { menu(); printf("请选择:>\n"); scanf("%d", &input); switch (input) { case 1: printf("请输入两个操作数:>\n"); scanf("%d %d", &x, &y); printf("%d\n", add(x, y)); break; case 2: printf("请输入两个操作数:>\n"); scanf("%d %d", &x, &y); printf("%d\n", sub(x, y)); break; case 3: printf("请输入两个操作数:>\n"); scanf("%d %d", &x, &y); printf("%d\n", mul(x, y)); break; case 4: printf("请输入两个操作数:>\n"); scanf("%d %d", &x, &y); printf("%d\n", div(x, y)); break; case 0: printf("退出程序\n"); break; default: printf("选择错误,重新选择\n"); break; } } while (input); return 0; }
如果我们需要一个函数Calc实现4种函数功能的话,那这个函数参数就必须是函数指针,我们在Calc函数里实现了四种运算法则的函数,这四种函数就是回调函数
2.2使用场景二:
void print( const char* str) { printf("%s\n", str); } void test(void(*p)( const char*)) { p("I LOVE YOU");//将字符串首字符地址传过去了 } int main() { test(print); return 0; }
2.3使用场景三qsort函数:
我们先介绍一下,qsort函数如何使用吧😁
parameters是参数的意思,第一张图片向我们介绍了,qsort函数的返回类型和参数类型,第二张图片向我们介绍了各种参数所代表的意思
base是你要排序的数组的起始地址,num是数组所有元素的大小(而不是所有元素的字节的大小),width是数组每个元素的字节大小,最后一个参数是一个函数指针,用来接收你传过去的函数名(也就是函数地址)第三张图片是我们所设计的函数的功能,他只要负责返回大于0或小于0或等于0的数字就完全OK了
下面通过代码,来演示一下qsort函数的使用
int cmp(const void* p1, const void* p2) { return (*(int*)p1 - *(int*)p2); } int main() { int arr[10] = { 1,3,5,7,9,2,4,6,8,0 }; int i = 0; qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), cmp); for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } return 0; }
我们完美的实现了,qsort函数的使用**(如果你阅读到了这里,小编真心佩服你,你一定能拿到理想的offer的,我们一起加油,耶耶耶🤩🤩🤩)**
2.4自己实现一下qsort函数
struct stu { char name[20]; int age; char sex[5]; int tele[12]; }; int cmp_by_int(void* buf1, void* buf2) { return (*(int*)buf1 - *(int*)buf2);//需要把无类型指针转换为int*的指针,因为我们需要返回一个整数(>0,<0,=0这三种) } int cmp_by_float(void* buf1, void* buf2) { return (*(int*)buf1 - *(int*)buf2); } int cmp_by_struct_by_age(void* buf1, void* buf2) { return ((struct stu*)buf1)->age - ((struct stu*)buf2)->age; } void swap(char* e1, char* e2,int width) { int i = 0; for (i = 0; i < width; i++) { char tmp = '1'; tmp = *e1; *e1 = *e2; *e2 = tmp; e1++; e2++; } } void my_qsort(void* base, int num, int width, int(*ps)(void* elem1, void* elem2)) { //确定冒泡排序的趟数 int i = 0; for (i = 0; i < num; i++) { int j = 0; //确定每一趟需要排序的元素对的对数 for (j = 0; j < num - 1 - i; j++) { //确定是否要进行交换元素,也就是是否要排序 if (ps((char*)base + j*width, (char*)base + (j+1)*width) > 0) //不同元素类型的字节宽度是不一样的,所以传j*width,不同元素传过去的地址大小是不同的 { //进行交换元素 swap((char*)base + j*width, (char*)base + (j+1)*width, width); //我们在进行交换元素时,其实道理和上面的if判断条件是相同的,我们传过去的地址大小也是无法确定的 //所以要传他字节宽度的整数倍 } else { ; } } } } void test3() { struct stu s1[] = { {"zhangsan",20,"man",15598303778}, { "wangwu",30,"women",13232746588 }, { "lisi", 40, "man", 13231244563 } , }; my_qsort(s1, sizeof(s1) / sizeof(s1[0]),sizeof(s1[0]), cmp_by_struct_by_age); } void test2() { float arr2[10] = { 1.0,3.0,5.0,7.0,9.0,2.0,4.0,6.0,8.0,10.0 }; my_qsort(arr2, sizeof(arr2) / sizeof(arr2[0]), sizeof(arr2[0]), cmp_by_float); int i = 0; for (i = 0; i < sizeof(arr2) / sizeof(arr2[0]); i++) { printf("%f ", arr2[i]); } } void test1() { int arr1[10] = { 1,3,5,7,9,2,4,6,8,0 }; my_qsort(arr1, sizeof(arr1) / sizeof(arr1[0]), sizeof(arr1[0]), cmp_by_int); int i = 0; for (i = 0; i < sizeof(arr1) / sizeof(arr1[0]); i++) { printf("%d ", arr1[i]); } printf("\n"); } int main() { test1(); test2(); test3(); return 0; }
欧克,从上面的四张图片,我们就可以看出,my_qsort函数完美的成功实现,小编在自
己实现这个函数的过程中,遇到了4个bug,每个都能让我生不如死😣😣😣
下面给大家,说说我遇到的bug吧,这样你们在自己编写代码时,就可以不用犯小编遇到的错误了,更加节省你们的时间,少走些弯路,嘻嘻🤭🤭🤭
1.结构体的声明放在使用结构体函数的下面,一定要把类型声明放在cmp_by_struct_age函数声明的上面
2.my_qsort函数内部的for循环结构的判断条件要设置好
3.my_qsort函数内部,我们再回调函数时,要注意传过去的地址,因为不同元素单个个体的地址大小是不同的,所以我们要用下面这样的传地址方式
swap((char*)base + j*width, (char*)base + (j+1)*width, width); if (ps((char*)base + j*width, (char*)base + (j+1)*width) > 0)
4.my_qsort函数的第二和第三个参数分别是,数组的元素个数(记住是元素个数,比如一个结构成员,一个浮点数,一个整型,都是一个元素)和单个元素的字节大小(记住是字节大小,也就是1,2,3,4这些大小,是整数)