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.rhkb.cn/news/197461.html

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

相关文章

C++多线程编程(2):四种线程管理方法

文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 文章目录 线程管理get_idsleep_forsleep_untilyield 线程管理 有一个this_thread的名称空间中定义了许多的线程管理方法&#xff1a; get_id&#xff1a;获取当前线程idsleep_for&#xff1a;当前线程休眠一段时间sleep_…

Linux系统编程学习 NO.9——git、gdb

前言 本篇文章简单介绍了Linux操作系统中两个实用的开发工具git版本控制器和gdb调试器。 git 什么是git&#xff1f; git是一款开源的分布式版本控制软件。它不仅具有网络功能&#xff0c;还是服务端与客户端一体的软件。它可以高效的处理程序项目中的版本管理。它是Linux内…

实验五:Java多线程程序设计

一、线程接力 编写一个应用程序&#xff0c;除了主线程外&#xff0c;还有三个线程&#xff1a;first、second和third。first负责模拟一个红色的按钮从坐标&#xff08;10&#xff0c;60&#xff09;运动到&#xff08;100&#xff0c;60&#xff09;&#xff1b;second负责模…

【机器学习基础】正则化

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;后面的内容会越来越有意思~ ⭐特别提醒&#xff1a;针对机器学习&#xff0c;特别开始专栏&#xff1a;机器学习python实战 欢迎订阅&am…

OpenCV图像处理、计算机视觉实战应用

OpenCV图像处理、计算机视觉实战应用 专栏简介一、基于差异模型模板匹配缺陷检测二、基于NCC多角度多目标匹配三、基于zxing多二维码识别四、基于tesseract OCR字符识别 专栏简介 基于OpenCV C分享一些图像处理、计算机视觉实战项目。不定期持续更新&#xff0c;干货满满&…

ExoPlayer架构详解与源码分析(9)——TsExtractor

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…

Kafka入门教程与详解(一)

Kafka入门教程与详解&#xff08;一&#xff09; 一、Kafka入门教程 1.1 消息队列&#xff08;Message Queue) Message Queue消息传送系统提供传送服务。消息传送依赖于大量支持组件&#xff0c;这些组件负责处理连接服务、消息的路由和传送、持久性、安全性以及日志记录。消…

飞鼠异地组网工具实战之访问k8s集群内部服务

飞鼠异地组网工具实战之访问k8s集群内部服务 一、飞鼠异地组网工具介绍1.1 飞鼠工具简介1.2 飞鼠工具官网 二、本次实践介绍2.1 本次实践场景描述2.2 本次实践前提2.3 本次实践环境规划 三、检查本地k8s集群环境3.1 检查k8s各节点状态3.2 检查k8s版本3.3 检查k8s系统pod状态 四…

HTML5学习系列之标题和正文、描述性信息

HTML5学习系列之标题和正文、描述性信息 标题和正文标题段落 描述性信息强调注解备选上下标术语代码预定义格式缩写词编辑提示引用引述换行显示修饰非文本注解 总结 标题和正文 标题 按语义轻重排列&#xff1a;h1\h2\h3\h4\h5\h6 <h1>诗词介绍</h1> <h2>…

802.11ax-2021协议学习__$27-HE-PHY__$27.5-Parameters-for-HE-MCSs

802.11ax-2021协议学习__$27-HE-PHY__$27.5-Parameters-for-HE-MCSs 27.3.7 Modulation and coding scheme (HE-MCSs)27.3.8 HE-SIG-B modulation and coding schemes (HE-SIG-B-MCSs)27.5 Parameters for HE-MCSs27.5.1 General27.5.2 HE-MCSs for 26-tone RU27.5.3 HE-MCSs f…

系列一、JVM概述

一、概述 1.1、Java发展中的重大事件 1.2、虚拟机 vs Java虚拟机 1.2.1、虚拟机 1.2.2、Java虚拟机 1.2.3、Java虚拟机的作用 Java虚拟机是二进制字节码的运行环境&#xff0c;负责装载字节码到其内部&#xff0c;解释/编译为对应平台上的机器指令指令。每一条Java指令&#…

Linux进程间通信之匿名管道

文章目录 为什么要有进程间通信pipe函数共享管道原理管道特点管道的应用场景&#xff08;进程池&#xff09;ProcessPool.ccTask.hpp 为什么要有进程间通信 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通…

ARM CoreLink CCN 互连总线介绍

NIC NOC CCI CMN CNN NI cmn-700 nic-700 ni-700 MLGB这都是啥玩意? 后期博文或视频将会更新这一系列。今天先温习一下CNN的概念,这是来自2014年的文章,然后稍微整理总结一番。 以下是正文… 现代主流和优质 ARM 片上系统 (SoC) 产品使用 CoreLink 缓存一致性网络 (CCN) 504…

NSSCTF第13页(2)

[HNCTF 2022 Week1]Challenge__rce 提示?hint 访问看到了源码 <?php error_reporting(0); if (isset($_GET[hint])) { highlight_file(__FILE__); } if (isset($_POST[rce])) { $rce $_POST[rce]; if (strlen($rce) < 120) { if (is_string($rce…

基于阿基米德优化算法优化概率神经网络PNN的分类预测 - 附代码

基于阿基米德优化算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于阿基米德优化算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于阿基米德优化优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xf…

国科大数据挖掘期末复习——聚类分析

聚类分析 将物理或抽象对象的集合分组成为由类似的对象组成的多个类的过程被称为聚类。由聚类所生 成的簇是一组数据对象的集合&#xff0c;这些对象与同一个簇中的对象彼此相似&#xff0c;与其他簇中的对象相异。 聚类属于无监督学习&#xff08;unsupervised learning&…

vue之浏览器存储方法封装实例

我们在项目中通常会对缓存进行一些操作&#xff0c;为了便于全局调用&#xff0c;会对缓存的设置、获取及删除方法进行封装成一个工具类。 首先我们在src目录下创建一个plugins文件夹&#xff0c;在plugins下创建cache文件夹并创建index.js&#xff0c;代码如下&#xff1a; c…

[Linux版本Debian系统]安装cuda 和对应的cudnn以cuda 12.0为例

写在前面 先检查自己有没有安装使用wget的命令&#xff0c;没有的话输入下面命令安装&#xff1a; apt-get install wget -y查看gcc的安装 sudo apt install gcc #安装gcc gcc --version #查看gcc是否安装成功 #若上述命令不成功使用下面的命令尝试之后再执行上面…

【数据结构算法(一)】递归篇(常见实例讲解)

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; ⭐本篇讲解实例&#xff1a; 斐波那契、兔子问题、猴子吃桃问题、跳台阶问题、汉诺塔、杨辉三角 ⭐用到的递归思想&#xff1a; 无记忆递归、记忆递归(重点掌握) 目录 一、斐波那契&#xff1a; ①无记忆多路递归&am…

【飞控调试】DJIF450机架+Pixhawk6c mini+v1.13.3固件+好盈Platinium 40A电调无人机调试

1 背景 由于使用了一种新的航电设备组合&#xff0c;在调试无人机起飞的时候遇到了之前没有遇到的问题。之前用的飞控&#xff08;Pixhawk 6c&#xff09;和电调&#xff08;Hobbywing X-Rotor 40A&#xff09;&#xff0c;在QGC里按默认参数配置来基本就能平稳飞行&#xff0…