1、回调函数的定义
什么是回调函数呢?
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
注意:回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
(说点大白话,其实就是通过函数指针来调用另一个函数,先设置一个函数,然后在另一个函数里调用这个设置好的函数)
类似这段代码
不难看出,这是实现计算器的一段代码,先是定义了加减乘除这四个函数,在主函数中并没有去直接使用它,因为这四个函数的类型都一样,所以可以通过calc函数,形参用函数指针来接收这四个函数的地址,间接调用这四个函数。通过依赖函数指针,来间接调用函数,calc函数这种就称为回调函数。
2.回调函数的一个实例(qsort函数)
(1)qsort函数的介绍和使用
qsort函数是一个库函数,底层使用的是一个快速排序的方式,对数据进行排序,可以是任何的数据类型,这个函数可以直接使用
注意:这里的排序是按照升序的,
语法:
//qsort 底层使用的快速排序
void qsort( void* base, //待排序数据的起始地址
size_t num, //待排序数据的元素个数
size_t size,
//待排序数据的一个元素的大小,单位是字节
int (*compar)(const void*, const void*)
//函数指针 - 指向了一个比较函数,这个函数是用来比较2个元素的
);
例1: 对整型数组进行比较
#include <stdlib.h>
#include <stdio.h>
void print(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void test1()
{
int arr[10] = { 5,2,1,4,6,7,8,9,3,10, };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz,sizeof(arr[0]), cmp_int);
//使用的时候需要头文件#include<stdlib.h>
print(arr,sz);
}
int main()
{
test1();
}
这里其实需要注意的就是比较函数,为什么回调函数能实现任何类型数据的比较呢,其中很大一部分原因在于能够将两个数的比较方法剥离出来,你就拿冒泡排序来说,他就只能够比较两个整型数据的大小。
具体是怎么剥离出来的呢,且听我一 一到来。通过观察这个函数的使用方式,你会发现待排序的起始地址是void类型的,这就表明可以接收任何类型的数据,而且将这个待排序的元素个数和元素大小都接收过来,就表明可以一个字节一一个字节的进行比较。最后不直接使用这个比较函数,而是使用一个函数指针将这个比较函数的地址接收,间接的调用了这个比较函数,所以被称之为回调函数。
需要注意的点是,这个比较函数只能返回0和1,就拿上述例子来说,
int cmp_int(const void e1, const void* e2),形参用void接收,加const是因为防止指针变量被指向的内容被修改。因为void不能解引用,所以需要强制类型转化为int*
例2:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
typedef struct stu
{
char name[20];
int age;
}stu;
//比较结构体变量的方法
//按年龄比
int cmp_age(void* e1, void* e2)
{
return ((stu*)e1)->age - ((stu*)e2)->age;
}
void test2()
{
stu arr[] = { {"zhangsan",21},{"lishi",18},{"wangwu",20} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_age);
}
//按名字比
int cmp_name(void* e1, void* e2)
{
return strcmp(((stu*)e1)->name, ((stu*)e2)->name);
//恰好stcmp函数的返回类型为0和1
}
void test3()
{
stu arr[] = { {"zhangsan",21},{"lishi",18},{"wangwu",20} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_name);
}
int main()
{
test2();
test3();
return 0;
}
(2)利用冒泡排序模拟实现一个qsort
1、冒泡排序
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)//趟数
{
int j = 0;
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;
}
}
}
}
2、模拟实现
我们已经回顾了一下冒泡排序,如何在冒泡排序的基础上进行修改,变成实现qsort的底层逻辑。
void print(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void swap(char* buf1, char* buf2, size_t size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
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; i++)//趟数
{
int j = 0;
for (j = 0; j < num - 1 - i; j++)
{
if (cmp((char*)base + j * size,
(char*)base + (j + 1) * size) > 0)
{
//交换
swap((char*)base + j * size,
(char*)base + (j + 1) * size, size);
}
}
}
}
int main()
{
int arr[10] = { 5,2,1,4,6,7,8,9,3,10, };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz,sizeof(arr[0]), cmp_int);
print(arr,sz);
}
在冒泡排序里,两个for循环都不需要变。后面比较两个数据需要改变一下,因为不一定是整型,所以不能直接使用>或<,因此就需要去使用一个比较函数,所以先必须找到这两个元素的地址,然后通过交换函数来进行交换。
那么到底该如何去找到这两个元素呢,我们当然会想到从传过来的base指针变量来下手,我们前面说过void是不能直接解引用的,而是需要强制类型转化,那么到底是强制类型转化为int还是char还是等等类型,这个就需要我们好好考虑一下了。因为数据的类型是不断变化的,如果使用int就只能交换int类型的数据,访问四个字节的元素,显然这是极其不方便的,而且也违背了qsort函数的通用性,所以我们就想了一个办法,使用char类型,char访问一个字节,是单位最小的,既然是单位最小的,其他单位我们不就可以在他们类型的大小吗,你又会发现我们传进来就真有这个类型的大小。所以char)base + j * size和(char*)base + (j + 1) * size就是两个元素的地址,把他传入到cmp_int函数。
比较以后还需要去交换,再次把两个元素的地址传过去,一个字节一个字节的交换,实现排序。