目录
1. 回顾:万能指针void* 与 回调函数 的特性
1.1 万能指针void*
1.2 回调函数
2. qsort函数的使用规则
2.1 qsort的头文件和排序方向
2.2 qsort的函数参数表解析
2.3 结构体数组排序举例
3. 冒泡排序模拟万能排序qsort的实现
3.1 冒泡排序的回顾与疑问
3.2 qsort的模拟实现(万能版冒泡排序)
1. 回顾:万能指针void* 与 回调函数 的特性
1.1 万能指针void*
在《指针之旅(2)——const修饰词 && 野指针、空指针与泛型指针》中我们就学习过void*型指针了,本篇我们需要用到它的3个性质:
- void*指针可以⽤来接收任意类型地址。(这也是它被称作万能指针的原因)
- void* 指针有些指针运算是不能直接进行:(1)指针的+-整数(2)指针解引⽤。
- 如果非要用void*指针进行上述运算,必须对void*指针进行强制类型转换。(指针类型决定访问步长,void*指针的步长为0,不能运算)
1.2 回调函数
在《指针之旅(4)—— 指针与函数:函数指针、转移表、回调函数》中也学习过回调函数了,这里再简单介绍一下回调函数:
- 回调函数是回调触发函数(主调函数)中的一个形式参数。
- 回调触发函数(主调函数)用一个函数指针来接收回调函数的地址,方便主调函数调用回调函数。(该函数指针的去*类型是跟回调函数的类型是一致的)
- 主调函数的函数体中,会使用该函数指针来调用回调函数,具体要实现什么功能由回调函数的内容决定。
2. qsort函数的使用规则
2.1 qsort的头文件和排序方向
使用qsort函数要包含头文件<stdlib.h>
qsort默认的排序规则是"从小到大排序(升序排序)",这与回调函数的规则有关。
2.2 qsort的函数参数表解析
qsort的函数原型:
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
qsort与其他排序函数一样,无返回值。下面介绍一下这4个参数:
1. 参数base:指的是待排序数组的首地址。
- 作用:这个参数告诉qsort函数需要排序的数据从何处开始。
2. 参数num:表示的是数组中元素的个数。
- 作用:这个参数确保qsort函数知道排序操作需要处理多少个数据项。
3. 参数size:是数组中每个元素的大小(类型),以字节为单位。
- 作用:这个参数至关重要,因为它告诉qsort函数每个数组元素占据多少内存空间。
4. 参数compar:compar是指向比较函数的函数指针。
该比较函数由使用者实现,有以下默认的限定:
- 如果 元素1 大于 元素2,则返回大于0的值。
- 如果 元素1 等于 元素2,则返回0。
- 如果 元素1 小于 元素2,则返回小于0的值。
qsort只规定这3条默认限定,不做其他规定。如果限定1和限定3的内容刚好相反,那么qsort由升序排序 变为 降序排序。
举例说明:用qsort函数对一个整型数组排序。
首先我们要自己写出一个整型数据的比较函数,针对上述3项限定,我们可以写出这两个比较函数:
int int_cmp1(const void* p1, const void* p2)
{
if (*(int*)p1 > *(int*)p2)
return 1;
else if (*(int*)p1 < *(int*)p2)
return -1;
else
return 0;
}
int int_cmp2(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2 );
}
疑点解惑:
1. 应该用哪个比较函数?
答:这两个比较函数都是正确的,因为它们都符合qsort的3条默认限定。
2. 为什么比较函数中的参数类型是const void*?
答:首先,qsort是个万能排序函数,void*可以接收任意类型的数据。
其次,去掉void*类型来看,const修饰的对象是*p1和*p2,也就是说*p1和*p2的值不能被改变,这保证了数组的元素不会在比较函数中不会被指针改变。
3. 作比较时,为什么使用的是*(int*)p1,而不是*p1?
答:因为p1是void*型的指针,该类型的指针访问的步长为0,即不能访问。要先进行强制类型转换,才有访问的步长(权限)。
现在我们有了自己的比较函数,可以对整型数组排序了:
2.3 结构体数组排序举例
现在知道怎么使用qsort了,让我们尝试用qsort对一个“学生信息”结构体数组进行排序:
结构体的定义:
struct Stu //“学生信息”结构体
{
char name[20]; //名字
int age; //年龄
};
1.按名字升序排序:
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
int cmp_stu_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照名字来排序
void test1()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_name);
}
2.按年龄升序排序:
int cmp_stu_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//按照年龄来排序
void test2()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_age);
}
3. 冒泡排序模拟万能排序qsort的实现
库函数中的万能排序函数是由快速排序算法quick_sort演化而来的,鉴于大多数人没学过快速排序,所以这里我用冒泡排序算法来演示。
3.1 冒泡排序的回顾与疑问
我们先来回顾一下冒泡排序怎么写:
void bubble_sort(int arr[], int sz)
{
for (int i = 0; i < sz-1; i++) //i是趟数,要排sz-1趟
{
for (int j = 1; j < sz-i; j++) //arr[sz-i]以及它后面的元素已经排好顺序
{
if (arr[j - 1] > arr[j]) //整型数据的比较
{
int t = arr[j]; //整型数据的交换
arr[j] = arr[j - 1];
arr[j - 1] = t;
}
}
}
}
在冒泡排序的框架下,对于其他类型的数组来说:还是要排sz-1趟,排序中的arr[sz-i]以及它后面的元素已经排好顺序。所以 i 和 j 的for循环大体不用改变。
但是原本bubble_sort的比较部分和数据交换部分都是只针对int型的。而万能排序是能对所有类型都能排序的(比如正数、浮点数、字符、字符串),所以这一部分要改。
3.2 qsort的模拟实现(万能版冒泡排序)
我们仿照库函数qsort的参数表写出了冒泡排序版的万能排序函数bubsort:
void void_swap(const void* e1, const void* e2, size_t size)
{
for (int n = 0; n < size; n++)
{
char t = *((char*)e1+n); 重点
*((char*)e1+n) = *((char*)e2+n);
*((char*)e2 + n) = t;
}
}
void bubsort(void* base, size_t num, size_t size, int (*compar)(const void* e1, const void* e2))
{
for (int i = 0; i < num-1; i++)
{
for (int j = 1; j < num-i; j++)
{
//如果arr[j-1] > arr[j]就交换
if (compar((char*)base + (j - 1) * size, (char*)base + j * size) > 0) 重点
void_swap((char*)base+(j-1)*size, (char*)base+j*size, size); 重点
}
}
}
这里有几个重点需要说明一下:
1. 怎样得到数组arr[j-1]与arr[j]的地址来比较大小?
很简单,对base加够整数使得该结果能指向元素arr[j-1]与arr[j]的首地址就行。
需要注意的是:base现在是void*指针,需要强制类型转换;我们有参数size,代表着数组每个元素的大小,单位是一个字节。即base+j*size就是数组arr[j]的地址,所以base要强制类型转换成char*型。
2. 怎样交换arr[j-1]和arr[j]的数据内容?
首先要知道一点:数据是一个字节一个字节来存储的,如果一个字节一个字节地交换数据,交换到该元素的最后一个字节,那么整个元素就完成了交换。
所以在这里我们交换size次,每次只交换char型大小的数据。
现在我们来试试这个万能冒泡排序bubsort能不能使用:
int int_comp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//用bubsort排序整型数组:
int main()
{
int arr[] = { 1,3,4235,353,54,53,323,4,2,42,7,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubsort(arr, sz, sizeof(arr[0]), int_comp);
for (int i = 0; i < sz; i++)
printf("%d ", arr[i]);
return 0;
}
结果:
我们成功了!!!
本期分享结束,感谢大家的支持Thanks♪(・ω・)ノ