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 表示程序正常结束。