铁门关市网站建设_网站建设公司_数据统计_seo优化
2025/12/29 8:29:36 网站建设 项目流程

系列文章目录

学习系列文章:
【初识C语言】选择结构(if语句和switch语句)详细解答
【初识C语言】循环结构(while语句、do…while语句和for语句)详细解答

实战项目文章:
【初识C语言】经典扫雷C语言实战(原码+解析),看完就能上手拆解与修改


文章目录

  • 系列文章目录
  • 前言
  • 一、qsort 函数是什么?
    • 1、核心定位
    • 2、函数原型(牢记4个参数!)
    • 3、4 个参数通俗解读
  • 二、qsort 的核心:自定义比较函数(重点中的重点!)
    • 1. 比较函数通用规则
    • 2.分类型写比较函数
      • 场景 1:排序 int 类型(最基础,入门必学)
      • 场景 2:排序 float/double 类型(避坑!不能直接返回差值)
      • 场景 3:排序 char 类型(单个字符,按 ASCII 码排序)
      • 场景 4:排序字符串类型(两种写法,重点区分!)
        • 写法 1:二维 char 数组排序(常用,每行一个字符串)
        • 写法 2:char * 指针数组排序(推荐,节省内存)
      • 场景 5:排序结构体类型(进阶用法,面试高频)
        • 写法 1:按结构体 int 成员排序(按年龄排序)
        • 写法 2:按结构体字符串成员排序(按姓名字典序排序)
  • 三、qsort 通用技巧 & 避坑总结
    • 1. 通用技巧
    • 2. 避坑指南(必看!)
  • 总结

前言

小伙伴们!今天给大家分享一个 C 语言里的 “排序万能工具”——qsort函数。平时我们写排序,要么手写冒泡、快排,还得针对 int、char、结构体分别改代码,巨麻烦!而qsort函数能一键搞定所有数据类型的排序,高效又省心,这篇文章就带大家吃透它的含义和用法,希望对大家有用~


一、qsort 函数是什么?

1、核心定位

qsort是 C 语言标准库中的通用快速排序函数,定义在 <stdlib.h> 头文件中,支持对任意类型的数组进行排序(int、float、字符串、结构体统统不在话下),底层实现了快速排序算法,时间复杂度接近 O (NlogN),效率拉满!

2、函数原型(牢记4个参数!)

先看官方原型,对应我代码里的注释:

voidqsort(void*base,// 要排序的数组首地址(直接传数组名就行,比如arr_int)size_tnitems,// 数组元素个数(通用写法:sizeof(arr)/sizeof(arr[0]))size_tsize,// 单个元素的字节大小(比如sizeof(int)、sizeof(arr[0]))int(*compar)(constvoid*,constvoid*)// 自定义比较函数指针(核心!决定排序规则));

3、4 个参数通俗解读

参数名通俗含义代码示例(int 数组)
base告诉 qsort “要排序的数组在哪”,数组名本身就是首地址,直接传即可arr_int(对应 int arr_int [])
nitems告诉 qsort “数组有多少个元素”,可以用 sizeof 计算,通用又方便sz = sizeof(arr_int)/sizeof(arr_int[0])
size告诉 qsort “每个元素占多少内存”,推荐用sizeof(arr[0])(适配类型变更)si = sizeof(int) 或 si = sizeof(arr_int[0])
compar告诉 qsort “怎么比较两个元素”,需要我们自定义函数,决定是正序还是降序compar_int(自定义的 int 比较函数)

二、qsort 的核心:自定义比较函数(重点中的重点!)

qsort之所以能适配所有数据类型,全靠这个比较函数。它有固定的格式要求,也有通用的排序规则,我们先掌握基础,再分类型拆解。

1. 比较函数通用规则

格式固定:返回值是int,两个参数都是const void*(通用指针,兼容所有类型,不能直接解引用,必须强制转换);

返回值含义(敲黑板!):这是排序的关键,记死这三句话:

  1. 返回负数:第一个参数对应的元素,排在第二个参数对应的元素前面(正序核心);
  2. 返回0:两个元素相等,顺序不变;
  3. 返回正数:第一个参数对应的元素,排在第二个参数对应的元素后面

2.分类型写比较函数

下面结合我整理的代码,逐个讲解不同数据类型的比较函数写法,从简单到复杂,循序渐进~

场景 1:排序 int 类型(最基础,入门必学)

这是最简单的场景,比较函数直接返回两个数的差值即可,无精度丢失。

#include<stdio.h>#include<stdlib.h>// 自定义int类型比较函数(正序)intcompar_int(constvoid*a,constvoid*b){// 步骤1:const void* 强制转换为 const int*(还原int指针类型)// 步骤2:解引用,得到int类型的值// 步骤3:返回差值,满足比较函数返回规则return*(constint*)a-*(constint*)b;// 正序// return *(const int*)b - *(const int*)a; // 降序,颠倒a和b即可}intmain(){intarr_int[]={3,1,2,5,7,9,10,6,8,4};size_tsz=sizeof(arr_int)/sizeof(arr_int[0]);// 元素个数size_tsi=sizeof(arr_int[0]);// 单个元素大小inti=0;qsort(arr_int,sz,si,compar_int);// 调用qsort// 打印结果for(i=0;i<sz;i++){printf("%d ",arr_int[i]);}return0;}

关键细节:const void* a不能直接解引用,必须先转为const int*,再用*解引用获取具体数值。

场景 2:排序 float/double 类型(避坑!不能直接返回差值)

很多新手会踩坑:直接返回 f1 - f2 ,但浮点型差值转 int 会截断小数(比如 1.25 和 1.26 的差值 0.01,转 int 后变成 0,qsort 无法判断大小),必须显式判断!

#include<stdio.h>#include<stdlib.h>intcompar_float(constvoid*a,constvoid*b){// 先转换并解引用,获取浮点值floatval_a=*(constfloat*)a;floatval_b=*(constfloat*)b;// 显式判断大小,返回1/-1/0,避免精度丢失if(val_a>val_b){return1;// a在后,正序}elseif(val_a<val_b){return-1;// a在前,正序}else{return0;// 相等}// 降序只需颠倒判断逻辑://if (val_b > val_a) {// return 1;//}//else if (val_b < val_a) {// return -1;//}//else return 0;}intmain(){floatarr_float[]={1.25f,1.26f,2.58f,9.48f,4.37f};size_tsz=sizeof(arr_float)/sizeof(arr_float[0]);size_tsf=sizeof(arr_float[0]);inti=0;qsort(arr_float,sz,sf,compar_float);for(i=0;i<sz;i++){printf("%.2f ",arr_float[i]);}return0;}

避坑指南:double类型的比较函数和float完全一致,只需把类型换成double即可,同样不能直接返回差值!

场景 3:排序 char 类型(单个字符,按 ASCII 码排序)

char本质是 ASCII 码整数,比较逻辑和int一致,直接返回差值即可,还能实现不区分大小写排序(拓展用法)。

#include<stdio.h>#include<stdlib.h>#include<ctype.h>// tolower依赖头文件intcompar_char(constvoid*a,constvoid*b){// 正序:按ASCII码从小到大(A<Z<a<z)return*(constchar*)a-*(constchar*)b;// 不区分大小写排序:先转小写再比较// return tolower(*(const char*)a) - tolower(*(const char*)b);// 降序:return *(const char*)b - *(const char*)a;}intmain(){chararr_char[]={'b','A','r','E','P','z','a','o','t','W'};size_tsz=sizeof(arr_char)/sizeof(arr_char[0]);size_tsc=sizeof(arr_char[0]);inti=0;qsort(arr_char,sz,sc,compar_char);for(i=0;i<sz;i++){printf("%c ",arr_char[i]);}return0;}

小知识:ASCII 码中,大写字母 < 小写字母,数字 < 字母。

场景 4:排序字符串类型(两种写法,重点区分!)

字符串排序是高频场景,分为「二维 char 数组」和「char * 指针数组」,两者的比较函数写法不同,千万别搞混!

写法 1:二维 char 数组排序(常用,每行一个字符串)
#include<stdio.h>#include<stdlib.h>#include<string.h>// strcmp依赖头文件intcompar_str_2d(constvoid*a,constvoid*b){// 直接转换为const char*,传入strcmp比较完整字符串returnstrcmp((constchar*)a,(constchar*)b);// 正序// return strcmp((const char*)b, (const char*)a); // 降序}intmain(){// 二维char数组:5行20列,每行存一个字符串chararr_str[5][20]={"hello","apple","tiger","banana","marry"};size_tsz=sizeof(arr_str)/sizeof(arr_str[0]);size_tsc=sizeof(arr_str[0]);// 单个元素是一行,大小为20inti=0;qsort(arr_str,sz,sc,compar_str_2d);for(i=0;i<sz;i++){printf("%s ",arr_str[i]);}return0;}

关键细节:a/b直接指向每行字符串的首地址,转换为const char*后可直接传入strcmp(strcmp专门比较字符串,返回值符合 qsort 要求)。

写法 2:char * 指针数组排序(推荐,节省内存)
#include<stdio.h>#include<stdlib.h>#include<string.h>intcompar_str_ptr(constvoid*a,constvoid*b){// 步骤1:转换为const char**(指向char*指针的指针)// 步骤2:解引用,得到const char*(字符串首地址)// 步骤3:用strcmp比较returnstrcmp(*(constchar**)a,*(constchar**)b);// 正序// return strcmp(*(const char**)b, *(const char**)a); // 降序}intmain(){// char*指针数组:存储字符串常量的首地址char*arr_str[]={"hello","apple","tiger","banana","marry"};size_tsz=sizeof(arr_str)/sizeof(arr_str[0]);size_tsc=sizeof(arr_str[0]);// 单个元素是char*指针,大小4/8字节inti=0;qsort(arr_str,sz,sc,compar_str_ptr);for(i=0;i<sz;i++){printf("%s ",arr_str[i]);// arr_str[i] 等价于 *(arr_str + i)}return0;}

关键细节:a/b指向的是char*指针(数组元素),因此必须先转为const char**,解引用后才能得到字符串首地址,传入strcmp。

场景 5:排序结构体类型(进阶用法,面试高频)

结构体排序需按指定成员排序,分为「int 成员」和「字符串成员」,写法略有差异,但核心不变。

写法 1:按结构体 int 成员排序(按年龄排序)
#include<stdio.h>#include<stdlib.h>// 定义学生结构体structstudent{charname[20];intage;};intcompar_stu_age(constvoid*a,constvoid*b){// 两种等价写法,任选其一// 写法1:先解引用指针,再用.访问成员return(*(conststructstudent*)a).age-(*(conststructstudent*)b).age;// 写法2:先转换为指针,再用->访问成员(更简洁)// return ((const struct student*)a)->age - ((const struct student*)b)->age;}intmain(){structstudentarr_stu[]={{"zhangsan",18},{"lisi",20},{"wangwu",21},{"zhaoliu",17}};size_tst=sizeof(arr_stu)/sizeof(arr_stu[0]);size_tsu=sizeof(arr_stu[0]);// 单个结构体大小inti=0;qsort(arr_stu,st,su,compar_stu_age);for(i=0;i<st;i++){// 两种等价访问方式printf("姓名:%s\t,年龄:%d\n",arr_stu[i].name,arr_stu[i].age);// printf("姓名:%s\t,年龄:%d\n", (&arr_stu[i])->name, (&arr_stu[i])->age);}return0;}

关键细节:( * (struct student * )a).age 等价于 ((struct student * )a)->age,后者更简洁,推荐使用;正序 / 降序同样只需颠倒差值顺序(即改变a/b位置)。

写法 2:按结构体字符串成员排序(按姓名字典序排序)
#include<stdio.h>#include<stdlib.h>#include<string.h>structstudent{charname[20];intage;};intcompar_stu_name(constvoid*a,constvoid*b){// 两种等价写法,任选其一returnstrcmp(((conststructstudent*)a)->name,((conststructstudent*)b)->name);// return strcmp((*(const struct student*)a).name, (*(const struct student*)b).name);}intmain(){structstudentarr_stu[]={{"Zhangsan",18},{"alice",20},{"wangwu",21},{"zhaoliu",17}};size_tst=sizeof(arr_stu)/sizeof(arr_stu[0]);size_tsu=sizeof(arr_stu[0]);inti=0;qsort(arr_stu,st,su,compar_stu_name);for(i=0;i<st;i++){printf("姓名:%s\t,年龄:%d\n",arr_stu[i].name,arr_stu[i].age);}return0;}

关键细节:结构体字符串成员必须用strcmp比较,不能直接用==或差值比较!

三、qsort 通用技巧 & 避坑总结

1. 通用技巧

  • 元素个数计算:用 sizeof(arr)/sizeof(arr[0]),无需手动修改,适配所有数组;
  • 单个元素大小:优先用 sizeof(arr[0]),比直接写sizeof(int)更灵活,数组类型变更时无需修改;
  • 访问方式等价性:
    1. arr[i] 等价于 *(arr + i)(数组下标与指针偏移);
    1. arr[i].member 等价于 (&arr[i])->member(结构体变量与指针的访问转换);
    1. (*p).member 等价于 p->member(结构体指针的两种访问方式)。

2. 避坑指南(必看!)

  1. float/double类型:绝对不能直接返回差值,必须显式判断大小,避免精度丢失;
  2. 字符串类型:必须用strcmp比较完整字符串,不能直接比较指针或单个字符;
  3. void*指针:不能直接解引用,必须先强制转换为对应类型,再解引用;
  4. 结构体访问:变量用.,指针用->,不能随意互换;
  5. 比较函数返回值:必须遵循 “负数在前、正数在后” 的规则,否则排序结果异常。

总结

qsort函数是 C 语言排序的 “万能钥匙”,核心在于自定义比较函数,只要掌握了不同数据类型的转换技巧和比较逻辑,就能轻松搞定所有排序场景。总结一下核心要点:

  1. qsort 4 个参数:首地址、元素个数、单个元素大小、比较函数;
  2. 比较函数:固定格式,返回值决定排序顺序,不同数据类型只需调整转换和比较逻辑;
  3. 高频场景:int(差值)、float(显式判断)、字符串(strcmp)、结构体(按成员比较);
  4. 通用技巧:优先用sizeof(arr[0]),牢记等价访问方式,避开精度丢失、类型不匹配的坑。

掌握 qsort,不仅能节省手写排序的时间,还能应对学习和面试中的各种排序场景,小伙伴们抓紧在编译器里跑一遍加深理解吧~

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询