1. 如何使用qsort
qsort是C语言里的一个库函数,快速排序的方法实现的。
我们来看一下标准库里qsort的参数类型:
void* base 是一个指针,它传的是目标数组的起始位置。
size_t num 的意思是数组元素的个数。
size_t size 的意思是每个元素的大小(单位是字节)。
int ( * compar)(const void * ,const void * )) 这是一个函数指针。它指向的是一个比较函数。
我们来看看这比较函数的要求是什么?
p1指向的元素小于p2指向的元素返回负数。
p1指向的元素等于于p2指向的元素返回0。
p1指向的元素大于p2指向的元素返回正数。
我们现在来举一个例子,排一个整形数组:
我们要比较p1,p2肯定要解引用,但这里p1,p2不能直接解引用,因为它是
void * 的。
我们说一些void * 的一些特点:
void * 是一种无类型的指针。
void * 的指针变量可以存放任意类型的地址。
void * 的指针不能直接进行解引用操作。
void * 的指针不能直接进行加,减整数。
所以,我们在使用void * 的时候,应该强制类型转换成你想要的类型。
结果如下:
接下来,我们再用qsort来排序结构体数组
我们定义一个学生结构体:
我们按照成绩来排序:
然后我们强制类型转换成结构体指针进行比较:
运行结果如下:
我们可以看到成绩由小到大排序。
然后我们再用名字来排序:
在这里要注意名字比较是字符串,应该使用strcmp来比较。
运行结果如下:
到这里,我们应该都会使用qsort了吧。接下来,我们将模拟qsort。
2. 模拟qsort
我们先再说明一下这些参数:
第一个,为什么是void* ,因为排序的时候,我们可能传整型,浮点型,字符型,结构体类型等等,所以我们要接收各种各样的类型,只能是void * 。
第二个,我们肯定要知道数组里要排序的元素有多少个吧。
第三个,我们要知道一个元素的大小,不然,我们不知道从哪里找下一个元素。
第四个,像冒泡排序样,我们的比较趟数,和每趟比较的元素个数是不会发生变化的,只有我们比较的方法是不一样的。所以,我们应该将这个比较方法抽离出来,成为单独的一个函数。
现在,我们用冒泡排序的思想来模拟一下qsort。
首先,我们将冒泡排序里不需要改变的写出来:
只有比较的时候是不一样的。那我们该如何来写一个通用的比较呢?
首先,cmp函数指针,我们该传什么参数呢?我们需要相邻的两个元素比较,所以应该传两个元素的地址。
我们传base+j和base+j+1,行不行,不行。因为base是void * 的。我们应该强制类型转换为char * 的。因为char * 可以满足任意类型。
然后,我们再用width来找到每一个元素。
(char*)base + j * width, (char*)base + (j + 1) * width)
当j=0时,第一个就找到了第一个元素,第二个就跳过4个字节找到了第二个元素。
当j=1时,第一个跳过4个字节就找到了第二个元素,第二个就跳过8个字节找到了第三个元素。
…
找到了相邻的元素,我们就开始交换。用一个Swap函数来交换:
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
然后,我们实现一下这个Swap函数。
因为我们是char*,所以要一个一个字节交换。
完整的代码:
#include <stdio.h>
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//两个元素的交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
小结:
这里的主要问题就是我们要以不同的角度来看待,作为排序的创造者,我们不知道排序数组是什么类型的。但使用者是知道的。
因为比较的数组类型是多种多样的,比较方法是不相同的,所以比较函数的方法创造者是不知道的,但使用者知道。所以,使用者要把自己写的函数地址传过去。
所以cmp就可以找到使用者写的比较函数方法,然后我们将相邻的两个元素的地址传过去,为什么强制类型转换成char*,是因为char类型是最小的一个类型,它可以很方便的用来跳过相同的字节数。
总结:
到这里,这篇内容就先结束了。如果大家认为我有哪些不足之处或者知识上的错误都可以告诉我,我会及时改正,也请大家多多包涵。如果大家觉得这篇文章有用的话,也希望大家可以给我关注点赞,你们的支持就是对我最大的鼓励,我们下一篇文章再见。