qsort()函数讲解

在这里插入图片描述

1 qsort()函数的基本信息及功能

qsort --用来排序的
直接可用来排序数据,底层使用的是快速排序的方式
qsort函数可以排序任意类型的数据

接下来,让我们进入的qsort函数 的学习。

2 逐一解读qsort()函数的参数及其原理

我们从cplusplus官网(cplusplus(C语言函数查询网站)上我们不难看到qsort()函数是有四个参数如图:
在这里插入图片描述
下面我们对每个参数进行详细解释。

2.1 void* base

base参数官方给出的解释是:
在这里插入图片描述
base参数的类型为void * ,所以,qsort函数的第一个参数是一个void*的指针,指向的是待排序的数组的第一个元素。需要注意的是void * 类型的指针是无具体类型的指针,可以接受任何数据类型的地址,因此qsort函数就能排序任意类型的数据!

2.2 size_ t num

num参数官方给出的解释是:在这里插入图片描述
num参数的类型是 size_t是无符号整型,所以,qsort函数的第二个参数是一个 size_t类型的数,我们可以理解为是base指向的待排序数组中的元素个数

2.3 size_t size

size参数官方给出的解释是:
在这里插入图片描述
size参数的类型也是 size_t是无符号整型,所以,qsort函数的第三个参数是一个 size_t类型的数,我们可以理解为base指向的待排序数组中的元素的大小。

2.4 int (* compar)(const void*,const void*)

官方给出的解释是:
在这里插入图片描述
首先要理解这个参数是什么?
函数指针声明解析: int ( * compar)(const void*, const void*)在这里插入图片描述

因为qsort函数的实现者(专家)他们无法知道使用者要排序的是什么类型的数据,只有qsort函数的使用者—明确的知道要排序的是什么数据,以及这些数据应该如何比较,所以需要使用者来提供两个元素的比较函数。通过函数指针(int ( * compar)(const void*,const void*) )在qsort内部接受我们编写的函数,这个函数指针的类型就是 int ( * )(const void*,const void*)

那么编写函数的规则是什么呢?如何确定两个元素的大小关系呢?官方给出如下解释:
在这里插入图片描述
可以理解为由p1这个指针指向的位置解引用的元素如果大于由p2这个指针指向的位置解引用的元素,则返回一个大于0的数,如果小于,则返回一个小于0的数,如果等于就返回0。

两元素相比较返回值
大于>0
小于<0
等于=0

qsort()函数内部,调用完compar()函数指针指向的函数(由自己编写的函数)后,会接收到compar()返回的一个有符号的整型数字,当接收到comper()返回的 大于0的数字时,qsort()函数就会将这两个元素做交换。而如果接收到comper()函数返回的 小于等于0的数字时,qsort()函数不对其进行交换

因此,在compar()函数使用* p1-* p2的方式直接返回结果数字时,qsort()排出的序默认会是升序【(大的元素,小的元素)交换后(小的元素,大的元素)升序,】。而如果你希望qsort()函数排出一个降序数组时,那么就需要调换一下* p1和* p2的减法关系直接返回*p2-*p1的值即可。【(小的元素,大的元素)交换后(大的元素,小的元素)降序】。

3 使用qsort()函数完成整型,结构体排序(演示)

了解了qsort()函数的参数及其原理后,我们来尝试使用它完成一些排序任务,以此来熟悉qsort()函数的使用方法。

3.1使用qsort()函数完成对一维整型数组的排序:

要使用qsort()函数 ,就要先准备好它需要的四个参数,即数组的首元素地址 数组的长度 数组每个元素的大小,还有 自己编写的比较函数。我们依次准备好这四个参数:
在这里插入图片描述在这里插入图片描述

arr数组名对应首元素的地址
size数组的长度
sizeof(arr[0])数组每个元素的大小
cmp_int比较函数

接下来就可以调用qsort()函数查看结果了:
这里是引用

可以看到,qsort()函数成功帮我们将该组整形排列成了升序。该部分完整代码如下:

#include <stdio.h>
#include <stdlib.h>
void print_arr(int arr[], int size) {
	int i = 0;
	for ( i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int cmp_int(const void* p1, const void* p2) {
	return *(int*)p1 - *(int*)p2;
}
void test1() {//排序整型数据
	int arr[9] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	int size = sizeof(arr) / sizeof(arr[0]);//数组的长度
	qsort(arr, size, sizeof(arr[0]), cmp_int);
	print_arr(arr, size);
}
int main() {
	//写一段代码使用qsort排序整型数据
	test1();
	return 0;
}

3.2使用qsort()函数完成对结构体的排序:

要使用qsort()函数排序结构体 ,我们首先要 创建一个结构体变量,如下,我们先创建一个包含人名和年龄的结构体变量:
这里是引用在这里插入图片描述

下面会以这个结构体变量为例,分别实现使用qsort()函数完成对结构体按年龄 按姓名的排序。

3.2.1 使用qsort()完成对结构体中整型数据的升序

我们照例 要使用qsort()函数 ,就要先准备好它需要的四个参数,即数组的首元素地址 (此时数组的类型为结构体类型,即数组首元素的地址是结构体类型), 数组的长度 数组每个元素的大小,还有 自己编写的比较函数。我们依次准备好这四个参数:
在这里插入图片描述

我们需要注意的是结构体中成员变量的访问是有两种方法:
 1. 结构体变量.成员名
 2. 结构体指针——>成员名

即在cmp_stu_by_age函数 内部就有
在这里插入图片描述

arr数组名对应首元素的地址
size结构体数组的长度
sizeof(arr[0])结构体数组每个元素的大小
cmp_stu_by_age比较函数

接下来就可以调用 qsort()函数查看结果了:
在这里插入图片描述
可以看到,qsort()函数 帮助我们将该结构体成功按年龄从小到大重新排序了。该部分完整代码如下:

#include <stdio.h>
#include <stdlib.h>
int cmp_stu_by_age(const void* p1, const void* p2) {
	//return (struct Stu*)p1->age - (struct Stu*)p2->age;error 不能这么写
	//return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
	return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;
}
void test2() {//排序结构体中的数据
	struct Stu arr[] = { {"zhangsan", 20},{"lisi", 35},{"wangwu",18}};
	int size = sizeof(arr) / sizeof(arr[0]);//数组的长度
	qsort(arr, size, sizeof(arr[0]), cmp_stu_by_age);
}

int main() {
	//写一段代码使用qsort排序结构体的数据
	test2();
	return 0;
}

3.2.2 使用qsort()完成对结构体中字符串数据的升序

要使用qsort()函数 ,就要先准备好它需要的四个参数,即数组的首元素地址 数组的长度 数组每个元素的大小,还有 自己编写的比较函数。我们依次准备好这四个参数:
在这里插入图片描述
在这里插入图片描述
需要注意的是这次给结构体数组中的字符串进行排序,我们需要使用到库函数strcmp() ,来比较两个字符串的大小,并将比较的结果返回给qsort()函数。需要包含头文件#include <string.h>

strcmp()函数 相关信息如下:
在这里插入图片描述

可以观察到, strcmp函数的返回值与compar()函数指针指向的函数 (由自己编写的函数)的返回值一 一对应,大数 - 小数 return > 0的数;小数 - 大数 return < 0; 两数相等 return = 0;故我们可以直接使用strcmp()函数进行升序,要注意填入的参数的顺序。

strcmp()函数的比较原理:
是按照对应着字符串中的字符的ASCII码值比较的。
在这里插入图片描述

接下来就可以调用qsort()函数 查看结果了:

在这里插入图片描述

可以看到,qsort()函数 按照字符串 帮助我们成功排好了结构体数组的顺序,该部分完整代码如下:

#include <stdio.h>
#include <stdlib.h>
int cmp_stu_by_name(const void* p1, const void* p2) {
	return strcmp(((struct Stu*)p1) -> name, ((struct Stu*)p2 )-> name);
	//return strcmp( (*(struct Stu*)p1).name, (*(struct Stu*)p2).name );
}

void test2() {//排序结构体中的数据
	struct Stu arr[] = { {"zhangsan", 20},{"lisi", 35},{"wangwu",18}};
	int size = sizeof(arr) / sizeof(arr[0]);//数组的长度
	qsort(arr, size, sizeof(arr[0]), cmp_stu_by_name);
}

int main() {
	//写一段代码使用qsort排序结构体的数据
	test2();
	return 0;
}

4 模仿qsort实现一个冒泡排序

通过上面的例子,我相信你已经能够熟练使用并足够了解qsort()函数了,接下来我们就模仿qsort来实现一个冒泡排序的函数,这个函数可以拍戏任意类型的数据bubble_sort()

4.1 bubble_sort()函数定义及参数

因为要模拟实现 qsort()函数,因此直接 仿照qsort()函数的参数即可:

void bubble_sort(void* base, size_t size, size_t width, int (*compar)(const void*, const void*))

4.2 bubble_sort()函数函数体

因为整体结构是冒泡排序 ,所以, 外层循环(总共要比较几趟) 内层循环(一趟需要比较几次)不变

	int j = 0;
	for ( i = 0; i < size - 1; i++)//外层循环 eg:2个元素要比较 1 趟 ——> n个元素就要比较 n - 1 趟,0 到(size - 2) 共 size - 1趟
	{

		}

内层循环

	{
		for (j = 0; j < size - i - 1; j++) {

			}
		}

从第一个元素开始比较,需要比较几次呢?2个元素需要比较1词,关键点在现在不知道内循环变化的总共的元素,因为排序过的元素不再参与比较,内循环每一次的总元素 = 总共的元素数 - i(已排序好的元素);所以,比较的次数 = 内循环每一次的总元素 - 1, 即size - i - 1,0到 size - i - 2 就等于size - i - 1。

整个bubble_sort()函数的框架

void bubble_sort(void* base, size_t size, size_t width, int (*compar)(const void*, const void*)){
	int i = 0;
	int j = 0;
	for ( i = 0; i < size - 1; i++)
	{
		for (j = 0; j < size - i - 1; j++) {
		}
	}
}

通过前面的学习我们知道,qsort()函数通过在内部调用由compar函数指针指向的函数(我们自己编写的交换函数),根据返回值来判断是否交换,我们就要写函数指针的调用,直接上代码:

void bubble_sort(void* base, size_t size, size_t width, int (*compar)(const void*, const void*)){
	int i = 0;
	int j = 0;
	for ( i = 0; i < size - 1; i++)
	{
		for (j = 0; j < size - i - 1; j++) {
			if ( compar((char *)base + j * width, (char *)base + (j + 1) * width ) > 0 )//升序改变
			{
				//交换两个元素
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}

}

因为要交换任意数据类型的数据,我们并不知道base传进来的参数到底是什么类型的,因此我们base设置成 void * 类型的,void * 的指针,是无具体类型的指针它的作用就是接收任何类型的地址,又因为void* 类型的指针是无具体类型的指针,这种类型的指针不能直接解引用,也不能进行+ -整数的运算,因此把指针统一强制类型转换成char*类型,这有助于将他们统一视为一个字节一个字节的空间来进行比较及交换,该部分代码及解析如下:

void bubble_sort(void* base, size_t size, size_t width, int (*compar)(const void*, const void*)){
	int i = 0;
	int j = 0;
	for ( i = 0; i < size - 1; i++)
	{
		for (j = 0; j < size - i - 1; j++) {
			if ( compar((char *)base + j * width, (char *)base + (j + 1) * width ) > 0 )//升序改变
			{
				//交换两个元素
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}

}

4.3 bubble_sort()函数中的回调函数Swap()

因为我们根本不知道传进来的是什么类型的数据?没法写原先那种交换方法,把原本冒泡排序中的交换步骤直接重新分装成一个函数,专门用来交换需要交换的两个元素,同样因为我们并不知道该数据的类型,只知道该数据的大小size,因此我们不如直接将两个sz大小的字节内容采用循环的方式按照数据的大小逐个一个字节一个字节逐一交换,这样就能保证不论是什么类型的数据,交换完都不会出现差错了。该部分代码如下:

//交换任意类型的元素
void swap(char* p1, char* p2, size_t width) {
	int i = 0;
	char temp = 0;
	for ( i = 0; i < width; i++)
	{
		temp = *p1;
		*p1 = *p2;
		*p2 = temp;
		p1++;
		p2++;
	}
}

整型数据交换原理:

这里是引用

5 使用自己的bubble_sort()函数完成整形,结构体的排序(演示)

完成了 bubble_sort()函数的编写,接下来我们尝试使用它来代替前面的qsort()函数 给数组及结构体进行排序:

5.1 使用bubble_sort()函数完成对一维整形数组的排序

一样要使用bubble_sort()函数,就要先准备好它需要的四个参数,即数组的首元素地址 数组的长度 数组每个元素的大小,还有 自己编写的比较函数。我们依次准备好这四个参数:
在这里插入图片描述在这里插入图片描述

接下来就可以调用bubble_sort() 查看结果了:
在这里插入图片描述

可以看到,bubble_sort()函数成功帮我们将该组整形排列成了升序。

该部分完整代码如下:

#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void* p1, const void* p2) {
	return *(int*)p1 - *(int*)p2;
}
void swap(char* p1, char* p2, size_t width) {
	int i = 0;
	char temp = 0;
	for ( i = 0; i < width; i++)
	{
		temp = *p1;
		*p1 = *p2;
		*p2 = temp;
		p1++;
		p2++;
	}
}
//我们模仿qsort来说实现一个冒泡排序的函数这个函数可以拍戏任意类型的数据bubble_sort()//改造
void bubble_sort(void* base, size_t size, size_t width, int (*compar)(const void*, const void*)){
	int i = 0;
	int j = 0;
	for ( i = 0; i < size - 1; i++)
	{
		for (j = 0; j < size - i - 1; j++) {
			if ( compar((char *)base + j * width, (char *)base + (j + 1) * width ) > 0 )//升序改变
			{
				//交换两个元素
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}

}

void test3()
{
	int arr[] = { 3, 1, 7, 8, 2, 4, 9, 0, 6 };
	int size = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, size, sizeof(arr[0]), cmp_int);
}
int main() {
	//调用自己的bubble_sort
	test3();
	return 0;
}

5.2 使用bubble_sort()函数完成对结构体的排序

要使用 bubble_sort()函数排序结构体 ,我们首先要 创建一个结构体变量,如下,我们先创建一个包含人名和年龄的结构体变量:

这里是引用在这里插入图片描述

下面会以这个结构体变量为例,分别实现使用 bubble_sort()函数完成对结构体按年龄 和 按姓名的排序。

5.2.1 使用bubble_sort()完成对结构体中整型数据的升序

我们照例 要使用bubble_sort()函数 ,就要先准备好它需要的四个参数,即数组的首元素地址 (此时数组的类型为结构体类型,即数组首元素的地址是结构体类型), 数组的长度 数组每个元素的大小,还有 自己编写的比较函数。我们依次准备好这四个参数:
在这里插入图片描述 > cmp_stu_by_age函数 :
在这里插入图片描述

接下来就可以调用bubble_sort() 查看结果了:

这里是引用

该部分完整代码如下:

#include <stdio.h>
#include <stdlib.h>
int cmp_stu_by_age(const void* p1, const void* p2) {
	//return (struct Stu*)p1->age - (struct Stu*)p2->age;error 不能这么写
	//return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
	return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;
}
void swap(char* p1, char* p2, size_t width) {
	int i = 0;
	char temp = 0;
	for ( i = 0; i < width; i++)
	{
		temp = *p1;
		*p1 = *p2;
		*p2 = temp;
		p1++;
		p2++;
	}
}
//我们模仿qsort来说实现一个冒泡排序的函数这个函数可以拍戏任意类型的数据bubble_sort()//改造
void bubble_sort(void* base, size_t size, size_t width, int (*compar)(const void*, const void*)){
	int i = 0;
	int j = 0;
	for ( i = 0; i < size - 1; i++)
	{
		for (j = 0; j < size - i - 1; j++) {
			if ( compar((char *)base + j * width, (char *)base + (j + 1) * width ) > 0 )//升序改变
			{
				//交换两个元素
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}

}
void test4() {
	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 35}, {"wangwu", 18} };
	int size = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, size, sizeof(arr[0]), cmp_stu_by_age);
}
int main() {
	test4();
	return 0;
}

5.2.2 使用bubble_sort()函数完成结构体中字符串数据的升序

要使用bubble_sort()函数 ,就要先准备好它需要的四个参数,即数组的首元素地址 数组的长度 数组每个元素的大小,还有 自己编写的比较函数。我们依次准备好这四个参数:
在这里插入图片描述
在这里插入图片描述

接下来就可以调用bubble_sort() 查看结果了:
在这里插入图片描述
接下来就可以调用bubble_sort() 查看结果了:

这里是引用

该部分完整代码如下:

#include <stdio.h>
#include <stdlib.h>
int cmp_stu_by_name(const void* p1, const void* p2) {
	return strcmp(((struct Stu*)p1) -> name, ((struct Stu*)p2 )-> name);
	//return strcmp( (*(struct Stu*)p1).name, (*(struct Stu*)p2).name );
}
void swap(char* p1, char* p2, size_t width) {
	int i = 0;
	char temp = 0;
	for ( i = 0; i < width; i++)
	{
		temp = *p1;
		*p1 = *p2;
		*p2 = temp;
		p1++;
		p2++;
	}
}
//我们模仿qsort来说实现一个冒泡排序的函数这个函数可以拍戏任意类型的数据bubble_sort()//改造
void bubble_sort(void* base, size_t size, size_t width, int (*compar)(const void*, const void*)){
	int i = 0;
	int j = 0;
	for ( i = 0; i < size - 1; i++)
	{
		for (j = 0; j < size - i - 1; j++) {
			if ( compar((char *)base + j * width, (char *)base + (j + 1) * width ) > 0 )//升序改变
			{
				//交换两个元素
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}

}
void test4() {
	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 35}, {"wangwu", 18} };
	int size = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, size, sizeof(arr[0]), cmp_stu_by_name);
}
int main() {
	test4();
	return 0;
}

后言

以上就是关于qsort()函数及其模拟实现bubble_sort()函数的全部内容,希望能对大家有所帮助或有所启发,感谢各位小伙伴的耐心阅读。咱们下期见!拜拜~

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值