【C进阶】qsort函数详解

news2024/11/18 0:45:04

qsort函数

  • 前言
  • qsort函数
    • (一)引例:简单的冒泡排序
    • (二)qsort函数接收
      • 1.介绍
      • 2.void*
    • (三)使用
      • 1.用qsort实现一个比较整型的函数
      • 2.用qsort实现一个比较结构体的函数
      • 3.用qsort实现一个比较浮点型的函数
    • (四)用冒泡排序模拟qsort函数的使用
      • 1.模拟qsort实现一个比较整数的函数
      • 2.模拟qsort实现一个比较结构体的函数
      • 3.模拟qsort实现一个比较浮点型的函数
  • 总结


前言

在学习回调函数的时候,不免会觉得难以理解或者难以运用,那就需要引入一个例子qsort库函数来让大家理解理解真正的回调函数是怎么样的,这就需要我们进行深入的了解什么是qsort函数,我们是先根据MSDN去查了查什么是qsort,然后就依据它的本质进行演示了不同类型的数的排序,最后模拟了一下冒泡排序运用qsort函数的使用,这都是干货满满!!!


qsort函数

要了解qsort函数,那就需要先引进一个简单的冒泡函数:

(一)引例:简单的冒泡排序

#include<stdio.h>

//缺陷:只能排序函数
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 temp = *(arr + j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = temp;
			}

		}
	}
}
int main() {
	//对数组进行排序,升序
	int arr[10] = { 0,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);

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

	return 0;
}

似乎有点缺陷啊,那我们要排字符呢?排结构体呢?是不是没法用大于号小于号和等于号进行比较了,那我们是不是需要把这个比较的式子单独抽离出来,在单独空间进行比较。

(二)qsort函数接收

1.介绍

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.void*

如果我们用整型指针去接收浮点型指针的时候,出现不兼容的情况,所以qsort的作者也考虑到了这一情况,既然不兼容,那我们找一个典当行,我用什么类型的指针传递给他(什么废铜烂铁都给他),他肯定看看不报错,因为总是有值钱的嘛,那这个典当行就是void*即空类型的指针。

在这里插入图片描述
所以我们为什么要用void*来接收传参呢?
在这里插入图片描述

我们发现float*到int*是不匹配的,那我们就需要void*了。

#include<stdio.h>

int main() {
	int a = 10;
	float f = 5.5f;
	int* p = &a;
	//p = &f;

	//好处:
	//pp为通用的指针,什么地址都可以放到pp指针里面
	//void*自己都不知道自己是什么类型的指针
	void* pp = &f;
	pp = &a;
	//弊端:
	//void*指针不能直接用,不能直接进行运算和打印
	pp++;//err void*未知的大小
	printf("%f\n", *pp);//err 非法的间接寻址

	return 0;
}

在这里插入图片描述
这个典当行行长void*说你们可以把不同的指针类型丢给我但是我也有条件的,你给我的什么破铜烂铁啊!我不输出了,你让我加价!?那肯定不可能的,可是我们想要达到我们的目的怎么办呢?我们就需要用暴力手段了,强制类型转换,跟典当行老板说,你今天必须给我加价并报出我满意的价格,不然告你黑店,典当行行长说,好好好,少侠饶命,我肯定加价报出好价格,这下子只要进行强制类型转换就好了。

如下图进行:
在这里插入图片描述

(三)使用

1.用qsort实现一个比较整型的函数

思路是利用qsort函数进行比较函数传参,即cmp_int函数,这个函数就是我们的回调函数,因为它进行传递地址给asort函数,qsort函数接收了cmp_int函数的地址并进行回调,回调以后接受比较的两个元素进行强制类型转换为整型指针进行相加减,根据qsort函数的返回值进行排序升序还是降序。
代码如下:

#include<stdio.h>
#include<stdlib.h>
//实现一个比较整型的函数
int cmp_int(const void* elem1, const void* elem2) {
	return *(int*)elem1 - *(int*)elem2;//升序
	//return *(int*)elem2 - *(int*)elem1;//降序
}
//使用qsort函数对数组进行排序,升序
void test1(void) {
	//对数组进行排序,升序
	int arr[10] = { 0,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bubble_sort(arr, sz);
	//库函数中有一个专门排序的函数叫qsort
	qsort(arr, sz, sizeof(arr[0]), &cmp_int);//&cmp_int是取出函数的地址并进行回调
	//打印
	int i = 0;
	for (i = 0; i < sz; i++) {
		printf("%d ", arr[i]);
	}
}

int main() {
	test1();

	//test2();

	return 0;
}

2.用qsort实现一个比较结构体的函数

思路是跟上面相似,而结构体这边要用的是要定义一个结构体并进行定义结构体数组,这里用到的技巧是结构体指针用的是->,还有一个比较强的技巧是strcmp,这里的strcmp库函数的返回值与qsort函数的返回值完全一致,我们打开MSDN看看:
在这里插入图片描述
结构体内元素类型不同不能进行比较哦,只能进行比较同类型的元素。
代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//使用qsort函数排序结构体
struct Stu {
	char name[30];
	int age;
};
//先按照年龄排序
int cmp_stu_by_age(const void* elem1, const void* elem2) {
	return ((struct Stu*)elem1)->age - ((struct Stu*)elem2)->age;
}
//再按照名字排序
int cmp_stu_by_name(const void* elem1, const void* elem2) {
	return strcmp(((struct Stu*)elem1)->name, ((struct Stu*)elem2)->name);
}
void test2(void) {
	struct Stu s[3] = { {"zhangsan",20 },{"lisi",32},{"wangwu",55} };
	int sz = sizeof(s) / sizeof(s[0]);
	//qsort(s, sz, sizeof(s[0]), &cmp_stu_by_age);
	qsort(s, sz, sizeof(s[0]), &cmp_stu_by_name);
	int i = 0;
	for (i = 0; i < sz; i++) {
		printf("%s %d\n", s[i].name, s[i].age);
	}
}


int main() {
	//test1();

	test2();

	return 0;
}

3.用qsort实现一个比较浮点型的函数

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_float(const void* elem1, const void* elem2) {
	if (*(float*)elem1 > *(float*)elem2){
		return 1;
	}
	else if (*(float*)elem1 < *(float*)elem2){
		return -1;
	}
	else {
		return 0;
	}
}
void test3(void) {
	//对数组进行排序,升序
	float arr[] = { 0.3, 0.4, 0.1, 0.2, 0.5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bubble_sort(arr, sz);
	//库函数中有一个专门排序的函数叫qsort
	qsort(arr, sz, sizeof(arr[0]), &cmp_float);
	//打印
	int i = 0;
	for (i = 0; i < sz; i++) {
		printf("%.1f ", arr[i]);
	}
}
int main() {
	//test1();

	//test2();

	test3();

	return 0;
}

(四)用冒泡排序模拟qsort函数的使用

1.模拟qsort实现一个比较整数的函数

先上代码再分析:

#include<stdio.h>

//改造冒泡排序函数,使用这个函数可以排序任意指定的数组
int cmp_int(const void* elem1, const void* elem2) {
	return *(int*)elem1 - *(int*)elem2;//升序
	//return *(int*)elem2 - *(int*)elem2;//降序
}

void Swap(char* buf1, char* buf2, int width) {
	int i = 0;
	for (i = 0; i < width; i++) {
		int temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}
//base就是未知参数的起始地址,
//sz就是传过来的数组大小,
//width是传过来数组内一个元素的字节长度
//int (*cmp)(const void*elem1, const void*elem2)是比较方法
//void*的意思就是不知道要传的是什么类型
bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void*elem1, const void*elem2)) {
	int flag = 1;
	size_t i = 0;
	for (i = 0; i < sz - 1; i++) {
		size_t 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);
				flag = 0;
			}
			
		}
		if (flag) {
			break;
		}
	}
}
//使用自己写的bubble_sort排序整型数组
void test3(void) {
	//对数组进行排序,升序
	int arr[10] = { 0,9,8,7,6,5,4,3,2,1 };
	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]);
	}
}
int main() {
	//test3();
	test4();
	return 0;
}

大家可以点开图片一点一点看,这个图片有点大。
在这里插入图片描述

细节:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.模拟qsort实现一个比较结构体的函数

先上代码再分析:

#include<stdio.h>
//使用自己写的bubble_sort排序结构体数组
struct Stu {
	char name[30];
	int age;
};
//先按照年龄排序
int cmp_stu_by_age(const void* elem1, const void* elem2) {
	return ((struct Stu*)elem1)->age - ((struct Stu*)elem2)->age;
}
//再按照名字排序
int cmp_stu_by_name(const void* elem1, const void* elem2) {
	return strcmp(((struct Stu*)elem1)->name, ((struct Stu*)elem2)->name);
}
void test4(void) {
		struct Stu s[3] = { {"zhangsan",20 },{"lisi",32},{"wangwu",55} };
		int sz = sizeof(s) / sizeof(s[0]);
		bubble_sort(s, sz, sizeof(s[0]), &cmp_stu_by_age);
		//bubble_sort(s, sz, sizeof(s[0]), &cmp_stu_by_name);
		int i = 0;
		for (i = 0; i < sz; i++) {
			printf("%s %d\n", s[i].name, s[i].age);
		}
}
int main() {
	//test3();
	test4();
	return 0;
}

比较结构体就和前面的qsort函数一样了,只不过换了个名字,因为要用到的都是地址。

3.模拟qsort实现一个比较浮点型的函数

比较浮点型的就需要考虑考虑了,那为了方便起见我们还是比较大小吧,如果相加减精度超过了范围那就不好办了,所以还是直接判断大小更加稳妥一点。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//使用自己写的bubble_sort排序浮点型数组
int cmp_float(const void* elem1, const void* elem2) {
	if (*(float*)elem1 > *(float*)elem2) {
		return 1;
	}
	else if (*(float*)elem1 < *(float*)elem2) {
		return -1;
	}
	else {
		return 0;
	}
}

void Swap(char* buf1, char* buf2, int width) {
	size_t i = 0;
	for (i = 0; i < width; i++) {
		int temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* elem1, const void* elem2)) {
	int flag = 0;
	size_t i = 0;
	for (i = 0; i < sz - 1; i++) {
		size_t 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);
				flag = 1;
			}
		}
		if (flag) {
			break;
		}
	}

}

void test5(void) {
	//对数组进行排序,升序
	float arr[] = { 0.1,0.3,0.2,0.4,0.5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), &cmp_float);

	//打印
	int i = 0;
	for (i = 0; i < sz; i++) {
		printf("%.1f ", arr[i]);
	}
}
int main() {
	//test3();
	//test4();
	test5();
	return 0;
}


总结

总的来讲,qsort函数是很有趣的,当我们一步步去探索,一步步去剖析,会发现很多有趣的库函数,这篇博客细致讲解了qsort函数的使用,先是很细致的讲解了qsort函数的介绍以及使用,再是以一个冒泡排序的思想模拟实现了一个比较不同类型的函数,可谓是干货满满!


客官,码字不易,来个三连支持一下吧!!

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

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

相关文章

试读:目标检测定义及技术详解

1.通用目标检测Generic Object Detection定义 目标检测旨在从图像、视频或者类似高维数据中定位大量预定义类别的物体实例&#xff0c;原始的图像、视频或类似数据经过数据预处理后&#xff0c;进入目标检测模型进行前向预测&#xff0c;最终得到数据中每个实例的位置以及该实…

Compose 动画入门 (一) : animateXxxAsState 实现放大/缩小/渐变等效果

1. 前言 动画是Android Compose中很重要的一块内容。利用Compose可以简洁地定义动画&#xff0c;我们可以轻松快速地通过动画让应用变得生动有趣。 本文会介绍如何定义一个最简单的Compose动画&#xff0c;从而实现Compose动画。 1.1 定义一个Box 首先&#xff0c;我们先定义…

关于volatile解决内存可见性问题(保证线程安全)

Volatile是和内存可见性问题是密切相关的。先看下面一段代码&#xff0c;执行结果是什么&#xff1f; class MyCount{public int flag 0; } public class ThreadDemo15 {public static void main(String[] args) {MyCount myCount new MyCount();Thread t1 new Thread(()-&…

B站涨粉十万+!B站up主如何吸引高质量粉丝?

如何在b站快速增长粉丝&#xff0c;b站如何快速涨粉丝&#xff0c;这是所有Up主都关心的问题&#xff0c;对于初来乍到B站的up主来说&#xff0c;发布的作品内容是极为重要的&#xff0c;B站最初的粉丝积累往往都是靠这些在B站生产的视频。作品是否精彩&#xff0c;能否吸引粉丝…

AutoCAD打开文件提示“无法识别的版本,不能读取”

今天遇到一个很奇怪的问题&#xff0c;在CAD中执行自己创建的命令并关闭文档之后&#xff0c;重新打开CAD提示“无法识别的版本&#xff0c;不能读取”错误对话框。 后来查询资料&#xff0c;发现这是CAD的一个老bug了。原因是该dwg文档所在的目录下某个文件的文件名以“无”开…

MATLAB-常微分方程求解

MATLAB中可以用来求解常微分方程(组)的函数有ode23、 ode23s、 ode23t、 ode23tb 、ode45、ode15s和odel13等&#xff0c;见下表。它们的具体调用方法类似&#xff0c;为了方便后面的描述&#xff0c;在后面的介绍中将使用solver统一代替它们。函数的具体调用方法如下。[T,Y] s…

简述 synchronized 和 ReentrantLock 之间的区别?

相同点 synchronized 和 ReentrantLock 都是 Java 中提供的可重入锁。 可重入锁&#xff1a;什么是 “可重入”&#xff0c;可重入就是说某个线程已经获得某个锁&#xff0c;可以再次获取锁而不会出现死锁。 不同点 用法不同&#xff1a;synchronized 可以用来修饰普通方法、静…

红中私教:使用wamp64配置靶场

应朋友请求&#xff0c;出一篇配服务器的教程 首先安装软件 https://cowtransfer.com/s/9db1b9ad2c1d44 点击链接查看 [ wampserver3.3.0_x64.exe ] &#xff0c;或访问奶牛快传 cowtransfer.com 输入传输口令 pgs341 查看&#xff1b; 接下来&#xff0c;使用HbuilderX打开我…

基于Paddle实现实例分割

百度的Paddle这几年发展十分迅速&#xff0c;而且文档十分齐全&#xff0c;涉及到机器视觉的多个应用领域&#xff0c;感觉还是非常牛的&#xff0c;各种backbone&#xff0c;损失函数、数据增强手段以及NMS等&#xff0c;整体感觉复现的很全面&#xff0c;值得推荐学习。 本…

搭建Redisson流程以及解读MutilLock源码解决分布式锁的主从一致性问题

搭建Redisson流程以及解读MutilLock源码解决分布式锁的主从一致性问题1、搭建3台独立主节点的redis服务2、创建java redisson客户端3、获取分布式锁4、分析获取锁源码getMultiLocktryLock(long waitTime, long leaseTime, TimeUnit unit)5、总结1、搭建3台独立主节点的redis服务…

Apache Shiro教程(3)

shiro自定义realms及加密md5salt教程 1、添加pom 文件 <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.9.1</version> </dependency>2、创建realms自定义文件 import o…

【树莓派/入门】使用MAX30102测量血氧浓度

说在前面 树莓派版本&#xff1a;4b血氧模块&#xff1a;MAX30102树莓派系统&#xff1a;Linux raspberrypi 5.15.76-v8 #1597 SMP aarch64 GNU/Linuxpython版本&#xff1a;3.9.2 模块详情 某宝上买的MAX30102模块&#xff0c;包含杜邦线 准备工作 开启树莓派的GPIO&#x…

java 高级面试题整理

SpringMVC的控制器是单例的吗? 第一次&#xff1a;类是多例&#xff0c;一个普通属性和一个静态属性 总结 尽量不要在controller里面去定义属性&#xff0c;如果在特殊情况需要定义属性的时候&#xff0c;那么就在类上面加上注解Scope("prototype")改为多例的模式…

English Learning - L1-9 时态(中) 2023.1.3 周二

这里写目录标题8 时态8.1 一般时态&#xff08;三&#xff09;一般将来时核心思维&#xff1a;预测&#xff0c;计划&#xff0c;意愿will 和 be going to 的区别将来时的其它表示方式进行时表将来be about to (5 分钟之内)8.2 进行时态核心思维&#xff1a;持续有限的进行&…

黑苹果解决5500xt等navi14显卡引导二阶段黑屏几秒的问题

首先说结论&#xff1a;在注入缓冲帧FB Name的前提下&#xff0c;往显卡注入CFG_LINK_FIXED_MAP参数&#xff0c;类型为Number&#xff0c;值为1。注意一定要注入FB Name&#xff0c;注入FB Name&#xff0c;注入FB Name的前提下&#xff01;&#xff01;我试过不注入FB Name直…

【安全硬件】Chap.5 如何检测芯片中硬件木马?硬件木马的类型有哪些?检测硬件木马的技术

【安全硬件】Chap.5 如何检测芯片中硬件木马&#xff1f;硬件木马的类型有哪些&#xff1f;检测硬件木马的技术前言1. 硬件木马的种类1.1 硬件木马1.2 硬件木马的区分1.1 物理特性类别硬件木马——Physical hardware trojans1.2 激活特性类别硬件木马——Activation1.3 动作特性…

Kafka快速入门

文章目录安装部署集群规划集群部署kafka群起脚本Kafka命令行操作主题命令行操作生产者命令行操消费者命令行操作安装部署 集群规划 集群部署 官方下载地址&#xff1a;http://kafka.apache.org/downloads.html上传安装包到02的/opt/software目录下 [atguiguhadoop02 softwar…

5.hadoop系列之HDFS NN和2NN工作机制

1.第一阶段&#xff1a;NameNode启动 1.第一次启动NameNode格式化后&#xff0c;创建Fsimage和Edits文件&#xff0c;如果不是第一次启动&#xff0c;直接加载Fsimage和Edits到内存 2.客户端对元数据进行增删改请求 3.NameNode记录操作日志&#xff0c;更新滚动日志 4.NameNod…

elasticsearch-head使用问题汇总

1、elasticsearch-head数据预览、基本查询、复合查询模块无法查询es文档记录&#xff08;1&#xff09;解决办法复制vendor.jsdocker cp elasticsearch-head1:/usr/src/app/_site/vendor.js vendor.js修改vendor.js第6886行&#xff0c;将“contentType: "application/x-w…

高性能分布式缓存Redis-第三篇章

高性能分布式缓存Redis-第三篇章一、分布式锁1.1、高并发下单超卖问题1.2、何为分布式锁1.3、分布式锁特点1.4、基于Redis实现分布式锁1.4.1、实现思路&#xff1a;1.4.2、实现代码版本1.4.3、错误解锁问题解决1.4.4、锁续期/锁续命1.4.5、锁的可重入/阻塞锁&#xff08;rediss…