【C语言】指针的进阶(二)—— 回调函数的讲解以及qsort函数的使用方式
目录
1、函数指针数组
1.1、函数指针数组是什么?
1.2、函数指针数组的用途:转移表
2、扩展:指向函数指针的数组的指针
3、回调函数
3.1、回调函数介绍
3.2、回调函数的案例:qsort函数
3.2.1、回顾冒泡排序
3.2.1、什么是qsort函数?
1、函数指针数组
1.1、函数指针数组是什么?
函数指针数组是什么?首先主语是数组,数组是一个存放相同类型数据的存储空间。那我们已经学习了指针数组,比如:
char* arr[5] ———— 字符指针数组,它是一个数组,存放的是字符指针。
int* arr[5] ———— 整型指针数组,它是一个数组,存放的是整型指针。
假设有这么一个使用场景,我需要将几个函数的地址存放到一个数组中,那应该怎么存?下面给大家介绍一下:函数指针数组
int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int main()
{int (*pf1)(int, int) = &Add; //pf1和pf2是函数指针int (*pf2)(int, int) = ⋐//数组中存放类型相同的多个数组int (*pfArr[4])(int, int) = { &Add,&Sub }; //pfArr就是函数指针数组return 0;
}
函数指针数组的写法与函数指针非常相似,只需要在名字后加个方括号[ ]就可以了。
注意:因为数组是一个存放相同类型数据的存储空间,所以函数指针数组只能够存放返回类型和参数类型都一致的函数的函数地址。
1.2、函数指针数组的用途:转移表
用C语言实现一个计算器功能(加减乘除):
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}void menu()
{printf("*******************************\n");printf("****** 1.add 2.sub *****\n");printf("****** 3.mul 4.div *****\n");printf("****** 0.exit *****\n");printf("*******************************\n");
}int main()
{int input = 0;int a = 0;int b = 0;int ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("请输入2个操作数:");scanf("%d %d", &a, &b);ret = add(a, b);printf("%d\n", ret);break;case 2:printf("请输入2个操作数:");scanf("%d %d", &a, &b);ret = sub(a, b);printf("%d\n", ret);break;case 3:printf("请输入2个操作数:");scanf("%d %d", &a, &b);ret = mul(a, b);printf("%d\n", ret);break;case 4:printf("请输入2个操作数:");scanf("%d %d", &a, &b);ret = div(a, b);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}
上面的代码虽然能实现一个计算器功能,但是可以发现,这个代码特别地冗余,重复的部分非常多,并且如果需要添加多一个功能是,又需要再添加多一个case,导致代码越来越长,重复部分也越来越多,这是非常不好的代码习惯,那有什么办法能够解决呢?
其实我们通过观察可以发现,这些函数有一些特点,就是除了函数名不同之外,返回类型以及参数类型都是一致的。
既然除了函数名不同之外其余都相同,那么是否就可以使用函数指针数组来改造一下代码?
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}void menu()
{printf("*******************************\n");printf("****** 1.add 2.sub *****\n");printf("****** 3.mul 4.div *****\n");printf("****** 0.exit *****\n");printf("*******************************\n");
}int main()
{int input = 0;int a = 0;int b = 0;int ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);//创建一个函数指针数组int (*pfArr[])(int, int) = { NULL,add,sub,mul,div };//为了使数组下标与菜单序号对应起来,在0下标处放置一个NULLif (input == 0){printf("退出计算器\n");}else if (input >= 1 && input <= 4){printf("请输入2个操作数:");scanf("%d %d", &a, &b);ret = pfArr[input](a, b); //下标访问数组中的函数并调用printf("ret = %d\n", ret);}else{printf("选择错误,重新选择\n");}} while (input);return 0;
}
可以看到,使用函数指针数组一样可以完成。未来如果还需要添加其他功能时,只需要在菜单发生变化,然后写出实现功能的函数,再将函数放入函数指针数组当中就可以了。而我们把这种场景下使用的函数指针数组就叫做转移表。
这也就是说:使用函数指针数组不仅大大提高了代码的质量,而且大大降低了维护成本。
2、扩展:指向函数指针的数组的指针
指向函数指针数组的指针是一个指针,指针指向一个数组 ,数组的元素都是函数指针 。
如何定义?
void test(const char* str)
{printf("%s\n", str);
}
int main()
{//函数指针pfunvoid (*pfun)(const char*) = test;//函数指针的数组pfunArrvoid (*pfunArr[5])(const char* str);pfunArr[0] = test;//指向函数指针数组pfunArr的指针ppfunArrvoid (*(*ppfunArr)[5])(const char*) = &pfunArr;return 0;
}
void (*(*ppfunArr)[5])(const char*)
void (*(*ppfunArr)[5])(const char*),ppfunArr首先与*结合,所以它是一个指针。
void (*(*ppfunArr)[5])(const char*),再和[5]结合,表示指针指向一个大小为5的数组,每个数组存放的类型是函数指针void (*)(const char*)。
当然这里讲到的函数指针数组指针已经是很深入的内容了,使用场景非常少,只作为扩展了解即可,看不懂也不需要太过于担心。
3、回调函数
3.1、回调函数介绍
回调函数在C语言中的地位非常高,非常重要。回调函数是依赖函数指针的,有了函数指针才能实现回调函数。在前面计算器功能使用函数指针数组调用加减乘除函数的时候,加减乘除函数就被成为回调函数。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
再次用计算器功能作为例子讲解:
观察代码可以发现 ,只有调用函数部分不一样,那么能不能定义一个cacl()函数,通过将加减乘除函数作为参数传入到calc函数中,达到在calc函数中调用加减乘除函数?
按照这个思路修改后的代码:
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}void menu()
{printf("*******************************\n");printf("****** 1.add 2.sub *****\n");printf("****** 3.mul 4.div *****\n");printf("****** 0.exit *****\n");printf("*******************************\n");
}void calc(int(*pf)(int, int))
{int a = 0;int b = 0;int ret = 0;printf("请输入2个操作数:");scanf("%d %d", &a, &b);ret = pf(a, b);printf("%d\n", ret);
}int main()
{int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:calc(add);break;case 2:calc(sub);break;case 3:calc(mul);break;case 4:calc(div);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}
【图解】
可以把calc函数理解为中转站,给我参数传递什么函数地址,我就调用什么函数。
提示:在往期博客中我有得出过一个结论:函数指针在调用所指向函数时,可以不写*直接和函数名一样调用函数,而*号在这里其实就只是一个摆设,同样是为了照顾初学者的使用习惯,所以才会导致当加了很多*号去解引用时得出来的结果依然是正确的结果。
即(*pf)(a,b)等价于pf(a,b)。
如果想要了解更透彻,可以前往我的往期博客阅读函数指针部分。(链接:点击前往)
3.2、回调函数的案例:qsort函数
3.2.1、回顾冒泡排序
为了方便对比,我们先复习一下冒泡排序:
//冒泡排序算法
//给一组整型数据,然后使用冒泡排序对数据进行升序排序。void bubble_sort(int arr[], int sz)
{//趟数int i = 0;for ( i = 0; i < sz - 1; i++){//每一趟冒泡排序的过程int j = 0;for ( j = 0; j < sz - 1 - i; j++){if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}int main()
{int arr[] = { 10,9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);int i = 0;for ( i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}
上面就是冒泡排序的实现,但是可以看到,这个冒泡排序其实是有缺陷的,它的参数是int类型,限制了它只能够排序整型数据!而这里即将讲到的qsort函数就是一个可以用来排序任意类型数据的函数。
3.2.1、什么是qsort函数?
qsort是一个库函数,底层使用的是快速排序的方式对数据进行排序。头文件:<stdlib.h>
这个函数可以直接使用用来排序任意类型的数据。
当然除了快速排序,还有很多排序,例如:冒泡排序、选择排序,希尔排序,归并排序等等
qsort函数定义原型:
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
- void* base:待排序数组的第一个元素的地址
- size_t num:待排序数组的元素个数
- size_t size:以字节为单位,待排序数组中一个元素的大小。
- int (*compar)(const void*,const void*):函数指针,指向一个函数,用来比较两个元素,由用户自行创建并封装。
比较函数的形参中为什么用的是void*:
void* 是无具体类型的指针,不能进行解引用操作符,也不能进行+-整数的操作,它是用来存放任意类型数据的地址(可以理解为垃圾桶,什么都能装,当需要用时再强制类型转换为需要的类型)。只有void*被允许存放任意类型数据的地址,如果是其他类型的指针编译器会报错。正是因为定义qsort函数时用的是void*,qsort函数才可以排序任意类型的数据。
使用qsort函数最重要的就是最后一个参数,这个参数决定了qsort函数比较两个元素的规则。这里先写一个用于排序整型数据的比较函数cmp_int:
int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}
比较函数的要求:
- 当p1指向的元素大于p2指向的元素时,返回大于0的数
- 当p1指向的元素等于p2指向的元素时,返回0
- 当p1指向的元素小于p2指向的元素时,返回小于0的数
【完整代码】
使用qsort函数排序整型数据。
int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}int main()
{int arr[] = { 10,9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_int);int i = 0;for ( i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}
同理,qsort函数排序结构体类型数据(下面例子以结构体中的年龄来排序):
struct Stu
{char name[20];int age;
};int cmp_struct(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}int main()
{struct Stu arr[] = { {"zhangsan",20},{"lisi",21},{"wangwu",22} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_struct);return 0;
}
【运行结果】
可以发现确实是完成了按年龄排序。
如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!
如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!
如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!
相关文章:
【C语言】指针的进阶(二)—— 回调函数的讲解以及qsort函数的使用方式
目录 1、函数指针数组 1.1、函数指针数组是什么? 1.2、函数指针数组的用途:转移表 2、扩展:指向函数指针的数组的指针 3、回调函数 3.1、回调函数介绍 3.2、回调函数的案例:qsort函数 3.2.1、回顾冒泡排序 3.2.1、什么是qso…...
Java集合之HashSet接口
Set Set接口、HashSet类、TreeSet类 Set(组、集):表示无序,元素不能重复的集合,组中的元素必须唯一 Set接口 Set接口定义了组/集/集合(Set)。他扩展了Collection接口,并声明了不允…...
uniapp----微信小程序 日历组件(周日历 月日历)【Vue3+ts+uView】
uniapp----微信小程序 日历组件(周日历&& 月日历)【Vue3tsuView】 用Vue3tsuView来编写日历组件;存在周日历和月日历两种显示方式;高亮显示当天日期,红点渲染有数据的日期,点击显示数据 1. calenda…...
【记录】深度学习环境配置(pytorch版)
1080面对Transformer连勉强也算不上了,还是要去用小组的卡 完整记一个环境配置,方便后面自用✍️ 目前要简单许多,因为显卡驱动已经装好,后安装的库版本与其对应即可。 nvidia-smi查看GPU信息 ** CUDA版本12.2 conda -V查询conda…...
如何将项目推送到GitHub中
将项目推送到 GitHub 仓库并管理相关操作,遵循以下步骤: 创建 GitHub 账户:如果您没有 GitHub 账户,首先需要在 GitHub 官网 上创建一个账户。 创建新仓库:在 GitHub 页面上,点击右上角的加号图标…...
数据库直连提示 No suitable driver found for jdbc:postgresql
背景:我在代码里使用直连的方式在数据库中创建数据库等,由于需要适配各个数据库服务所以我分别兼容了mysql、postgresql、oracal等。但是在使用过程中会出现错误: No suitable driver found for jdbc:postgresql 但是我再使用mysql的直连方式…...
Stability AI推出Stable Audio;ChatGPT:推荐系统的颠覆者
🦉 AI新闻 🚀 Stability AI推出Stable Audio,用户可以生成个性化音乐片段 摘要:Stability AI公司发布了一款名为Stable Audio的工具,用户可以根据自己的文本内容自动生成音乐或音频。免费版可生成最长20秒音乐片段&a…...
HTML中的<canvas>元素
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ canvas元素⭐ 用途⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们…...
【论文阅读】MARS:用于自动驾驶的实例感知、模块化和现实模拟器
【论文阅读】MARS:用于自动驾驶的实例感知、模块化和现实模拟器 Abstract1 Introduction2 Method2.1 Scene Representation2.3 Towards Realistic Rendering2.4 Optimization3.1 Photorealistic Rendering3.2 Instance-wise Editing3.3 The blessing of moduler des…...
Leetcode 2856. Minimum Array Length After Pair Removals
Leetcode 2856. Minimum Array Length After Pair Removals 1. 解题思路2. 代码实现 题目链接:2856. Minimum Array Length After Pair Removals 1. 解题思路 这一题思路而言个人觉得还是挺有意思的,因为显然这道题没法直接用greedy的方法进行处理&am…...
深入了解Vue.js框架:构建现代化的用户界面
目录 一.Vue前言介绍 二.Vue.js框架的核心功能与特性 三.MVVM的介绍 四.Vue的生命周期 五.库与框架的区别 1.库(Library): 2.框架(Framework): 六.Vue常用指令演示 1.v-model 2.v-on:click&…...
力扣 -- 673. 最长递增子序列的个数
小算法: 通过一次遍历找到数组中最大值出现的次数: 利用这个小算法求解这道题就会非常简单了。 参考代码: class Solution { public:int findNumberOfLIS(vector<int>& nums) {int nnums.size();vector<int> len(n,1);auto…...
43.248.189.X网站提示风险,存在黑客攻击页面被篡改,改如何解决呢?
当用户百度搜索我们的网站,准备打开该网站时,访问页面提示风险,告知被黑客攻击并有被篡改的情况,有哪些方案可以查看解决问题? 当遇到网站提示风险到时候,可以考虑采用下面几个步骤来解决问题:…...
Java8中判断一个对象不为空存在一个类对象是哪个
Java8中判断一个对象不为空存在一个类对象是哪个? 在Java 8中,你可以使用java.util.Optional类来处理可能为空的对象。Optional类可以帮助你优雅地处理空值情况,而不需要显式地进行空值检查。 这是一个简单的Optional示例: imp…...
项目:点餐系统
项目扩展: 1.订单操作 2.用户管理(临时用户生成用户注册与登录) 项目有可能涉及到的面试: 说说你的项目 为什么要做这个项目 服务器怎么搭建的 最初我自己写了一个简单的服务器,但是不太稳定,比较粗…...
ElasticSearch 5.6.3 自定义封装API接口
在实际业务中,查询 elasticsearch 时会遇到很多特殊查询,官方接口包有时不便利,特殊情况需要自定义接口,所以为了灵活使用、维护更新 编写了一套API接口,仅供学习使用 当前自定义API接口依赖 elasticsearch 5.6.3 版本…...
企业架构LNMP学习笔记51
企业案例使用: 主从模式: 缓存集群结构示意图: 去实现Redis的业务分离: 读的请求分配到从服务器上,写的请求分配到主服务器上。 Redis是没有中间件来进行分离的。 是通过业务代码直接来进行读写分离。 准备两台虚…...
rom修改----安卓系列机型如何内置app 如何选择so文件内置
系统内置app的需求 在与各工作室对接中操作单中,很多需要内置客户特定的有些app到系统里,这样方便客户刷入固件后直接调用。例如内置apk 去开机引导 去usb调试 默认开启usb安全设置等等。那么很多app内置有不同的反应。有的可以直接内置。有的需要加so…...
SpringMvc中的请求转发和重定向
之前的案例,我们发现request域中的值可以传到jsp页面中,也就是通过视图解析器跳转到视图的底层是请求转发。 如果我们跳转时不想使用视图解析器,可以使用原生HttpServletRequest进行请求转发或HttpServletResponse进行重定向: Req…...
Oracle,高斯创建自增序列
某些时候,需要获取到一个自增值 然后点击左下 Apply 也可以通过SQL语句执行 dual在Oracle中是张虚拟表,通常用于执行这样的查询 Oracle中查询语句: select 序列名.nextval from dual 在高斯数据库中:查询是 select my_sequence.nextval 不需要加form xxx …...
操作系统学习笔记-精简复习版
文章目录 操作系统概述1、操作系统2、主要功能3、用户态和内核态4、系统调用 进程管理1、进程和线程2、引入线程的好处3、线程间同步4、进程控制块 PCB5、进程的状态6、进程的通信方式7、进程的调度算法8、僵尸进程&孤儿进程9、死锁 内存管理1、内存碎片2、内存管理3、虚拟…...
系统架构:软件工程速成
文章目录 参考概述软件工程概述软件过程 可行性分析可行性分析概述数据流图数据字典 需求分析需求分析概述ER图状态转换图 参考 软件工程速成(期末考研复试软考)均适用. 支持4K 概述 软件工程概述 定义:采用工程的概念、原理、技术和方法来开发与维护软件。 三…...
VUE之proxy配置实现跨域
什么是跨域 要了解跨域,首先得知道浏览器的同源策略。 同源策略:是由Netscape提出的一个安全策略,能够阻挡恶意文档,保护本地数据。它能限制一个源的文档或脚本对另一个源的交互,使得其它源的文档或脚本,…...
AI与医疗保健:革命性技术如何拯救生命
文章目录 引言AI的应用领域1. 影像识别2. 疾病诊断3. 药物研发4. 个性化治疗 AI技术1. 机器学习2. 深度学习3. 自然语言处理4. 基因组学 实际案例1. Google Health的深度学习模型2. IBM Watson for Oncology3. PathAI的病理学分析 道德和隐私考虑结论 🎉欢迎来到AIG…...
Spring Boot + Vue3前后端分离实战wiki知识库系统<十三>--单点登录开发二
接着Spring Boot Vue3前后端分离实战wiki知识库系统<十二>--用户管理&单点登录开发一继续往下。 登录功能开发: 接下来则来开发用户的登录功能,先准备后端的接口。 后端增加登录接口: 1、UserLoginReq: 先来准备…...
基于Java的高校科研信息管理系统设计与实现(亮点:完整严谨的科研项目审批流程、多文件上传、多角色)
高校科研信息管理系统 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序(小蔡coding)2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 主要功能描述 五、系统实现5.1…...
【uniapp】Dcloud的uni手机号一键登录,具体实现及踩过的坑,调用uniCloud.getPhoneNumber(),uni.login()等
一键登录Dcloud官网请戳这里,感兴趣的可以看看官网,有很详细的示例,选择App一键登录,可以看到一些常用的概述 比如: 1、调用uni.login就能弹出一键登录的页面 2、一键登录的流程,可以选择先预登录uni.prelo…...
Qt Quick Layouts Overview
Qt快速布局概述 #【中秋征文】程序人生,中秋共享# Qt快速布局是用于在用户界面中排列项目的项目。由于Qt快速布局还可以调整其项目的大小,因此它们非常适合可调整大小的用户界面。 开始 可以使用文件中的以下导入语句将 QML 类型导入到应用程序中。.qml…...
星臾计划 | 第六期优秀实习生访谈合集
此处划重点:优秀实习生评比活动将每三个月进行一次,获评同学可获得优秀实习生证书和丰厚的奖励 —— 是心动的感觉! 作为实习生培养计划,星臾计划不但能帮助在校生提前了解企业、熟悉工作环境,还能提前锁定正式 Offer…...
《数字图像处理-OpenCV/Python》连载(7)视频文件的读取与保存
《数字图像处理-OpenCV/Python》连载(7)视频文件的读取与保存 本书京东优惠购书链接:https://item.jd.com/14098452.html 本书CSDN独家连载专栏:https://blog.csdn.net/youcans/category_12418787.html 第1章 图像的基本操作 为…...
网站建设技术规范/网络推广和网站推广平台
import java.util.Calendar; import java.util.Scanner; /* * 获取任意一年的二月有多少天 * * 分析: * A:键盘录入任意的年份 * B:设置日历对象的年月日 * 年就是A输入的数据 * 月是2 * 日是1 * C:把时间往前推一天,就是2月的最后一天 * D:…...
怎样做自己网站/今日新闻最新头条10条
0 引 言 近些年来,随着计算机应用需求的不断增强,计算机科学与技术的发展日新月异。然而在这种快速发展的同时,也面临着种种的困难。主要的困难包括:知识的表示、信息的组织、软件的复用等。特别是由于因特网的快速发展,面对信息的海洋,如何组织、管理和维护海量信息并为…...
会网站制作的职业是/怎么优化网络
PLC梯形图设计全自动洗衣机S7-1200博途以及PLC仿真实现之 3. 仿真设计 上一节链接: PLC梯形图设计S7-1200博途以及PLC仿真实现之 2. 编程梯形图实现 下一节链接:PLC梯形图设计全自动洗衣机S7-1200博途以及PLC仿真实现之 4. 仿真演示过程...
怎么做企业招聘网站/seo网络推广知识
pb调用md5.dll文件是一款十分重要的dll文件,缺少了它你的软件可能就无法运行啦,给工作和生活带来许多不便,请不用担心,这里可以完美解决你的系统问题!感兴趣的小伙伴快来当易网下载体验吧!md5.dll内容简介如…...
网站备案 新闻审批号/程序员培训
一直用clion做远程开发,不管是颜值还是自动补全都挺舒服,但是今天遇到一个问题,就是clion找不到Linux中的库文件,所以就没有代码补全,但是编译是正常的。更具体点,找得到 unistd.h但是找不到sys/types.h。 …...
wordpress启动广告/互联网全网营销
我从interweb上截取了这段代码,它最初不起作用。我改了线:rcParams[animation.convert_path] r/usr/bin/convert指向我的convert二进制文件,它就启动了。诚然,这是在Linux上实现的,但我不认为它应该有所不同。正如Mar…...