目录
qsort函数
qsort函数是<stdlib.h>库函数中的,基于快速排序算法的函数,可以对任意类型的数据进行排序
void qsort (
void* base,//待排序数组的起始位置
szie_t num,//数组的元素个数
size_t width,//一个元素的字节大小
int(*cmp)(const void* el,const void* e2)//函数指针
//cmp:比较函数,要求qsort函数的使用者自定义一个比较函数
//如果排序的是结构体数据,就不方便直接使用<>比较了
//使用者根据实际情况,提供一个函数
//该函数的返回值需要:e1 < e2,返回负数,否则是整数或者是0(升序)
//该函数的返回值需要:e1 > e2,返回负数,否则是整数或者是0(降序)
//e1,e2是待比较的两个元素的地址
);
该函数有四个参数:
void* base : 这是待排序数组的起始位置,我们肯定必须要先找到那个数组才能进行排序啊
size_t num : 需要我们传入该数组的元素个数
size_t width : 需要我们传入一个元素的字节大小,具体为啥需要这个数据,我们后面自然就懂了
int(*cmp)(const void* el,const void* e2) : 这个参数可能有一点难以理解,我们可以知道,这其实是一个函数指针,指向一个参数是const void* el,const void* e2,返回值是 int 的函数。这个函数需要我们使用者自己去创建,注意,参数和返回类型必须严格按照它给的那样写。至于这个函数里面写什么,请看下文。
cmp函数的自定义
cmp,顾名思义,是compare(比较),就是一个比较函数。那么为什么qsort函数不自己写,而是需要我们使用者自己写呢?因为在设计qsort函数的时候,我们并不能知道使用者比较的是什么类型的数据,我们没有办法写一个函数来比较所有可能的类型的数据,比如,如果我们排序的是结构体数据,我们必须告诉qsort函数应该使用什么样的规则来比较。
所以这个函数的内容就是,比较我们需要比较的数据类型,如果e1大于e2,那就返回一个大于零的数,反之,就返回一个小于零的数(显然,这是针对于升序排序的,如果你想要进行降序的排序,就要将结果的正负号反过来)。
int cmp_int(const void* e1,const void* e2)
//void类型的指针是不确定类型的指针,是不能直接解引用的
//它更像是一个垃圾桶,用来接受任意类型的指针,但是不能(p + 1)
//因为他不知道它步长是多少
{
if (*(int*)e1 > *(int*)e2)
{
return 1;
}
else if (*(int*)e1 == *(int*)e2)
{
return 0;
}
else
return -1;
}
但是这样写着太复杂了,我们不妨换个方式:
int cmp_int(const void* e1,const void* e2)
{
return (*(int*)e1 - *(int*)e2) ;
}
即两个数相减,如果第一个数大于第二个数,那他们的差肯定就是负数了呗。
ps.我们习惯把这个函数命名为:cmp_数据类型,这样大大提高了程序的可读性。
写完这个函数,我们的准备工作就做好了
主函数
我们飞速地写出测试函数的主函数:
我们随便创建了一个乱序的数组,然后传入相应的参数到qsort函数中,并将最后的结果打印出来。
int main(void)
{
int arr[] = {2,1,4,5,7,5,8,9};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_int);
for(int i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
排序int类型代码
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
int main(void)
{
int arr[] = { 2,1,4,5,7,5,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这其实就已经实现我们的排序了,不过我们为了深入掌握这个函数,不妨多排序几组不同类型的数据
排序结构体
首先,我们要创建一个结构体:
struct Stu
{
char name[20];
int age;
double score;
};
然后初始化结构体。并写出qsort函数,这里我们为了更透彻理解这个函数,我们排序了两种,一种是结构体中的字符串排序(至于字符串怎么比较大小,请参考你查英语字典的顺序),和结构体中的整型的大小
int main(void)
{
struct Stu arr[3] = {{"zhangsan",20,55.5},
{"lisi",30,88.0},
{"wangwu",50,90.0}};
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_age);
qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_name);
return 0;
}
接着,我们就需要自定义我们的cmp函数了,注意定义的时候参数一定要按照规定的来,最好不要自己自由发挥哈(为了方便大家观看,我特地写成了这种代码风格,希望大家别介意)
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)
{
return strcmp(
((struct Stu*)e1)->name , ((struct Stu*)e2)->name
);
}
因为void*指针,电脑不知道他的步长,怎么可能知道每次解引用要访问后面多大的空间呢,所以在这里我们必须要进行强制类型转换,转换为结构体指针变量,然后用操作符访问age和name,之后比较大小。
当然,我上文提到过,用这种逻辑我们最后得到的是一个升序的数组,如果我们想要得到一个降序的数组怎么办呢?
我们拿age的排序举个例子吧:
//升序排序
int cmp_stu_by_age(const void* e1,const void* e2)
{
return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
//降序排列:
int cmp_stu_by_age2(const void* e1,const void* e2)
{
return (((struct Stu*)e2)->age - ((struct Stu*)e1)->age);
}
不难发现,只需要将返回值取个相反数(或者把减数和被减数交换位置,最终的效果都是一样的)。这样我们便能将一个数组进行降序的排序了,这便是qsort的强大之处,它允许你排序任何数据类型,也允许你自己设置排序的顺序。
最后,运行即可
排序结构体的总代码(供读者测试)
//创建一个结构体类型
struct Stu
{
char name[20];
int age;
double score;
};
//升序排序年龄
int cmp_stu_by_age1(const void* e1,const void* e2)
{
return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
//降序排列年龄:
int cmp_stu_by_age2(const void* e1,const void* e2)
{
return (((struct Stu*)e2)->age - ((struct Stu*)e1)->age);
}
//升序排序名字
int cmp_stu_by_name(const void* e1,const void* e2)
{
return strcmp(
((struct Stu*)e1)->name , ((struct Stu*)e2)->name
);
}
int main(void)
{
struct Stu arr[3] = {{"zhangsan",20,55.5},
{"lisi",30,88.0},
{"wangwu",50,90.0}};
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_age1);
qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_age2);
qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_name);
return 0;
}