从排序算法的艺术看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,一经查实,立即删除!

相关文章

基于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…

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

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

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;海外媒体宣发套餐成为了重要的推广方式之一。本文将为…

【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;那么我们拿到一份代码后如何从头开始学习一个新项目呢。…

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

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

MySQL | 库的操作 | 表的操作

目录 1. 库的操作 1.1. 创建数据库 1.2. 字符集和校验规则 1.2.1. 查看系统默认字符集以及校验规则 1.2.2. 查看数据库支持的字符集 1.2.3. 查看数据库支持的字符校验规则 2. 操作数据库 2.1. 查看数据库 2.2. 显示创建语句 2.3. 修改数据库 2.4. 数据库的删除 2.4.…

维基百科推广秘诀13个方法助你成为行业领导者-华媒舍

维基百科&#xff08;Wikipedia&#xff09;作为全球最大、最权威的在线百科全书&#xff0c;拥有海量的知识内容&#xff0c;被广大用户广泛使用。对于任何一个领域的从业者来说&#xff0c;建立自己的维基百科页面&#xff0c;无疑是提升行业影响力的重要手段。本文将向您介绍…

Linux学习(4)——使用编辑器

1.gedit编辑器 简单易懂&#xff0c;依赖图形界面。可以使用ctrlc ctrlv等快捷键&#xff0c;ctrls进行保存&#xff0c;与windows系统中相类似。 2.vi/vim编辑器 vi/vim可以直接通过控制台的终端完成文本的编辑&#xff0c;不依赖图形界面&#xff0c;使用范围更广。它的编辑…

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

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

【Cute】MMA抽象代码理解 c2d9bff3d88846eb8c523fb722166bc9

【Cute】MMA抽象代码理解 导读&#xff1a; cute 之 Layoutcute Layout 的代数和几何解释cute 之 Tensorcute 之 MMA抽象cute 之 简单GEMM实现 阅读本文前建议先读上面reed大神的数篇文章&#xff0c;文本逻辑主要是针对具体的代码&#xff0c;记录一下自己学习过程中的理解…

Atlas200板卡部署车道线

本博客包含推理的准备和部署代码&#xff0c;一步步实现部署。 这个运行时生成的一个batch的数据&#xff0c;NCHW,就是输入的N&#xff0c;单图片推理就是1&#xff0c;把里面的数量改成1&#xff0c;但是你可以多生成一些bin图片放到校准文件夹中&#xff0c;更改输出文件名…

“城市绿肺诊断:集成GIS、RS、VORS模型、CCDM模型、geodetecto、GWR模型技术深入解析生态系统与城镇化协调发展“

基于GIS、RS、VORS模型、CCDM模型、geodetecto、GWR模型集成的生态系统健康的耦合协调分析 城市群是一国经济发展水平的象征&#xff0c;也是一国经济发展到一定阶段的标志&#xff0c;我国城市群建设体量不断增加&#xff0c;将成为全球经济的核心&#xff0c;中国城市群的建…

MyFileServer

靶场下载地址 https://download.vulnhub.com/myfileserver/My_file_server_1.ova 信息收集 # nmap -sn 192.168.56.0/24 -oN live.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-24 22:07 CST Nmap scan report for 192.168.56.2 (192.168.56.2) Host is up (0.…

HarmonyOS NEXT应用开发—状态栏显隐变化

介绍 本示例介绍使用Scroll组件的滚动事件 onScroll 实现状态栏显隐变化。该场景多用于各种软件的首页、我的等页面中。 效果预览图 使用说明 加载完成后显示状态栏显隐变化页面&#xff0c;上下拖动屏幕&#xff0c;顶端状态栏出现显隐变化。 实现思路 在置顶位置使用sta…

文件夹秒变应用程序?别慌,数据恢复有妙招!

在日常使用电脑的过程中&#xff0c;我们有时会遇到一个令人头疼的问题&#xff1a;原本好好的文件夹突然变成了应用程序的图标&#xff0c;点击也无法正常打开。这种“文件夹变应用程序”的现象不仅让人感到困惑&#xff0c;还可能导致重要文件的丢失或损坏。那么&#xff0c;…