C语言之qsort()函数的模拟实现
C语言之qsort()函数的模拟实现
文章目录
- C语言之qsort()函数的模拟实现
- 1. 简介
- 2. 冒泡排序
- 3. 对冒泡排序进行改造
- 4. 改造部分
- 4.1 保留部分的冒泡排序
- 4.2 比较部分
- 4.3 交换部分
- 5. bubble_sort2完整代码
- 6. 使用bubble_sort2来排序整型数组
- 7. 使用bubble_sort2来排序结构体数组
- 7.1 按名字来排序结构体数组
- 7.2 按年龄来排序结构体数组
1. 简介
qsort()函数全称为Quicksort,因为底层使用的是快速排序,对初学者来说只学过冒泡排序,使用我们使用冒泡排序来实现下qsort()函数,qsort()函数(冒泡排序版)
不知道qsort函数怎么使用的可以看看这篇qsort函数
2. 冒泡排序
既然要使用冒泡排序改模拟实现qsort,那得知道什么是冒泡排序
代码如下:
#include <stdio.h>void bubble_sort(int arr[], int sz)
{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]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}int main()
{int arr[] = { 1,4,7,2,5,8,3,10,6,9 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);return 0;
}
3. 对冒泡排序进行改造
void bubble_sort(int arr[], int sz);
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
先来看看冒泡排序和qsort函数的函数声明
要想用冒泡排序模拟实现qsort函数,首先函数的形参要一致
void bubble_sort2(void* base,size_t sz,size_t width, int (*cmp)(const void* p1,const void* p2));
仿照着qsort的形参来写
- void*:由于我们不知道要排序什么数组,所以我们使用void*来接收
- sz:为数组中元素的个数
- width:为数组中一个元素的大小(单位为字节)
- int (cmp)(const void p1,const void* p2):是函数指针
这个函数指针指向的函数是用来比较两个元素的大小
p1指向一个元素,p2也指向一个元素
函数的声明部分写好了,就得改造函数内部了
先来看看冒泡排序中的代码

- 首先函数的形参得更改吧 改为上边的函数声明
- 趟数取决于数组中元素个数使用不需要修改
- 冒泡排序只能排序整型,所以一趟冒泡排序中的比较部分需要修改
- 交换两个元素的位置也需要修改
4. 改造部分
4.1 保留部分的冒泡排序
先将冒泡排序还能使用的部分保留下来
void bubble_sort2(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 (){//Swap用于交换两个元素的位置Swap();}}}
}int main()
{int arr[] = { 1,4,7,2,5,8,3,10,6,9 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}
其中的比较部分和交换部分需要我们自己来实现
4.2 比较部分
cmp((char*)base + j * width, (char*)base + (j + 1) * width)
-
由于我们得到的数组名,也就是首元素的地址,不知道第二个元素的地址
我们可以通过指针偏移来找到第二个元素 -
我们得到的是void类型的数据,我们需要将其强制转换成char类型的数据,以便于我们进行指针偏移
-
不知道数组的类型,我们只知道一个元素的大小,我们可以通过(char*)base + j + width的方式来找到一个元素的地址,(char*)base + (j+1) + width的方式来找到后一个元素的地址
用来模拟arr[j] 和 arr[j+1]

-
为什么其他类型的指针不行呢,我来举个例子:
假设用来排序double类型的数据,也就是每个元素是8字节 width = 8
(char*)base + j * width 和 (char*)base + (j+1) * width
当j等于0时,char类型的指针会偏移8个字节,找到第二个元素
如果换成其他类型的指针 int* 在这个情况下就不能使用了,只有char*类型的指针偏移是1个字节,适用于任意类型的排序
4.3 交换部分
void Swap(char* p1, char* p2,size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}
- 由于在比较部分已经将数据强制转换成char类型,所以Swap函数的形参就可以使用char来接收
- 由于不知道接收的是什么类型的数据,但是我们知道每个元素的大小,我们可以通过一个字节一个字节进行交换,和冒泡排序一样定义一个临时变量来交换两个元素,交换完一个字节的内容之后,p1++ p2++来找到第二个字节的内容,直到交换完成
5. bubble_sort2完整代码
void Swap(char* p1, char* p2,size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}
void bubble_sort2(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 (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){//Swap用于交换两个元素的位置Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}
6. 使用bubble_sort2来排序整型数组
和使用qsort函数一样,第四个形象需要根据需要排序的数据类型来编写函数,然后将其传给qsort函数,整型数据的比较只需要两个元素作差就好了
代码如下:
int cmp_int(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}
排序整型数组的完整代码:
#include <stdio.h>void Swap(char* p1, char* p2,size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}
void bubble_sort2(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 (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){//Swap用于交换两个元素的位置Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}int cmp_int(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = { 1,4,7,2,5,8,3,10,6,9 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}
代码运行结果如下:

7. 使用bubble_sort2来排序结构体数组
由于结构体中的数据类型较多,可以选择一种来排序,然后根据不同的数据类型来排序
按以下两种数据来举例子
struct Stu
{char name[20];int age;
};```c
int main()
{struct Stu arr[] = { {"zhangsan",25},{"lisi",18} ,{"wangwu",30} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort2(arr, sz, sizeof(arr[0]), cmp_struct_by_name);int i = 0;for (i = 0; i < sz; i++){printf("%s %d\n", arr[i].name, arr[i].age);}return 0;
}
7.1 按名字来排序结构体数组
字符串的比较是比较ASCII,不是比较字符串的长度
int cmp_struct_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);//return strcmp((*(struct Stu*)p1).name, (*(struct Stu*)p2).name);//两段代码等价
}
完整代码如下:
#include <stdio.h>
#include <string.h>void Swap(char* p1, char* p2,size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}
void bubble_sort2(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 (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){//Swap用于交换两个元素的位置Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}struct Stu
{char name[20];int age;
};int cmp_struct_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);//return strcmp((*(struct Stu*)p1).name, (*(struct Stu*)p2).name);//两段代码等价
}int main()
{struct Stu arr[] = { {"zhangsan",25},{"lisi",18} ,{"wangwu",30} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort2(arr, sz, sizeof(arr[0]), cmp_struct_by_name);int i = 0;for (i = 0; i < sz; i++){printf("%s %d\n", arr[i].name, arr[i].age);}return 0;
}
代码运行结果如下:

7.2 按年龄来排序结构体数组
int cmp_struct_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;//return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;//两段代码等价
}
完整代码如下:
#include <stdio.h>
void Swap(char* p1, char* p2,size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}
void bubble_sort2(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 (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){//Swap用于交换两个元素的位置Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}struct Stu
{char name[20];int age;
};int cmp_struct_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;//return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;//两段代码等价
}
int main()
{struct Stu arr[] = { {"zhangsan",25},{"lisi",18} ,{"wangwu",30} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort2(arr, sz, sizeof(arr[0]), cmp_struct_by_age);int i = 0;for (i = 0; i < sz; i++){printf("%s %d\n", arr[i].name, arr[i].age);}return 0;
}
代码运行结果如下:

相关文章:
C语言之qsort()函数的模拟实现
C语言之qsort()函数的模拟实现 文章目录 C语言之qsort()函数的模拟实现1. 简介2. 冒泡排序3. 对冒泡排序进行改造4. 改造部分4.1 保留部分的冒泡排序4.2 比较部分4.3 交换部分 5. bubble_sort2完整代码6. 使用bubble_sort2来排序整型数组7. 使用bubble_sort2来排序结构体数组7.…...
数字化未来:实时云渲染在智慧城市中的创新应用
数字中国战略"是国家推动数字经济发展的战略框架。这个战略旨在加速数字化转型,推动信息技术在各个领域的应用,提高社会经济效益和人民生活质量。而智慧城市作为其中的重要一环,重要性不言而喻。 智慧城市是当今城市发展的热点和趋势&a…...
Go语言常用命令详解(二)
文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令,这些命令可以帮助您在Go开发中进行编译、测试、运行和…...
ChatGPT 从零到一打造私人智能英语学习助手
近几年,随着智能化技术的发展和人工智能的兴起,越来越多的应用程序开始涌现出来。在这些应用中,语音识别、自然语言处理以及机器翻译等技术都得到了广泛的应用。其中,聊天机器人成为了最受欢迎的人工智能应用之一,它们…...
算法升级之路(七)-盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 原题链接: 盛最多水的容器 解题思路&…...
milvus数据库索引管理
一、建立向量索引 默认情况下,Milvus不会对小于1,024行的段进行索引。 1.准备索引参数 index_params {"metric_type":"L2","index_type":"IVF_FLAT","params":{"nlist":1024} } #"nlist"…...
JVM中的 -Xms参数 设置 JVM 的初始堆大小
在 Java 虚拟机(JVM)的配置中,-Xms 是一个启动参数,用于设置 JVM 的初始堆大小(Initial Heap Size)。这个参数对于优化 Java 应用程序的性能非常重要,特别是在处理需要大量内存的应用程序时。 …...
Idea 创建 Spring 项目(保姆级)
描述信息 最近卷起来,系统学习Spring;俗话说:万事开头难;创建一个Spring项目在网上找了好久没有找到好的方式;摸索了半天产出如下文档。 在 Idea 中新建项目 填写信息如下 生成项目目录结构 pom添加依赖 <depende…...
C++多线程学习(一):C++11 多线程快速入门
参考引用 C11 14 17 20 多线程从原理到线程池实战代码运行环境:Visual Studio 2019 1. 为什么要用多线程 任务分解 耗时的操作,任务分解,实时响应 数据分解 充分利用多核CPU处理数据 数据流分解 读写分离,解耦合设计 2. 第一个…...
Linux系统之lsof命令的基本使用
Linux系统之lsof命令的基本使用 一、lsof命令的基本使用二、lsof命令的使用帮助2.1 lsof命令的help帮助信息2.2 lsof命令帮助解释 三、lsof的基本使用3.1 直接使用lsof命令3.2 查看某个进程打开的所有文件3.3 查看某个用户打开的所有文件3.4 查看某个文件被哪些进程打开3.5 查看…...
性能压力测试的优势与重要性
性能压力测试是软件开发过程中至关重要的一环,它通过模拟系统在极限条件下的运行,以评估系统在正常和异常负载下的表现。这种测试为确保软件系统的可靠性、稳定性和可伸缩性提供了关键信息。下面将探讨性能压力测试的优势以及为什么在软件开发中它具有不…...
AtCoder Beginner Contest 329 题解A~F
A - Spread 输入字符串,字符之间加上空格输出 B - Next 输出数组当中第二大的数 C - Count xxx 统计每个字符出现过的最长长度,再累加即可 #include<bits/stdc.h> #pragma GCC optimize("Ofast") #define INF 0x3f3f3f3f #define I…...
Windows网络「SSL错误问题」及解决方案
文章目录 问题方案 问题 当我们使用了神秘力量加持网络后,可能会和国内的镜像源网站的之间发生冲突,典型的有 Python 从网络中安装包,如执行 pip install pingouin 时,受网络影响导致无法完成安装的情况: pip config…...
python数据可视化
绘制简单的折线图 1.1json数据格式 JSON是一种轻量级的数据交互格式。可以按照JSON指定的格式去组织和封装数据,其本质上是一个带有特定格式的字符串。 主要功能:json就是一种在各个编程语言中流通的数据格式,负责不同编程语言中的数据传递…...
LV.12 D18 中断处理 学习笔记
一、ARM的异常处理机制及工程代码结构 1.1异常概念 处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序。 1.2异常处理机制 不同的处…...
蓝桥杯每日一题2023.11.19
题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 首先想到的方法为dfs去寻找每一个数,但发现会有超时 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, cnt, a[N]; void dfs(int dep, int sum, int start) {if(dep 4){if(s…...
<b><strong>,<i><em>标签的区别
1. b标签和strong标签 b标签:仅仅是UI层面的加粗样式,并不具备HTML语义 strong标签:不仅是在UI层面的加粗样式,具备HTML语义,表示强调 2. i标签和em标签 i 标签:仅仅是UI层面的斜体样式,并不具备…...
c++中的特殊类设计
文章目录 1.请设计一个类,不能被拷贝2. 请设计一个类,只能在堆上创建对象3. 请设计一个类,只能在栈上创建对象4. 请设计一个类,不能被继承5. 请设计一个类,只能创建一个对象(单例模式) 1.请设计一个类,不能…...
开源更安全? yum源配置/rpm 什么是SSH?
文章目录 1.开放源码有利于系统安全2.yum源配置,这一篇就够了!(包括本地,网络,本地共享yum源)3.rpm包是什么4.SSH是什么意思?有什么功能? 1.开放源码有利于系统安全 开放源码有利于系统安全 2.yum源配置…...
庖丁解牛:NIO核心概念与机制详解 04 _ 分散和聚集
文章目录 Pre概述分散/聚集 I/O分散/聚集的应用聚集写入Code Pre 庖丁解牛:NIO核心概念与机制详解 01 庖丁解牛:NIO核心概念与机制详解 02 _ 缓冲区的细节实现 庖丁解牛:NIO核心概念与机制详解 03 _ 缓冲区分配、包装和分片 概述 分散/聚…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)
旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据!该数据集源自2025年4月发表于《地理学报》的论文成果…...
