从排序算法的艺术看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;elsereturn -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 > str2str1 = "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;
}

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

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

相关文章

【LGR-179-Div.2】复旦勰码 3 月月赛 II ZHYOI Round 4(A~B)

T431492 农场 有n个农场&#xff0c;告诉你农场的对角顶点坐标&#xff0c;农场都是矩阵。 要找出一个最大的矩阵&#xff08;平行于x和y轴&#xff09;把所有农场都圈起来。 那就保证最下面的点和最上面的点&#xff0c;最左边的点&#xff0c;最右边的点都能被圈到。 用l…

基于matlab使用 fmincon 函数来进行有约束条件的最小化问题求解

一、一般步骤 生成带有噪声的正态分布数据&#xff1b;定义拟合模型。 model (params, x) normpdf(x, params(1), params(2)); 初始参数猜测 initial_guess [mu, sigma]; 设置约束条件 lb [0, 0]; % 参数的最小值 ub [10, 10]; % 参数的最大值 定义优化问题 opts …

deepseek-coder模型量化

1 简介 DeepSeek-Coder在多种编程语言和各种基准测试中取得了开源代码模型中最先进的性能。 为尝试在开发板进行部署&#xff0c;首先利用llama.cpp对其进行量化。 2 llama.cpp安装 git clone之后进入文件夹make即可&#xff0c;再将依赖补全pip install -r requirements.tx…

IOS面试题object-c 131-135

131. 简述category如何被加载的?两个 category 的load方法的加载顺序?两个 category 的同名方法的加载顺序? ?category的加载是在运行时发生的,加载过程是:把category的实例方法、属性、协议添加到类对象上,把category的类方法、属性、协议添加到metaclass上。 category…

【吊打面试官系列】Java虚拟机JVM篇 - 关于双亲委派模型

大家好&#xff0c;我是锋哥。今天分享关于JVM双亲委派模型的JVM面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是双亲委派模型&#xff1f; 双亲委派模型针对的是 Java 虚拟机中三个类加载器的&#xff0c;这三个类加载器分别是&#xff1a; 启动类加载器&#xff08;B…

基于深度学习的车辆检测技术

基于深度学习的车辆检测技术是现代智能交通系统的重要组成部分&#xff0c;它利用计算机视觉和机器学习算法&#xff0c;特别是深度学习模型&#xff0c;来识别和定位图像或视频中的车辆。这项技术广泛应用于自动驾驶、交通监控、违章抓拍等多个领域。 深度学习车辆检测技术的…

Nacos 如何实现配置文件动态更新的

Nacos采用的是一个长轮询的方式&#xff0c;向Nacos Server 端去发起配置更新查询的这样一个功能。长轮询&#xff1a;客户端发起一次轮询请求到服务端&#xff0c;当服务器端配置没有任何变更的时候&#xff0c;这个连接会一直打开&#xff0c;直到服务端有配置变更或者连接超…

node.js快速入门-day03

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;给自己一个梦想&#xff0c;给世界一个惊喜。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章目录 web服务器创建…

力扣hot100:34. 在排序数组中查找元素的第一个和最后一个位置(二分查找的理解)

我们知道使用二分查找能找到值所在的位置。假如我们在找到值后仍然不断的更新指针会发生什么&#xff1f;我们可以利用这一点来找到最左边的以及最右边的值。 如果当nums[mid]target时&#xff0c;使得 rightmid-1&#xff0c;那么最终会使得target在right的右边。 如果当nums[…

海外媒体宣发套餐推广攻略实现品牌全球化-华媒舍

如今&#xff0c;在全球经济一体化的浪潮下&#xff0c;品牌全球化已成为企业成功的重要因素之一。海外市场作为一个巨大而具有潜力的机会&#xff0c;吸引着越来越多的企业前往探索。而在海外市场的推广过程中&#xff0c;海外媒体宣发套餐成为了重要的推广方式之一。本文将为…

Linux系统优化及性能调优

目录 一、基本优化 1. SELinux和防火墙优化 1.1 selinux概述 1.2 selinux三种工作模式 1.3 切换selinux模式 1.4 防火墙概述 1.5 firewalld管理工具 2. 自启动服务优化 2.1 Systemd 2.2 SysVinit 3. 禁用超级管理员 4. 普通用户提权 5. 使用国内yum源 5.1 配置阿…

量化交易入门(一)学习量化交易需要掌握哪些知识

学习量化&#xff08;Quantitative Analysis&#xff09;&#xff0c;特别是在金融领域的量化分析或量化交易&#xff0c;需要掌握以下几个方面的知识&#xff1a; 数学和统计学&#xff1a;高等数学&#xff08;微积分、线性代数等&#xff09;、概率论与数理统计是量化分析的…

【S5PV210_视频编解码项目】裸机开发:实现按键的外部中断处理

加粗样式本文所作内容&#xff1a; 基于S5PV210芯片实现按键的外部中断处理程序&#xff0c;搭建中断处理流程框架 S5PV210对于中断处理的操作流程 1 外部中断得到触发&#xff1a; 1&#xff09;外部中断在初始化阶段得到使能 2&#xff09;外界达到了外部中断的触发条件 …

24考研数学最大教训❗️660/880过时了?

我没看错吧&#xff0c;说660题和880题过时了&#xff1f; 660题和880题好好用&#xff0c;这俩很经典不会过时。 660题是客观题训练必刷的一本题集&#xff0c;而880是强化阶段非常好的一本综合性题集。我本身在考研的时候使用的也是这两本题集&#xff0c;所以对这两本题集…

如何学习一个大型分布式Java项目

前言 很多同学在没有实习经验的时候看到一个多模块分布式项目总是有一种老虎吃天的无力感&#xff0c;就像我刚毕业去到公司接触项目的时候一样&#xff0c;模块多的夸张&#xff0c;想学都不知道从哪开始学&#xff0c;那么我们拿到一份代码后如何从头开始学习一个新项目呢。…

pyinstaller使用笔记

1. 简介 pyinstaller是一个第三方库&#xff0c;它能够在Windows、Linux、 Mac OS X 等操作系统下将 Python 源文件打包&#xff0c;通过对源文件打包&#xff0c; Python 程序可以在没有安装 Python 的环境中运行&#xff0c;也可以作为一个 独立文件方便传递和管理。 PyIns…

Oracle Primavera Analytics 是什么,与P6的关系?

前言 Oracle Primavera P6 Analytics 是与P6有关的一个相对较新的模块&#xff0c;Primavera 用户社区在很大程度上尚未对其进行探索。 那么它到底有什么作用呢&#xff1f; 通过了解得知它旨在通过深入了解组织的项目组合绩效&#xff0c;帮助高级管理层对其项目组合做出更好…

「Linux系列」Linux 系统目录结构/忘记密码解决方法

文章目录 一、Linux 系统目录结构Linux系统目录结构的概念案例 二、Linux 忘记密码解决方法方法一&#xff1a;使用root用户重置密码方法二&#xff1a;使用单用户模式重置密码方法三&#xff1a;使用恢复模式或救援模式案例&#xff1a;使用CentOS的救援模式重置密码 三、相关…

PE文件格式知识点汇总

简单概述&#xff1a;可执行文件之所以被操作系统加载且运行&#xff0c;是因为它们遵循相同的规范&#xff0c;而这个规范正是PE文件格式所确定。 1、PE文件格式的定义 PE文件格式&#xff1a;PE&#xff08;Portable Executable&#xff09;是win32平台下可执行文件遵守的数…

Python进阶学习(6)异常

文章目录 异常1.异常的定义2.异常错误3.异常处理&#xff08;捕捉异常&#xff09;4.assert断言5. 定义异常 异常 1.异常的定义 什么是异常&#xff1a;报错 也可以去链接资源访问某个网站时&#xff0c;中断程序&#xff0c;也叫异常 捕捉异常&#xff1a;因为我们在程序编…