qsort函数用法 + 模拟实现qsort函数

news2025/1/17 2:49:20

👦个人主页:@Weraphael

✍🏻作者简介:目前是C语言学习者

✈️专栏:【C/C++】算法
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬  点赞👍🏻 收藏 📂 加关注😍


前言:

往期我们学习了二分查找、归并排序、快速排序和冒泡排序。今天我们学习qsort函数,qsort函数是C语言库中实现的快速排序算法。并且qsort要求提供一个自己定义的比较函数。比较函数使得qsort通用性更好,qsort函数可以实现对数组、字符串、结构体等结构进行升序或降序排序。闲言少叙,开快车🚝🚝

目录

一、qsort函数简介

二、qsort函数的使用

                1、对int类型数组排序

                2、对char类型排序

                3、对浮点型排序

                4、对结构体类型排序

三、模拟实现qsort函数

                1、代码详解

                2、 代码实现之整型排序

                3、代码实现之结构体类型排序

四、总结


一、qsort函数简介

对于陌生的库函数,可通过cplusplus网站来了解它

【qsort参数介绍】

【比较函数的返回值】

​ 

 所以,qsort函数大致的模板为

int compare(const void* p1, const void* p2) //
{
	return p1 - p2; //返回的是升序
	return p2 - p1; //返回的是降序   
    
    //注:p1和p2的类型根据实际情况写
}

int main()
{
	int arr[] = { 1,3,4,6,7,2,10,8,5,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数 40 / 4 = 10

	qsort(arr, sz, sizeof(arr[0]), compare);
	//arr - 指向要排序的数组的第一个元素的指针。
	//sz -  由 arr 指向的数组中元素的个数
	//sizeof(arr[0]) - 数组中每个元素的大小,以字节为单位。
	//compar - 用来比较两个元素的函数。

	return 0;
}

Q:为什么compare的形参的两个参数是void*类型?

因为qsort函数可以实现对数组(int)、字符串(char)、结构体(stuct)等类型进行升序或降序排序,而void*是不介意类型的,就像一个“垃圾桶”,任意的类型的地址都能往void*塞,但就是不能对其直接使用(解引用操作,++,--等等)。若想使用,只要进行强制类型转化即可。

二、qsort函数的使用

1、对int类型数组排序

【升序情况】

//升序的情况
#include <stdlib.h>  //使用qsort需要包含头文件
#include <stdio.h>  
int compare_int(const void* p1, const void* p2) 
{
	return *(int*)p1 - *(int*)p2; //强制类型转化并解引用
}

int main()
{
	int arr[] = { 1,3,4,6,7,2,10,8,5,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr, sz, sizeof(arr[0]), compare_int);
	
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

【程序结果】

【降序情况】

//降序的情况
#include <stdlib.h>  //使用qsort需要包含头文件
#include <stdio.h>  
int compare_int(const void* p1, const void* p2) 
{
	return *(int*)p2 - *(int*)p1; //强制类型转化并解引用
}

int main()
{
	int arr[] = { 1,3,4,6,7,2,10,8,5,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr, sz, sizeof(arr[0]), compare_int);
	
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

2、对char类型排序

//升序的情况
#include <stdio.h>
#include <stdlib.h>

int compare_char(const void* p1, const void* p2) 
{
	return *(char*)p1 - *(char*)p2; //强制类型转化并解引用
}

int main()
{
	char arr[] = { 'f', 'b','e','a','d','c'};
	int sz = sizeof(arr) / sizeof(arr[0]);

	
	qsort(arr, sz, sizeof(arr[0]), compare_char);
	
	for (int i = 0; i < sz; i++)
	{
		printf("%c ", arr[i]);
	}
	return 0;
}

 【程序结果】

3、对浮点型排序

#include <stdio.h>
#include <stdlib.h>

int compare_double(const void* p1, const void* p2) 
{
	return (*(double*)p1 > *(double*)p2 ? 1 : -1); //三目操作符
}

int main()
{
	double arr[] = { 3.14,2.6,2.3,1.7};
	int sz = sizeof(arr) / sizeof(arr[0]);

	
	qsort(arr, sz, sizeof(arr[0]), compare_double);
	
	for (int i = 0; i < sz; i++)
	{
		printf("%lf ", arr[i]);
	}


	return 0;
}

注意

用qsort对浮点型的一定要用三目运算符,由于浮点数的精度问题,如果是两个很接近的数相减,则可能返回一个接近0的小数,而compare_double的返回值是int型,因此会将这个小数返回0。

4、对结构体类型排序

【按姓名排序】

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
	char name[20];
	int age;
};

int compare_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

int main()
{
	struct Stu s[3] = { {"张三",10},{"李四",30},{"王五",21} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), compare_name);

	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", s[i].name, s[i].age);
	}
	return 0;
}

【程序结果】

注意

姓名是字符串,比较的是字典序大小,要用strcmp

比较年龄和比较整型一样,这里就不为大家展示了 

 三、模拟实现qsort函数

模拟实现qsort函数是要基于冒泡排序实现的    (冒泡排序讲解)

【冒泡排序的实现】

#include <stdio.h>
void Sort(int arr[], int sz)
{
	for (int i = 0; i < sz - 1; i++)
	{
		
		for (int 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;
			}
		}
	}
}
int main()
{
	int arr[] = { 2,6,8,7,6,0,1,5,9,3 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Sort(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

现要求改造冒泡排序,使整个函数可以排序任意类型的数组

以整型数组为例

【主函数部分】

#include <stdio.h>
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

int main()
{ 
	int arr[] = { 2,6,8,7,6,0,1,5,9,3 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	Sort(arr, sz, sizeof(arr[0]), cmp_int); 

	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

【Sort函数部分】

参考qsort函数的参数

void Swap(char* x1, char* x2, int width)
{
	//因为不知道是什么类型,要一个字节一个字节交换
	for (int i = 0; i < width; i++)
	{
		char tmp = *x1;
		*x1 = *x2;
		*x2 = tmp;
		x1++;
		x2++;
	}
}

//void* base - 要求排序不同类型的数组,void*恰好能接收任意类型
//int sz - 元素个数
//int width - 一个元素的大小
//int (*p)(const void*, const void*)  函数传参函数指针接收
//size_t - 无符号整型

void Sort(void* base, size_t sz, size_t width, int (*p)(const void*, const void*))
{
	for (size_t i = 0; i < sz - 1; i++)
	{
		for (size_t j = 0; j < sz - 1 - i; j++)
		{
            //通过函数指针p调用的函数cmp_int,所以这是个回调函数
			if (p((char*)base + j * width,(char*)base + (j + 1) * width) > 0)
			{
				//写一个Swap函数来交换
				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 Swap(char* x1, char* x2, int width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *x1;
		*x1 = *x2;
		*x2 = tmp;
		x1++;
		x2++;
	}
}

void Sort(void* base, size_t sz, size_t width, int (*p)(const void*, const void*))
{
	for (size_t i = 0; i < sz - 1; i++)
	{
		for (size_t j = 0; j < sz - 1 - i; j++)
		{
			if (p((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[] = { 2,6,8,7,6,0,1,5,9,3 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	Sort(arr, sz, sizeof(arr[0]), cmp_int); 

	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

【程序结果】

【代码实现之结构体类型排序】 

以排列姓名为例

struct Stu
{
	char name[20];
	int age;
};
int compare_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}


void Swap(char* x1, char* x2, int width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *x1;
		*x1 = *x2;
		*x2 = tmp;
		x1++;
		x2++;
	}
}

void Sort(void* base, size_t sz, size_t width, int (*p)(const void*, const void*))
{
	for (size_t i = 0; i < sz - 1; i++)
	{
		for (size_t j = 0; j < sz - 1 - i; j++)
		{
			if (p((char*)base + j * width,(char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

int main()
{ 
	struct Stu s[3] = { {"张三",10},{"李四",30},{"王五",21} };
	int sz = sizeof(s) / sizeof(s[0]);

	Sort(s, sz, sizeof(s[0]), compare_name);

	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", s[i].name,s[i].age);
	}
	return 0;
}

【程序结果】

四、总结 

本章重点讲解qsort函数用法以及如何模拟实现qsort函数,如果这篇博客对你有帮助,别忘了评论、点赞 、收藏、关注哟😘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/178282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文笔记目录(ver2.0)

1 时间序列 1.1 时间序列预测 论文名称来源主要内容论文笔记&#xff1a;DCRNN &#xff08;Diffusion Convolutional Recurrent Neural Network: Data-Driven Traffic Forecasting&#xff09;_UQI-LIUWJ的博客-CSDN博客iclr 2017使用双向扩散卷积GRU&#xff0c;建模空间和…

目标跟踪心得篇五:MOT数据集标注、TrackEval的使用、DarkLabel不能自动跟踪解决方案及如何在MMTracking上输出跟踪评测

跟踪方向的标注成本非常很大的 ,那么我们如何尽可能一次性弄好呢? 所选标注工具:DarkLabel DarkLabel是一个轻量的视频标注软件,尤其做MOT任务非常友好,其标注可以通过脚本转化为标准的目标检测数据集格式、ReID数据集格式和MOT数据集格式。 使用之前: darklabel.yml:保…

传参的理解

前言 当我们调用函数的时候&#xff0c;参数是怎么传递给被调用方的&#xff0c;有想过这个问题吗&#xff1f;传递不同大小的参数对调用方式有影响吗&#xff1f;本文将带你探究这些问题&#xff0c;阅读本文需要对函数栈帧有一定的理解&#xff0c;并了解基本的汇编指令。 …

傅一平:2022年我的私人书单

2022年过去了&#xff0c;推荐我的TOP 10 书单&#xff0c;同时附上我的一句话评语和豆瓣的评分&#xff0c;这些书代表了我学习的方向&#xff0c;包括学习方法、思考方法、数据治理、数字化转型、系统架构、职场管理、个人修养、生活态度等。TOP 1 学习究竟是什么一句话评语…

【Ajax】HTTP超文本传输协议

一、HTTP协议简介什么是通信通信&#xff0c;就是信息的传递和交换。通信三要素&#xff1a;通信的主体通信的内容通信的方式1.1 现实生活中的通信案例&#xff1a;张三要把自己考清北大学的好消息写信告诉自己的好朋友李四。其中&#xff1a;通信的主体是张三和李四&#xff1…

Linux网络:应用层之HTTP协议

文章目录一、应用层1.协议2.网络版计算器二、HTTP 协议1. URL2. HTTP 协议格式3.查看 HTTP 请求4.发送 HTTP 响应5. HTTP 的方法6. HTTP 的状态码7. HTTP 的版本8. HTTP 常见 Header9. Cookie 与 session三、HTTP 与 HTTPS一、应用层 我们程序员写的一个个解决实际问题&#x…

jvm 堆 栈中存什么?

数据类型 Java虚拟机中&#xff0c;数据类型可以分为两类&#xff1a;基本类型和引用类型。基本类型的变量保存原始值&#xff0c;即&#xff1a;他代表的 值就是数值本身&#xff1b;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用&#xff0c;而不是对象本身&…

矩阵理论复习(六)

Q代表有理数&#xff0c;即整数和小数部分有限的分数和小数部分无限循环的分数。无限不循环的小数就是无理数。所有无理数和有理数加起来就是实数集R。与实数对应的就是虚数。 数域的定义 线性空间的定义 线性空间的基和维数 子空间的定义 子空间的判别方法 最常见的…

【唐诗学习】二、初唐诗词领路人

二、初唐诗词领路人 唐朝之前的主流诗人都是在宫廷混口饭吃&#xff0c;他们整天围着皇帝转&#xff0c;写的大多是宫廷奢靡的生活&#xff0c;还会拍皇帝马屁。主流诗人受前朝影响很大&#xff0c;就这么发展到了初唐。照这个剧情发展下去&#xff0c;诗歌迟早要完蛋。 可有些…

狂神聊Git~

版本控制&#xff1a; 版本控制的概念: 它是一种在开发的过程中用于管理我们对文件&#xff0c;目录或工程等内容的修改历史&#xff0c;方便我们查看历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术 版本控制的作用: 用于管理多人协同开发项目的技术 实现跨区…

Tomcat进程占用CPU过高怎么办?

在性能优化这个主题里&#xff0c;前面我们聊过了Tomcat的内存问题和网络相关的问题&#xff0c;接下来我们看一下CPU的问题&#xff0c;CPU资源经常会成为系统性能的一个瓶颈&#xff0c;这其中的原因是多方面的&#xff0c;可能是内存泄漏导致频繁GC&#xff0c;进而引起CPU使…

Linux命令--查看发行版本/内核版本的方法

原文网址&#xff1a;Linux命令--查看发行版本/内核版本的方法_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Linux查看发行版本和内核版本的方法。 查看发行版本 cat /etc/lsb_release 说明 这个命令适用于大部分linux发行版本&#xff08;除了redhat和centos等&#xff09; …

C 语言零基础入门教程(九)

C 函数 函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数&#xff0c;即主函数 main() &#xff0c;所有简单的程序都可以定义其他额外的函数。 您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的&#xff0c;但在逻辑上&#xff0c;…

用Zybo调试CY7C68013A核心板的Slave FIFO模式

用Zybo调试CY7C68013A核心板简介CY7C68013A核心板CY7C68013程序设计硬件连接主要代码Zybo程序设计心得简介 最近在调试CY7C68013A核心板的Slave FIFO模式时&#xff0c;因为电路板的丝印bug&#xff0c;绕了一大圈。最终不但调试成功&#xff0c;也发现了用Zybo调试其它电路板…

C语言对数组元素进行排序

在实际开发中&#xff0c;有很多场景需要我们将数组元素按照从大到小&#xff08;或者从小到大&#xff09;的顺序排列&#xff0c;这样在查阅数据时会更加直观&#xff0c;例如&#xff1a;一个保存了班级学号的数组&#xff0c;排序后更容易分区好学生和坏学生&#xff1b;一…

教练,我想学设计之禅

欢迎来到PaQiuQiu的空间 本文为【教练,我想学设计之禅】,方便大家更好的阅读! <—写在前面—> 本专栏分四部分展开,设计模式与设计原则、算法与数据结构、架构设计以及实战为王。 设计模式介绍了经典的23种设计模式,设计原则重点阐述SOLID原则; 算法与数据结构详…

Linux常用命令——slabtop命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) slabtop 实时显示内核slab内存缓存信息 补充说明 slabtop命令以实时的方式显示内核“slab”缓冲区的细节信息。 语法 slabtop(选项)选项 --delayn, -d n&#xff1a;每n秒更新一次显示的信息&#xff0c;默…

使用树莓派3B、RTL-SDR、OpenWebRX搭建无线电监测站

方案介绍&#xff1a; OpenWebRX是一个国外开源项目&#xff0c;基于Python语言编写&#xff0c;配合SDR设备使用&#xff0c;能将SDR接收软件Web化&#xff0c;通过网络实现多用户远程访问&#xff0c;无需安装任何客户端软件&#xff0c;功能非常强大&#xff0c;支持&#x…

Python位置参数

位置参数&#xff0c;有时也称必备参数&#xff0c;指的是必须按照正确的顺序将实际参数传到函数中&#xff0c;换句话说&#xff0c;调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。实参和形参数量必须一致在调用函数&#xff0c;指定的实际参数的数量&#…

DaVinci:Camera Raw(CinemaDNG)

本文主要介绍 CinemaDNG Raw 格式素材相关的 Camera Raw 参数。解码质量Decode Quality解码质量决定了图像解拜耳之后所呈现的素质。默认为“使用项目设置” Use project setting&#xff0c;表示使用项目设置对话框中的“Camera RAW”解码质量设置。还可选择&#xff1a;全分辨…