持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
一、回调函数
1、什么是回调函数
🍳 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
简单来说:就是有一个A函数,这里不是直接去调用A函数,而是把A函数的地址传给B函数(这里的B函数的参数就是一个函数指针),这时通过B函数去调用A函数时就称为回调函数
2、实例:
/***********************************************************************
目的:继续使用switch语句来实现并简化【实例1(实现计算器) - 纠正2】中的代码
想法:这里将会把红色标识区域封装成一个函数,调用即可。但这里有个问题是printf和scanf两条语句是相同的,但是每次调用这个函数时传的参数可不一样
分析:这里将封装一个函数Calc来帮我们分别计算加减乘除,而这个函数的参数将是一个函数指针pf,来接收不同模块的代码。然后再通过pf所指向的这个函数的地址去间接调用那个函数。这里就用到了回调函数
平台:Visual studio 2017 && windows
*************************************************************************/
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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 Calc(int (*pf)(int, int))//这里使用函数指针来做为Calc的参数
{
int x = 0;
int y = 0;
printf("请输入2个操作数\n");
scanf("%d %d", &x, &y);
return pf(x, y);//这里通过pf指针来调用所对应传过来的函数
}
int main()
{
int input = 0;
do
{
menu();
int ret = 0;
printf("请选择:>");
scanf("%d", &input);
switch(input)
{
case 1:
ret = Calc(Add);//这里应把Add函数的地址传给Calc,且在Calc中通过指针调用Add
printf("ret = %d\n", ret);
break;
case 2:
ret = Calc(Sub);
printf("ret = %d\n", ret);
break;
case 3:
ret = Calc(Mul);
printf("ret = %d\n", ret);
break;
case 4:
ret = Calc(Div);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
}while(input);
return 0;
}
3、qsort函数
1、bubble_sort
💨 之前有学习过冒泡排序,再来回顾一下
#include<stdio.h>
void bubble_sort(int arr[], int sz)
{
int i = 0;
int j = 0;
for(i = 0; i < sz - 1; i++)
{
for( j = 0; j < sz - 1 - i; j++)
{
if(arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
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);
return 0;
}
2、解读qsort函数
💨 bulbble_sort是我们自己实现的函数,缺点是只能排序一种类型,如果还想排序其它类型的数组,就需要自己设计。 qsort ():快速排序,可以排序整型、字符型、结构体等等。接下来我们就来了解一下qsort函数:
qsort函数头:<stdlib.h> qsort函数原型:
函数参数(我们来看一下文档):
全英文的文档可能对大家还是有点不友好,这里把大概意思翻译一下 void* base:base中存放的是待排序数据中第一个对象的地址 size_t num:num是排序数组元素的个数 size_t size:size是排序数据中一个元素的大小,单位是字节 int (* cmp) (const void**, const void*) :cmp是用来比较待排序数据中的2个元素的函数。 返回的是>0的数字:第1个元素 > 第2个元素 返回的是=0的数字:第1个元素 = 第2个元素 返回的是<0的数字:第1个元素 < 第2个元素 ==注:我们不防可以推理一下,当开发人员在设计这个函数时可能会想到的场景== ==1、这个函数是实现不同类型的数组排序,所以base必需有能力指向不同的数据类型,所以base这个指针的类型就是void类型== ==2、同样的这个函数必需知道你待排序的数组里有几个元素,所以这里又设计了num来接收== ==3、如果void base指向数组时,它并不知道一个元素有多大(因为这是void类型),所以这里还需要一个参数size来接收一个数组元素有多大== ==4、因为不同的类型比较,比较的方法也有所差异(比如int类型使用>、<比较;而char类型使用strcmp比较),所以这里就使用函数指针来接收,且所指向的参数部分也是void类型, 所以开发人员就把两个元素比较的这个函数交给使用者来确定==
💨 使用qsort函数来排序整型数组
#include<stdlib.h>
#include<stdio.h>
int cmp_int(const void* e1, const void* e2)
{
//return *(int*)e1 - *(int*)e2;//升序
return *(int*)e2 - *(int*)e1;//降序
}
void print_arr(int arr[], int sz)
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[10] = {8,9,0,6,5,1,3,2,4,7};
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
return 0;
}
💨 使用qsort函数来排序结构体
```c
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
struct Stu
{
char name[20];
int age;
};
int sort_by_age(const void* e1, const void* e2)
{
//return ((struct Stu*)e1) -> age - ((struct Stu*)e2) -> age;//升序
return ((struct Stu*)e2) -> age - ((struct Stu*)e1) -> age;//降序
}
int sort_by_name(const void* e1, const void* e2)
{
//return strcmp(((struct Stu*)e1) -> name, ((struct Stu*)e2) -> name);//升序
return strcmp(((struct Stu*)e2) -> name, ((struct Stu*)e1) -> name);
}
int main()
{
struct Stu s[] = {{"zhangshan", 30}, {"lisi", 34}, {"wangwu", 20}};
//按照年龄来升序
qsort(s, sizeof(s)/sizeof(s[0]), sizeof(s[0]), sort_by_age);
//按照名字来排序
qsort(s, sizeof(s)/sizeof(s[0]), sizeof(s[0]), sort_by_name);
return 0;
}
3、模仿qsort函数实现冒泡排序的通用算法
#include<stdio.h>
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for(i = 0; i < width; i++)
{
char temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for(i = 0; i < sz - 1; i++)
{
int j = 0;
for(j = 0; j < sz -1 - i; j++)
{
if(cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void print(int arr[], int sz)
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int cmp_int(const void* e1, const void* e2)
{
//return *(int*)e1 - *(int*)e2;//升序
return *(int*)e2 - *(int*)e1;//降序
}
int main()
{
int arr[] = {8,9,0,6,5,1,3,2,4,7};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print(arr, sz);
return 0;
}
分析: bubble_sort这个函数要模仿qsort函数,这是待排序的数组:
1、调用bubble_sort函数:首先参数部分一定是按照qsort函数参数来模拟的 (数组首元素,数组元素个数,数组元素大小,自定义的比较函数)
2、定义cmp_int函数 这里e1和e2接收了2个要比较的参数,如果要把它们的结果返回去,就需要将void* 类型强转为int* 类型才可以
3、定义bubble_sort函数:
3.1、确定好循环次数
3.2、接着就是应用回调用函数机制(通过bubble_sort函数的参数 -> cmp函数指针来调用cmp_int函数并把两两元素传参) ==确定要传参的元素:== base + j, base + j + 1 -> 逻辑上是可行的,但是base的类型是void*,语法不支持 所以: 这里不管是什么类型都将base转换为char*类型(因为它是类型大小的最小单位),再加上 j 乘上1个元素的大小就可以确定元素地址 ==确定函数的返回值:== 如果cmp这个函数的返回值>0则交换,刚好对应qsort函数
![]()
3.3、再定义并调用一个交换的函数 把两元素的地址传给Swap函数,因为类型的缘故,我们并不知道交换多大空间,所以这里把wide也传参的原因是让这个数据一个字节一个字节的进行交换