1、qsort函数基本介绍
包含参数、返回值(为void),主要功能。
qosrt函数有四个参数。
第一个是要排序的数组的数组名,即数组首元素地址。
第二个是要排序的元素的个数。
第三个是每个元素所占内存空间的大小。(单位为byte)
第四个是一个函数指针,也就是一个函数的地址,这个函数的两个参数都是const void*类型,返回值是int类型。
2、参数中函数指针作用的解释
这里的函数指针指向的是规定排序规则的函数。这是因为我们在排序如整型,结构体,字符串时,排序的具体规则是不同的。当我们试图完成一个通用的qsort函数时,我们是不知道使用者想要完成的排序的规则是什么的,因此这里我们用qsort中的函数指针在qsort的实现过程中,去回调这个实现规则的函数,这样一来,使用者将自己完成的排序规则函数传入qsort中,就可以实现相应的排序。
3、冒泡排序基本原理
下面我们以整型升序为例。
{
int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
//这里假设我们用将arr中的数据升序处理
//十个元素需要进行冒泡排序的躺数为10-1
//因为每一趟冒泡排序都会将最大的数放到最后
//排好n-1次个数,最后一个自然也就排好了
int sz = sizeof(arr) / sizeof(arr[0]);//元素个数sz
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//每一次冒泡排序
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//第一次比较sz-1对数据
//每排好一个,下一次就少比较一个
if (arr[j] > arr[j + 1])//升序的判断规则
{
//交换的过程
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
//排完一对,j++,再排下一对
}
}
}
}
冒泡排序的简单理解是烧水的气泡,最大的气泡在底部时,会一个一个往上走,直到最顶部,然后保持不变,第二大的也从底部往上走,直到最大气泡的正下方,其他气泡也是如此。排好一个气泡的位置就是一趟冒泡排序,每一趟冒泡排序会进行sz-i-1次比较,满足交换规则就会交换。
4、qsort函数的实现
void my_qsort_bubble(void* base, size_t sz, size_t width, int (*pf)(const void*, const void*))
{
//利用冒泡排序实现qsort
size_t i = 0;
//冒泡排序的次数
for (i = 0; i < sz - 1; i++)
{
//每一趟冒泡排序
size_t j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
// 两个元素是否需要交换 的判断条件 利用判断函数
// 先找到两个元素的地址 利用回调函数 利用width每个元素宽度 来找到下一个元素
if (pf((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//大于0 交换顺序 最终是升序
//交换的过程
swap_byte((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
//通过字节交换函数的实现
void swap_byte(char* s1, char* s2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *s1;
*s1 = *s2;
*s2 = tmp;
s1++;
s2++;
}
}
5、几种交换规则函数的实现
1、整型排序
//整型排序函数 规则
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
2、结构体中的整型
先定义一个结构体
struct stu
{
char name[20];
int age;
};
int cmp_stu_age(const void* e1, const void* e2)
{
return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
void test2()
{
//定义一个结构体数组 排序排的是数组中的内容 一组数据
struct stu s1[3] = { {"zhangsan",20},{"lisi",50},{"wangwu",30} };
my_qsort_bubble(s1, sizeof(s1) / sizeof(s1[0]), sizeof(s1[0]), cmp_stu_age);
}
由于qsort函数的参数的要求,这些规则实现函数的参数也为两个const void*类型的指针。
void* e1和e2指向的是两个要判断是否排序的元素,但是由于void*的特性(可以看作一个垃圾桶,可以接收任意类型的指针,但是由于不知道具体指针访问的字节大小,必须先强制类型转换为其他的类型的指针,然后根据其他的类型确定要访问的字节数)
1中转化为int,找到两个元素直接相减,判断返回值的正负。
2中转化为struct stu*类型的指针,然后通过箭头->找到里面的age元素,相减后返回。
3、结构体中的字符串
//结构体字符串排序规则
int cmp_stu_str(const void* e1, const void* e2)
{
return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
字符串不是普通的整型,不能通过简单的相减来进行比较。这里我们调用的库函数strcmp用来比较两个字符串的大小。
strcmp函数的功能如下
其中,我们发现strcmp的参数与规则实现函数完全相同,即e1和e2满足strcmp函数对参数的要求 同时,strcmp比较字符串时,按照每个字符串中对应字符的ASCLL码值进行比较,相同则比较下一个。如果str1<str2就返回负值。str1>str2返回正值。
有趣的是,返回值的规则和我们判断升序降序的规则是相同的。升序时,我们在pf调用函数返回值大于0时才进行两个元素的交换,strcmp在str1>str2时也返回正值,最终排序出来的满足升序规则