说起排序,我们会想起许多算法,在之前的博客中我也写到过,比如:冒泡排序法、快速排序法、选择排序法等等。其实在C语言中一直有一个可以将数组中的内容进行排序的函数且功能完善内容齐全的库函数——qsort函数。今天就让我们来探索一下吧!
回调函数
在了解qsort函数时,我们先来说一下什么时回调函数。
回调函数:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当 这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调 用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
回调函数其实是对某一类函数的一种称呼,在使用时我更感觉类似一种嵌套关系,在对应的时间使用回调函数就会有意想不到的收获。
在使用qsort函数时我们会用到回调函数,所以qsort函数可以在更广泛的常见进行运用(整型、浮点型、字符串、结构体等等)。接下来就让我们走进qsort函数中!
初始qsort函数
qsort库函数是在#include<stdlib.h>中的,作用就是对数组中的元素进行排序。
qsort函数的特点:
1.使用的是快速排序的方法
2.适合于任何类型数据的排序
以上就是qsort函数的返回值与各个参数的内容,现在我们进行分析:
返回值:void(不返回任何内容)
参数:
void* base:指向要排序的数组的第一个对象的指针,转换为 void *
size_t num:数组中由base指向的元素数。 size_t是无符号整型。
size_t size:数组中每个元素的大小(以字节为单位)size_t是无符号整型。
int (*compar) (const void*, const void*):这是一个函数指针,实参应该给其一个函数的地址,其中给予的函数的返回值为int,两个参数是指向比较两个元素的函数的指针。
所以在使用qsort函数时,我们应该创建一个比较的函数传入qsort函数中让其使用。 创建的比较函数应该与自己想要比较的数组内容进行匹配,对应不同的数组内容,我们应该创建不同的比较函数进行比较。
如何创建比较函数
在创建自己想要比较的compar函数时,最重要的一点时遵循qsort参数的模板进行创建,否则qsort函数将无法使用。
void*(空指针):它可以接收任何类型的指针变量,但是不能进行指针操作,因为它没用空间访问权限,如果没有注意此情况编译器就会报错。所以我们在给予赋值时必须将它强制类型转换,转换为自己需要的数据类型即可。
当排序数组为整型数组:
int compar(const void* p1, const void* p2) { return (*(int*)p1 - *(int*)p2); }
排序数组为字符串数组时,我们应该使用#include<string.h>中的strcmp函数进行比较:
int compar(const void* p1, const void* p2) { return (strcmp(*(char*)p1 , *(char*)p2)); }
结构体数组时,比较内容为字符串时:
int compar(const void* p1, const void* p2) { return strcmp((struct stu*)p1->name , (struct stu*)p2->name); }
以上是几个比较典型的例子,看完这些例子我相信已经知道compar函数应该怎么创建了,下面让我们来上一段代码进行实操。
qsort函数的引用
根据上面的说明,我们基本已经掌握qsort函数的基本使用方法,现在让我们通过代码对qsort函数有更进一步的认识。
#include<stdio.h> #include<string.h> #include<stdlib.h> struct Stu { char name[20]; int age; }; void print(int arr[10], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } } int compar1(const void* p1, const void* p2) { return (*(int*)p1 - *(int*)p2); } int compar2(const void* p1, const void* p2) { return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); } void test1() { int arr1[10] = { 3,1,5,2,9,7,4,8,0,6 }; int sz = sizeof(arr1) / sizeof(arr1[0]); qsort(arr1, sz, sizeof(arr1[0]), compar1); print(arr1, sz); } void test2() { struct Stu arr2[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu",16} }; int sz = sizeof(arr2) / sizeof(arr2[0]); qsort(arr2, sz, sizeof(arr2[0]), compar2); for (int i = 0; i < sz; i++) { printf("\n%s %d", arr2[i].name, arr2[i].age); } } int main(void) { test1(); test2(); return 0; }
这段代码是将一个整数数组和结构体数组中的字符串进行升序排序。这里我想说一下字符之间的比大小是通过它们的ASCII码值进行比较。运行结果如下:
如果想要进行降序排序,我们只需要将compar函数中的比较的两个值进行交换即可。
模拟qsort函数
根据我们在使用qsort函数的一些功能特点,来模拟出qsort函数的功能。
qsort函数的功能就是排序,所以这个函数中一定有某种排序的方法,在这里我们将使用冒泡排序法进行。(其他排序方法也是可以的)
void bubble_sort(int arr[], int sz) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } }
这是一个最标准的冒泡排序法,我们通过上面的排序代码为框架进行模拟qsort函数。
假设就使用这个函数当作qsort函数,我们就会发现很多问题!!!
问题一:参数只能接收整型数组,其他数组不能被接收。
解决:我们就可以模仿qsort函数中的第一个参数,将第一个参数改为void* base,这样什么类型的数组我们都可以接收。
问题二:当我们使用void*base作为第一个参数时,指针的移动应该怎么实现?(数组元素的大小和数量)
解决:继续仿照qsort函数,加入size_t num和size_t size.
问题三:上述函数中两数两两比较是建立于整数基础上的比较,所以使用> < >= <=就可以实现。如果传入的数组为结构体数组时,我们就不能用比较整数的比较方法实现了。(对于不同类型的数据不能简单的使用>进行比较)
解决:我们就需要用到回调函数,将两个元素的比较方法,以函数参数的形式传递。(函数指针将成为第四个参数)
当创建好compar函数时,我们应该怎么去给compar函数去传递参数呢?
compar函数是多个函数,通过不同的调用我们可以比较不同的数组,但是在模拟函数中给compar函数传参的位置只有一个,我们应该考虑兼顾所以的数组类型。
我们知道了数组中每个元素的大小,相当于我们知道其元素在内存中所占的大小,为了兼顾所有数据,我们用访问内存权限最小的指针char*来作为基本单位,在乘上每个元素的大小,就可以实现任何数据的访问。
在for循环中使用即可访问数组中的所有元素。
当传入的元素在compar函数中比较如果大于0,就要进行元素内容交换。又有问题出现了,传入的数据类型不同,交换的内容大小不同,为了其中的兼容性,我们可以继续沿用上述思想,创建一个swap交换函数,传入刚才compar函数使用的两个函数地址和数组一个元素所占内存大小size即可。
swap函数会将两个数据中的数据一个字节一个字节的交换,这也可以保证所有类型数组都可以进行。
所有的准备工作已经完成,下面让我们完成qsort函数的模拟。
#include<stdio.h> #include<string.h> void print(int arr[10], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } } void swap(char*p1,char*p2,int size) { int i = 0; char tmp = 0; for (i = 0; i < size; i++) { tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } } int compar(const void* p1, const void* p2) { return *(int*)p1 - *(int*)p2; } void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*)) { int i = 0; for (i = 0; i < num-1; i++) { for (int 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); } } } } void test1() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), compar); print(arr, sz); } int main(void) { test1(); }
如果想加入其他类型数组进行排序,只需要加入新的compar函数和要比较的内容即可,如果想要降序排序,只需将if函数中>0改为<0即可。下面是成功运行的结果:
模拟的qsort函数主体为bubble_sort函数和swap函数:
void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*)) { int i = 0; for (i = 0; i < num-1; i++) { for (int 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); } } } } void swap(char*p1,char*p2,int size) { int i = 0; char tmp = 0; for (i = 0; i < size; i++) { tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } }