当前位置: 首页 > news >正文

C语言指针:从零掌握指针(5) 完结篇

文章目录

  • C语言指针:从零掌握指针(5) 完结
    • 前言
    • 一、回调函数
      • 1.1 什么是回调函数?
      • 1.2 使用回调函数
    • 二、qsort函数
      • 2.1 qsort基础
      • 2.2 排序整形数据
      • 2.3 排序浮点型数据
      • 2.4 排序结构数据
    • 三,qsort函数模拟实现

C语言指针:从零掌握指针(5) 完结

前言

本篇是C语言指针知识的收尾。我们学习回调函数,qsort函数及其模拟实现

一、回调函数

1.1 什么是回调函数?

回调函数就是⼀个通过函数指针调用的函数。

把一个函数,当作参数传给另一个函数,在合适的时机被 “回头调用”,这个函数就叫回调函数。

如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数
回调函数不是由该函数的实现直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

这里我们举个生活中的例子加以理解:

你去奶茶店点单:

  1. 你点奶茶(主函数)
  2. 你跟店员说:做好了喊我一声(这就是回调函数)
  3. 你去玩手机(主函数继续执行)
  4. 奶茶做好 → 店员喊你(调用回调函数)

1.2 使用回调函数

第13讲中我们写的计算机的实现的代码中,红色框中的代码是重复出现的,其中虽然执行计算的逻辑是区别的,但是输入输出操作是冗余的,有没有办法,简化⼀些呢?
因为红色框中的代码,只有调用函数的逻辑是有差异的,我们可以把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能

如图:

实现:

voidmenu(){printf("--------- 计算器 ---------\n");printf("----- 1. add 2. sub ----\n");printf("----- 3. mul 4. div ----\n");printf("----- 0. exit ----\n");printf("---------------------------\n");}intAdd(intx,inty){returnx+y;}intSub(intx,inty){returnx-y;}intMul(intx,inty){returnx*y;}intDiv(intx,inty){returnx/y;}//计算函数 - 根据参数的不同,就能执行不同的运算voidcalc(int(*pf)(int,int)){intx=0,y=0;intr=0;printf("请输入两个操作数:");scanf("%d %d",&x,&y);r=pf(x,y);printf("%d\n",r);}intmain(){intinput=0;do{menu();printf("选择:");scanf("%d",&input);switch(input){case1:calc(Add);break;case2:calc(Sub);break;case3:calc(Mul);break;case4:calc(Div);break;case0:printf("退出计算器\n");break;default:printf("选择错误\n");![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/785781552e424c8eb64cd4f9e9a143e5.png#pic_center)break;}}while(input);return0;}

二、qsort函数

在C语言中,也有一个函数qsort内部就使用了回调函数
之前我们写的冒泡排序

voidbubble_sort(intarr[],intsz)

函数参数的类型为int ,只能处理整型数组的排序,显然这是有局限的

我们如何优化呢?
恰好qsort函数可以排序任意类型的数据
下面我们来学习qsort函数

2.1 qsort基础

头文件 + 函数原型:

#include<stdlib.h>voidqsort(void*base,size_tnum,size_tsize,int(*cmp)(constvoid*,constvoid*));

参数解析:

  • *void base:要排序数组首地址,万能指针,能接收任意类型数组
  • size_t num:数组元素个数
  • size_t size:单个元素占用字节大小
  • int (*cmp)(…):比较函数指针(核心!回调函数)

比较规则:

intcmp(constvoid*a,constvoid*b){// 1. 强制转成你要排序的类型// 2. 写比较逻辑}

返回值:

  • 返回负数:a 放前面(a < b)
  • 返回 0:相等不动
  • 返回正数:b 放前面(a > b)

升序:*a - *b
降序:*b - *a

原理:

  • qsort 内部是快速排序算法
  • 它不知道你排什么类型,所以用 void* 接收
  • 排序时不知道怎么比大小,所以必须让你传回调比较函数
  • 排序过程中反复调用你写的 cmp 函数判断先后顺序

易错:

  • 第三个参数必须写 sizeof(元素类型),不能乱写
  • void* 必须强制类型转换才能取值
  • 浮点型禁止直接相减做返回值
  • 字符串数组是char**强转,不是char*
  • 比较函数参数必须加 const 规范写法

2.2 排序整形数据

//测试qsort来排序整型数组//是用来比较p1和p2指向的两个整型值的//p1是指向一个整数的//p2是指向一个整数的#include<stdlib.h>#include<stdio.h>voidprint_arr(intarr[],intsz){inti=0;for(i=0;i<sz;i++){printf("%d ",arr[i]);}printf("\n");}intcmp_int(constvoid*p1,constvoid*p2){return*(int*)p1-*(int*)p2;//升序//降序:return *(int*)p1 - * (int*)p2}voidtest1(){intarr[]={3,1,8,6,0,9,4,2,7,5};intsz=sizeof(arr)/sizeof(arr[0]);//默认是升序的排序qsort(arr,sz,sizeof(arr[0]),cmp_int);print_arr(arr,sz);}intmain(){test1();return0;}

2.3 排序浮点型数据

//浮点数测试:intcmp_float(constvoid*p1,constvoid*p2){if(*(float*)p1<*(float*)p2)return-1;elseif(*(float*)p1>*(float*)p2)return1;elsereturn0;}//测试的qsort排序浮点型数据voidtest2(){floatarr[]={2.0,1.0,5.0,7.0,3.0};intsz=sizeof(arr)/sizeof(arr[0]);qsort(arr,sz,sizeof(arr[0]),cmp_float);}

2.4 排序结构数据

//排序结构数据structStu{charname[20];intage;};intcmp_stu_by_age(constvoid*p1,constvoid*p2){return(*(structStu*)p1).age-(*(structStu*)p2).age;}//测试qsort函数来排序结构体数据voidtest3(){structStuarr[]={{"zhangsan",18},{"lisi",25},{"wangwu",12}};intsz=sizeof(arr)/sizeof(arr[0]);qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_age);}// 两个字符串比较大小的方法:strcmp//strcmp(str1, str2)//str1 > str2, 返回 >0//str1 == str2, 返回 0//str1 < str2, 返回 <0#include<string.h>intcmp_stu_by_name(constvoid*p1,constvoid*p2){returnstrcmp(((structStu*)p1)->name,((structStu*)p2)->name);}voidtest4(){structStuarr[]={{"zhangsan",18},{"lisi",25},{"wangwu",12}};intsz=sizeof(arr)/sizeof(arr[0]);qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_name);}intmain(){//test1();//test2();//test3();test4();//struct Stu s = { "cuihua", 18 };////printf("%s\n", s.name);////printf("%d\n", s.age);////结构体变量.成员名////struct Stu* ps = &s;////-> 结构体成员访问操作符////结构体指针->成员名//printf("%s\n", ps->name);//(*ps).name//printf("%d\n", ps->age);//(*ps).agereturn0;}

重点:

  1. 结构体变量.成员名 -----访问

  2. struct Stu* ps = &s;

    -> 结构体成员访问操作符

  3. 结构体指针->成员名

printf("%s\n", ps->name);//(*ps).name
printf("%d\n", ps->age);//(*ps).age

三,qsort函数模拟实现

使用回调函数,模拟实现qsort(采用冒泡的方式)

bubble_sort函数——只能排整型

//qsort函数的模拟实现//使用回调函数,模拟实现qsort(采用冒泡的方式)#include<stdlib.h>//void qsort(void* base,// size_t num,// size_t size,// int (*compar)(const void*, const void*));//提供一个函数,这个函数能够比较2个整型数据的大小intcmp_int(constvoid*e1,constvoid*e2){return*(int*)e1-*(int*)e2;}//*(int*)e1 > *(int*)e2 --> >0//*(int*)e1 == *(int*)e2 --> 0//*(int*)e1 <-> *(int*)e2 ---> <0voidprint_arr(intarr[],intsz){inti=0;for(i=0;i<sz;i++){printf("%d ",arr[i]);}printf("\n");}voidbubble_sort(intarr[],intsz){//躺输inti=0;for(i=0;i<sz-1;i++){intflag=1;//假设待排序的数据已经有序//一趟冒泡排序的过程intj=0;for(j=0;j<sz-1-i;j++){if(arr[j]>arr[j+1]){inttmp=arr[j];arr[j]=arr[j+1];arr[j+1]=tmp;flag=0;}}if(flag==1){break;}}}

下面优化bubble_sort 使得可以排任意类型的数据:

我们要解决的问题:
1. 数据类型的通用性

  • 只能排整型:直接用int*操作数组,类型固定。 通用排序:需要用 void*通用指针
  • 接收任意类型的数组首地址,不能直接对void*做算术运算(C 语言不允许),必须强转成char*(步长 1 字节)来计算元素地址

2. 元素大小(宽度)的适配

  • 只能排整型:int大小固定(通常 4 字节),可以直接用&arr[j+1]取相邻元素。
  • 通用排序:不同类型(int/float/结构体)的元素字节数不同,必须额外传入单个元素的宽度width,通过(char*)base + j * width计算第j个元素的地址。

3. 比较规则的自定义

  • 只能排整型:直接用>/<比较数值大小,逻辑写死在排序函数里。
  • 通用排序:不同类型的比较逻辑不同(比如浮点数、字符串、结构体成员),必须通过函数指针传入自定义的比较函数(比如写的cmp_int),由调用者决定排序规则。

4. 交换操作的通用性问题

  • 只能排整型:直接用临时变量做int类型的交换,代码简单。
  • 通用排序:不能用固定类型交换,必须实现按字节交换(比如写的Swap函数),逐字节拷贝元素的所有字节,才能适配任意大小的类型。

5. 类型安全与兼容性

  • 只能排整型:类型单一,不会出现类型不匹配。 通用排序:需要用const
  • void*保证指针的通用性,同时在比较函数里正确强转成目标类型,避免类型错误;还要处理不同平台的字节序、对齐问题(结构体场景)

6. 排序逻辑的解耦

  • 只能排整型:排序逻辑 + 比较逻辑 + 交换逻辑都写死在一个函数里,耦合度高。
  • 通用排序:需要把排序流程(冒泡 / 快排)、比较逻辑、交换逻辑三者解耦,排序函数只负责流程,比较和交换由外部实现。

1. 交换函数:

voidSwap(char*buf1,char*buf2,intwidth){inti=0;for(i=0;i<width;i++){chartmp=*buf1;*buf1=*buf2;*buf2=tmp;buf1++;buf2++;}}
  • 这个函数实现按字节交换,不管元素是int、float还是结构体,都可以通过逐字节交换完成数据交换。
  • width是元素的字节宽度,循环width次就能完整交换两个元素的所有字节。

2. 比较函数——cmp

  • 根据不同类型的数据,自定义比较函数

3. bubble_sort函数实现—排任意类型

实现:

//默认:排成升序voidbubble_sort(void*base,intsz,intwidth,int(*cmp)(constvoid*e1,constvoid*e2)){//躺数inti=0;for(i=0;i<sz-1;i++){intflag=1;//假设待排序的数据已经有序//一趟冒泡排序的过程intj=0;for(j=0;j<sz-1-i;j++){//借助cmp指针指向的函数来比较arr[j] , arr[j + 1]的大小 cmp函数是自定义的比较函数//传给给cmp函数的是arr[j] , arr[j + 1]的地址if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0){Swap((char*)base+j*width,(char*)base+(j+1)*width,width);flag=0;}}if(flag==1){break;}}}

参数说明:

  • void* base:待排序数组的首地址(通用类型)
  • int sz:数组元素个数
  • int width:单个元素的字节宽度
  • int (cmp)(const voide1, const void* e2):比较函数指针,用来定义排序规则

核心逻辑:

  1. 用char强转base,是因为char的步长是 1 字节,方便通过j * width计算每个元素的地址。
  2. 调用cmp函数比较相邻两个元素,如果需要交换,就调用Swap函数。
  3. flag是冒泡排序的优化:如果某一轮没有发生交换,说明数组已经有序,直接跳出循环。

qsort排序整形数据

voidtest1(){intarr[]={1,3,5,7,9,2,4,6,8,0};intsz=sizeof(arr)/sizeof(arr[0]);qsort(arr,sz,sizeof(arr[0]),cmp_int);//print_arr(arr,sz);}

测试bubble_sort 排序整型数据

voidtest2(){//写自己重新改造一下我们自己的bubble_sort()函数,来实现对任意类型数据的排序intarr[]={1,3,5,7,9,2,4,6,8,0};intsz=sizeof(arr)/sizeof(arr[0]);bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);//print_arr(arr,sz);}

测试bubble_sort 排结构体数据

#include<string.h>structStu{charname[20];intage;};intcmp_stu_by_name(constvoid*e1,constvoid*e2){returnstrcmp(((structStu*)e1)->name,((structStu*)e2)->name);}intcmp_stu_by_age(constvoid*e1,constvoid*e2){return((structStu*)e1)->age-((structStu*)e2)->age;}voidtest3(){structStuarr[]={{"zhangsan",20},{"lisi",25},{"wangwu",18}};//intsz=sizeof(arr)/sizeof(arr[0]);//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);bubble_sort(arr,sz,sizeof(arr[0]),cmp_stu_by_age);}

主函数:

intmain(){test1();test2();test3();return0;}

流程:

http://www.jsqmd.com/news/805687/

相关文章:

  • 2026年当下,成都路虎专业保养如何选?深度解析“007至臻汽车”服务价值 - 2026年企业推荐榜
  • OpenClaw狂欢暗藏安全隐患,深圳机密计算科技端云一体方案筑牢AI Agent安全基座
  • 从零开始通过taotoken平台文档快速完成首个ai对话应用的原型开发
  • 什么是进销存库存表?进销存库存表包含哪些内容?
  • 【智慧社区实战】2026 门禁行业分水岭:不做“认得出”的机器,要做“懂你”的智能体
  • 功率模块电热耦合建模与快速仿真【附模型】
  • 汽车后市场品牌营销路径:以奇正沐古和康明斯为例
  • 2026冶金行业湿电除尘器性能评测报告:湿式湿电除尘器/湿式静电除尘器/烟气脱硝成套设备/烟气脱硫塔/砖厂玻璃钢脱硫塔/选择指南 - 优质品牌商家
  • 从惊叹到依赖:软件定义时代的技术信任与实用指南
  • 如何利用ComfyUI-SUPIR实现专业级图像超分辨率:完整实践指南
  • 二维码门禁一体机
  • AI编程实战6:用 Codex 给按钮增加 loading 状态
  • 长期使用Taotoken的Token Plan套餐在项目开发成本控制上的实际感受
  • 2026现阶段鹅卵石马赛克选购指南:为何绿磊装饰成为性价比优选? - 2026年企业推荐榜
  • N41 SRS与LTE共用XPXT开关的一些考虑
  • 当资本垄断审美,《凰标》偏要立东方标准@凤凰标志
  • 解放双手!BetterGI智能助手如何让你每天节省2小时原神游戏时间
  • 兰亭妙微|全球化产品设计手册:从国际化适配到本地化深耕,UI设计公司出海实践指南
  • 从Gemini Nano到Orion Core:Google 2026 AI芯片级升级路线图(附17个真实POC性能基准数据)
  • 硅谷创新精神:从车库、真空管到一美元年薪的启示
  • 2026潮汕生腌店避坑指南:汕头生腌打包、汕头网红生腌店、潮汕毒药、生腌海鲜店、金平生腌、龙湖生腌、龙眼南生腌选择指南 - 优质品牌商家
  • 如何在3分钟内完成Windows与Office智能激活:KMS_VL_ALL_AIO完全指南
  • FPGA HDMI显示开源项目:从时序生成到SERDES的完整实现指南
  • 大学生在公司考什么证书?2026年就业必备的高含金量考证指南
  • 5个维度解析Poppins字体:多语言排版设计的革命性突破
  • 基于大语言模型的智能翻译工具NextAI Translator:从原理到实践
  • 5分钟精通D3KeyHelper:免费开源的暗黑3按键宏终极解决方案
  • 2026不锈钢水箱优质品牌推荐榜:不锈钢球形板水箱/不锈钢球板水箱/不锈钢组合板/不锈钢组合水箱/卧式水箱/立式圆形水箱/选择指南 - 优质品牌商家
  • 5个维度深度解析:如何实现高性能黑苹果系统的架构设计与优化策略
  • 2026硅PU场地权威品牌推荐适配多场景需求:河北田径场跑道/河北透气性塑胶跑道/河北预制型塑胶跑道/河北EPDM颗粒/选择指南 - 优质品牌商家