在本篇中,将深入了解qsort函数的用法。
1.qsort函数的基础知识



该函数是用来排序的,这是一个可以直接用来排序数据的库函数(#include<stdlib.h>),底层使用的是快速排序的方式。
常见的排序方式有:
选择排序
插入排序
冒泡排序
快速排序
希尔排序
......
举一个冒泡排序的例子:
运行代码:
cpp
//冒泡排序,对一组整型数据进行排序,排序为升序
void bubble_sort(int arr[], int sz)
{
//趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟内部的两两比较
int j = 0;
for (j = 0; j < sz-1-i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void print_arr(int arr[],int sz)
{
int i = 0;
for (i = 0; i < sz ; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
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);
print_arr(arr, sz);
return 0;
}
运行结果:

其中
这个函数只能排序整型数据,但是qsort函数可以排序任意类型的数据。
这里实现的是两个元素的比较,把两个元素比较的方法,封装成函数
然后,把函数的地址传给排序函数
假设,在使用冒泡排序的情况下,要改造这个函数,让其能够排序任意类型的数据,要在那一些地方改造呢?
1.//趟数
不用改造
2.//一趟内部两两比较
这个思想不用改造
3.两个元素的比较部分需要改变,如下:

为什么呢?
两个元素可以直接使用>比较
!!!但是两个字符串,结构体就不可以使用>的!!!
(比如:学生有身高、体重等等,所以不可以用>比较)
qsort 函数有实现者,比如:微软......
我们程序员是函数的使用者,需要做到以下几点:
1.要知道排序的是什么数据,
2.这些数据应该如何比较
3.提供两个元素的比较函数
。
2.使用qsort函数排序整型数据
比较规则eg:
int cmp_int(const void* e1, const void* e2)
e1和e2存放的是两个待比较元素的值
e1 < e2 返回小于0的数字
e1 = e2 返回等于0的数字
e1 > e3 返回大于0的数字
注意void* 类型的指针是无具体类型的指针,这种类型的指针不能直接引用,也不能进行 +- 整数的运算
运行代码:
cpp
//qsort 排序整型数据
int cmp_int(const void* p1, const void* p2)
{
if (*(int*)p1 > *(int*)p2)
//强制类型转换直接用
{
return 1;
}
else if (*(int*)p1 == *(int*)p2)
{
return 0;
}
else
{
return -1;
}
}
void print_arr(int arr[],int sz)
{
int i = 0;
for (i = 0; i < sz ; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test1()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test1();
return 0;
}
运行结果:

其中:

这段代码,可以简化为直接相减:

改为降序:

3.使用qsort排序结构数据
. 和 -> 操作符都属于结构体成员访问操作符,怎么使用呢?如下:
结构体变量 . 成员名
结构体指针 -> 成员名
这里的结构体之间怎么比较呢?
1.按照名字比 ------ 字符串比较 ------ strcmp
2.按照年龄比 ------ 整型比较
关于strcmp(#include<string.h>),是按照对应着字符串中的字符的ASCII码值比较的。eg:
a b c d e f 大
a b b c 小
首先按照名字排序:
cpp
//qsort排序结构数据
struct Stu
{
char name[20];
int age;
};
//这里的结构体之间怎么比较呢?
//1.按照名字比 ------ 字符串比较 ------ strcmp
cmp_stu_by_name(const void*p1,const void*p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
//2.按照年龄比 ------ 整型比较
void test2()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
test2();
return 0;
}
调试结果:

接下来按照年龄:
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//qsort排序结构数据
struct Stu
{
char name[20];
int age;
};
//这里的结构体之间怎么比较呢?
//1.按照名字比 ------ 字符串比较 ------ strcmp
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
//2.按照年龄比 ------ 整型比较
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
//void test2()
//{
// struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
// int sz = sizeof(arr) / sizeof(arr[0]);
// qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
//}
void test3()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int main()
{
//test2();
test3();
return 0;
}
调试结果:

4.qsort函数的模拟实现
我们模仿qsort来实现一个冒泡排序的函数这个函数可以排序为任意类型的数据
也就是完成 bubble_sort 的改造
void* 的指针,是无具体类型的指针,它的作用就是接受任何类型的
需要进行以下调整:


怎么实现这一步的调整呢?
9 8 7 6 5 4 3 2 1 0
arr[0] arr[1]
arr[1] arr[2]
arr[2] arr[3]
arr[3] arr[4]
......
arr[j] arr[j+1]
arr[0] 的地址是 base
arr[1] 的地址是 (char * )base + 4
arr[2] 的地址是 (char * )base + 8
......
arr[j] 的地址是 (char * )base + j * width
所以:

交换函数的改写:


综上所述,冒泡排序改造后的代码如下:
cpp
//交换函数
void Swap(char* buf1,char* buf2,size_t 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* p1, const void* p2))
// 起始位置 元素个数 元素宽度 两元素比较
{
//趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟内部的两两比较
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//比较arr[j] 和 arr[j + 1]
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
//改变
{
//交换两个元素
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test4()
{
int arr[] = { 8,5,4,6,1,2,3,0 };
//排为升序
//两两相邻的元素比较,如果不满足顺序就交换
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test4();
return 0;
}
运行结果:

以上是整型数据,接下来尝试一下结构体数据:
cpp
//交换函数
void Swap(char* buf1,char* buf2,size_t 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* p1, const void* p2))
// 起始位置 元素个数 元素宽度 两元素比较
{
//趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟内部的两两比较
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//比较arr[j] 和 arr[j + 1]
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
//改变
{
//交换两个元素
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
//void print_arr(int arr[], int sz)
//{
// int i = 0;
// for (i = 0; i < sz; i++)
// {
// printf("%d ", arr[i]);
// }
// printf("\n");
//}
整型
//void test4()
//{
// int arr[] = { 8,5,4,6,1,2,3,0 };
// //排为升序
// //两两相邻的元素比较,如果不满足顺序就交换
// int sz = sizeof(arr) / sizeof(arr[0]);
// bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
// print_arr(arr, sz);
//}
struct Stu
{
char name[20];
int age;
};
//1.按照名字比 ------ 字符串比较 ------ strcmp
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
//结构体
void test5()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
//test4();
test5();
return 0;
}
姓名调试结果:

年龄同理。