从排序算法的艺术看C语言qsort函数的魅力:一场数据的时空穿越

欢迎来到白刘的领域   Miracle_86.-CSDN博客

系列专栏  C语言知识

先赞后看,已成习惯

   创作不易,多多支持!

目录

一 、回调函数

二、qsort函数

1.qsort函数排序整型数据

2.qsort函数排序结构数据


一 、回调函数

何为回调函数?听起来很装逼的样子,实际上它是一个很简单的概念:回调函数就是一个通过函数指针调用的函数。

就是说,你把一个函数的地址,作为参数传给另一个函数,当这个指针被调用时,被调用的函数就叫回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的条件发生时才被另一方调用,用于对该事件进行响应。

灵魂指针,教给(三)-CSDN博客

在上一篇博客中,我们实现了计算器。从一段冗余的代码,我们使用新学的转移表进行了优化。我们知道其本质是函数指针,那我们现在学习了回调函数,是不是也可以对其优化呢?

冗余代码:

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

我们会发现,每次都有这么一个小片段,很相似:

这段代码其实只有调用函数的逻辑是有差异的,我们可以把调用函数的地址以参数的形式传过去,用函数指针接收,函数指针指向什么就调用什么函数,也就实现了回调函数的功能。

代码如下:

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
void calc(int(*pf)(int, int))
{
	int ret = 0;
	int x, y;
	printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 1;
	do {
		printf("******************\n");
		printf("* 1:add    2:sub *\n");
		printf("* 3:mul    4:div *\n");
		printf("*     0:exit     *\n");
		printf("******************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(add);
			break;
		case 2:
			calc(sub);
			break;
		case 3:
			calc(mul);
			break;
		case 4:
			calc(div);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

二、qsort函数

什么是qsort函数呢?它其实是Quicksort的缩写,也就是快速排序,简称快排。它可以对一个数组进行快速排序,支持自定义比较函数。我们之前学过冒泡排序:

灵魂指针,教给(二)-CSDN博客

但是冒泡排序,只能实现对整型数组的排序,而这个快速排序就比较牛逼了,它可以对任何类型的数据进行排序。

qsort函数的头文件是<stdlib.h>。

下面来看看qsort的原型:

void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *));

 很长,我们一点一点来剖析。

它接收四个参数:

1.base:很好理解,中文是基础的意思,这里是要排序的数组的首元素地址。为什么是void*类型的呢?为了实现对任何类型的数据进行排序。

2.num:也很好理解,意思是数组的元素个数。类型是size_t也很好理解,因为数组元素个数不可能为负数。

3.size:这个是数组的元素的大小(单位字节)。

4.compar:它是一个函数指针,这个需要我们自己传进去,因为在设计这个函数的时候,它不会预料到我们今天要比较的是整型还是字符型或者是结构体,所以也就是我们自己来决定,在外部写一个比较函数,然后传进去。

前三个参数我们都很容易传参,这里着重说一下第四个参数:

我们需要自己写一个比较函数,来决定类型。比较函数的原型如下:

int compar(const void *p1, const void *p2);

比较函数 接受两个参数,返回值为整型。如果返回值小于0,则认为p1小于p2;如果返回值等于0,则认为p1等于p2;如果返回值大于0,则认为p1大于p2。

我们来练习一下:

1.qsort函数排序整型数据

其实这个函数很简单,我们只需要会写它的比较函数即可。

正常来讲,对于一个整型,我们习惯用>、<以及=来判断两个数据的大小。

正常我们可能写比较函数会这么写:

int int_cmp(const void* p1, const void* p2)
{
	if (*((int*)p1) > *((int*)p2))
		return 1;
	else if (*((int*)p1) == *((int*)p2))
		return 0;
	else
		return -1;
}

注意,我们p1和p2是void*类型的,不可以进行指针运算,所以我们要先对其进行强转。这里将(int*)p1也加上括号是因为强转是短暂的,所以加上比较保险,增强了代码可读性。

但是这么写还是不够好,我们可以这么写:

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

这样也可以实现功能,如果p1>p2,那返回大于0的数,其它同理。

这个是比较函数,我们来看总体代码:

#include <stdio.h>
//qsort函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int i = 0;

	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

 来看运行结果:

2.qsort函数排序结构数据

首先我们定义一个结构体:

struct Stu //学⽣
{
	char name[20];//名字
	int age;//年龄
};

还是先来分析比较函数:

整型我们可以用>、<,那字符型,我们可以使用一个函数,叫strcmp(以后我们会详细介绍,继续挖坑),今天就简单介绍一下怎么用的。

它的原型是:

int strcmp(const char *str1, const char *str2);

该函数会比较字符串str1和str2,并根据比较结果返回一个整数值。

如果str1和str2相等,则返回0;如果str1小于str2,则返回一个负数;如果str1大于str2,则返回一个正数。

比较的规则是按照字符的ASCII码值依次进行比较,从字符串的第一个字符开始比较,直到遇到不相等的字符或者字符串的结束标志'\0'。不会考虑字符串的长度。

str1 = "abcd";
str2 = "abc";
//str1 > str2

str1 = "abz";
str2 = "abccccc";
//str1 > str2

回归正题,我们怎么比较结构体呢?我们可以看到,结构体里有两个成员变量,一个是字符串-姓名,一个是整型-年龄。所以我们可以有两种方法,一种是按姓名首字母来排序,一种是按年龄大小。也就是一种是用strcmp,一种是用>、<。

方法了解了,我们还要来复习一下如何访问结构体成员。

武器大师——操作符详解(下)-CSDN博客

我们之前讲过,如果传地址的话,使用->来访问,

所以先来看按姓名来排序:

int cmp_stu_by_name(const void* e1, const void* e2)
{
 return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

同样,对于void*类型的p1、p2,我们还是要强制类型转换,之后访问的结果传到strcmp函数中,比较出的结果再return。

整型就更简单了:

int cmp_stu_by_age(const void* e1, const void* e2)
{
 return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

来看总代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu //学⽣
{
	char name[20];//名字
	int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
	test2();
	test3();
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STL容器之list类

文章目录 STL容器之list类1、list的介绍2、list的使用2.1、list的常见构造2.2、list的iterator的使用2.3、list空间增长问题2.4、list的增删查改2.5、list迭代器失效问题 3、list的模拟实现&#xff08;含反向迭代器&#xff09; STL容器之list类 1、list的介绍 list是序列容器…

五、保持长期高效的七个法则(二)Rules for Staying Productive Long-Term(1)

For instance - lets say youre a writer.You have a bunch of tasks on your plate for the day, but all of a sudden you get a really good idea for an essay. You should probably start writing now or youll lose your train of thought.What should you do? 举例来说…

rust引用本地crate

我们可以动态引用crate&#xff0c;build时从crate.io下载&#xff0c;但可能因无法下载导致build失败。首次正常引用三方crate&#xff0c;build时自动下载的crate源码&#xff0c;我们将其拷贝到固定目录中&#xff1b; build后可在RustRover中按住Ctrl键&#xff0c;在crat…

【Linux】环境基础开发工具使用

目录 Linux软件管理器 yum 1.什么是软件包 2.查看软件包 3安装与卸载 vim-Linux编辑器 1.vim基础概念 2.vim的基础操作 命令模式基本操作 底层模式基本操作 3、其它模式 Linux编译器 gcc/g 1.如何进行编译 2.编译的四个过程 预处理(-E) 编译(-S) 汇编(-c) 链接…

代码算法训练营day10 | 232.用栈实现队列、225. 用队列实现栈

day10: 232.用栈实现队列225. 用队列实现栈 232.用栈实现队列 题目链接 状态&#xff1a; 文档&#xff1a;programmercarl.com 思路&#xff1a; 用栈实现队列。要先明白两者的区别。 栈&#xff1a;单开门&#xff0c;先进后出&#xff0c;只有一端能进出。 队列&#xff1a;…

【论文笔记合集】LSTNet之循环跳跃连接

本文作者&#xff1a; slience_me LSTNet 循环跳跃连接 文章仅作为个人笔记 论文链接 文章原文 LSTNet [25] introduces convolutional neural networks (CNNs) with recurrent-skip connections to capture the short-term and long-term temporal patterns. LSTNet [25]引入…

1、鸿蒙学习-为应用/服务进行签名

针对应用/服务的签名&#xff0c;DevEco Studio为开发者提供了自动签名方案&#xff0c;帮助开发者高效进行调试。也可选择手动方式对应用/服务进行签名&#xff0c;如果使用了需要ACL的权限&#xff0c;需采用手动方式进行签名。 自动签名 说明 使用自动签名前&#xff0c;请…

简单!实用!易懂!:Java如何批量导出微信收藏夹链接-->转换成Markdown/txt

文章目录 前言参考方案方案1&#xff1a;Python方案2&#xff1a;Python 我的方案手动前置操作代码处理 前言 不知道是否有很多小伙伴跟我一样&#xff0c;有个问题非常愁&#xff0c;对于收藏党来说&#xff0c;收藏了学会了&#xff01;然后导致微信收藏夹的东西越来越多了&…

2024年5家香港服务器推荐,性价比top5

​​香港服务器是中小企业建站、外贸建站、个人博客建站等领域非常受欢迎的服务器&#xff0c;2024年有哪些云厂商的香港服务器是比较有性价比的&#xff1f;这里根据小编在IT领域多年服务器使用经验&#xff0c;给大家罗列5家心目中最具性价比的香港服务器厂商。 这五家香港服…

Wireshark抓包工具的使用

提示&#xff1a;本文为学习记录&#xff0c;若有错误&#xff0c;请联系作者&#xff0c;谦虚受教 文章目录 前言一、下载二、首页三、使用1.读入数据2.分析数据3.筛选IP4.保存数据 四、过滤器表达式五、TCP总结 前言 低头做事&#xff0c;抬头看路。 一、下载 下载路径wire…

Java面试题总结200道(三)

51、什么是 Spring IOC 容器 Spring 框架的核心是 Spring 容器。容器创建对象&#xff0c;将它们装配在一起&#xff0c;配置它 们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的 组件。容器通过读取提供的配置元数据来接收对象进行实例化&#xff0…

【vue video.js】The element or ID supplied is not valid. (videojs) element Ui

问题&#xff1a;使用video.js做了一个弹窗显示视频&#xff0c;效果如下 但是发现弹窗再次打开&#xff0c;视频播放失败&#xff0c;报错The element or ID supplied is not valid 原因是videojs找不到需要初始化的视频id&#xff0c;在关闭弹窗的时候需要重置video.js&…

小迪安全42WEB攻防-通用漏洞文件包含LFIRFI伪协议

#知识点: 1、解释什么是文件包含 2、分类-本地LFI&远程RFI 3、利用-配合上传&日志&会话 4、利用-伪协议&编码&算法等 #核心知识: 1、本地包含LFI&远程包含RF1-区别 一个只能包含本地&#xff0c;一个可以远程加载 具体形成原因由代码和环境配置文件决定…

便利店小程序有哪些功能

​便利店小程序为附近的住户提供小程序在线购物的服务。用户只需要打开小程序&#xff0c;就可以购买需要的商品&#xff0c;可以选择自取或者配送。整个过程非常简单快速。下面具体介绍便利店小程序的功能。 1. **商品展示**&#xff1a;展示便利店的商品信息&#xff0c;包括…

芯片顶级盛会HotChip历年-未来芯片论坛及资料全集下载

提示&#xff1a;下载链接在文章最后。 Hotchips是全球芯片行业影响力最大的会议。 Hot Chips是一个技术会议&#xff0c;聚焦于半导体和微处理器的设计与创新。以下是一些关于Hot Chips的信息和相关链接&#xff1a; Hot Chips会议官方网站&#xff1a;https://www.hotchips…

Prometheus 基于 Consul 实现服务自动发现注册

文章目录 一、概述二、docker-compose 部署 Prometheus1&#xff09;部署 docker2&#xff09;部署 docker-compose3&#xff09;配置 prometheus.yml4&#xff09;配置 rules.yml5&#xff09;配置 alertmanager.yml6&#xff09;编排 docker-compose yaml 文件7&#xff09;开…

【Windows Defender 排除指定 文件夹、文件夹以提升性能】

使用webStorm时候提醒排出程序和目录提升性能, 于是我就把我的代码目录和常用程序全部排出, 不过不知道能不能提升多少性能, 先加上再说 一.使用UI配置排出项 隐私与安全性安全中心 病毒与威胁防护 添加或删除排出项 配置 二.使用命令配置 使用 PowerShell开启自动排除列表…

Redis数据结构对象之字符串对象

字符串对象 字符串对象的编码可以是int、raw或者embstr 如果一个字符串对象保存的是整数值&#xff0c;并且这个整数值可以用long类型来表示&#xff0c;那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void *转换成long)&#xff0c;并且将字符串对象的编码设…

面试算法-39-删除链表的倒数第 N 个结点

题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 解 class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {L…

在idea中配置tomcat服务器,然后部署一个项日

1.下载tomcat Tomcat下载 点击右边的tomcat8 找到zip点击下载 下载完&#xff0c;解压到你想放置的路径下 2.配置环境变量 打开设置找到高级系统设置点击环境变量 点击新建&#xff0c;变量名输入&#xff1a;CATALINA_HOME&#xff0c;变量值就是Tomcat的安装路径&#x…