拓冰建站拓冰建站
首页 / 资讯中心 / 正文

C语言qsort函数详解:从原理到实战,掌握万能排序技巧

1. 为什么说 qsort 是“能给万物排序”的函数在 C 语言的世界里排序是一个绕不开的经典话题。无论是处理学生成绩、整理文件列表还是对复杂结构体数组进行组织排序都是让数据变得有序、便于后续处理的关键一步。很多初学者会自己动手写冒泡排序、选择排序代码写了一大堆还容易出错性能也往往不尽如人意。而 C 语言标准库stdlib.h中提供的qsort函数就像一位深藏不露的“排序大师”它用一种极其通用和高效的方式解决了“给任何类型的数据排序”这个难题。“能给万物排序”这个说法听起来有点夸张但当你理解了qsort的核心设计思想后就会觉得这个形容非常贴切。它的强大之处在于“解耦”和“委托”。qsort函数本身只负责实现高效的快速排序算法但它完全不知道你要排序的数据是什么类型、比较的规则是什么。它把“如何比较两个元素”这个最核心、也最个性化的任务完全交给了调用者——也就是你通过一个叫做“比较函数”的回调函数来实现。qsort只告诉你“你给我一个数组的起始地址、元素个数、每个元素占多大内存再告诉我一个能比较两个元素的函数指针。剩下的事交给我。”这种设计是 C 语言面向过程编程中“函数指针”和“泛型编程”思想的经典体现。它让qsort摆脱了数据类型的束缚。你可以用它排序int、double、char这些基本类型也可以排序struct Student、struct Book这样的自定义结构体甚至可以对指针数组、字符串数组进行排序。只要你能定义出两个元素之间的大小关系qsort就能帮你排好序。这种通用性是那些写死了数据类型的排序函数无法比拟的。我刚开始接触qsort时也被它的函数原型吓到过觉得参数太多、指针绕来绕去。但真正用熟之后我发现它其实是“把复杂留给自己把灵活留给用户”的典范。你只需要精心编写一个正确的比较函数就能享受到标准库带来的、经过高度优化的排序性能。这比自己重复造轮子要可靠和高效得多。接下来我们就一层层剥开qsort的外壳看看这个“万能排序器”到底怎么用以及如何避开使用它时那些常见的“坑”。2. 深入拆解 qsort 的函数原型与参数要驾驭qsort第一步就是彻底理解它的函数原型。这个原型包含了它所有能力的秘密。我们来看一下void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));这个声明看起来有点复杂我们把它拆解成四个部分每一个参数都至关重要。2.1void *base: 指向待排序数组的通用指针base参数是一个void *类型的指针它指向你要排序的那个数组的第一个元素。void *在 C 语言中被称为“通用指针”或“无类型指针”。它就像一个“万能插座”可以接收任何类型数据的地址int*,char*,struct MyStruct*等。这里有一个关键点qsort通过这个指针只知道数组从哪里开始但它完全不知道数组里存的是什么类型的数据。这就是它“泛型”能力的基石。无论你传进来的是int数组、double数组还是结构体数组对qsort来说在排序算法内部它们都是一段连续的、由size参数指定大小的内存块。实操注意在传递这个参数时通常直接使用数组名。对于数组数组名在大多数情况下会退化为指向其首元素的指针。例如int arr[10];那么调用时base参数直接写arr即可。2.2size_t nmemb: 数组中元素的数量nmemb是number of members的缩写类型是size_t一种专门用于表示大小的无符号整数类型。它告诉qsort你的数组里总共有多少个元素需要排序。这个参数看起来简单但却是许多错误的源头。最常见的错误是“差一错误”Off-by-one error。比如你的数组有 10 个有效元素但你可能错误地传入了 11这会导致qsort访问到数组边界之外的内存引发未定义行为通常是程序崩溃。另一种情况是如果你只想排序数组的前一部分比如前 5 个元素那么这里就应该传入 5而不是数组的总容量 10。经验之谈我习惯在定义数组有效元素个数时使用一个单独的变量比如int count 10;然后在调用qsort时直接使用这个变量nmemb count。这样既能避免手动数数出错也方便后续如果元素数量动态变化时进行修改。2.3size_t size: 每个元素所占内存的字节数size参数指定了数组中每个元素占用的内存大小单位是字节。这是qsort能够在“无类型”的void*内存块中正确移动和交换元素的关键。qsort内部需要做这些事情比较两个元素、交换两个元素。它不知道元素是什么类型但它知道每个元素占size个字节。当它需要访问第i个元素时它会计算(char*)base i * size这个地址。因为char类型是 1 字节所以用char*进行指针运算可以精确地以字节为单位进行偏移。如何获取size最标准、最推荐的方法是使用sizeof运算符。例如排序int数组sizeof(int)排序double数组sizeof(double)排序struct Student数组sizeof(struct Student)一个经典错误对于字符串数组即char* strArray[]每个元素是一个char*指针所以size应该是sizeof(char*)而不是sizeof(char)或strlen(strArray[0])。混淆这一点会导致排序过程完全错乱。2.4int (*compar)(const void *, const void *): 灵魂所在的比较函数这是qsort最核心、也最需要你亲自定制的部分——函数指针。compar是一个指向函数的指针该函数接受两个const void*参数并返回一个int值。参数const void *a, *b:qsort在需要比较时会将两个待比较元素的地址传递给你的比较函数。注意这里传递的是指向元素的指针的指针。也就是说如果元素是int那么a就是一个指向某个int变量的指针即int*类型但被转换成了void*。由于是const void*你在这个函数内部不应该修改a和b所指向的内存。返回值int: 这个返回值决定了两个元素的顺序。如果a应该排在b之前函数应返回一个负整数通常用 -1 表示。如果a和b相等对于排序目的而言函数应返回0。如果a应该排在b之后函数应返回一个正整数通常用 1 表示。qsort根据这个返回值来调整元素的位置。整个快速排序的逻辑都依赖于你提供的这个比较规则。你可以通过编写不同的比较函数轻松实现升序、降序或者基于结构体中某个特定字段的排序。函数指针的写法int (*compar)(const void *, const void *)声明了一个名为compar的变量它是一个指针指向一个具有特定参数和返回类型的函数。在调用qsort时你只需要传入你编写的比较函数的函数名函数名本身就是一个指针。例如你写了一个比较函数叫compareInts那么调用时第四个参数就写compareInts不需要加括号和参数。理解了这四个参数你就掌握了调用qsort的全部语法。接下来最关键的一步就是学会如何为不同类型的数据编写正确的比较函数。3. 编写比较函数从基础类型到复杂结构体比较函数是qsort的灵魂也是使用者唯一需要精心编写的部分。它的核心任务就是在qsort交给它两个元素的地址void*类型后正确地解引用、比较并返回符合预期的整数。下面我们由浅入深看看各种场景下的写法。3.1 基础数据类型的比较整型、浮点型对于基本类型比较函数的编写通常很直观但需要注意类型转换和比较方式。整型升序排序示例int compareInts(const void *a, const void *b) { // 1. 将void指针转换为int指针 const int *pa (const int *)a; const int *pb (const int *)b; // 2. 解引用指针获取实际值并做减法 // 如果 *pa - *pb 0 返回负数表示a在前 // 如果 *pa - *pb 0返回0表示相等 // 如果 *pa - *pb 0 返回正数表示b在前 return *pa - *pb; }这是最经典的写法。利用两数相减其差的正负号正好符合比较函数的返回值要求。简洁高效。重要警告整型溢出的坑但是上面的写法存在一个巨大的隐患整数溢出。如果*pa是一个很大的正数例如INT_MAX而*pb是一个很大的负数例如INT_MIN那么*pa - *pb的结果会超出int类型能表示的范围发生溢出导致返回值错误排序结果不可预测。注意这是一个非常隐蔽的错误在数据值范围不大时可能不会暴露但一旦遇到边界情况程序就会产生诡异的排序错误。在编写生产代码时必须避免。安全的整型比较函数int compareIntsSafe(const void *a, const void *b) { const int *pa (const int *)a; const int *pb (const int *)b; if (*pa *pb) return -1; if (*pa *pb) return 1; return 0; }或者使用三目运算符但逻辑更清晰int compareIntsSafe(const void *a, const void *b) { const int *pa (const int *)a; const int *pb (const int *)b; return (*pa *pb) - (*pa *pb); // 巧妙且安全的写法 }(*pa *pb)和(*pa *pb)是布尔表达式结果为 1 或 0。这个表达式在pa pb时返回 1-01在pa pb时返回 0-1-1相等时返回 0-00。完全避免了减法溢出。浮点数比较示例 浮点数不能直接用相等或减法来比较因为存在精度问题。通常使用一个极小的误差范围EPS。#include math.h #define EPS 1e-9 int compareDoubles(const void *a, const void *b) { const double *pa (const double *)a; const double *pb (const double *)b; double diff *pa - *pb; if (fabs(diff) EPS) { // 绝对值小于误差认为相等 return 0; } else if (diff 0) { return 1; // a b } else { return -1; // a b } }降序排序怎么办非常简单只需要在比较函数中调换比较逻辑即可。例如整型降序int compareIntsDesc(const void *a, const void *b) { const int *pa (const int *)a; const int *pb (const int *)b; if (*pa *pb) return 1; // 原本ab返回-1现在返回1让a排后面 if (*pa *pb) return -1; // 原本ab返回1现在返回-1让a排前面 return 0; }或者更简单在升序比较函数的结果前加个负号。return -((*pa *pb) - (*pa *pb));。3.2 字符串数组的排序字符串数组有两种常见形式它们的排序方式不同极易混淆。情况一二维字符数组char strArr[N][LEN]这种数组的每个元素本身就是一个字符数组字符串。内存是连续分配的。char names[][20] {Alice, Bob, Charlie, David}; // 每个元素是 char[20]即一个20字节的内存块里面存放了一个字符串。比较函数需要将void*转换为char (*)[20]指向长度为20的字符数组的指针但更简单的做法是转成char*因为数组名也是首元素地址。int compareStrings2D(const void *a, const void *b) { // a和b是指向每个字符串即char[20]的指针 // 我们需要的是字符串的起始地址直接强制转换即可 const char *pa (const char *)a; const char *pb (const char *)b; // 使用标准库函数strcmp进行比较 return strcmp(pa, pb); }调用qsort时size参数应为每个字符串占用的内存大小sizeof(names[0])或20。情况二指针数组char* strArr[N]这种数组的每个元素是一个char*指针指向存储在别处可能是只读常量区或动态堆内存的字符串。const char *names[] {Alice, Bob, Charlie, David}; // 每个元素是 char*即一个指针大小为 sizeof(char*)这时qsort传递给比较函数的是指针的地址。我们需要先取出这个指针再用strcmp比较指针所指向的字符串。int compareStringPtrs(const void *a, const void *b) { // a和b是指向“数组元素”的指针。数组元素是 char*。 // 所以先得到 char**再解引用得到 char* const char **pa (const char **)a; const char **pb (const char **)b; // 现在 *pa 和 *pb 才是真正的字符串指针 return strcmp(*pa, *pb); }这是最容易出错的地方很多人会写成return strcmp((char*)a, (char*)b);这会导致比较的是指针值本身的内存内容而不是它们指向的字符串排序结果完全错误。 调用qsort时size参数应为sizeof(char*)。3.3 结构体数组的多级排序这是qsort真正展现威力的地方。假设我们有一个学生结构体typedef struct { char name[50]; int score; int age; } Student;我们有一个Student students[100];数组。按单个字段排序例如按分数降序int compareByScoreDesc(const void *a, const void *b) { const Student *pa (const Student *)a; const Student *pb (const Student *)b; // 分数高的排前面所以是降序 if (pa-score pb-score) return -1; if (pa-score pb-score) return 1; return 0; }多级排序例如主要按分数降序分数相同则按年龄升序 多级排序的逻辑是先比较第一级关键字段如果分出高低就直接返回结果如果相等再比较第二级关键字段。int compareByScoreThenAge(const void *a, const void *b) { const Student *pa (const Student *)a; const Student *pb (const Student *)b; // 第一级分数降序 if (pa-score pb-score) return -1; if (pa-score pb-score) return 1; // 分数相同进入第二级年龄升序 if (pa-age pb-age) return -1; if (pa-age pb-age) return 1; // 分数和年龄都相同 return 0; }如果需要更多级依此类推。这种写法清晰且高效qsort在元素交换时会将整个结构体作为一个内存块进行移动保证了所有字段的一致性。4. 实战演练qsort 的完整使用流程与案例理解了原理和比较函数的写法我们通过几个完整的案例把qsort的使用流程串起来。我会在案例中穿插一些调试技巧和容易忽略的细节。4.1 案例一对整数数组进行快速排序这是一个最基础的例子但涵盖了所有步骤。#include stdio.h #include stdlib.h // 包含 qsort 的原型 // 安全的整型升序比较函数 int compareInt(const void *a, const void *b) { const int *pa (const int *)a; const int *pb (const int *)b; return (*pa *pb) - (*pa *pb); } // 用于打印数组的辅助函数 void printArray(const int *arr, size_t n) { for (size_t i 0; i n; i) { printf(%d , arr[i]); } printf(\n); } int main() { int numbers[] {34, 12, 5, 66, -2, 90, 7}; size_t count sizeof(numbers) / sizeof(numbers[0]); // 计算元素个数 printf(原始数组: ); printArray(numbers, count); // 调用 qsort // numbers: 数组首地址 // count: 元素个数 // sizeof(numbers[0]): 每个元素的大小 // compareInt: 比较函数 qsort(numbers, count, sizeof(numbers[0]), compareInt); printf(排序后数组: ); printArray(numbers, count); return 0; }关键点解析#include stdlib.h是必须的。sizeof(numbers) / sizeof(numbers[0])是计算数组元素个数的经典宏可以避免手动修改。如果数组作为参数传递给函数此时会退化为指针这个技巧就失效了所以元素个数通常需要额外传递。调用qsort后原数组numbers的内容就被原地修改了。qsort是“就地排序”不需要额外的返回数组。4.2 案例二按字典序对字符串指针数组排序这个案例演示了如何对char*数组进行排序并重点区分了size参数。#include stdio.h #include stdlib.h #include string.h // 比较函数注意参数是指向指针的指针 int compareString(const void *a, const void *b) { // 正确写法先转换为 char**再解引用 const char **pa (const char **)a; const char **pb (const char **)b; return strcmp(*pa, *pb); // 比较字符串内容 } int main() { // 这是一个指针数组每个元素指向一个字符串常量 const char *fruits[] {apple, orange, banana, grape, pear}; size_t count sizeof(fruits) / sizeof(fruits[0]); printf(排序前:\n); for (size_t i 0; i count; i) { printf(%s\n, fruits[i]); } // 调用 qsort // fruits: 指针数组的首地址 // count: 指针的个数 // sizeof(fruits[0]): 每个指针的大小即 sizeof(char*) // compareString: 比较函数 qsort(fruits, count, sizeof(fruits[0]), compareString); printf(\n按字典序排序后:\n); for (size_t i 0; i count; i) { printf(%s\n, fruits[i]); } return 0; }输出结果排序前: apple orange banana grape pear 按字典序排序后: apple banana grape orange pear核心要点这里的fruits数组里存放的是指针排序交换的是这些指针的值即地址而不是字符串本身的内容。所以排序效率很高只是重新排列了指针的顺序。字符串常量本身在内存中的位置没有改变。4.3 案例三对复杂结构体进行多级排序我们用一个更贴近实际的学生管理系统案例演示多级排序和动态数组的排序。#include stdio.h #include stdlib.h #include string.h typedef struct { int id; char name[50]; float gpa; // 平均绩点 } Student; // 比较函数先按gpa降序gpa相同则按id升序 int compareStudent(const void *a, const void *b) { const Student *sa (const Student *)a; const Student *sb (const Student *)b; // 由于gpa是浮点数不能直接相减比较 if (sa-gpa sb-gpa) return -1; // gpa高的在前 if (sa-gpa sb-gpa) return 1; // gpa相同比较id return sa-id - sb-id; // id小的在前这里id是整数减法安全 } void printStudents(const Student *students, int n) { printf(ID\tName\t\tGPA\n); printf(----------------------------\n); for (int i 0; i n; i) { printf(%d\t%-10s\t%.2f\n, students[i].id, students[i].name, students[i].gpa); } } int main() { // 假设从文件或网络动态加载了一批学生数据 Student class[] { {101, Alice, 3.8}, {102, Bob, 3.5}, {103, Charlie, 3.8}, // 与Alice GPA相同 {104, David, 4.0}, {105, Eve, 3.5} }; int studentCount sizeof(class) / sizeof(class[0]); printf(原始学生列表:\n); printStudents(class, studentCount); qsort(class, studentCount, sizeof(class[0]), compareStudent); printf(\n排序后学生列表 (按GPA降序GPA相同按ID升序):\n); printStudents(class, studentCount); return 0; }输出结果原始学生列表: ID Name GPA ---------------------------- 101 Alice 3.80 102 Bob 3.50 103 Charlie 3.80 104 David 4.00 105 Eve 3.50 排序后学生列表 (按GPA降序GPA相同按ID升序): ID Name GPA ---------------------------- 104 David 4.00 101 Alice 3.80 103 Charlie 3.80 102 Bob 3.50 105 Eve 3.50可以看到David GPA最高排第一。Alice和Charlie GPA相同(3.8)则按ID升序101的Alice排在103的Charlie前面。Bob和Eve同理。动态数组的排序如果学生数据是动态分配在堆上的例如Student *class malloc(n * sizeof(Student));qsort的调用方式完全一样。qsort不关心内存来自栈还是堆它只认起始地址和内存布局。5. 进阶话题qsort 的内部机制与性能考量虽然我们不需要自己实现qsort但了解其内部机制和性能特点能帮助我们在关键时刻做出正确决策并理解一些看似奇怪的现象。5.1 qsort 不一定是“快速排序”这是一个很有趣的点。C 语言标准如 C99、C11只规定了qsort的函数原型和行为并没有规定它必须使用快速排序算法。标准只要求它以一种“未指定”的方式对数组进行排序并且时间复杂度为 O(N log N) 量级。为什么叫qsort(Quick Sort) 呢这主要是历史原因。在早期的 C 语言实现中它通常使用快速排序算法。快速排序在平均情况下非常高效O(N log N)而且是原地排序空间复杂度为 O(log N)递归栈。但是快速排序有一个著名的弱点在最坏情况下例如数组已经有序或逆序其时间复杂度会退化到 O(N²)。因此现代的标准库实现如 glibc中的qsort通常是一种混合排序算法以规避最坏情况。例如glibc 的实现对于小数组元素数量少会使用插入排序。因为插入排序在小数据量时常数因子小且是稳定排序。对于大数据集会使用快速排序但会精心选择枢轴pivot比如使用“三数取中法”来尽量避免最坏情况。当快速排序的递归深度过深时意味着遇到了近似最坏情况可能会切换到堆排序Heap Sort。堆排序的最坏时间复杂度也是 O(N log N)可以保证性能下限。所以当你调用qsort时你实际上调用的是一个经过高度优化、针对不同数据规模自适应选择策略的“排序工具箱”。这保证了它在绝大多数实际场景下都有良好且稳定的性能。5.2 稳定性问题qsort 是不稳定排序排序算法的“稳定性”是指如果两个元素比较结果相等排序后它们的相对顺序是否保持不变。qsort不是稳定排序。这是由快速排序算法的本质决定的。在分区partition过程中与枢轴相等的元素可能会被交换到任意一边从而打乱它们原有的顺序。这意味着什么回顾我们“多级排序”的例子。我们实现了“先按 GPA 降序GPA 相同再按 ID 升序”。这个逻辑是在一个比较函数里一次性完成的。如果我们分两次调用qsort会怎样// 错误做法试图通过两次调用实现多级排序 qsort(students, n, sizeof(Student), compareByGPA); // 第一次只按GPA排 qsort(students, n, sizeof(Student), compareByID); // 第二次只按ID排第二次排序会完全按照 ID 重新排列数组这将彻底破坏第一次按 GPA 排序的结果。最终数组只是按 ID 排序了而已。正确做法必须像我们之前那样在一个比较函数compareByScoreThenAge中定义好所有排序规则。这是实现多级排序的唯一可靠方法。如果确实需要稳定排序怎么办C 标准库没有提供稳定的qsort。你可以自己实现归并排序等稳定算法。在比较函数中当主要字段相等时加入一个永远不会相等的次要字段比如唯一的 ID来“强制”稳定。但这要求你的数据结构本身包含这样的唯一标识。5.3 性能优化与小技巧比较函数的效率至关重要qsort在排序过程中会成千上万次地调用你的比较函数。因此比较函数应尽可能简单、高效。避免在比较函数内部进行复杂的计算、动态内存分配或 I/O 操作。对于结构体排序直接访问字段比通过函数调用获取字段要快得多。避免在比较函数中调用昂贵函数例如如果要对字符串排序而字符串比较本身strcmp就是 O(N) 的操作。对于非常长的字符串列表这可能会成为瓶颈。有时可以考虑在排序前创建一个包含字符串指针和其哈希值或前缀的结构体数组先对这个辅助数组排序再根据排序结果调整原数组。空间与时间的权衡qsort是原地排序除了递归栈外几乎不需要额外空间。如果你有严格的内存限制qsort是很好的选择。如果你需要稳定排序或者数据是链表形式就需要考虑其他算法或自己实现。调试比较函数一个写错的比较函数可能导致排序结果乱序、程序崩溃访问非法内存或陷入无限循环如果比较函数对某些输入返回不一致的结果例如ab和ba不互反。调试时可以在比较函数开头打印传入的地址和值确保你解引用和转换是正确的。对于字符串排序尤其要检查你转换的是char**还是char*。6. 常见陷阱与排错指南即使理解了原理在实际使用qsort时依然会遇到一些棘手的错误。下面是我总结的几个最常见的问题和排查思路。6.1 段错误Segmentation Fault这是最直接的运行时错误通常是由于内存访问越界造成的。原因1nmemb或size参数错误。排查检查nmemb是否大于数组实际元素个数。检查size是否等于sizeof(数组元素类型)。对于结构体确保sizeof的是结构体本身而不是其内部的某个指针。案例对char* arr[]使用sizeof(arr[0])得到的是char*的大小通常4或8字节这是正确的。如果错误地用了strlen(arr[0])size会是一个很小的数导致qsort在内存中疯狂错位访问。原因2比较函数中的指针转换错误。排查这是字符串指针数组排序的经典错误。在比较函数中你是否正确地将const void*转换为了const char**然后再解引用还是错误地直接转换成了const char*在比较函数开头打印*((const char**)a)和*((const char**)b)看看是不是你期望的字符串。原因3数组本身已损坏或指针无效。排查在调用qsort前确保数组内存是有效的。如果数组是动态分配的确保没有提前释放。如果数组作为函数参数传递确保没有发生数组退化为指针后丢失大小信息的问题。6.2 排序结果不正确或乱序排序完成了但结果不是预期的顺序。原因1比较函数的返回值逻辑错误。排查牢记规则a应排在b之前时返回负值。检查你的比较逻辑是否写反了。特别是降序排序时是否在应该返回1的时候返回了-1。编写一个简单的测试用例手动调用你的比较函数验证返回值是否正确。原因2整数溢出。排查如前所述在整型比较函数中使用return *pa - *pb;可能导致溢出。改用安全的比较方式if (*pa *pb) return 1; ...或return (*pa *pb) - (*pa *pb);。原因3浮点数比较使用了或直接相减。排查浮点数有精度误差判断相等应使用fabs(a-b) EPS。直接返回(int)(*pa - *pb)更是错误因为浮点数差可能是0.2强制转成int后变成0导致本应不等的元素被判定为相等。原因4多级排序逻辑错误。排查确保你的多级排序逻辑是“短路”的。即先判断第一级字段如果不相等就立即返回只有相等时才进入下一级判断。逻辑嵌套错误会导致排序优先级混乱。6.3 无限循环或程序卡死这种情况相对少见但一旦发生就很严重。原因比较函数违反了“严格弱序”规则。解释一个有效的比较函数必须满足几个数学性质比如自反性、反对称性、传递性。简单来说就是比较结果必须是一致的、可预测的。例如如果compare(a, b) 0那么必须有compare(b, a) 0。如果compare(a, b) 0那么compare(b, a)也必须等于 0。如果compare(a, b) 0且compare(b, c) 0那么必须有compare(a, c) 0。排查检查你的比较函数特别是当涉及浮点数有NaN、或比较逻辑非常复杂时是否可能对某些特定的输入返回不一致的结果。例如一个存在未初始化字段的结构体其比较结果可能是随机的这会导致排序算法陷入混乱。6.4 通用调试步骤当遇到qsort相关问题时可以按以下步骤排查隔离问题创建一个最小的、可复现的测试程序。只包含有问题的数组、比较函数和qsort调用。移除所有无关代码。验证输入在调用qsort前打印数组的所有元素确认数据是你期望的。单元测试比较函数写一个简单的测试手动调用比较函数传入几组你知道大小关系的元素打印返回值看是否符合预期正、负、零。检查参数再次确认qsort的四个参数base地址正确吗、nmemb个数对吗、size大小对吗、compar函数名写对了吗。使用调试器在比较函数内设置断点观察每次被调用时传入的a和b指针值以及解引用后的内容。这能最直观地发现问题。掌握了这些排查方法你就能独立解决大部分qsort使用过程中遇到的问题。这个函数虽然接口简单但细节决定成败尤其是在处理复杂数据类型时对指针和内存的理解至关重要。花时间把这些基础打牢以后遇到任何排序需求你都能自信地拿出qsort这把瑞士军刀游刃有余地解决问题。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门