目录
8 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在B函数中通过函数指针去调用A函数。
举例1——计算器的实现
解决计算机代码冗余
#define _CRT_SECURE_NO_WARNINGS 1
//计算器
#include<stdio.h>
void meau()
{
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;
int ret = 0;
do
{
meau();
printf("请选择>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("ret=%d\n", ret);
break;
case 2:
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("ret=%d\n", ret);
break;
case 3:
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("ret=%d\n", ret);
break;
case 4:
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("ret=%d\n", ret);
break;
case 0:
printf("退出游戏");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
修改过后为:
//计算器
#include<stdio.h>
void meau()
{
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(*p)(int, int))//函数指针传参
{
int x = 0;
int y = 0;
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = p(x, y);//函数调用
printf("ret=%d\n", ret);
}
int main()
{
int input = 0;
do
{
meau();
printf("请选择>\n");
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("退出游戏");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
函数调用将函数指针传过去,用函数指针类型接收,通过函数指针就可以找到需要的函数。此刻函数指针指向某个函数,然后返回计算出来的值。
在main函数中,没有直接去调用函数。而是把函数指针传参给另外的一个函数calc,在calc内部使用函数指针调用,通过函数指针就可以找到指针指向的函数,此刻被指向的函数就是回调函数。
像上图所示,通过calc函数调用Add函数指针,找到Add函数,就把Add函数称为回调函数。
举例2——qsort函数
qsort是一个库函数,底层使用的是快速排序的方式,对不同数据进行排序的。
这个函数可以直接使用。
这个函数可以用来排序任意类型的数据。
对数据进行排序方法很多:
冒泡排,序选择排序,插入排序,快速排序等等。
关于qsort函数
1、关于qsort函数--->qsort - C++ Reference (cplusplus.com)
void qsort(void* base, //指向了待排序数组第一个元素的首地址
size_t num, //待排序数组的元素个数
size_t size,//每个待排序数组元素的大小,单位是字节
int (*compar)(const void* e1, const void* e2));
//函数指针,compar指向了一个比较函数,这个函数是用来比较两个元素的大小,
//e1和e2存放的是两个元素的地址
//在qsort内部调用这个函数,指向这个函数,这个函数就被称为回调函数
//qsort内部怎么排序我们不需要过多去探讨
//const也暂不做讲解
需要包含头文件 #include<stdlib.h>
- 排序整型数组,两个整型可以直接使用>比较
- 排序结构体数组,两个结构体的数据可能不能直接使用>比较
也就是不同类型的数据,比较大小的方法是有差异的。
最后一个参数,排序不同数据的重要点,需要封装不同的函数去比较不同的数据的大小。
2、那怎样通过元素地址,去比较两个整型元素数据的大小呢?
首先,我们要先对void*进行讲解。
- void* 类型的指针 — 不能进行解引用操作符,也不能进行+-整数的操作
- void* 类型的指针是用来存放任意类型数据的地址
- void* 无具体类型的指针
int main()
{
char c = 'w';
char* pc = &c; //可以
int a = 100;
int* p = &c; //不可以
void* pv = &c; //可以
pv = &a; //可以
return 0;
}
void* 就像一个垃圾桶,可以放任意类型数据的地址!
在以上qsort的参数中,因为不知道要比较的元素类型,所以我们使用void*指针的类型,来统一存放各种类型的指针。
这里规定,如果两数相减>0,就返回一个大于0的数; 如果两数相减=0,就返回一个等于0的数; 如果两数相减<0,就返回一个小于0的数; 我们可以直接巧妙转化为返回两者的差!
int的数据:将void*类型的数据强制转化成(int*),再作差。
当e1>e2,函数返回>0的值;
当e1<e2,函数返回<0的值;
当e1=e2,函数返回=0
//整型
void cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}//此时该函数就是回调函数
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test1()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
3、怎样通过元素地址,去比较两个结构体中元素数据呢?
例如,我们现在有如下结构体:
struct Stu
{
char name[20];
int age;
};
结构体数据怎么比较呢?
1. 按照年龄比较
2. 按照名字比较
//1. 按照年龄比较
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}//此时该函数就是回调函数
void test2()
{
struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12}};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
//2. 按照名字比较
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}//此时该函数就是回调函数
void test3()
{
struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
注意:这里用到了strcmp要用头文件 #include<string.h>。strcmp - C++ Reference (cplusplus.com)
我们可以发现,qsort可以比较各种各样类型的数据,这有很大一部分原因是
int (*compar)(const void* e1, const void* e2) 这一参数的功劳。
整体代码:
以整型举例。
#include<stdio.h>
#include<stdlib.h>
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
}
void qsort(void* base,
size_t num,
size_t size,
int (*compar)(const void*, const void*));
void compar_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void test1()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
printf("\n");
qsort(arr, sz, sizeof(arr[0]), compar_int);
print(arr, sz);
}
int main()
{
test1();
return 0;
}
举例3——冒泡排序
这个函数只能排序整型。
//冒泡排序
#include<stdio.h>
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 = 0;
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
print_arr(arr, sz);
}
void bubble_sort(void* base, size_t num, size_t size,
int (*cmp)(const void* e1, const void* e2))
cmp_int比较大小
以整型为例
int (*cmp)(const void* e1, const void* e2)
e1是一个指针,存放了一个要比较的元素的地址。
e2是一个指针,存放了一个要比较的元素的地址。
- e1指向的元素>e2指向的元素,返回>0的数字。
- e1指向的元素<e2指向的元素,返回>0的数字。
- e1指向的元素==e2指向的元素,返回>0的数字。
cmp是函数指针指向一个我们程序想要待排序的数组。
将比较函数cmp_int的地址传给cmp即可。
//比较大小
void cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//这里就是将cmp_int的地址在调用函数bubble_sort时将其传过去即可。
cmp传参数
1、直接对待排序的数组首元素地址解引用找到e1的地址,然后通过一个元素的大小或者+1可以找到e2的地址,可以吗?
当然不可以。
- void* 类型的指针—不能进行解引用操作符,也不能进行+-整数的操作。
- void* 类型的指针是用来存放任意类型数据的地址。
- void* 无具体类型的指针。
- void*和int*和char*一样都是指针类型。
2、将void*的指针强制转换成我们想要的int*或double*等,再+1可以吗?
不可以。因为强制转换存在在于我们公共的bubble_sort排序函数中时,不能随着待排序的数组数据类型不同而改变,我们只能改变不同数据类型的不同比较方法。
需要什么数据大小就写什么数据类型去比,但是我们的排序函数是不能改变的!
那怎么办呢?
我们可以利用最小的数据指针类型 char(*)
//if(arr[j]>arr[j+1])
if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
{
int tmp = 0;
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
交换swap
当我们只知道元素的起始地址,并不知道元素的类型所以我们并没有合适的中间值类型tmp创建。所以我们换一种方法。
我们已知元素e1和e2的起始地址和每个元素的大小。
那我们可以用一个一个char类型的数据交换,用for循环
直到每个元素的每个字节都交换结束,也就是一对元素交换完成。
注意:使用一次swap交换的是一堆数据。
//交换数据
void swap(char* buf1, char* buf2,size_t size)
{
char i = 0;
for (i = 0; i < size; i++)
{
char tmp = 0;
tmp = *buf1;
*buf1=*buf2;
*buf2 = tmp;
buf1++;//*buf1++
buf2++;//*buf2++
}
}
那么以下就以整型为例,写一个用冒泡排序的算法实现qsort函数的代码。
//冒泡排序
#include<stdio.h>
#include<stdlib.h>
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//交换数据
void swap(char* buf1, char* buf2,size_t size)
{
char i = 0;
for (i = 0; i < size; i++)
{
char tmp = 0;
tmp = *buf1;
*buf1=*buf2;
*buf2 = tmp;
buf1++;//*e1++
buf2++;//*e2++
}
}
//比较大小
void cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;//>0
}
void bubble_sort(void* base, size_t num, size_t size,
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++)
{
//if(arr[j]>arr[j+1])
if (cmp_int( (char*)base+j*size,(char*)base+(j+1)*size )>0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
void test1()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
printf("\n");
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test1();
}
结构体类型比较——用年龄比较。
#include<stdio.h>
#include<stdlib.h>
//交换数据
void swap(char* buf1, char* buf2, size_t size)
{
char i = 0;
for (i = 0; i < size; i++)
{
char tmp = 0;
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;//*e1++
buf2++;//*e2++
}
}
void bubble_sort(void* base, size_t num, size_t size,
int (*cmp_int)(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++)
{
//if(arr[j]>arr[j+1])
if (cmp_int((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
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;
}
void test2()
{
struct Stu arr[] = { {"zhangsan",20},{"list",30},{"wangwu",12} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]),cmp_stu_by_age);
}
int main()
{
test2();
}
结构体类型比较——用姓名比较。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//交换数据
void swap(char* buf1, char* buf2, size_t size)
{
char i = 0;
for (i = 0; i < size; i++)
{
char tmp = 0;
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;//*e1++
buf2++;//*e2++
}
}
void bubble_sort(void* base, size_t num, size_t size,
int (*cmp_int)(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++)
{
//if(arr[j]>arr[j+1])
if (cmp_int((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test3()
{
struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
test3();
}
上面的题目我们都是正序排序。那如果我们想要倒序排序,代码又要怎样去修改呢?(以整型为例)
bubble_sort()函数已经写死了,没有办法去更改了。那么如果你想要达到一个降序的效果,就可以去更改比较大小的函数cmp_int。
//正序比较大小
void cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;//>0
}
//倒叙比较大小
void cmp_int(const void* e1, const void* e2)
{
return *(int*)e2 - *(int*)e1;//>0
}
我们把回调函数这种情况叫做泛型编程。泛型编程在C语言中比较牵强。