C语言qsort函数介绍

前言

学到了函数指针,那这篇博客我们可以根据函数指针,了解一个函数qsort的应用与模拟实现

欢迎关注个人主页:小张同学zkf

若有疑问   评论区见


目录

 

1.回调函数

2.qsort函数使用

3.qsort模拟实现


1.回调函数

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

简单来说,就是把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这就是回调函数的功能。
 

2.qsort函数使用

qsort是一个库函数,函数功能就是快速排序,可以将数组里的东西按照一定的顺序升序降序排列

qsort的形式:

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

我们来一个一个分析,第一个参数是指针指向的是待排序数组的第一个元素,由于不知道你会传递什么指针类型,就用void*(是无具体类型的指针,它的作用就是接收任何类型的地址)暂时来作为指针类型。第二个参数是base指向的待排序数组的元素个数,第三个参数是每个元素的大小(字节长度),最后一个传入的是一个函数compar的指针,指向的是两个元素的比较函数。

举一个例子:

#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
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;
}

这个是排序整形数据,我们可以看到,arr首元素地址,元素个数,每个元素的大小,以及比较方式都传入了qsort函数,这个比较函数int_cmp,在qsort函数内部调用,返回的是整型类型的两个元素的整型差,根据结果推断,如果第一个元素比第二个元素大,返回的是大于零的数,按的是升序排列,如果返回时两个元素交换位置,返回的若是大于零的数,则是降序排列

那如果不是整型数组能排列吗,我要是结构体类型那

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;
}

可以看到结构体类型也可以按照某个成员类型来排列数组。

所以若使用qsort函数快速排序时这个比较函数必须自己定义好比较方式,然后把它的地址作为参数传入qsort内部进行调用。就能实现qsort快速排序的功能。

3.qsort模拟实现

那这个函数内部到底是怎么实现的那,qsort函数是快速排序的功能,那我们可以先想想我之前发的指针博客里的冒泡排序。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void maopao(int arr[], int x)
{for (int i = 0; i < x-1; i++)//趟数{int s = 1;//假设有序for (int j = 0; j <x-i-1 ; j++)//一趟比较几次{if (arr[j]>arr[j+1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j+1] = tmp;s = 0;//不完全有序}}if (s == 1)//以完全有序{break;}}
}
int main()
{int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);maopao(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}

两个for循环,外部那个for循环代表一共要比较几趟,如果10个数比较9趟,11个数比较10趟可见最后一个数不需要再走,内部那个for循环代表一趟要比较几次,第一个数比较,除了自身以外比较元素个数减1次,第二个数开始比较时由于已经和第一个数比较过了,就不需要再和它比较,所以比较次数再减1,所以随着趟数的增加,比较次数依次减小,直到完全有序。

那我们想想,qsort内部是不是也是类似于冒泡排序那,我们来模拟一下


void qsort_s(void* a, size_t x, size_t whits, int (*campare)(const void* as1, const void* as2))
{//趟数for (int i = 0; i < x-1; i++){for (int j = 0; j < x - i - 1; j++)//每一趟{if (campare((char*)a + j*whits, (char*)a+(j+1)*whits) > 0)//满足交换的条件{swap((char*)a + j * whits, (char*)a + (j + 1) * whits,whits);//内部一个单元一个单元的交换}}}}
void print(int arr[], int x)
{for (int i = 0; i < x; i++){printf("%d ",arr[i]);}
}
int main()
{int arr[] = { 23,5,67,89,36,2,300};int sz = sizeof(arr) / sizeof(arr[0]);qsort_s(arr, sz, sizeof(arr[0]), campare);print(arr, sz);return 0;
}

我们假设qsort_s是模仿qsort的函数,qsort不仅仅可以排列整型,一切类型都可以排序,所以元素和下一个元素之间的字节宽度我们要考虑到,就是第三个参数whits,两次for循环内部是一个判断条件,就是比较函数返回值是否大于零,大于零就交换,也就是传入这个函数的地址是为了在此处调用比较函数进行判断是否达成交换的条件,在判断的时候不管元素地址是什么类型都要将它强制转换成char*类型,是因为我们交换这俩元素,这俩元素不管什么类型都是俩内存块,内存块交换要内部一个字节一个字节交换,从而达到两个元素的整体交换。

从这里我们也看出qsort函数内部模仿原理跟冒泡排序有点类似。

我们在定义比较函数时,也要注意,传过来的元素地址不管什么类型,都要将它强制转换成整型指针,这是因为因为比较函数返回值就是整型。以下是比较函数的定义

int campare(const void* as1, const void* as2)
{return *(int*)as1 - *(int*)as2;}

别忘了定义交换函数

void swap(char* cv1, char* cv2, size_t whits)
{for (int i = 0; i < whits; i++)//一一对应交换{char tmp = *cv1;*cv1 = *cv2;*cv2 = tmp;cv1++;cv2++;}
}

以下是qsort函数模拟实现的所有代码 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int campare(const void* as1, const void* as2)
{return *(int*)as1 - *(int*)as2;}
void swap(char* cv1, char* cv2, size_t whits)
{for (int i = 0; i < whits; i++)//一一对应交换{char tmp = *cv1;*cv1 = *cv2;*cv2 = tmp;cv1++;cv2++;}
}
void qsort_s(void* a, size_t x, size_t whits, int (*campare)(const void* as1, const void* as2))
{//趟数for (int i = 0; i < x-1; i++){for (int j = 0; j < x - i - 1; j++)//每一趟{if (campare((char*)a + j*whits, (char*)a+(j+1)*whits) > 0)//满足交换的条件{swap((char*)a + j * whits, (char*)a + (j + 1) * whits,whits);//内部一个单元一个单元的交换}}}}
void print(int arr[], int x)
{for (int i = 0; i < x; i++){printf("%d ",arr[i]);}
}
int main()
{int arr[] = { 23,5,67,89,36,2,300};int sz = sizeof(arr) / sizeof(arr[0]);qsort_s(arr, sz, sizeof(arr[0]), campare);print(arr, sz);return 0;
}

 

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

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

相关文章

开发者分享:利用 EMQX Cloud 与 ESP32 微控制器实现智能液冷散热系统

作者&#xff1a;陶德坤&#xff0c;EMQX Cloud 开发者。 作为一名后端开发人员&#xff0c;我经常需要同时运行多个 Jetbrains IDE &#xff08;集成开发环境&#xff09;&#xff0c;所以经常面临笔记本电脑过热问题。我曾尝试过各种散热方法&#xff0c;从传统的风扇到更先…

《数字图像处理(MATLAB版)》相关算法代码及其分析(3)

目录 1 对边界进行子采样 1.1 输入参数检查 1.2 处理重复坐标 1.3 计算边界最大范围 1.4 确定网格线数量 1.5 构建网格位置向量 1.6 计算曼哈顿距离 1.7 整理输出结果 1.8 返回结果 2 改变图像的存储类别 2.1 函数输入 2.2 数据类型转换 2.3 错误处理 2.4 返回结…

Chrome禁止自动升级

一、关闭计划任务 1、首先我们需要右键点击我的电脑&#xff0c;在打开的选项里选择管理。   2、在打开的对话框中选择任务计划程序。   3、在任务计划程序库中找到两个和chrome自动更新相关的任务计划GoogleUpdateTaskMachineCore与GoogleUpdateTaskMachineUA。     4…

【MATLAB】语音信号识别与处理:滑动平均滤波算法去噪及谱相减算法呈现频谱

1 基本定义 滑动平均滤波算法是一种经典的滤波方法&#xff0c;它通过计算信号的移动平均值来消除噪声。该算法的主要思想是对信号进行滑动窗口处理&#xff0c;窗口内的数据进行平均化&#xff0c;以得到平滑后的信号。这样可以有效地去除周期性噪声和高频噪声&#xff0c;同…

【MySQL使用】show processlist 命令详解

show processlist 命令详解 一、命令含义二、命令返回参数三、Command值解释四、State值解释五、参考资料 一、命令含义 对于一个MySQL连接&#xff0c;或者说一个线程&#xff0c;任何时刻都有一个状态&#xff0c;该状态表示了MySQL当前正在做什么。SHOW PROCESSLIST 命令的…

高级大数据技术 实验一 scala编程

​ 高级大数据技术 实验一 scala编程 写的不是很好&#xff0c;大家多见谅&#xff01; 1. 计算水仙花数 实验目标; &#xff08;1&#xff09; 掌握scala的数组&#xff0c;列表&#xff0c;映射的定义与使用 &#xff08;2&#xff09; 掌握scala的基本编程 实验说明 …

Spring中Bean的作用域、实例化方式、生命周期、循环依赖问题

Spring中Bean的作用域、实例化方式、生命周期、循环依赖问题 一、Bean的作用域1.singleton2.prototype3.其他scope值 二、Bean的实例化方式1.通过构造方法实例化2.通过简单工厂模式实例化3.通过factory-bean实例化4.通过FactoryBean接口实例化5.BeanFactory和FactoryBean的区别…

Java基础-运算符,表达式和语句

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 一、Java 运算符 算术运算符 关系运算符 位运算符 逻辑运算符 赋值运算符 条件运算符&#xff…

基于MVO优化的Bi-LSTM多输入时序预测(Matlab)多元宇宙算法优化长短期神经网络时序预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&am…

05. Nginx入门-Nginx访问控制

测试环境 此处使用的yum安装的Nginx路径。 此处域名均在本地配置hosts。 主配置文件 路径&#xff1a;/etc/nginx/nginx.conf user nginx; worker_processes auto;error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;events {worker_connection…

计算机网络-物理层-传输媒体

传输媒体的分类 导向型-同轴电缆 导向型-双绞线 导向型-光纤 非导向型

H12-821_126

126.如图所示&#xff0c;在MA网络中&#xff0c;若要实现R1一定为DR&#xff0c;R2一定为BDR&#xff0c;R3&#xff0c;R4&#xff0c;R5不参与选举&#xff0c;那么R1的dr-priority最大为_____;R2的dr-priority________;R3的dr-priority为________;R4的dr-priority为_____;R…