qsort函数使用方法总结

目录

一、qsort函数原型

二、compar参数

三、各种类型的qsort排序

1. int 数组排序 

2. 结构体排序 

3. 字符串指针数组排序 

4. 字符串二维数组排序

四、回调函数

1. 什么是回调函数

2. 为什么要用回调函数?

3. 怎么使用回调函数?

4.下面是一个四则运算的简单回调函数例子:

五、qsort函数的模拟实现

一、qsort函数原型

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

头文件:<stdlib.h> 函数功能:qsort()函数的功能是对数组进行排序,数组有nmemb个元素,每个元素大小为size。 

参数base - base指向数组的起始地址,通常该位置传入的是一个数组名 

参数nmemb - nmemb表示该数组的元素个数 

参数size - size表示该数组中每个元素的大小(字节数) 

参数(*compar)(const void *, const void *) - 此为指向比较函数的函数指针,决定了排序的顺序。 

函数返回值:无 

注意:如果两个元素的值是相同的,那么它们的前后顺序是不确定的。也就是说qsort()是一个不稳定的排序算法。

二、compar参数

compar参数是qsort函数排序的核心内容,它指向一个比较两个元素的函数,注意两个形参必须是const void *型,同时在调用compar 函数(compar实质为函数指针,这里称它所指向的函数也为compar)时,传入的实参也必须转换成const void *型。在compar函数内部会将const void *型转换成实际类型,见下文。

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

如果compar返回值小于0(< 0),那么p1所指向元素会被排在p2所指向元素的前面,如果compar返回值等于0(= 0),那么p1所指向元素与p2所指向元素的顺序不确定,如果compar返回值大于0(> 0),那么p1所指向元素会被排在p2所指向元素的后面。因此,如果想让qsort()进行从小到大(升序)排序,那么一个通用的compar函数可以写成这样: 

int compare (const void * a, const void * b){if ( *(MyType*)a <  *(MyType*)b ) return -1;if ( *(MyType*)a == *(MyType*)b ) return 0;if ( *(MyType*)a >  *(MyType*)b ) return 1;}

注意:你要将MyType换成实际数组元素的类型。 或者

//升序排序
int compare(const void* a, const void* b)
{return (*(int*)a - *(int*)b);
}
//降序排列
int compare(const void* a, const void* b)
{return (*(int*)b - *(int*)a);
}

三、各种类型的qsort排序

1. int 数组排序 

#include <stdio.h>     
#include <stdlib.h>     int values[] = { 40, 10, 100, 90, 20, 25 };int compare(const void* a, const void* b)
{return (*(int*)a - *(int*)b);
}int main()
{int n;qsort(values, sizeof(values) / sizeof(values[0]), sizeof(int), compare);for (n = 0; n < sizeof(values) / sizeof(values[0]); n++)printf("%d ", values[n]);return 0;
}

2. 结构体排序 

#include <stdio.h>
#include<stdlib.h>
// void qsort(void* base, size_t num, size_t size, int(*compare)(const void*, const void*))typedef struct
{char name[30];   // 学生姓名int Chinese;    // 语文成绩int Math;        // 数学成绩  int English;     // 英语成绩
}st;
int cmp(const void* a, const void* b)
{st* pa = (st*)a;st* pb = (st*)b;int num1 = pa->Chinese + pa->English + pa->Math;int num2 = pb->Chinese + pb->English + pb->Math;//return (int)num1 - num2;   // 从小到大,return (int)num2 - num1;   //  从大到小
}
int main(void)
{st students[7] = {{"周",97,68,45},{"吴",100,32,88},{"郑",78,88,78},{"王",87,90,89},{"赵",87,77,66},{"钱",59,68,98},{"孙",62,73,89}};qsort(students, 7, sizeof(st), cmp);   // 注意区别 students 与 stfor (int i = 0; i < 7; i++){printf("%s%4d%4d%4d\t", students[i].name, students[i].Chinese, students[i].Math, students[i].English);printf("总分:%d\n", students[i].Chinese + students[i].English + students[i].Math);}system("pause");return 0;
}

3. 字符串指针数组排序 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int compare(const void* arg1, const void* arg2);int main(int argc, char** argv)
{int i;char* arr[5] = { "i", "love", "c", "programming", "language" };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(char*), compare);for (i = 0; i < 5; i++) {printf("%s ", arr[i]);}printf("\n");}int compare(const void* arg1, const void* arg2) {char* a = *(char**)arg1;char* b = *(char**)arg2;int result = strcmp(a, b);if (result > 0) {return 1;}else if (result < 0) {return -1;}else {return 0;}
}

那么我们向qsort传入arr之后,qsort将arr理解为指向数组中第一个元素的指针,所以形参表中,arg1和arg2其实是指向“指向常量字符串的指针”的指针,是char**。而我们需要传给strcmp这个字符串比较函数的,是“指向字符串的指针”,是char*,所以我们将void*转换为char**,然后解引用,得到char*,赋予a和b。接下来使用strcmp对a和b进行比较。(数组名本身算一层指针,而里面的内容又是一层指针,数组存放的是指向字符串的地址) 

4. 字符串二维数组排序

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int compare(const void* arg1, const void* arg2);int main(int argc, char** argv)
{int i;char arr[5][16] = { "i", "love", "c", "programming", "language" };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare);printf("%s\n", arr[0]);for (i = 0; i < 5; i++) {printf("%s ", arr[i]);}printf("\n");
}int compare(const void* arg1, const void* arg2) 
{char* a = (char*)arg1;char* b = (char*)arg2;int result = strcmp(a, b);if (result > 0) {return 1;}else if (result < 0) {return -1;}else {return 0;}
}

这里对二维数组进行排序,其实是对二维数组的第二维中存放的字符串进行排序。所以qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr[0]), compare);对qsort函数的调用中,第二个参数是待排元素的个数(5个),第三个参数是待排元素的大小(16)。

我们将arr传入qsort函数,qsort函数将arr理解为指向数组第一个元素的指针,arr的第一个元素是arr[0][0],所以参数arg1和arg2指的是指向"a[i][0]"的指针,我们知道,a[i][0]是字符,就是char,所以arg1和arg2指的是char *。我们将void*转换为char*,赋予a和b,调用strcmp函数对a和b进行比较。

四、回调函数

1. 什么是回调函数

我们先来看看百度百科是如何定义回调函数的:

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

这段话比较长,也比较绕口。下面我通过一幅图来说明什么是回调:

假设我们要使用一个排序函数来对数组进行排序,那么在主程序(Main program)中,我们先通过库,选择一个库排序函数(Library function)。但排序算法有很多,有冒泡排序,选择排序,快速排序,归并排序。同时,我们也可能需要对特殊的对象进行排序,比如特定的结构体等。库函数会根据我们的需要选择一种排序算法,然后调用实现该算法的函数来完成排序工作。这个被调用的排序函数就是回调函数(Callback function)。

结合这幅图和上面对回调函数的解释,我们可以发现,要实现回调函数,最关键的一点就是要将函数的指针传递给一个函数(上图中是库函数),然后这个函数就可以通过这个指针来调用回调函数了。注意,回调函数并不是C语言特有的,几乎任何语言都有回调函数。在C语言中,我们通过使用函数指针来实现回调函数。

我的理解是:把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调

如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

2. 为什么要用回调函数?

因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。

简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

int Callback()    ///< 回调函数
{// TODOreturn 0;
}
int main()     ///<  主函数
{// TODOLibrary(Callback);  ///< 库函数通过函数指针进行回调// TODOreturn 0;
}

回调似乎只是函数间的调用,和普通函数调用没啥区别。

但仔细看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。

这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样有没有觉得很灵活?并且当库函数很复杂或者不可见的时候利用回调函数就显得十分优秀。

3. 怎么使用回调函数?

#include <stdio.h>
int Callback_1(int a)   ///< 回调函数1
{printf("Hello, this is Callback_1: a = %d \n", a);return 0;
}int Callback_2(int b)  ///< 回调函数2
{printf("Hello, this is Callback_2: b = %d \n", b);return 0;
}int Callback_3(int c)   ///< 回调函数3
{printf("Hello, this is Callback_3: c = %d \n", c);return 0;
}int Handle(int x, int (*Callback)(int)) ///< 注意这里用到的函数指针定义
{Callback(x);
}int main()
{Handle(4, Callback_1);Handle(5, Callback_2);Handle(6, Callback_3);return 0;
}

看看结果:

 

如上述代码:可以看到,Handle()函数里面的参数是一个指针,在main()函数里调用Handle()函数的时候,给它传入了函数Callback_1()  /  Callback_2()  /  Callback_3()的函数名,这时候的函数名就是对应函数的指针,也就是说,回调函数其实就是函数指针的一种用法。

4.下面是一个四则运算的简单回调函数例子:

#include <stdio.h>
#include <stdlib.h>/***************************************** 函数指针结构体***************************************/
typedef struct _OP {float (*p_add)(float, float);float (*p_sub)(float, float);float (*p_mul)(float, float);float (*p_div)(float, float);
} OP;/***************************************** 加减乘除函数***************************************/
float ADD(float a, float b)
{return a + b;
}float SUB(float a, float b)
{return a - b;
}float MUL(float a, float b)
{return a * b;
}float DIV(float a, float b)
{return a / b;
}/***************************************** 初始化函数指针***************************************/
void init_op(OP* op)
{op->p_add = ADD;op->p_sub = SUB;op->p_mul = &MUL;op->p_div = &DIV;
}/***************************************** 库函数***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{return (*op_func)(a, b);
}int main(int argc, char* argv[])
{OP* op = (OP*)malloc(sizeof(OP));init_op(op);/* 直接使用函数指针调用函数 */printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", (op->p_add)(1.3, 2.2), (*op->p_sub)(1.3, 2.2),(op->p_mul)(1.3, 2.2), (*op->p_div)(1.3, 2.2));/* 调用回调函数 */printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n",add_sub_mul_div(1.3, 2.2, ADD),add_sub_mul_div(1.3, 2.2, SUB),add_sub_mul_div(1.3, 2.2, MUL),add_sub_mul_div(1.3, 2.2, DIV));return 0;
}

五、qsort函数的模拟实现

使⽤回调函数,模拟实现qsort(采⽤冒泡的⽅式)。

注意:这里第⼀次使用 void* 的指针,讲解 void* 的作⽤。

#include <stdio.h>int cmp_int(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}void Swap(char* buf1, char* buf2, size_t width)
{int i = 0;for (i = 0; i < width; i++) {char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{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])if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){/*int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;*///交换Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}void print_arr(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}void test()
{//设计和实现bubble_sort2(),这个函数能够排序任意类型的数据int arr[] = { 3,1,5,7,9,2,4,0,8,6 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print_arr(arr, sz);
}int main()
{test();return 0;
}

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

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

相关文章

分支限界法(1)--旅行商问题

一、概述 有n个城市&#xff0c;旅行者要访问所有n个城市&#xff0c;最终回到起始点&#xff0c;假设起始点给定为1&#xff0c;城市间距离已知&#xff0c;求能够完成旅行的最短距离。题干如下图。 算法&#xff1a;分支限界法&#xff0c;使用队列进行bfs搜索。 二、代码 i…

低代码在ERP中的理解与应用:提升开发效率与业务灵活性

企业资源规划&#xff08;ERP&#xff09;指通过融合不同部门的信息和流程&#xff0c;提升企业效率、融洽运营的管理体系。ERP系统通过提供一套集成化应用程序&#xff0c;助力企业管理工作流程&#xff0c;包含选购、库存、销售、生产规划等。 低代码&#xff08;Low-Code&a…

在listener.ora配置文件中配置listener 1527的监听并且使用tnsnames连接测试

文章目录 前言&#xff1a;一、命令语句实现1、监听介绍2、编辑 listener.ora 文件&#xff1a;寻找配置文件对配置文件进行配置 3、重启监听4、配置TNS 二、图形化界面实现1、listener.ora文件配置2、tnsnames.ora文件配置 三、测试连接 前言&#xff1a; 命令实现和图形化实…

遥感数据

在研究中&#xff0c;我们常需要遥感数据。在下面的网站中&#xff0c;可以得到遥感数据。 EarthExplorer (usgs.gov)https://earthexplorer.usgs.gov/登陆网站&#xff1a; 通常&#xff0c;在Additional Criteria中&#xff0c;可以下载遥感数据。 不过&#xff0c;这个选项…

安顿APP3.0全新升级,引领智能穿戴健康革新,专注预警疾病风险

随着人们生活水平的提高和工作压力的增加&#xff0c;心脑血管疾病已经成为现代社会的严重问题&#xff0c;特别是心梗、脑卒中等疾病已经开始夺去年轻人的生命。 据报道&#xff0c;近年来&#xff0c;多位年轻人因心脑血管疾病突发去世&#xff0c;如42岁的知名男演员、30岁的…

【C刷题】day7

&#x1f3a5; 个人主页&#xff1a;深鱼~&#x1f525;收录专栏&#xff1a;【C】每日一练&#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 一、选择题 1、以下对C语言函数的有关描述中&#xff0c;正确的有【多选】&#xff08; &#xff09; A: 在C语言中&#xff0c;一…

java--拼图游戏

1、了解拼图游戏基本功能&#xff1a; 拼图游戏内容由若干小图像块组成的&#xff0c;通过鼠标点击图像块上下左右移动&#xff0c;完成图像的拼凑。 2、拼图游戏交互界面设计与开发&#xff1a; 通过创建窗体类、菜单、中间面板和左右面板完成设计拼图的交互界面 &#xff…

java 实现串口通讯

1、引入依赖 <dependency><groupId>org.scream3r</groupId><artifactId>jssc</artifactId><version>2.8.0</version> </dependency>2、配置启动串口 Component public class ContextHolder implements ApplicationContextAw…

使用共享内存进行通信的代码和运行情况分析,共享内存的特点(拷贝次数,访问控制),加入命名管道进行通信的代码和运行情况分析

目录 示例代码 头文件(comm.hpp) log.hpp 基础版 -- 服务端 代码 运行情况 加入客户端 代码 运行情况 两端进行通信 客户端 代码 注意点 服务端 代码 两端运行情况 共享内存特点 拷贝次数少 管道的拷贝次数 共享内存的拷贝次数 没有访问控制 管道 共享…

神辅助 Cursor 编辑器,加入 GPT-4 让编码更轻松!

分类 互联网 在 ChatGPT 问世之前&#xff0c;我们的编码方式很多时候都是面向搜索引擎编码&#xff0c;需要不断地进行搜索&#xff0c;然后复制粘贴&#xff0c;俗称复制粘贴工程师。 但是&#xff0c;随着ChatGPT的出现&#xff0c;这一切将彻底改变。 ChatGPT 是一种基于…

AI工具合集

网站&#xff1a;未来百科 | 为发现全球优质AI工具产品而生 (6aiq.com) 如今&#xff0c;AI技术涉及到了很多领域&#xff0c;比如去水印、一键抠图、图像处理、AI图像生成等等。站长之家之前也分享过一些&#xff0c;但是在网上要搜索找到它们还是费一些功夫。 今天发现了一…

STM32 HAL库函数HAL_SPI_Receive_IT和HAL_SPI_Receive的区别

背景 前段时间开发一个按键板驱动&#xff0c;该板用的STM32F103系列单片机&#xff0c;前任工程师用STM32CubeMX生成的工程&#xff0c;里面全是HAL库调用&#xff0c;我接手后&#xff0c;学习了下HAL库的用法&#xff0c;踩坑不少&#xff0c;特别是带IT后缀的函数&#xf…

【左程云算法全讲11】贪心算法 并查集

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于左程云算法课程进行的&#xff0c;每个知识点的修正和深入主要参考…

电子电器架构 —— 车载网关边缘节点总线转换

电子电器架构 —— 车载网关边缘节点路由转发策略 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 PS:小细节,本文字数3000+,详细描述了网关在车载框架中的具体性能设置。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无…

系列十、你说你做过JVM调优和参数配置,请问如何盘点JVM系统的默认值?

一、JVM的参数类型 1.1、标配参数 java -versionjava -help 1.2、XX参数 1.2.1、Boolean类型 公式&#xff1a;-XX:或者- 某个属性值 表示开启、-表示关闭 # 是否打印GC收集细节 -XX:PrintGCDetails -XX:-PrintGCDetails# 是否使用串行垃圾收集器 -XX:UseSerialGC -XX:-UseS…

使用FFmpeg合并多个ts视频文件转为mp4格式

前言 爬取完视频发现都是ts文件&#xff0c;而且都是几百KB的视频片段&#xff0c;.ts 全名叫&#xff1a;MPEG Transport Stream&#xff0c;它是一个万能的多媒体容器&#xff0c;可以装下音频、视频、字幕。有时我们需要将.ts文件转换为其他更加广泛被支持的格式&#xff0…

springboot项目中没有识别到yml文件解决办法

springboot项目中没有识别到yml文件解决办法 ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传] 1、这个意思就是没有配置数据库的数据源路径。所以需要配置数据源&#xff0c;比如mysql的驱动和路径。检查是否在properties或者yml文件中是否已经配置好。…

简单的用Python实现一下,采集某牙视频,多个视频翻页下载

前言 表弟自从学会了Python&#xff0c;每天一回家就搁那爬视频&#xff0c;不知道的以为是在学习&#xff0c;结果我昨天好奇看了一眼&#xff0c;好家伙&#xff0c;在那爬某牙舞蹈区&#xff0c;太过分了&#xff01; 为了防止表弟做坏事&#xff0c;我连忙找了个凳子坐下&…

图像分类(四) 全面解读复现GoogleNet_InceptionV1-V4

论文解读 InceptionV1 前言 论文题目: Going Deeper with Convolutions Googlenet论文原文地址:https://arxiv.org/pdf/1409.4842.pdf 之前看过VGG的论文&#xff08;VGG精读直达&#xff09;。当时VGG获得了 2014 ILSVRC 图像分类的第二名&#xff0c;今天来看一下第一名…

Yolov5

Yolov5 Anchor &#xff11;&#xff0e;Anchor是啥&#xff1f; anchor字面意思是锚&#xff0c;是个把船固定的东东&#xff08;上图&#xff09;&#xff0c;anchor在计算机视觉中有锚点或锚框&#xff0c;目标检测中常出现的anchor box是锚框&#xff0c;表示固定的参考框…