带你快速了解如何使用qsort和模拟qsort

在这里插入图片描述

1. 如何使用qsort

qsort是C语言里的一个库函数,快速排序的方法实现的。
我们来看一下标准库里qsort的参数类型:
在这里插入图片描述
void* base 是一个指针,它传的是目标数组的起始位置。
size_t num 的意思是数组元素的个数。
size_t size 的意思是每个元素的大小(单位是字节)。
int ( * compar)(const void * ,const void * )) 这是一个函数指针。它指向的是一个比较函数。
我们来看看这比较函数的要求是什么?
在这里插入图片描述
p1指向的元素小于p2指向的元素返回负数。
p1指向的元素等于于p2指向的元素返回0。
p1指向的元素大于p2指向的元素返回正数。

我们现在来举一个例子,排一个整形数组:
在这里插入图片描述
我们要比较p1,p2肯定要解引用,但这里p1,p2不能直接解引用,因为它是
void * 的。
我们说一些void * 的一些特点:
void * 是一种无类型的指针。
void * 的指针变量可以存放任意类型的地址。
void * 的指针不能直接进行解引用操作。
void * 的指针不能直接进行加,减整数。

所以,我们在使用void * 的时候,应该强制类型转换成你想要的类型。
结果如下:
在这里插入图片描述
接下来,我们再用qsort来排序结构体数组
我们定义一个学生结构体:
在这里插入图片描述
我们按照成绩来排序:
在这里插入图片描述
然后我们强制类型转换成结构体指针进行比较:
在这里插入图片描述
运行结果如下:
在这里插入图片描述
我们可以看到成绩由小到大排序。

然后我们再用名字来排序:
在这里插入图片描述
在这里要注意名字比较是字符串,应该使用strcmp来比较。
在这里插入图片描述
运行结果如下:
在这里插入图片描述
到这里,我们应该都会使用qsort了吧。接下来,我们将模拟qsort。

2. 模拟qsort

在这里插入图片描述
我们先再说明一下这些参数:
第一个,为什么是void* ,因为排序的时候,我们可能传整型,浮点型,字符型,结构体类型等等,所以我们要接收各种各样的类型,只能是void * 。

第二个,我们肯定要知道数组里要排序的元素有多少个吧。

第三个,我们要知道一个元素的大小,不然,我们不知道从哪里找下一个元素。

第四个,像冒泡排序样,我们的比较趟数,和每趟比较的元素个数是不会发生变化的,只有我们比较的方法是不一样的。所以,我们应该将这个比较方法抽离出来,成为单独的一个函数。

现在,我们用冒泡排序的思想来模拟一下qsort。
首先,我们将冒泡排序里不需要改变的写出来:
在这里插入图片描述
只有比较的时候是不一样的。那我们该如何来写一个通用的比较呢?
首先,cmp函数指针,我们该传什么参数呢?我们需要相邻的两个元素比较,所以应该传两个元素的地址。
我们传base+j和base+j+1,行不行,不行。因为base是void * 的。我们应该强制类型转换为char * 的。因为char * 可以满足任意类型。
然后,我们再用width来找到每一个元素。
(char*)base + j * width, (char*)base + (j + 1) * width)
当j=0时,第一个就找到了第一个元素,第二个就跳过4个字节找到了第二个元素。
当j=1时,第一个跳过4个字节就找到了第二个元素,第二个就跳过8个字节找到了第三个元素。

在这里插入图片描述

找到了相邻的元素,我们就开始交换。用一个Swap函数来交换:

Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

然后,我们实现一下这个Swap函数。
因为我们是char*,所以要一个一个字节交换。
在这里插入图片描述
完整的代码:

#include <stdio.h>
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
 
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

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, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

小结:

这里的主要问题就是我们要以不同的角度来看待,作为排序的创造者,我们不知道排序数组是什么类型的。但使用者是知道的。
因为比较的数组类型是多种多样的,比较方法是不相同的,所以比较函数的方法创造者是不知道的,但使用者知道。所以,使用者要把自己写的函数地址传过去。
所以cmp就可以找到使用者写的比较函数方法,然后我们将相邻的两个元素的地址传过去,为什么强制类型转换成char*,是因为char类型是最小的一个类型,它可以很方便的用来跳过相同的字节数。

总结:

到这里,这篇内容就先结束了。如果大家认为我有哪些不足之处或者知识上的错误都可以告诉我,我会及时改正,也请大家多多包涵。如果大家觉得这篇文章有用的话,也希望大家可以给我关注点赞,你们的支持就是对我最大的鼓励,我们下一篇文章再见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学代码的咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值