回调函数概念

准确来说,回调函数不是一个函数,而是一种函数调用的机制。假设实现方A函数中设计了一种算法,将A函数的地址(函数名)传递给调用方B函数,B函数的形参中有一个函数指针变量pFun,该函数指针指向了A函数。在B函数内部通过该函数指针调用了A函数,就称这种调用机制为回调函数。

图解:

C库函数-qsort运用回调函数机制

qsort函数原型:

void qsort(void* base,

size_t num,

size_t width,

int(__cdecl* compare)(const void* elem1, const void* elem2))

参数1:void*类型(空指针类型)的变量base,可以接收任意类型的地址,用于接收待排序的数组名

参数2:待排序数组的长度

参数3:带排序数组每个元素占用内存的字节数

参数4:函数指针,指向一个用户自定义的判断数组两个元素大小的函数。函数的返回值是int,两个参数都是const void*类型,也表示可以接收任意类型的地址。const的作用是因为在用户自定义的函数中,只是比较传递的两个元素的大小,并不会改变该元素的值。

qsort排序原理:快速排序

示例:使用qsort完成整形数组升序/降序排序

1:对于整形数组,以升序来说,数组两个元素最直接的比较方式就是看前一个是否比后一个大,如果前一个减去后一个大于0,那么就大于,如果等于0, 那么就相等,如果小于0,那么就小于。

2:在qsort的内部,会将数组的两个元素的元素传递给用户自定义的比较大小的函数,但是比较大小的函数类型是const void*类型的指针,在指针的概念中,指针的类型决定了指针跨越的步长(指针+/-整数跨越的距离)和对指针解引用之后操作的权限有多大。所以不能直接在比较大小函数中直接对e1和e2进行解引用或者+/-整数,因为void*类型是不确定大小的。所以需要将额e1和e2先强制类型转换成int*类型。这里面要引申一个问题。

引申:强制类型转换,强制类型转换只是改变了我们或者说编译器看待该变量的方式,但是并没有改变数据在底层的二进制值。比如说,我将一个float类型的变量a强制类型转换成int,然后赋值给变量b,其实就是将这个float类型的变量的值9.3当成int看待,但是并没有改变这个float类型变量在底层的二进制值。

同样的,在qsort函数的内部,它一定是传递了数组两个元素的地址,假设是array[0]的地址0x0000FF10和array[1]的地址0x0000FF14,传递给了void*类型的指针变量e1和e2,也就是这个时候,指针变量e1和e2就指向了该地址的内存空间,如下图所示

在Up_Compare_Int函数内部,将e1和e2强制类型转换成了int*,那么其实就是将e1和e2指针指向的空间的内容以整形指针的方式去看待。既然以整形指针的形式去看待,那么对其解引用,就是将0x0000FF14和0x0000FF18以整形的方式去解读。就达到了判断两个整形元素大小的作用。

所以说,qsort函数最精髓的地方,就是这个函数指针的形参,通过用户自定义的判断方式,就可以实现不同类型数组的排序。

模拟qsort运用回调函数机制完成冒泡排序

传统排序整形数组的排序冒泡排序

函数Bubble的形参和函数内部的判断逻辑,限制了冒泡函数只能接收数组元素是int类型,如果去实现一个通用的冒泡函数,可以参考qsort函数进行改造。

通用性冒泡排序函数原型

void BubbleSort(void* base,

size_t len,

size_t size,

int (*pFun)(const char*e1, const char*e2));

参数解释同qsort函数。

冒泡排序的排序原理是不变的,变化的是内部的判断和交换逻辑如何实现?

因为用户自定义的判断大小函数,其实和qsort是一致的,都是由使用方自己设计。所以,要考虑的就是传递什么参数给用户自定义判断大小的函数

base中保存数组首元素的地址,但base是void*的指针,所以不能直接使用,直接解引用或者+/-整数都是错误的。所以,我们需要考虑将其强制类型转换。

但是转换成什么类型是最合适的,如果是int*类型,那么如果传入的数组是字符数组,那么判断大小就会出错,因为字符数组的排序需要逐字节比较。所以,最合适的还是强制类型转换成char*类型,因为内存单元的大小就是1byte。

判断部分代码如下:

最核心的地方就在于通过传入的参数size,也就是每个元素占用内存的字节数,和变量j来控制指针变量base移动的距离,从而达到每次发送的数组待比较前后两个元素的地址。

完成了判断部分,就需要考虑到交换部分,也就是将满足判断交换的两个元素交换。达到冒泡的效果

备注:既然要作为类似qsort的同样函数,那么理所当然,除了用户自定义比较大小函数,交换函数也要实现通用函数。

考虑到传入的数组元素的类型可能是多变的,所以,为了实现通用函数,交换函数我们直接以逐字节的方式进行交换。可以设计成参数是char*类型的指针,但是char类型的每次只能访问一个字节,如果传入的元素,实际上是多字节的,那么就无法确定距离交换的字节数,所以还需要一个参数size,也就是元素占用内存的字节数

交换部分代码:

实现原理如下:

通过char*类型指针解引用每次只能访问一个字节的内存空间和变量i以及size来达到两个待交换元素逐字节交换的效果

完整代码如下: