目录
1、qsort 函数的基本介绍
1.1 qsort 的标准形式
我们先来看一下库函数 qsort 的标准形式
void qsort(void* base,
size_t num,
size_t width,
int(*cmp)(const void* e1, const void* e2));
qsort 函数包含4个参数,无返回类型,使用时需要引头文件 <stdlib.h>。其中第一个参数 void* base 是待排序数组的首元素地址;第二个参数 size_t num 是待排序数组的元素个数;第三个参数 size_t num 是待排序数组的每个元素的大小,单位是字节;而第四个参数 int(*cmp)(const void* e1, const void* e2)) 是函数指针,比较两个元素的所用函数的地址,这个函数使用者自己实现,函数指针的两个参数是:待比较的两个元素的地址。
1.2 qsort 的使用情况
下面以排序浮点型数组来简单介绍 qsort 的使用情况,
#include <stdlib.h>
#include <string.h>
int cmp_float(const void* e1, const void* e2)
{
return ((int)(*(float*)e1 - *(float*)e2));
}
int main()
{
//给定单精度浮点型数组 f
float f[] = { 9.0,8.0,5.0,4.0,7.0,2.0,6.0 };
//用变量 sz 来存放数组的元素个数
int sz = sizeof(f) / sizeof(f[0]);
//其中 sizeof(f[0]) 表示数组每个元素的大小
//这里的比较函数 cmp_float由我们自定义,需要使用者自己来实现
qsort(f, sz, sizeof(f[0]), cmp_float);
return 0;
}
1.3 qsort 中的比较函数
int cmp_float(const void* e1, const void* e2)
实现比较函数时,有如下规定:
1.前一个元素大于后一个元素,返回大于0的数
2.前一个元素小于后一个元素,返回小于0的数
3.前一个元素等于后一个元素,返回数字0
下面介绍比较函数的基本情况。
1.3.1 比较数字类型
此时只需将 void* 类型的指针e1,e2强制类型转换为数组元素类型,对其解引用后再相减,最后将结果强制类型转化为 int 即可。如:
((int)(*(float*)e1 - *(float*)e2))
1.3.2 比较字符串类型
由于字符串不能直接用数学符号来比较,我们这里使用 strcmp 函数,而 strcmp 正好满足:
1.前一个元素大于后一个元素,返回大于0的数
2.前一个元素小于后一个元素,返回小于0的数
3.前一个元素等于后一个元素,返回数字0
故按照如下方法使用即可(这里以结构体指针指向其结构体成员 name 来举例):
strcmp(((struct Stu*)e2)->name, ((struct Stu*)e2)->name)
2. 模拟实现 qsort 函数
2.1 自定义 bubble_sort 函数
为了与 qsort 函数保持一致,我们给定 bubble_sort 函数,并设置相同的形式参数。
void bubble_sort(void* base,
size_t sz,
size_t width,
int (*cmp)(const void* e1, const void* e2))
2.2 bubble_sort 函数的内部结构
由于每次都进行一趟比较,且每趟比较的对数都会递减1,故使用两层 for 循环结构
void bubble_sort(void* base,
size_t sz,
size_t 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++)
{
//...
}
}
}
下面,我们只需要在第二层 for 循环内部实现两个功能,第一个是将两个元素进行比较,第二个是交换。
2.2.1 两个元素的比较
由于比较函数在 qsort 已做出说明,故我们只需解决比较函数的传参问题。我们先来看参数类型,
int cmp(const void* e1, const void* e2)
这里的 e1,e2 都是无类型的不可修改的指针,进行元素比较时,我们需要让 e1,e2 准确的指向相邻的两个元素。但元素存在字符型、整型、浮点型、自定义类型等等,且每个类型在内存中的大小都不相同,我们如何才能将指针准确的指向相邻元素的首地址呢?
这里我们注意到,虽然每个类型在内存中所占的字节数不同,但字符类型只占一个字节,我们将 e1,e2 强制类型转换成 char* 后,仅需在后面加上 j*width ,就可以让指针指向每个元素的首地址。这里我们使用 if 进行判断,
//指向前一个元素的后面一个
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
}
2.2.2 交换
当指针 cmp 指向的比较函数的返回值大于0时,表示相邻的两个元素前一个大,故需要将两个元素进行交换。而返回值小于等于0时,无需交换。
下面将交换功能封装成函数 Swap ,由于每个类型所占字节数不同,故我们每次仅交换一个字节,交换元素大小的次数后,两个元素便实现了交换。
这里我们需要对 Swap 函数传递三个参数,
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width)
前两个参数与比较函数相同,指向两个元素的首地址,第三个参数则是元素的大小。
由于交换函数较简单,下面直接给出交换函数的具体内容,
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++;
}
}
至此,我们模拟实现的冒泡排序函数已经完成。
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++;
}
}
void bubble_sort(void* base,
size_t sz,
size_t 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);
}
}
}
}
3. 常用的比较函数举例
3.1 整型比较函数
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
3.2 单精度浮点型比较函数
int cmp_float(const void* e1, const void* e2)
{
return ((int)(*(float*)e1 - *(float*)e2));
}
3.3 结构体类型比较函数
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
//比较名字就是比较字符串
//字符串比较不能直接用><=来比较,应该用strcmp函数
return strcmp(((struct Stu*)e2)->name, ((struct Stu*)e2)->name);
}