排序之玩转qsort函数——【C语言】

news2024/11/17 11:55:20

说起排序,我们会想起许多算法,在之前的博客中我也写到过,比如:冒泡排序法、快速排序法、选择排序法等等。其实在C语言中一直有一个可以将数组中的内容进行排序的函数且功能完善内容齐全的库函数——qsort函数。今天就让我们来探索一下吧!


目录

回调函数

初始qsort函数

如何创建比较函数

qsort函数的引用

模拟qsort函数


回调函数

在了解qsort函数时,我们先来说一下什么时回调函数。

回调函数:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当 这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调 用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

回调函数其实是对某一类函数的一种称呼,在使用时我更感觉类似一种嵌套关系,在对应的时间使用回调函数就会有意想不到的收获。

在使用qsort函数时我们会用到回调函数,所以qsort函数可以在更广泛的常见进行运用(整型、浮点型、字符串、结构体等等)。接下来就让我们走进qsort函数中!


初始qsort函数

qsort库函数是在#include<stdlib.h>中的,作用就是对数组中的元素进行排序。

qsort函数的特点:

1.使用的是快速排序的方法

2.适合于任何类型数据的排序

 以上就是qsort函数的返回值与各个参数的内容,现在我们进行分析:

返回值:void(不返回任何内容)

参数:

void* base:指向要排序的数组的第一个对象的指针,转换为 void *

size_t num:数组中由base指向的元素数。 size_t是无符号整型。

size_t size:数组中每个元素的大小(以字节为单位)size_t是无符号整型。

int (*compar) (const void*, const void*):这是一个函数指针,实参应该给其一个函数的地址,其中给予的函数的返回值为int,两个参数是指向比较两个元素的函数的指针。

 

所以在使用qsort函数时,我们应该创建一个比较的函数传入qsort函数中让其使用。 创建的比较函数应该与自己想要比较的数组内容进行匹配,对应不同的数组内容,我们应该创建不同的比较函数进行比较。


如何创建比较函数

在创建自己想要比较的compar函数时,最重要的一点时遵循qsort参数的模板进行创建,否则qsort函数将无法使用。

void*(空指针):它可以接收任何类型的指针变量,但是不能进行指针操作,因为它没用空间访问权限,如果没有注意此情况编译器就会报错。所以我们在给予赋值时必须将它强制类型转换,转换为自己需要的数据类型即可。

当排序数组为整型数组:

int compar(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}

排序数组为字符串数组时,我们应该使用#include<string.h>中的strcmp函数进行比较:

int compar(const void* p1, const void* p2)
{
	return (strcmp(*(char*)p1 , *(char*)p2));
}

 结构体数组时,比较内容为字符串时:

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

 以上是几个比较典型的例子,看完这些例子我相信已经知道compar函数应该怎么创建了,下面让我们来上一段代码进行实操。


qsort函数的引用

根据上面的说明,我们基本已经掌握qsort函数的基本使用方法,现在让我们通过代码对qsort函数有更进一步的认识。

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

struct Stu
{
	char name[20];
	int age;
};
void print(int arr[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int compar1(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}

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


void test1()
{
	int arr1[10] = { 3,1,5,2,9,7,4,8,0,6 };
	int sz = sizeof(arr1) / sizeof(arr1[0]);
	qsort(arr1, sz, sizeof(arr1[0]), compar1);
	print(arr1, sz);
}

void test2()
{
	struct Stu arr2[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu",16} };
	int sz = sizeof(arr2) / sizeof(arr2[0]);
	qsort(arr2, sz, sizeof(arr2[0]), compar2);
	for (int i = 0; i < sz; i++)
	{
		printf("\n%s %d", arr2[i].name, arr2[i].age);
	}
}
int main(void)
 {
	test1();
	test2();
	return 0;
}

这段代码是将一个整数数组和结构体数组中的字符串进行升序排序。这里我想说一下字符之间的比大小是通过它们的ASCII码值进行比较。运行结果如下: 如果想要进行降序排序,我们只需要将compar函数中的比较的两个值进行交换即可。


模拟qsort函数

根据我们在使用qsort函数的一些功能特点,来模拟出qsort函数的功能。

qsort函数的功能就是排序,所以这个函数中一定有某种排序的方法,在这里我们将使用冒泡排序法进行。(其他排序方法也是可以的)

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;
			}
		}
	}
}

这是一个最标准的冒泡排序法,我们通过上面的排序代码为框架进行模拟qsort函数。

假设就使用这个函数当作qsort函数,我们就会发现很多问题!!!

问题一:参数只能接收整型数组,其他数组不能被接收。

解决:我们就可以模仿qsort函数中的第一个参数,将第一个参数改为void* base,这样什么类型的数组我们都可以接收。

问题二:当我们使用void*base作为第一个参数时,指针的移动应该怎么实现?(数组元素的大小和数量)

解决:继续仿照qsort函数,加入size_t num和size_t size.

问题三:上述函数中两数两两比较是建立于整数基础上的比较,所以使用> < >= <=就可以实现。如果传入的数组为结构体数组时,我们就不能用比较整数的比较方法实现了。(对于不同类型的数据不能简单的使用>进行比较)

解决:我们就需要用到回调函数,将两个元素的比较方法,以函数参数的形式传递。(函数指针将成为第四个参数) 

当创建好compar函数时,我们应该怎么去给compar函数去传递参数呢?

compar函数是多个函数,通过不同的调用我们可以比较不同的数组,但是在模拟函数中给compar函数传参的位置只有一个,我们应该考虑兼顾所以的数组类型。

我们知道了数组中每个元素的大小,相当于我们知道其元素在内存中所占的大小,为了兼顾所有数据,我们用访问内存权限最小的指针char*来作为基本单位,在乘上每个元素的大小,就可以实现任何数据的访问。

 在for循环中使用即可访问数组中的所有元素。

当传入的元素在compar函数中比较如果大于0,就要进行元素内容交换。又有问题出现了,传入的数据类型不同,交换的内容大小不同,为了其中的兼容性,我们可以继续沿用上述思想,创建一个swap交换函数,传入刚才compar函数使用的两个函数地址和数组一个元素所占内存大小size即可。

swap函数会将两个数据中的数据一个字节一个字节的交换,这也可以保证所有类型数组都可以进行。 

所有的准备工作已经完成,下面让我们完成qsort函数的模拟。

#include<stdio.h>
#include<string.h>
void print(int arr[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

void swap(char*p1,char*p2,int size)
{
	int i = 0;
	char tmp = 0;
	for (i = 0; i < size; i++)
	{
		tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}


int compar(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
	int i = 0; 
	for (i = 0; i < num-1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

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



int main(void)
{
	test1();
}

如果想加入其他类型数组进行排序,只需要加入新的compar函数和要比较的内容即可,如果想要降序排序,只需将if函数中>0改为<0即可。下面是成功运行的结果: 

模拟的qsort函数主体为bubble_sort函数和swap函数:

void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
	int i = 0; 
	for (i = 0; i < num-1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

void swap(char*p1,char*p2,int size)
{
	int i = 0;
	char tmp = 0;
	for (i = 0; i < size; i++)
	{
		tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}

 

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

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

相关文章

OpenPCDet系列 | 8.2 nuScenes数据集的eval流程

0. eval转换的目标 模型的训练和测试过程输出结果是不一样的&#xff0c;对于训练过程是为了构建损失函数来进行训练&#xff0c;而对于测试过程是为了对object进行预测生成预测内容。下面以VoxelNeX检测器的类代码可见&#xff0c;training和testing将会输出两个内容。 clas…

C++数据结构笔记(7)——队列的顺序结构实现

1.队列&#xff0c;和现实生活中的规则类似&#xff0c;先进先出 2.队尾只允许元素进入&#xff0c;队头只允许元素退出 3.用数组来实现队列的顺序存储&#xff0c;无论哪一段都可以作为队头或者队尾 SeqQueue.h头文件 #ifndef SEQQUEUE_H #define SEQQUEUE_H #include<…

仿大众点评项目 —— Day02【优惠券秒杀、分布式锁】

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

Java字符串类

string类的理解(以JDK8为例说明) 1.1的声明 public final class String implements java.io.Serializable&#xff0c; Comparable<String>&#xff0c; CharSequence final:String是不可被继承的 Serializable:可序列化的接口。凡是实现此接口的类的对象就可以通过…

建筑施工脚手架安全技术统一标准

为统一建筑施工脚手架设计、施工、使用及管理&#xff0c;做到技术先进、安全适用、经济合理&#xff0c;制定本标准。 本标准适用于房屋建筑工程和市政工程施工用脚手架的设计、施工、使用及管理。 建筑施工脚手架的设计、施工、使用及管理&#xff0c;除应符合本标准外&…

第一百零二天学习记录:数据结构与算法基础:初识数据结构与算法

管理系统模型&#xff08;仓库管理系统&#xff09;—顺序表 操作对象之间的关系&#xff1a;线性关系 数据结构&#xff1a;线性数据结构、线性表 &#xff08;例如&#xff1a;学生成绩管理系统、人事管理系统、仓库管理系统、通讯录等。&#xff09; 操作对象&#xff1a;若…

OWASP 定义的大模型应用最常见的10个关键安全问题

7月15日之前入驻华为云&#xff0c;可参与Check抽奖活动&#xff0c;抽奖活动在文末 1. 《OWASP 大模型应用最常见的10个关键安全问题》项目简介&#xff08;OWASP TOP10 LLMs Project&#xff09; *OWASP Top 10 for Large Language Model Applications OWASP 大模型应用程序…

vue3使用腾讯地图(‘关键词搜索、逆地址解析‘)

1.登录腾讯地图位置服务进入控制台 申请腾讯地图开发者进入控制台申请自己的key 腾讯位置服务 - 立足生态&#xff0c;连接未来 2.进入vue项目的public文件下的index.html 引入腾讯资源包&#xff0c;并把申请的key填入 <script src"https://map.qq.com/api/js?v2…

文心一言 VS 讯飞星火 VS chatgpt (57)-- 算法导论6.4 1题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;57&#xff09;-- 算法导论6.4 1题 一、参照图 6-4 的方法&#xff0c;说明 HEAPSORT 在数组 A(5&#xff0c;13&#xff0c;2&#xff0c;25&#xff0c;7&#xff0c;17&#xff0c;20&#xff0c;8&#xff0c;4)上的操作过程…

怎么修复损坏的视频文件?视频文件修复办法分享!

随着科技的不断发展&#xff0c;我们的生活中已经离不开各种类型的视频文件。因为各式各样的原因&#xff0c;有时候我们的视频文件可能会损坏。 而损坏的视频文件通常是无法正常播放&#xff0c;这无疑会给我们的生活和工作造成极大的困扰。那么&#xff0c;怎么修复损坏的视…

【Linux学习】记录下Linux的常用基本指令~

1、Linux是一个操作系统&#xff0c;和windows是“并列”关系。Linux已经成为"世界第一大操作系统"。 2、Linux这种使用命令的方式比图形化界面的好处&#xff1f; &#xff08;1&#xff09;节省系统资源&#xff1a;运行图形化界面需要让系统付出一些额外开销&am…

stm32(时钟和中断事件知识点)

一、复位和时钟控制&#xff08;RCC&#xff09; 复位 系统复位 当发生以下任一事件时&#xff0c;产生一个系统复位&#xff1a; 1. NRST引脚上的低电平(外部复位) 2. 窗口看门狗计数终止(WWDG复位) 3. 独立看门狗计数终止(IWDG复位) 4. 软件复位(SW复位) 5. 低功耗管…

软件为什么需要进行应急演练脚本?

软件为什么需要进行应急演练脚本&#xff1f;在当今互联网时代&#xff0c;安全问题愈加突出&#xff0c;不断有新的网络攻击方式不断涌现。针对软件系统的安全漏洞和攻击活动不断增加&#xff0c;软件应急演练变得尤为重要。 首先&#xff0c;应急演练可以帮助软件团队建立应急…

C++11可变参数模板,lambda表达式,包装器

目录 可变参数模板 lambda表达式 问题的引入 lambda表达式语法 捕捉列表的使用 函数对象和lambda表达式 function包装器 可变参数模板 C11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板&#xff0c;相比C98/&#xff0c;类模版和函数模版中只能…

基于redis实现延时队列(一)

背景 最近项目中需要对一条数据&#xff0c;在半小时候更改其状态&#xff0c;类似于提交商城订单半小时后未支付的订单需要更改为超时状态&#xff0c;当然这个解决方案有很多&#xff0c;最好的解决方案是用MQ的死信队列&#xff1b;但由于项目中没有引入MQ&#xff0c;故本…

PMP-质量管理的重要性

本篇文章主要是方便从事于项目管理的“初学者”们了解质量管理的重要性&#xff01;&#xff01;&#xff01; 一、什么是质量管理 项目质量管理包括把组织的质量政策应用于规划、管理、控制项目和产品质量要求&#xff0c;以满足相关方目标的各个过程。此外&#xff0c;项目质…

Latex公式炫酷技巧

最近看到一个炫酷的latex公式用法&#xff0c;特意在此记录一下 效果如下 latex代码如下 \begin{equation}\mathcal{L}_{mot}^{\textcolor{magenta}{\bullet}} \frac{1}{\sum_{i1}^{N}{s_i^l}}\sum_{i1}^{N}\Big\Vert{s}^{l}_i(\mathbf{\hat{f}}_i-\mathbf{f}^{fg}_i)\Big…

网络安全系统教程+学习路线

一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

【MySQL系列】表的学习及基本操作

「前言」文章内容大致是数据库表的基本操作 「归属专栏」MySQL 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「句子分享」 人生当苦无妨&#xff0c;良人当归即好。 ——烽火戏诸侯《雪中悍刀行》 目录 一、创建表二、修改表三、 删除表 一、创建表 创建…

组合模式的例子

// 组合模式的接口 public interface AccessDecisionVoter {// 投票结果的常量int ACCESS_GRANTED 1;int ACCESS_ABSTAIN 0;int ACCESS_DENIED -1;// 投票方法&#xff0c;根据用户和请求判断是否授权int vote(User user, Request request); }// 组合模式的叶子节点&#xf…