前言:
学习C语言,一般情况下都会接触到冒泡排序,你知道吗,用冒泡排序的思想可以模拟实现qsort函数(库函数的一种,可以实现快排)跟我来看看吧。
注:此博客包含进阶知识,建议学完C语言初阶知识再进行学习哦 ~
1. qsort 函数介绍
打开你的 C/C++资源网站/软件,搜索 qsort 函数 cplusplus - qsort
可以解读出:
qsort 函数的功能是排序数组中的元素;
qsort 函数不返回数据;
使用 qsort 需要传递四个参数。
传递的四个参数大有讲头,这里详细解释:
先来看看前三个:
base: 直译是 “指向数组中要排序的第一个对象的指针,转换为 void* ”。
简单说就是 要排序,你得先把你要排序的数组给我 ,那为什么要转换为 void* 呢?
这里就不得不说 void* 的利弊 了:
好处:void* 可以接收任意指针类型,包容性极强;
缺陷:接收的指针不可直接使用,也就意味着使用前要进行类型转换。
原因 就是 qsort 函数的作者不知道你要传递什么类型的数组,所以干脆就用覆盖范围最广的喽 ~
num: 直译是 “ 数组中由base指向的元素个数,size_t 是无符号整型 ” 。
这个比较好理解,就是数组中元素总和,size_t 就是 unsigned int ,因为总和不可能为负数,类型是无符号整型也理所应当了。
size: 直译是 “ 数组中每个元素的字节大小 ” 。
这个倒是好理解,但你知道它有什么用吗?嘿嘿,这里留个小悬念 ~
看最后一个:
这里就直接解释吧:
int (*compar)(const void*,const void*) 是函数指针,它叫做 compar;
它指向的函数 需要两个参数(都是 void* 型,且指向不可变);
它指向的函数 返回整型数值。
那就是要给 qsort 传个函数呗,但传递的这个函数的作用是?
你想啊,假如我用冒泡排序的思想给一个整数数组排升序,是不是先要比较相邻两个元素的大小 在来决定是否进行交换?
是的,比较两个元素就是这个函数的作用,这两个元素可以是相邻两个元素(注意:qsort 函数比较的不一定是两个相邻的元素,因为我们用冒泡排序的思想,所以可以理解为两个相邻的元素),指向它们的指针类型同样是 void* ,最广泛。
返回值呢?
简单来说,可以用两个元素做差来解释:
a - b (a , b 都是整型)
a > b ,返回大于 0 的整数;
a = b ,返回 0 ;
a < b ,返回小于 0 的整数。
2.实现过程
充分认识了 qsort ,接下来就是用冒泡排序的思想来模拟它,
我们先来复习一遍冒泡排序:
//普通的冒泡排序: void bub_sort(int* arr, int sz) { for (int i = 0; i < sz - 1; i++) { for (int j = 0; j < sz - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = 0; temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
在这个冒泡排序中,只能实现整型类型元素的排序;
我们的目标是实现多种类型元素的排序;
所以要改善的部分是判断与交换的部分。
我们先来写出框架:
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void* e2)) { for (int i = 0; i < num - 1; i++) { for (int j = 0; j < num - 1 - i; j++) { if (cmp()>0) { //交换 Swap(); } } } }
接下来就是实现 cmp 函数与 Swap 函数了
2.1 cmp 函数部分
如果传参类型是 整型:
直接返回差值就好。
int int_cmp(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; //强制类型转换 }
如果传参类型是包含整型和字符串类型的 结构体:
先把结构体创建:
struct Stu { char name[20]; int age; }; struct Stu s[3] = { {"zhangsan",20},{"lisi",60},{"wangwu",30} };
如果按年龄排序结构体:
int age_cmp(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; //莫忘记强制类型转换 指向具体结构体成员 }
如果按名字排序结构体:
实际上是字符串的比较,我们很容易想到 strcmp 函数,将比较的两个字符串传入函数即可。
int name_cmp(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); }
2.2 Swap 函数部分
Swap 函数就是执行 交换相邻元素 任务的
既然两个数组的元素不能直接交换,那么我们就开辟一块小空间,通过这块小空间交换一个元素后再进行下一个元素的交换。
理应,Swap 函数需要指向数组第一个元素的指针,还有数组中每个元素的字节大小(揭开悬念)
代码:
void Swap(char* buf1, char* buf2, int size) { for (int i = 0; i < size; i++) { char temp = *buf1; *buf1 = *buf2; *buf2 = temp; buf1++; buf2++; } }
到这里基本上就大功告成啦
2.3函数整体代码
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<string.h> #include<stdlib.h> //交换函数 void Swap(char* buf1, char* buf2, int size) { for (int i = 0; i < size; i++) { char temp = *buf1; *buf1 = *buf2; *buf2 = temp; buf1++; buf2++; } } //冒泡排序思想实现指定类型数组的排序 void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void* e2)) { for (int 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); } } } } //比较函数 int int_cmp(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } struct Stu { char name[20]; int age; }; int age_cmp(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } int name_cmp(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } //测试部分 void int_test() { 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]), int_cmp); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } } void struct_test() { struct Stu s[3] = { {"zhangsan",20},{"lisi",60},{"wangwu",30} }; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), age_cmp); } int main() { int_test(); struct_test(); return 0; }
总结:
这篇博客带大家用冒泡排序的思想模拟了 qsort 函数,应用到了 函数回调 的知识点,属于指针进阶知识,你学会了吗?