深入解析 qsort 函数(下),用冒泡排序模拟实现 qsort 函数

        前言:对于库函数有适当了解的朋友们,对于 qsort 函数想必是有认知的,因为他可以对任意数据类型进行排序的功能属实是有点厉害的,本次分享,笔者就给大家带来 qsort 函数的全面的解读

        本次知识的分享笔者分为上下俩卷文章进行讲解,在本篇文章中,给大家带来 qsort 函数的函数内部构造解析,以及如何通过冒泡排序模拟实现 qsort 的函数功能

上卷:深入解析 qsort 排序(上),它为什么是万能排序?


目录

一.模拟实现函数参数

二.确定算法大体的框架

三.实现函数的判断逻辑、

函数指针部分:

具体判断方法函数:

四.交换函数的实现

代码实现:

五.完整功能测试

六.完整代码


一.模拟实现函数参数

首先我们打开 cplusplus 官网查看 qsort 的参数要求和含义

qsort - C++ Reference (cplusplus.com)

具体参数信息如下 

  •  void* base:待排序数组的第一个元素的地址
  • size_t num:待排序数组的元素个数
  • size_t size:待排序数组中一个元素的大小
  • int (*compar)(const void*, const void*):函数指针-cmp指向了一个函数,这个函数是用来比较两个元素的,e1和e2中存放的是需要比较的两个元素的地址

根据上面的定义说明,我们对函数参数进行模拟

void my_qsort(void* base, size_t num, size_t size, int (*compare)(const void* e1, const void* e2))

二.确定算法大体的框架

        正如标题所说,笔者这里使用较为容易理解的冒泡排序,当然其他的快速排序选择排序堆排序希尔排序等算法都是可以使用的,笔者这里只是为了方便讲解具体实现流程和细节,所以选择的冒泡排序的算法

         在选择好了使用什么算法进行模拟实现后,我们就需要来设计大体的框架了,首先,我们列出一般冒泡排序的形式,然后在这基础上进行删改

	for (int i = 0; i < num - 1; i++){//每一趟for (int j = 0; j < num - i - 1; j++){//排序:前一个元素大于后一个就进行交换if (arr[j] > arr[j + 1]){int temp = 0;temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}

在这里,我们需要修改的地方主要是俩点

  1. 判断部分,考虑到不同数据的复杂类型,判断部分不能再使用这样简单数组的方式进行判断,应该设计成一个函数指针针对不同的数据类型调用不同的函数来进行不同的判断
  2. 交换部分 ,考虑不同的数据的复杂类型,交换部分也不能使用 int 型的临时变量来进行交换数据,应该设计成对任意数据类型都能交换的方式,也就是对字节直接进行操作对于不同的数据类型,我们直接对其内存存储的字节进行交换,这样就能达到目的和要求

三.实现函数的判断逻辑、

函数指针部分:

        首先,为了实现不同的判断方法,我们要有一个函数指针去调用不同的判断方法,我们根据 qsort 的参数要求设计如下函数指针

int (*compare)(const void* e1, const void* e2)

        这个函数指针有俩个参数,分别代表着需要判断排序的数据,我们调用的参数的返回值是有要求的,如下图 

那我们就可以将这个判断逻辑的函数的返回值来作为 if 语句判断依据,实现实例如下

if (compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{}

        这代表着,如果判断函数返回值大于0,那我们就执行交换操作,如果等于0或者小于0就不执行交换操作,那我们实现了函数调用的接口,我们还得具体的实现判断逻辑函数,方便进行调用

具体判断方法函数:

        根据我们在上卷文章中讲解的判断逻辑和实现方法,我们可以同样试用在这里,具体细节如有不懂可以访问下面的链接

深入解析 qsort 排序(上),它为什么是万能排序?

//比较函数--整形
int comp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}//比较函数--浮点型
int comp_float(const void* a, const void* b)
{return *(float*)b - *(float*)a;
}//比较函数--按照年龄
int comp_age(const void* a, const void* b)
{return ((struct stu*)b)->age - ((struct stu*)a)->age;
}//比较函数--按照姓名
int comp_name(const void* a, const void* b)
{return strcmp(((struct stu*)a)->name, ((struct stu*)b)->name);
}

四.交换函数的实现

        首先,我们要确定我们要进行交换的对象,对于冒泡排序来说,需要交换的只是前后俩个元素,那我们拿到他们的地址就可以进行交换了,我们具体实现如下

//交换
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);

这里分别对三个参数进行一下解释:

  1. (char*)base + j * size :确定要交换的第一个数据的地址
  2. (char*)base + (j + 1) * size :确定要交换的第二个数据的地址
  3. size :确定这俩个数据直接隔了多少个字节,方便逐字节进行操作

        对于参数为什么这样设定,有小伙伴可能不理解,下面笔者就以 int 类型举例,我们知道,int 类型占 4 个字节,而 char* 指针解引用可以访问一个字节,而数据在内存中存储的最小字节都是 1 个字节那我们就可以 通过 char* 指针来逐个访问字节,就像下面这张图一样,我们逐个交换俩个数据中的每一个字节,在交换完成后,我们就相当于把这俩个数字进行了交换 

 

        在逐个交换字节后,宏观上给我们的感受就是直接交换了我们的数据内容,也就是说我们使用俩个 char* 指针分别访问要交换的俩个在数据,每一次交换一个字节后,指针加一,我们就可以继续访问交换后面的字节,在这里循环往复交换 size 个字节后,这俩个数据就被我们完全交换了

 

代码实现:

        注意,在具体写代码的过程中不要非法访问空间,在下方代码的注释部分,笔者对错误代码进行了注释,大家在写过程中一定得注意,在指针访问一些只读区域或者不是我们申请的空间的区域就会出现段错误就像注释部分的代码,我们定义一个空指针,然后再对这个空指针进行解引用访问赋值的操作就会出现报错

//交换函数
void swap(char* buf1, char* buf2, size_t size)
{//逐个字节进行交换,一共有size个字节for (int i = 0; i < size; i++){//错误示范//char* temp = 0;//*temp = *buf1;//*buf1 = *buf2;//*buf2 = *temp;//buf1++;//buf2++;//正确示范char temp = *buf1;*buf1 = *buf2;*buf2 = temp;buf1++;buf2++;}
}

五.完整功能测试

        我们仍然使用在上篇文章中的用例进行测试,观察我们设计的函数能不能完成我们预期的功能,在这里对整形数组,浮点型数组,结构体数组进行测试

int main()
{int arr1[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr1) / sizeof(arr1[0]);print1(arr1);my_qsort(arr1, sz, sizeof(int), comp_int);print1(arr1);float arr2[5] = { 1.2,3.4,5.6,7.8,9.9 };print2(arr2);my_qsort(arr2, 5, sizeof(float), comp_float);print2(arr2);struct stu arr3[] = { {"zhangsan",19},{"lisi",20},{"wangswu",21} };my_qsort(arr3, 3, sizeof(arr3[0]), comp_name);my_qsort(arr3, 3, sizeof(arr3[0]), comp_age);system("pause");return 0;
}

观察结果:

        我们可以发现和我们预期的一样,对于不同数据类型,我们自己模拟的函数都可以实现排序功能 

六.完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<windows.h>//结构体
struct stu
{char name[20];int age;
};void print1(int* arr)
{for (int i = 0; i < 10; i++){printf("%d ", *(arr + i));}printf("\n");
}void print2(float* arr)
{for (int i = 0; i < 5; i++){printf("%.2f ", *(arr + i));}printf("\n");
}//比较函数--整形
int comp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}//比较函数--浮点型
int comp_float(const void* a, const void* b)
{return *(float*)b - *(float*)a;
}//比较函数--按照年龄
int comp_age(const void* a, const void* b)
{return ((struct stu*)b)->age - ((struct stu*)a)->age;
}//比较函数--按照姓名
int comp_name(const void* a, const void* b)
{return strcmp(((struct stu*)a)->name, ((struct stu*)b)->name);
}//交换函数
void swap(char* buf1, char* buf2, size_t size)
{//逐个字节进行交换,一共有size个字节for (int i = 0; i < size; i++){//错误示范//char* temp = 0;//*temp = *buf1;//*buf1 = *buf2;//*buf2 = *temp;//buf1++;//buf2++;//正确示范char temp = *buf1;*buf1 = *buf2;*buf2 = temp;buf1++;buf2++;}
}//void qsort(void* base, size_t num, size_t size,int (*compar)(const void*, const void*));
void my_qsort(void* base, size_t num, size_t size, int (*compare)(const void* e1, const void* e2))
{//使用冒泡排序for (int i = 0; i < num - 1; i++){//每一趟for (int j = 0; j < num - i - 1; j++){//排序:前一个元素大于后一个if (compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0){//交换swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}int main() 
{int arr1[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr1) / sizeof(arr1[0]);print1(arr1);my_qsort(arr1, sz, sizeof(int), comp_int);print1(arr1);float arr2[5] = { 1.2,3.4,5.6,7.8,9.9 };print2(arr2);my_qsort(arr2, 5, sizeof(float), comp_float);print2(arr2);struct stu arr3[] = { {"zhangsan",19},{"lisi",20},{"wangswu",21} };my_qsort(arr3, 3, sizeof(arr3[0]), comp_name);my_qsort(arr3, 3, sizeof(arr3[0]), comp_age);system("pause");return 0;
}

        

        本次分享的俩篇文章就到此为止啦,希望我的分享对您有所帮助,文章中如有错误,欢迎积极指正,您的认可就是我最大的动力,那我们下次分享再见

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

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

相关文章

Linux中如何执行命令

目录 命令格式&#xff1a; 命令分类&#xff1a; 命令帮助&#xff1a; 1、man 2、help 3、--help 4、info命令 终止命令&#xff1a; 补全命令&#xff1a; 1&#xff09;补全命令&#xff1a; 2&#xff09;补全文件名和目录名&#xff1a; 命令格式&#xff1a;…

034:vue项目利用qrcodejs2生成二维码示例

第034个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

目标跟踪:Mobile Vision Transformer-based Visual Object Tracking

论文作者&#xff1a;Goutam Yelluru Gopal,Maria A. Amer 作者单位&#xff1a;Concordia University 论文链接&#xff1a;https://arxiv.org/pdf/2309.05829v1.pdf 项目链接&#xff1a;https://github.com/goutamyg/MVT 内容简介&#xff1a; 1&#xff09;方向&#…

vscode搭建Django自带后台管理系统

文章目录 一、django自带的后台管理系统1. 建表2. 后台管理系统2.1 创建账号2.2 运行后台2.3 登录 二、模版渲染1. 直接将数据渲染到页面2. 数据传递给js 三、数据库1. 查看当前数据库2. 创建UserInfo数据表3. Django rest framework配置 四、vue前端搭建1. 在Django项目的根目…

vue3+ts+uniapp小程序封装获取授权hook函数

vue3tsuniapp小程序封装获取授权hook函数 小程序授权的时候&#xff0c;如果点击拒绝授权&#xff0c;然后就再也不会出现授权了&#xff0c;除非用户手动去右上角…设置打开 通过uni官方api自己封装一个全局的提示: uni.getSetting :http://uniapp.dcloud.io/api/other/settin…

数据结构——图(图的存储及基本操作)

文章目录 前言一、邻接矩阵法&#xff08;顺序存储&#xff09;1.无向图存储邻接矩阵算法2.有向图存储邻接矩阵算法 二、邻接表法(图的链式存储结构)总结 前言 邻接矩阵法(图的顺序存储结构) 1.1 无向图邻接矩阵算法 1.2 有向图邻接矩阵算法邻接表法(图的一种链式存储结构) 一…

matlab 间接平差法拟合二维圆

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。爬虫自重。 一、算法原理 圆的方程为: ( x - x 0 )

大范围XSS扫描工具:XSS-Freak,BurpSuite随机用户代理,Hades 静态代码审核系统

大范围XSS扫描工具&#xff1a;XSS-Freak&#xff0c;BurpSuite随机用户代理&#xff0c;Hades 静态代码审核系统。 #################### 免责声明&#xff1a;工具本身并无好坏&#xff0c;希望大家以遵守《网络安全法》相关法律为前提来使用该工具&#xff0c;支持研究学习…

文件操作(个人学习笔记黑马学习)

C中对文件操作需要包含头文件<fstream > 文件类型分为两种: 1.文本文件&#xff1a;文件以文本的ASCII码形式存储在计算机中 2.二进制文件&#xff1a;文件以文本的二进制形式存储在计算机中&#xff0c;用户一般不能直接读懂它们 操作文件的三大类: 1.ofstream: 写操作 …

MySQL基础运维知识点大全

一. MySQL基本知识 1. 目录的功能 通用 Unix/Linux 二进制包的 MySQL 安装下目录的相关功能 目录目录目录binMySQLd服务器&#xff0c;客户端和实用程序docs信息格式的 MySQL 手册manUnix 手册页include包括&#xff08;头&#xff09;文件lib图书馆share用于数据库安装的错…

Apache Kafka 基于 S3 的数据导出、导入、备份、还原、迁移方案

在系统升级或迁移时&#xff0c;用户常常需要将一个 Kafka 集群中的数据导出&#xff08;备份&#xff09;&#xff0c;然后在新集群或另一个集群中再将数据导入&#xff08;还原&#xff09;。通常&#xff0c;Kafka集群间的数据复制和同步多采用 Kafka MirrorMaker&#xff0…

安全实战 | 怎么用零信任防范弱密码?

防范弱密码&#xff0c;不仅需要提升安全性&#xff0c;更需要提升用户体验。 比如在登录各类业务系统时&#xff0c;我们希望员工登录不同系统不再频繁切换账号密码&#xff0c;不再需要3-5个月更换一次密码&#xff0c;也不再需要频繁的输入、记录、找回密码。 员工所有的办…