C语言qsort深度解析:从函数原型到比较函数避坑指南
简介这份PDF资料聚焦C语言标准库中qsort函数的实际用法面向正在学习C语言、需要掌握通用排序技巧的初学者与进阶开发者帮助解决不同数据类型排序时比较函数如何编写的问题。资源包共1个PDF文件大小约32KB内容以代码示例与要点说明为主便于随时查阅和对照练习。资料系统梳理了qsort在int、char、double等基本类型数组中的排序写法并进一步延伸到结构体一级排序、二级排序以及字符串字典序排序等场景同时给出qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *))的参数含义与调用格式。通过对比不同比较函数的返回值处理方式读者可以理解浮点数比较、多关键字排序和strcmp配合使用的细节从而在课程设计、算法练习或日常开发中灵活套用。目前已有2959人学习下载适合作为C语言排序知识点的速查手册与实例参考。1. qsort 不是“快排”是 C 标准库给你的通用排序接口很多人第一次在 C 语言里看到qsort会下意识以为它就是“快速排序函数”。这个理解只对了一半qsort的名字确实来自 quick sort但标准只规定它的行为和接口不规定底层一定用快排实现。它真正解决的是“同一套排序逻辑怎么排 int、排结构体、排字符串指针”的问题。你只要给它数组首地址、元素个数、单个元素大小再给一个比较函数它就能把任意连续内存块按你的规则排好。适合谁写嵌入式、写算法题、写后台小工具凡是需要排序又不想为每种类型手写一遍排序的人。代价是比较函数要自己写指针和类型转换的坑也全在这里。2. 把 qsort 的四个参数一次讲透从函数原型到比较函数返回值2.1 函数原型里每个参数到底管什么qsort的原型在stdlib.h里长这样void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));四个参数逐个拆base指向待排序数组的第一个元素。注意它是void *所以任何类型的数组都能传但传进去之后类型信息就丢了。nmemb元素个数不是字节数。很多人第一次翻车就是把sizeof(arr)直接填进来结果排出来一堆乱码。size单个元素的字节大小通常写sizeof(arr[0])或sizeof(int)。compar比较函数指针决定“谁在前谁在后”。比较函数的返回值规则是固定的返回值含义小于 0第一个参数应排在第二个参数前面等于 0两者顺序无所谓大于 0第一个参数应排在第二个参数后面这个“小于 0 / 等于 0 / 大于 0”的约定是 qsort 所有用法的地基。你不需要返回 -1、0、1返回a - b这种差值也可以但要注意溢出问题后面避坑章节会讲。2.2 排 int 数组的最小可运行例子先看一个能直接编译运行的完整例子#include stdio.h #include stdlib.h /* 比较函数升序排列 int */ int cmp_int_asc(const void *a, const void *b) { int va *(const int *)a; /* 先转成 int 指针再解引用 */ int vb *(const int *)b; if (va vb) return -1; if (va vb) return 1; return 0; } int main(void) { int arr[] {5, 2, 9, 1, 7, 3}; size_t n sizeof(arr) / sizeof(arr[0]); /* 元素个数 */ qsort(arr, n, sizeof(arr[0]), cmp_int_asc); for (size_t i 0; i n; i) { printf(%d , arr[i]); } printf(\n); return 0; }逻辑说明cmp_int_asc里先把void *转成const int *再解引用拿到真正的值。为什么不能直接*(int *)a因为a指向的是数组里某个 int 元素转成int *再解引用才是那个整数。main里用sizeof(arr) / sizeof(arr[0])算元素个数这是最稳的写法数组退化之前算好。参数说明qsort的第三个参数写sizeof(arr[0])比写sizeof(int)更好因为将来数组类型改了这里不用跟着改。比较函数声明成const void *const 是标准要求的去掉虽然多数编译器能过但不规范。2.3 降序、结构体、字符串三种常见变体降序最简单把比较函数的返回逻辑反过来int cmp_int_desc(const void *a, const void *b) { int va *(const int *)a; int vb *(const int *)b; return (va vb) - (va vb); /* 升序是 (va vb) - (va vb) */ }排结构体是 qsort 最能体现价值的地方。假设有一个学生成绩结构体typedef struct { char name[32]; int score; } Student; /* 按分数升序分数相同按名字字典序 */ int cmp_student(const void *a, const void *b) { const Student *sa (const Student *)a; const Student *sb (const Student *)b; if (sa-score ! sb-score) { return sa-score - sb-score; } return strcmp(sa-name, sb-name); }调用时Student list[100]; /* ... 填充数据 ... */ qsort(list, 100, sizeof(Student), cmp_student);排字符串数组要特别注意如果数组是char *arr[]每个元素本身是char *传进比较函数的是“指向 char * 的指针”也就是char **int cmp_str(const void *a, const void *b) { const char *sa *(const char * const *)a; const char *sb *(const char * const *)b; return strcmp(sa, sb); }这里*(const char * const *)a的读法是a是const void *转成“指向 const char 指针的 const 指针”再解引用得到const char *。这一层指针套指针是新手最容易写错的地方写错了轻则排序结果不对重则段错误。3. 比较函数的写法决定成败四种典型场景与参数细节3.1 用减法写比较函数的边界与溢出风险很多人喜欢把比较函数写成一行int cmp_int(const void *a, const void *b) { return *(const int *)a - *(const int *)b; }这写法简洁在数据范围小的时候没问题。但int相减可能溢出。比如a 2000000000b -2000000000两者相减超过INT_MAX结果是未定义行为。在 64 位平台上int还是 32 位这个坑真实存在。稳妥写法是用比较判断返回 -1/0/1或者把两个值转成long long再相减int cmp_int_safe(const void *a, const void *b) { long long va *(const int *)a; long long vb *(const int *)b; return (va vb) - (va vb); }(va vb) - (va vb)这个技巧值得记住比较表达式结果是 0 或 1相减正好得到 1、0、-1不产生溢出也不用写 if。3.2 多关键字排序结构体里先按谁再按谁实际业务里排序很少只有一个关键字。比如订单先按金额降序金额相同按时间升序typedef struct { double amount; long timestamp; } Order; int cmp_order(const void *a, const void *b) { const Order *oa (const Order *)a; const Order *ob (const Order *)b; if (oa-amount ob-amount) return -1; /* 金额降序 */ if (oa-amount ob-amount) return 1; /* 金额相同时间升序 */ if (oa-timestamp ob-timestamp) return -1; if (oa-timestamp ob-timestamp) return 1; return 0; }多关键字排序的关键是第一个关键字相等时必须继续比较第二个关键字最后才返回 0。如果第一个关键字相等就直接返回 0那第二个关键字就永远不生效排序结果不稳定且不符合预期。这里用 if 逐级判断而不是减法是因为double相减再转 int 会丢精度金额比较尤其不能这么干。3.3 浮点数比较为什么不能直接相减返回 int浮点数比较函数如果写成return *(double *)a - *(double *)b;编译器通常会警告“从 double 到 int 的隐式转换可能丢失精度”。更严重的是两个很接近的浮点数相减结果可能是个很小的数转成 int 变成 0qsort 会认为它们相等排序结果可能不符合预期。正确做法是显式比较int cmp_double(const void *a, const void *b) { double va *(const double *)a; double vb *(const double *)b; if (va vb) return -1; if (va vb) return 1; return 0; }如果业务上需要容忍误差比如两个数相差小于1e-9就算相等那就在比较函数里加一个 epsilon 判断。但要注意比较函数必须满足传递性epsilon 用不好会破坏排序算法的前提导致结果不可预测。这是浮点排序的玄学之处能不用 epsilon 就不用。3.4 用 qsort 排指针数组和二维数组的区别排指针数组比如char *lines[]时数组元素是指针比较函数收到的是“指向指针的指针”。排二维数组比如int matrix[10][5]时数组元素是“长度为 5 的 int 数组”比较函数收到的是“指向长度为 5 的 int 数组的指针”/* 按每行第一个元素排序二维数组 */ int cmp_row(const void *a, const void *b) { const int *ra (const int *)a; const int *rb (const int *)b; return ra[0] - rb[0]; } /* 调用 */ int matrix[10][5]; qsort(matrix, 10, sizeof(matrix[0]), cmp_row);这里sizeof(matrix[0])是 5 个 int 的大小qsort 每次移动一个“行”的宽度。理解这一点就能明白 qsort 的size参数本质上是“步长”它不关心元素内部结构只按固定步长在内存里挪动。4. 避坑与排查qsort 用错时的五个血泪现场4.1 现象排序后数组没变或只变了一部分原因nmemb传成了字节数。比如int arr[10]写成qsort(arr, sizeof(arr), sizeof(int), cmp)sizeof(arr)是 40qsort 会认为有 40 个元素越界访问后面 30 个位置的内存。这种越界不一定立刻崩溃但结果一定不对。解决元素个数永远用sizeof(arr) / sizeof(arr[0])或者单独用一个变量存数组长度。如果数组已经退化成指针传进函数那就必须额外传长度参数不能在函数里用sizeof算。4.2 现象比较函数里直接解引用导致段错误原因比较函数收到的是“指向元素的指针”不是元素本身。排int数组时a是int *要*(int *)a才能拿到值。如果写成(int)a或直接*a编译器可能报错也可能悄悄按错误的方式解释内存。解决记住固定套路——先转成正确的指针类型再解引用。排int用*(const int *)a排结构体用(const Student *)a然后访问成员排字符串指针用*(const char * const *)a。4.3 现象结构体排序结果混乱部分字段像没参与比较原因比较函数只比较了第一个字段第一个字段相等时直接返回 0没有继续比较后续字段。qsort 不保证稳定排序相等元素的相对顺序可能被打乱如果业务依赖第二个字段的顺序结果就会看起来“乱”。解决把所有需要参与排序的字段按优先级依次比较最后一个字段也比完才返回 0。如果确实需要稳定排序要么在比较函数里加一个唯一的 tie-breaker比如元素下标要么换用归并排序自己实现。4.4 现象比较函数返回值溢出大数排序结果错乱原因用a - b写比较函数两个大数相减溢出。比如INT_MAX - (-1)超过int范围结果是负数qsort 会认为第一个元素应该排在前面排序方向反了。解决用(va vb) - (va vb)这种不溢出的写法或者把值转成更宽的类型再相减。对于unsigned类型尤其要注意无符号相减得到的是无符号结果永远大于等于 0比较函数会失效。4.5 现象qsort 排字符串数组时崩溃或乱序原因字符串数组char *arr[]的元素是char *比较函数收到的是char **。如果写成return strcmp((const char *)a, (const char *)b);就是把“指向指针的指针”当成“字符串指针”用strcmp 会去读指针本身的内存当字符直接跑飞。解决比较函数里先解引用一层拿到char *再传给 strcmp。写法是strcmp(*(const char * const *)a, *(const char * const *)b)。如果数组是二维字符数组char arr[][32]那元素类型是char[32]比较函数收到的是char (*)[32]写法又不一样要转成const char *再 strcmp。5. 进阶技巧用 qsort_r 传上下文、用函数指针表切换排序规则标准 C 的 qsort 比较函数只能拿到两个元素没法传额外参数。比如你想按“用户指定的字段”排序或者按“升序还是降序”运行时决定纯 qsort 做不到。常见做法是用全局变量存排序配置但全局变量在多线程下不安全也不优雅。如果平台支持qsort_rGNU 扩展或 BSD 扩展可以多传一个上下文指针/* GNU 风格 qsort_r比较函数多一个 arg 参数 */ int cmp_with_ctx(const void *a, const void *b, void *arg) { int mode *(int *)arg; /* 1 升序-1 降序 */ int va *(const int *)a; int vb *(const int *)b; return mode * ((va vb) - (va vb)); } /* 调用 */ int mode -1; /* 降序 */ qsort_r(arr, n, sizeof(int), cmp_with_ctx, mode);注意qsort_r的原型在不同平台不一样GNU 版本是(base, nmemb, size, compar, arg)BSD 版本是(base, nmemb, size, arg, compar)参数顺序不同。跨平台代码里用qsort_r要加条件编译或者干脆自己封装一层。另一个实用技巧是用函数指针表切换排序规则typedef int (*CmpFunc)(const void *, const void *); CmpFunc select_cmp(int field, int desc) { static CmpFunc table[2][2] { { cmp_by_name_asc, cmp_by_name_desc }, { cmp_by_score_asc, cmp_by_score_desc } }; return table[field][desc]; } /* 使用 */ qsort(list, n, sizeof(Student), select_cmp(1, 1));这样排序规则可以配置化不用写一堆 if-else。代价是每个组合都要写一个比较函数组合多了会膨胀适合字段少、规则固定的场景。验证排序结果是否正确除了肉眼检查还可以写一个断言函数排序后遍历一遍确认相邻元素满足比较函数的顺序关系。对于结构体排序重点检查相等元素的处理是否符合预期。我自己的习惯是比较函数写完先单独测几个边界值最小值、最大值、相等值、负数确认返回值符号正确再放进 qsort 里跑。这个习惯帮我省过很多次通宵排查的时间。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

K2算法实战:贝叶斯网络结构学习与Python实现

K2算法实战:贝叶斯网络结构学习与Python实现

简介:这份资源围绕K2算法从数据中学习贝叶斯网络结构这一经典问题展开,面向具备一定概率图模型基础、希望动手实现结构学习的研究生与算法工程师。包内共7个文件,以4个m脚本为主体,配合1个c源码、1个mat数据文件和1个txt说明&…

2026/10/17 23:15:09 阅读更多 →
同一条UPDATE为何死锁?锁轨迹揭示变长字段与Include索引的陷阱

同一条UPDATE为何死锁?锁轨迹揭示变长字段与Include索引的陷阱

简介:一份聚焦 SQL Server 死锁(Deadlock)问题的技术分析文档,适合数据库管理员、开发人员以及需要排查事务并发异常的进阶学习者。文档从一次奇怪死锁现象入手,深入讲解聚集索引、非聚集索引与行锁机制如何共同诱发死…

2026/10/17 23:15:09 阅读更多 →
3 分钟给 Agent 装上眼睛:agent-vision-toolkit 一句话安装实战教程

3 分钟给 Agent 装上眼睛:agent-vision-toolkit 一句话安装实战教程

人工智能AI 技能计算机视觉OCRGUI 自动化大模型DeepSeek 【免费下载链接】agent-vision-toolkit 为纯文本模型"看图“设计更好的视觉工具箱和技能,支持多图理解,图片问答,前端UI还原、GUI 自动化等,并可选无缝接入多个主流ag…

2026/10/17 23:15:09 阅读更多 →

最新新闻

Folia Sync-Server 完整部署指南:10 分钟让 AI 主题多端同步

Folia Sync-Server 完整部署指南:10 分钟让 AI 主题多端同步

Folia Sync-Server 完整部署指南:10 分钟让 AI 主题多端同步 【免费下载链接】folia-major 专注于绚丽的歌词动画效果的本地音乐/navidrome/第三方多平台在线音乐播放器 项目地址: https://gitcode.com/GitHub_Trending/fo/folia-major Folia 是一款本地优先…

2026/10/19 1:57:24 阅读更多 →
Buzz 离线语音转文字:把会议录音和播客变成带时间戳的文字

Buzz 离线语音转文字:把会议录音和播客变成带时间戳的文字

Buzz 离线语音转文字:把会议录音和播客变成带时间戳的文字 【免费下载链接】buzz Buzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper. 项目地址: https://gitcode.com/GitHub_Trending/buz/buzz 周五傍…

2026/10/19 1:57:24 阅读更多 →
硬件开发入门十讲:从选型到量产全流程实战指南

硬件开发入门十讲:从选型到量产全流程实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 1:57:24 阅读更多 →
Cortex-M系列MCU的IAP在线升级设计与排坑指南

Cortex-M系列MCU的IAP在线升级设计与排坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 1:57:24 阅读更多 →
树莓派5外接NVMe SSD实战:USB转接架构与性能调优全指南

树莓派5外接NVMe SSD实战:USB转接架构与性能调优全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 1:57:24 阅读更多 →
显存不够怎么办:HERO-35B-A3B-i1-GGUF用7.6GB跑起35B大模型的极限技巧

显存不够怎么办:HERO-35B-A3B-i1-GGUF用7.6GB跑起35B大模型的极限技巧

【免费下载链接】HERO-35B-A3B-i1-GGUF 项目地址: https://ai.gitcode.com/hf_mirrors/mradermacher/HERO-35B-A3B-i1-GGUF 点击查看 免费下载 显存不够,还想本地跑35B大模型?HERO-35B-A3B-i1-GGUF 就是为你准备的。这个项目提供了一套完整的…

2026/10/19 1:56:24 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →