首先我们了解一下qsort函数的功能
之前的冒泡排序太过局限,我们仿qsort写一个可对任意类型的数组进行排序的冒泡排序
目录
一、了解qsort函数
qort函数是对数组元素进行快速排序的函数
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
可以看到qsort函数共有4个参数:
- void *base 数组起始地址(可直接用数组名来传参),用指针来接收,这里用的void类型的指针使得这个qsort函数可以接收不同类型数组的首元素地址,使qsort函数能用于多种类型数组的元素排序
- size_t num 数组的长度
- size_t width 数组元素的大小(以字节为单位) (因为第一个和第二个参数即数组元素大小和数组长度不可能为负数,所以这里用的是size_t无符号整型,我们模拟的时候用int类型也完全没有问题,而且int类型和size_t类型相比,int类型有时候可以避免掉一些不必要的歧义以及错误的产生)
- int (__cdecl *compare )(const void *elem1, const void *elem2 ),为方便理解,我们将它简化如下,int ( *compare )(const void *elem1, const void *elem2 ),可以看出这其实就是一个函数指针,该指针所指向的函数有两个空类型的指针作为参数,返回值为int类型。 第4个参数函数指针所指向的函数其实就是qsort函数需要我们给它提供一种比较的方法,不同类型的数组排序方法不同,所以这个比较函数是需要我们自己来写的。比较函数比较完两个元素后返回一个值给qsort,第一个数小于第二个数返回负数,等于返回0,大于返回正数
注意
- qsort函数无返回值,只是进行数组元素的排序
- qsort函数默认按从小到大排序,若要从大到小,可更改自己所写的比较函数的返回值正负
- 因为要广泛适用于多种类型,所以自己所写的比较函数的两个参数均为void类型
二、模拟实现
1.main函数调用
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 }; //定义整型数组并初始化
int sz = sizeof(arr) / sizeof(arr[0]); //计算数组长度
int i = 0;
bubble_sort(arr, sz, sizeof(arr[0]), cmp); //模拟qsort函数实现冒泡排序,bubble_sort传
//过去4个参数,cmp为比较函数
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]); //排序完后对数组进行打印,验证排序是否成功
}
}
下面进行qsort函数功能的模拟实现,我们以整型数组为例
2.qsort函数模拟实现
冒泡排序函数bubble_sort如下
void bubble_sort(void* arr, int sz, int width, int(*cmp)(void* e1, void* e2))
bubble_sort函数中我们接收到了数组首元素地址,数组长度,以及每个数组的大小,比较函数均可以用,在进行冒泡排序时,我们应该清楚在符合什么条件的情况下要将两个元素交换位置,以达到排序的目的。
我们拿到首元素的地址arr,即可以通过地址访问数组,arr+1表示第二个元素的地址,这里将arr和arr+1传给cmp比较函数并接收它的返回值决定要不要交换即可,但是这里的arr为void*类型,不能进行arr+1的操作,所以我们对它进行强制类型转换,char*(arr),而我们要将整个数组的元素都要进行传参比较,所以char*(arr)和char*(arr)+1比较局限,将width这个参数用上,则相邻的两个元素地址可以写成(char*)arr + (j * width), (char*)arr + (j + 1) * width),将其传给cmp函数比较即可
整个bubble_sort函数实现如下
void bubble_sort(void* arr, int sz, int width, int(*cmp)(void* e1, void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
//冒泡排序趟数
for (j = 0; j < sz - 1 - i; j++) //每一趟冒泡排序
{
if (cmp((char*)arr + (j * width), (char*)arr + (j + 1) * width)>0)
{
//符合条件进行交换
swap((char*)arr + (j * width), (char*)arr + (j + 1) * width,width);
}
}
}
}
3.比较函数cmp的实现
这里是整形数组,我们进行比较时,只需要将两数相减作为返回值即可。cmp的两个参数为void*类型,不能解引用,所以我们对它需要进行强制类型转换后解引用
代码实现如下:
int cmp(void* e1, void* e2) //所选择的比较方法
{
return *((int*)e1) - *((int*)e2);
}
4.交换函数的实现
写交换函数,在cmp的返回值满足交换条件时,调用交换函数来交换两个元素
交换函数需要width(数组单个元素的大小)用作for语句的边界,因为这里我们交换时一个字节一个字节进行交swap函数的前两个参数为char*类型,所以可以一个字符一个字符进行交换,但到底交换几对字符,不同类型数组都不一样,所以再传一个数组单个元素的大小给swap函数。
void swap(char* p1, char* p2, int width) //实现数组元素的交换
{
int t = 0;
int i = 0;
for (i = 0; i < width; i++)
{
t = *p1;
*p1 = *p2;
*p2 = t;
p1++;
p2++;
}
}
到这里,整个qsort函数的功能模拟完成
5.全部代码
#include<stdio.h>
//仿qsort函数重写冒泡排序
int cmp(void* e1, void* e2) //所选择的比较方法
{
return *((int*)e1) - *((int*)e2);
}
void swap(char* p1, char* p2, int width) //实现数组元素的交换
{
int t = 0;
int i = 0;
for (i = 0; i < width; i++)
{
t = *p1;
*p1 = *p2;
*p2 = t;
p1++;
p2++;
}
}
void bubble_sort(void* arr, int sz, int width, int(*cmp)(void* e1, void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
//冒泡排序趟数
for (j = 0; j < sz - 1 - i; j++) //每一趟冒泡排序
{
if (cmp((char*)arr + (j * width), (char*)arr + (j + 1) * width)>0)
{
//符合条件进行交换
swap((char*)arr + (j * width), (char*)arr + (j + 1) * width,width);
}
}
}
}
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 }; //定义整型数组并初始化
int sz = sizeof(arr) / sizeof(arr[0]); //计算数组长度
int i = 0;
bubble_sort(arr, sz, sizeof(arr[0]), cmp); //模拟qsort函数实现冒泡排序
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]); //排序完后对数组进行打印,验证排序是否成功
}
}
6.测试结果
成功实现排序
三、总结
今天我们学习了qsort函数,了解到了qsort函数中的4个不同参数的意义及作用,并以整型数组为例模仿qsort函数写出了适用于任意类型数组排序的bubble_sort函数,自己也可以用这个bubble_sort函数对字符数组,结构体数组等进行排序,排序方法cmp不同的类型都可能不同,需要自己根据具体情况去写比较函数cmp。关于这种方法你学会了吗?
欢迎关注,学习编程请关注 Go-ly,让我们一起将编程进行到底!
更多干货等你哦!