尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C语言qsort深度解析:从结构体排序到避坑指南
简介这份PDF资料面向C语言初学者与需要巩固标准库用法的开发者系统梳理了qsort函数在各类数据排序场景中的实战写法。内容覆盖int、char、double等基本类型数组的排序并深入结构体一级排序、二级排序以及字符串字典序排序等进阶用法逐一给出比较函数cmp的完整代码示例同时说明qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *))四个参数的含义与调用格式。资源包共1个PDF文件约32KB篇幅精炼、便于随时查阅适合作为排序算法练习与项目开发中的速查手册。目前已有2959人学习下载读者可从中掌握不同数据类型的比较函数编写技巧理解浮点数比较、多关键字排序与strcmp配合使用的要点从而提升C语言编程效率与代码复用能力。1. qsort 不是“能排序就行”从一次结构体数组翻车说起C 标准库里的qsort几乎是每个 C 程序员都会遇到的函数但真正把它用对、用稳的人并不多。我见过太多场景一个结构体数组按某个字段排序本地跑得好好的换台机器、换个编译器、换组数据就乱序甚至崩溃。问题往往不在排序逻辑本身而在比较函数的写法、元素大小的传递、以及指针解引用的类型匹配上。qsort的签名是void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *))四个参数每一个都有坑base是数组首地址nmemb是元素个数size是单个元素的字节大小compar是比较函数指针。它不关心你排的是什么类型只按字节搬运和比较所以类型安全完全靠你自己保证。这篇笔记面向已经会写 C、但想把qsort用得更扎实的开发者从最小可用示例一路讲到结构体多级排序、浮点数比较、稳定性替代方案和性能边界。读完你能直接抄走一套可复现的代码模板也能明白什么时候不该用qsort。2. qsort 的四个参数到底怎么传从整型数组到结构体数组2.1 整型数组排序最小可运行模板与参数逐项说明先看最基础的整型数组升序排序。很多人第一次写比较函数会写成return *(int*)a - *(int*)b;这在大多数情况下能跑但存在整数溢出风险。更稳妥的写法是用条件判断返回 -1、0、1。#include stdio.h #include stdlib.h /* 整型升序比较函数避免直接相减导致溢出 */ int cmp_int_asc(const void *a, const void *b) { int va *(const int *)a; int vb *(const int *)b; if (va vb) return -1; if (va vb) return 1; return 0; } int main(void) { int arr[] {42, 7, 19, 3, 88, 7, 0}; size_t n sizeof(arr) / sizeof(arr[0]); /* basearr, nmembn, sizesizeof(int), comparcmp_int_asc */ qsort(arr, n, sizeof(int), cmp_int_asc); for (size_t i 0; i n; i) { printf(%d , arr[i]); } printf(\n); return 0; }这段代码里四个参数的含义必须逐一对上base传数组名arr它会退化为指向首元素的指针nmemb是元素个数用sizeof(arr)/sizeof(arr[0])计算不要手写数字size是单个元素的字节数整型就是sizeof(int)compar是函数指针签名必须严格匹配int (*)(const void *, const void *)。比较函数内部先把void*转成const int*再解引用取值。返回值的符号决定顺序负数表示 a 应排在 b 前正数表示 a 应排在 b 后零表示相等。C 标准只要求返回负、零、正不要求一定是 -1、0、1但用条件判断写更安全。2.2 结构体数组按单个字段排序指针转换的常见错误结构体排序是qsort最容易翻车的地方。假设有一个表示学生成绩的结构体要按分数升序排。#include stdio.h #include stdlib.h #include string.h typedef struct { char name[16]; int score; } Student; /* 按 score 升序注意先把 void* 转成 Student*再取成员 */ int cmp_student_by_score(const void *a, const void *b) { const Student *sa (const Student *)a; const Student *sb (const Student *)b; if (sa-score sb-score) return -1; if (sa-score sb-score) return 1; return 0; } int main(void) { Student stu[] { {A, 88}, {B, 72}, {C, 95}, {D, 72} }; size_t n sizeof(stu) / sizeof(stu[0]); qsort(stu, n, sizeof(Student), cmp_student_by_score); for (size_t i 0; i n; i) { printf(%s:%d\n, stu[i].name, stu[i].score); } return 0; }这里的关键是size必须传sizeof(Student)而不是sizeof(Student*)。我见过有人写成sizeof(Student *)结果qsort按指针大小搬运整个数组被切碎排序结果完全错乱。另一个常见错误是在比较函数里写成(const Student *)a-score运算符优先级导致先取a-score再强转编译可能不报错但行为错误。正确写法是((const Student *)a)-score或者先赋值给局部指针。比较函数里不要修改元素内容虽然qsort不禁止但会破坏排序语义。2.3 多级排序分数相同时按名字字典序的写法实际业务里经常需要多级排序比如先按分数降序分数相同再按名字升序。比较函数里依次判断即可。#include stdio.h #include stdlib.h #include string.h typedef struct { char name[16]; int score; } Student; /* 先按 score 降序score 相同按 name 升序 */ int cmp_student_multi(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) ? 1 : -1; } return strcmp(sa-name, sb-name); } int main(void) { Student stu[] { {Wang, 88}, {Li, 95}, {Zhao, 88}, {Qian, 95} }; size_t n sizeof(stu) / sizeof(stu[0]); qsort(stu, n, sizeof(Student), cmp_student_multi); for (size_t i 0; i n; i) { printf(%s:%d\n, stu[i].name, stu[i].score); } return 0; }多级排序的写法要点第一级比较如果不等就直接返回相等才进入第二级。降序排序时返回值的符号要反过来但不要简单写return sb-score - sa-score同样有溢出风险。strcmp返回的负、零、正正好符合qsort对比较函数的要求可以直接用。如果还有第三级、第四级继续嵌套if即可。注意strcmp比较的是char数组如果结构体里存的是char *指针要确保指针指向有效字符串否则会段错误。3. 浮点数、指针数组和稳定性qsort 的边界与替代方案3.1 浮点数比较为什么不能直接相减浮点数排序不能写return *(double*)a - *(double*)b;因为两个相近的浮点数相减可能得到极小的值转成int后变成 0导致相等判断错误更严重的是如果差值超出int范围返回值截断后符号可能反转。正确做法是用显式比较。#include stdio.h #include stdlib.h #include math.h /* 浮点数升序比较用显式判断避免相减截断 */ int cmp_double_asc(const void *a, const void *b) { double da *(const double *)a; double db *(const double *)b; if (da db) return -1; if (da db) return 1; return 0; } int main(void) { double vals[] {3.14, 2.71, 1.41, 1.73, 2.71}; size_t n sizeof(vals) / sizeof(vals[0]); qsort(vals, n, sizeof(double), cmp_double_asc); for (size_t i 0; i n; i) { printf(%.2f , vals[i]); } printf(\n); return 0; }如果业务允许误差可以在比较函数里引入一个 epsilon比如if (fabs(da - db) 1e-9) return 0;但要注意这会让排序结果依赖比较顺序可能破坏严格弱序导致未定义行为。一般建议只在明确需要容差时使用并且 epsilon 要远小于数据本身的精度要求。3.2 指针数组排序比较函数要解引用两层有时候数组里存的是指针比如char *words[]排序时比较的是指针指向的内容而不是指针本身。#include stdio.h #include stdlib.h #include string.h /* 指针数组排序a 和 b 是 char** 类型 */ int cmp_str_ptr(const void *a, const void *b) { const char *sa *(const char * const *)a; const char *sb *(const char * const *)b; return strcmp(sa, sb); } int main(void) { const char *words[] {pear, apple, orange, banana}; size_t n sizeof(words) / sizeof(words[0]); qsort(words, n, sizeof(const char *), cmp_str_ptr); for (size_t i 0; i n; i) { printf(%s\n, words[i]); } return 0; }这里size传的是sizeof(const char *)因为数组元素是指针。比较函数里a是const void *实际指向的是数组元素也就是const char *所以要先转成const char * const *再解引用拿到字符串指针。如果写成(const char *)a直接当字符串用等于把指针的地址当字符串内容必然乱码或崩溃。指针数组排序不改变字符串本身只改变指针顺序适合字符串常量或动态分配字符串的场景。3.3 qsort 不保证稳定性需要稳定排序时怎么办qsort的名字里没有 stableC 标准也不要求它稳定。所谓稳定是指相等元素的相对顺序在排序后保持不变。如果你先按分数排再按名字排期望分数相同的保持名字有序用qsort可能被打乱。需要稳定排序时常见做法有三种一是自己在比较函数里加入原始下标作为最后一级比较把下标小的排前面二是改用归并排序自己实现三是先排序再对相等分组做二次处理。第一种最省事但需要额外存储下标。#include stdio.h #include stdlib.h typedef struct { int key; int origin_index; /* 记录原始下标用于稳定排序 */ } Item; int cmp_stable(const void *a, const void *b) { const Item *ia (const Item *)a; const Item *ib (const Item *)b; if (ia-key ! ib-key) { return (ia-key ib-key) ? -1 : 1; } /* key 相等时按下标升序保证稳定性 */ return (ia-origin_index ib-origin_index) ? -1 : 1; } int main(void) { Item items[] {{3, 0}, {1, 1}, {3, 2}, {2, 3}, {1, 4}}; size_t n sizeof(items) / sizeof(items[0]); qsort(items, n, sizeof(Item), cmp_stable); for (size_t i 0; i n; i) { printf((%d,%d) , items[i].key, items[i].origin_index); } printf(\n); return 0; }这种方法本质上是用原始下标打破相等时的平局让比较函数变成全序从而间接获得稳定效果。代价是每个元素多存一个下标字段比较时多一次判断。如果数据量很大且对稳定性要求高自己写归并排序可能更合适因为qsort的平均性能虽然不错但最坏情况是 O(n²)而标准库实现通常会做优化实际很少退化。4. 避坑与排查qsort 最常见的五类翻车现场4.1 比较函数返回值写成布尔值导致排序随机现象排序结果时对时错换组数据就乱。原因比较函数写成return *(int*)a *(int*)b;返回值只有 0 和 1缺少负数qsort无法判断 a 应排在 b 前面。解决严格返回负、零、正三种状态用if判断或返回(va vb) - (va vb)这种技巧但后者可读性差建议显式写。4.2 size 参数传成指针大小导致数组被切碎现象结构体数组排序后内容错位甚至越界访问。原因qsort第三个参数传了sizeof(Student *)而不是sizeof(Student)。解决始终用sizeof(数组元素类型)不要用指针大小。可以在代码里加_Static_assert(sizeof(Student) 期望值, ...)做编译期检查但更直接的是 review 时盯住这个参数。4.3 比较函数里修改元素或依赖全局状态现象排序结果依赖调用顺序多线程下崩溃。原因比较函数里写了static计数器、修改了元素字段、或者调用了非线程安全函数。解决比较函数必须是纯函数只读入参不修改任何外部状态。如果确实需要额外信息通过结构体字段携带不要用全局变量。4.4 对 NULL 指针或无效内存排序现象段错误且堆栈指向qsort内部。原因base传了 NULL或者nmemb大于实际元素个数或者数组元素里有无效指针。解决调用前检查base ! NULL nmemb 0确保nmemb不超过数组实际长度。对于指针数组确保每个指针都指向有效内存。4.5 浮点数用相减返回值导致相等判断失效现象浮点数排序后本应相等的元素顺序混乱或者 NaN 参与排序导致未定义行为。原因return *(double*)a - *(double*)b;截断成int小差值变 0大差值溢出。解决用显式比较并且处理 NaN 情况比如把 NaN 统一排到最后。如果数据里可能有 NaN比较函数要单独判断isnan。5. 进阶技巧用 qsort_r 传上下文与性能验证方法有些平台提供了qsort_r允许传入一个额外的上下文指针这样比较函数就不需要依赖全局变量。它的签名通常是void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg);比较函数多一个void *arg参数。这在需要按动态阈值排序、或者多线程各自带不同配置时非常有用。注意qsort_r不是 C 标准函数不同平台参数顺序可能不同移植时要小心。#define _GNU_SOURCE #include stdio.h #include stdlib.h typedef struct { int value; } Item; /* 带上下文的比较arg 指向一个阈值小于阈值的排前面 */ int cmp_with_ctx(const void *a, const void *b, void *arg) { int threshold *(int *)arg; const Item *ia (const Item *)a; const Item *ib (const Item *)b; int ka (ia-value threshold) ? 0 : 1; int kb (ib-value threshold) ? 0 : 1; if (ka ! kb) return ka - kb; return (ia-value ib-value) ? -1 : (ia-value ib-value) ? 1 : 0; } int main(void) { Item items[] {{5}, {1}, {9}, {3}, {7}}; size_t n sizeof(items) / sizeof(items[0]); int threshold 6; qsort_r(items, n, sizeof(Item), cmp_with_ctx, threshold); for (size_t i 0; i n; i) { printf(%d , items[i].value); } printf(\n); return 0; }这段代码把小于阈值的元素排在前面大于等于阈值的排在后面各自内部再升序。qsort_r的上下文参数让比较逻辑可以参数化避免了全局变量。如果你的平台没有qsort_r可以用一个结构体把阈值和数组一起传但那样需要自己实现排序或者用全局变量加锁不推荐。性能验证方面qsort的平均时间复杂度是 O(n log n)但常数因子比手写快排大因为每次比较都要通过函数指针间接调用。如果数据量在几千以内差异可以忽略如果到了百万级函数调用开销会显现。验证方法很简单用clock()或gettimeofday包住qsort调用分别测 1 万、10 万、100 万随机整数观察耗时增长是否接近 n log n。如果发现某组数据特别慢检查比较函数里是否有strcmp长字符串、或者浮点运算这些都会放大常数因子。另一个技巧是先用qsort排好再用二分查找验证有序性比逐对检查更快。我自己的习惯是只要结构体超过两个字段就把比较函数单独写成一个文件内的static函数命名带上字段名比如cmp_student_by_score_then_name这样 review 时一眼能看出排序意图。另外每次写完qsort调用我都会用一组包含相等元素、边界值、空数组的测试数据跑一遍确认没有越界和乱序。这些习惯帮我省下了不少调试时间。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

三小时SQL速通笔记:从SELECT到JOIN的实战查询指南

三小时SQL速通笔记:从SELECT到JOIN的实战查询指南

简介:这份资源是B站Mosh老师SQL三小时入门课程的配套笔记,以PDF形式整理,面向零基础或希望系统梳理SQL核心语法的开发者与数据分析初学者。内容围绕关系型数据库的查询基础展开,涵盖SELECT子句、WHERE条件筛选、AND/OR/NOT逻辑操作…

📅 2026/10/17 14:44:53
摄像头驱动起不来?从错误代码到事件日志全流程排查指南

摄像头驱动起不来?从错误代码到事件日志全流程排查指南

1. 问题现象与影响范围:摄像头驱动起不来的几种典型表现 摄像头驱动起不来,这个事几乎每个搞过电脑维护、做过嵌入式开发、甚至只是在家开视频会议的人都碰到过。别觉得这是个小事,它能把人卡到怀疑人生——明明设备管理器里能看到设备&#…

📅 2026/10/17 14:44:53
基于Hadoop+Spark+Hive的空气质量预测系统毕业设计实战指南

基于Hadoop+Spark+Hive的空气质量预测系统毕业设计实战指南

又到了做毕业设计的季节,很多同学在选题和实际开发之间反复纠结。如果你问我要不要选大数据方向,我一般会反问一句:你是想选题的时候舒服,还是答辩的时候舒服?如果两者都想要,那像“基于HadoopSparkHive的空…

📅 2026/10/17 14:39:52
MORE NEWS

更多资讯

📰

27B 等九款本地模型测试结果:用 TaoToken 统一 Key 跑通多模型对比

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

📰

数据库原理实验入门:从安装GreatSQL到掌握SQL查询

简介:《数据库系统原理》课程实验报告完整呈现了郑州大学计算机与人工智能学院数据库原理实验的核心内容,适合正在学习DBMS概念、需要完成同类实验报告的本校学生或数据库初学者。报告以万里数据库GreatSQL为操作对象,完整记录了从虚拟机Cent…

📰

在VMware-Ubuntu18中轻松安装oneAPI和oneDNN:用TaoToken统一Key跑通icx编译验证

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

📰

ArcGIS空间分析本质:从画图到地理问题数学求解

简介:本资源是一份系统讲解ArcGIS空间分析核心功能的PPT教学课件,面向地理信息科学、测绘工程、城乡规划等专业的本科生及GIS初学者,旨在帮助学习者掌握空间分析的基本原理、工具模块与典型应用场景。课件内容覆盖矢量与栅格数据的空间分析方…

📰

MCP Server官网入口与TaoToken统一API通道配置指南

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

📰

DSP实现主动降噪的工程落地:采样率、定点LMS与实时闭环设计

简介:本资源是一份面向嵌入式音频处理与自适应信号处理方向学习者的完整项目实践资料,聚焦发动机等设备产生的周期性噪声问题,提供基于DSP的主动降噪系统从算法设计到硬件落地的全流程实现方案。内容涵盖反馈滤波-X-最小均方(FXLM…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬