1.回调函数是什么?
基本概念
回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数
时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
使用场景
- 事件驱动编程:在图形用户界面(GUI)编程中,当用户点击按钮、输入文本等操作发生时,系统会调用预先注册的回调函数来处理这些事件。
- 排序算法 :标准库中的
qsort函数允许用户传入一个比较函数作为回调函数,以实现自定义的排序规则。 - 异步操作:在多线程或异步编程中,当一个异步任务完成时,可以通过回调函数通知主线程进行后续处理。
实现方式
在 C 语言中,实现回调函数主要涉及函数指针 的使用。函数指针是指向函数的指针变量,它可以存储函数的地址,并且可以通过该指针调用相应的函数。
示例代码
示例 1:简单的回调函数示例
#include <stdio.h>
// 定义回调函数类型
typedef int (*Callback)(int, int);//typedef是自定义类型名字
// 回调函数1:加法
int add(int a, int b)
{
return a + b;
}
// 回调函数2:减法
int subtract(int a, int b)
{
return a - b;
}
// 主调函数,接受一个回调函数作为参数
int operate(int a, int b, Callback func) //Callback func ==int(*func)(int,int)
{
return func(a, b);
}
int main()
{
int x = 10, y = 5;
// 使用加法回调函数
int result1 = operate(x, y, add);//函数k名就是地址
printf("加法结果: %d\n", result1);
// 使用减法回调函数
int result2 = operate(x, y, subtract);
printf("减法结果: %d\n", result2);
return 0;
}
代码解释:
1. 定义回调函数类型
typedef int (*Callback)(int, int);
typedef是 C 语言中的一个关键字,用于为已有的数据类型定义一个新的类型名,目的是让代码更具可读性和可维护性。int (*Callback)(int, int)定义了一个函数指针类型。具体来说,Callback是一个新的类型名,它代表的是一个指向函数的指针,这个函数接收两个int类型的参数,并且返回一个int类型的值。
2. 定义回调函数
加法函数 add
int add(int a, int b)
{
return a + b;
}
- 这是一个普通的函数,它接受两个
int类型的参数a和b,并返回它们的和。 - 该函数的参数和返回值类型与前面定义的
Callback类型相匹配,所以它可以作为Callback类型的回调函数使用。
减法函数 subtract
int subtract(int a, int b)
{
return a - b;
}
- 同样是一个普通函数,接受两个
int类型的参数a和b,返回它们的差。 - 它的参数和返回值类型也与
Callback类型匹配,也能作为回调函数使用。
3. 定义主调函数
int operate(int a, int b, Callback func)
{
return func(a, b);
}
operate是主调函数,它接受三个参数:两个int类型的参数a和b,以及一个Callback类型的参数func。Callback func等价于int(*func)(int, int),即func是一个函数指针,它指向一个符合Callback类型定义的函数。- 在函数体中,通过
func(a, b)调用func所指向的函数,并将a和b作为参数传递给该函数,最后返回该函数的返回值。
4. main 函数
int main()
{
int x = 10, y = 5;
// 使用加法回调函数
int result1 = operate(x, y, add );
printf("加法结果: %d\n", result1 );
// 使用减法回调函数
int result2 = operate(x, y, subtract );
printf("减法结果: %d\n", result2 );
return 0;
}
- 首先,定义并初始化两个
int类型的变量x和y,分别赋值为 10 和 5。 - 调用
operate函数进行加法运算:operate(x, y, add)中,将x和y作为前两个参数传递给operate函数,将add函数名作为回调函数传递给operate函数。在 C 语言中,函数名可以隐式转换为函数的地址,所以add实际上传递的是add函数的地址。operate函数内部会通过函数指针func调用add函数,计算x和y的和,并将结果返回给result1。- 使用
printf函数输出加法结果。
- 调用
operate函数进行减法运算:- 类似地,
operate(x, y, subtract)将subtract函数的地址作为回调函数传递给operate函数。 operate函数内部通过函数指针func调用subtract函数,计算x和y的差,并将结果返回给result2。- 使用
printf函数输出减法结果。
- 类似地,
2. qsort 使⽤举例
函数原型
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
参数解释
base:指向要排序的数组的第一个元素的指针。由于使用void *类型,所以可以处理任意类型的数组。nmemb:数组中元素的个数。size:数组中每个元素的大小(以字节为单位)。compar:一个指向比较函数的指针。该比较函数用于确定元素之间的顺序关系,它接受两个const void *类型的参数,并返回一个整数值,表示两个元素的相对顺序。
比较函数规则
比较函数 int_comp 的返回值规则如下:
-
如果返回值小于 0,表示第一个参数小于第二个参数。
-
如果返回值等于 0,表示两个参数相等。
-
如果返回值大于 0,表示第一个参数大于第二个参数。
#include <stdio.h>
#include <stdlib.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);//小于返回一个负数,大于返回一个正数,等于返回0.
}int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{printf( "%d ", arr[i]);
}
printf("\n");return 0;
}
代码解释:
1. 比较函数 int_cmp
- 参数 :
qsort函数要求使用者提供一个比较函数,这个比较函数必须接受两个const void *类型的参数。const void *是一种通用的指针类型,可以指向任意类型的数据,使用它是为了让qsort函数能够处理不同类型的数组。这里的p1和p2就是指向要比较的两个元素的指针。 - 类型转换 :在比较函数内部,由于
p1和p2是const void *类型,不能直接进行解引用操作,所以需要将它们转换为int *类型(因为这里要比较的是整数数组),然后再进行解引用,得到具体的整数值。 - 返回值 :比较函数的返回值决定了两个元素的相对顺序。如果
*(int *)p1 - *(int *) p2的结果小于 0,说明p1指向的元素小于p2指向的元素;如果结果等于 0,说明两个元素相等;如果结果大于 0,说明p1指向的元素大于p2指向的元素。
2. main 函数
2.1 数组定义与变量声明
- 定义了一个包含 10 个整数的数组
arr,初始化为{ 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }。 - 声明一个整型变量
i用于后续的循环操作。
2.2 调用 qsort 函数进行排序
qsort函数的原型为void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));。base:指向要排序的数组的第一个元素的指针,这里传入arr,即数组的首地址。nmemb:数组中元素的个数,通过sizeof(arr) / sizeof(arr[0])计算得到,即数组的总字节数除以单个元素的字节数。size:数组中每个元素的大小(以字节为单位),这里是sizeof(int),表示每个整数元素占用的字节数。compar:指向比较函数的指针,这里传入int_cmp,即前面定义的比较函数。
2.3 输出排序后的数组
-
使用
for循环遍历排序后的数组arr,并使用printf函数将每个元素输出,元素之间用空格分隔。 -
最后使用
printf("\n");输出一个换行符,使输出结果更美观。 -
main函数返回 0 表示程序正常结束。综上所述,这段代码的主要功能是使用
qsort函数对一个整数数组进行排序,并将排序后的数组元素输出。通过自定义比较函数int_cmp,可以灵活地控制排序的顺序。
3. qsort函数的模拟实现
下面就是模拟qsort函数的模拟实现的
#include<stdio.h>
int cmp_int(const void* p1, const void* p2)//整型排序
{
return (*(int*)p1 - *(int*)p2);//可以做差返回
}
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 width)//比较之后交换的函数
{
int i = 0;
char tmp = 0;
for (i = 0; i < width; i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))//总函数
{ // 趟数
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 test1()//需要比较的
{
int arr[] = { 9,8,7,4,6,2,10,1,3,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//cmp_int == &cmp_int 地址需要指针接收。
print_arr(arr, sz);
}
int main()
{
test1();
return 0;
}
1. 比较函数
1.1 cmp_int 函数
int cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
- 功能:用于比较两个整型数据的大小。
- 参数 :
p1和p2:const void*类型的指针,这是为了使函数具有通用性,可以接收任意类型的指针。
- 实现细节 :
(int*)p1和(int*)p2:将void*类型的指针强制转换为int*类型,以便可以解引用获取整型值。*(int*)p1和*(int*)p2:解引用指针,得到对应的整型值。*(int*)p1 - *(int*)p2:计算两个整型值的差。如果结果大于 0,表示p1指向的值大于p2指向的值;如果结果小于 0,表示p1指向的值小于p2指向的值;如果结果等于 0,表示两个值相等。
2. 打印函数
2.1 print_arr 函数
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
- 功能:用于打印整型数组的元素。
- 参数 :
arr:整型数组。sz:数组的元素个数。
- 实现细节 :
- 使用
for循环遍历数组的每个元素。 - 使用
printf函数以整数格式打印每个元素,并在元素之间添加一个空格。
- 使用
3. 交换函数 Swap
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
char tmp = 0;
for (i = 0; i < width; i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
- 功能:用于交换两个内存块的内容。
- 参数 :
buf1和buf2:char*类型的指针,指向要交换的两个内存块。width:每个内存块的字节数。
- 实现细节 :
- 使用
for循环逐字节交换两个内存块的内容。 - 通过
char类型的临时变量tmp来存储中间值,实现交换。 - 每次交换一个字节后,将指针
buf1和buf2向后移动一个字节,直到交换完整个内存块。
- 使用
4. 冒泡排序函数 bubble_sort
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
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);
}
}
}
}
- 功能:实现通用的冒泡排序算法。
- 参数 :
base:void*类型的指针,指向待排序数组的起始地址,使用void*类型可以接收任意类型的数组。sz:数组的元素个数。width:每个元素的字节数。cmp:函数指针,指向一个比较函数,用于确定元素的顺序。
- 实现细节 :
- 外层
for循环控制排序的趟数,共进行sz - 1趟。 - 内层
for循环进行每一趟的两两比较,比较相邻的两个元素。 cmp((char*)base + j * width, (char*)base + (j + 1) * width):调用比较函数cmp比较相邻两个元素的大小。(char*)base + j * width和(char*)base + (j + 1) * width分别计算相邻两个元素的地址。- 如果比较结果大于 0,表示顺序不正确,调用
Swap函数交换这两个元素的位置。
- 外层
5. 测试函数
5.1 test1 函数
void test1()
{
int arr[] = { 9,8,7,4,6,2,10,1,3,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
- 功能 :创建一个整型数组,调用
bubble_sort函数对数组进行排序,然后调用print_arr函数打印排序后的数组。 - 实现细节 :
int arr[] = { 9,8,7,4,6,2,10,1,3,5 };:定义一个包含 10 个整数的数组。int sz = sizeof(arr) / sizeof(arr[0]);:计算数组的元素个数。bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);:调用bubble_sort函数对数组进行排序,使用cmp_int函数作为比较规则。print_arr(arr, sz);:调用print_arr函数打印排序后的数组。
6. 主函数
int main()
{
test1();
return 0;
}
- 功能 :程序的入口点,调用
test1函数进行测试。 - 返回值:返回 0 表示程序正常结束。
下面是我自己画的图(画的不是特别好)方便大家理解理解:
