求求你,不要再纠结指针了(2)——函数指针
时间:2022-07-22
本文章向大家介绍求求你,不要再纠结指针了(2)——函数指针,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
【说在前面的话】
如果说指针在一些人心中是导致代码“极其不稳定的奇技淫巧”,那么“函数指针”则是导致代码跑飞和艰涩难懂的罪魁祸首。然而,函数指针的定义和使用其实非可以非常简单——请暂时忘记原本你从课本上所学的知识,让我们来看一种函数指针的正确打开方式。
【正文】
假设有一个目标函数,其函数原型是这样的:
extern bool serial_out(uint8_t chByte);
那么如何定义指向该函数原型的函数指针呢?
步骤1:用typedef定义一个函数原型类型:
typedef bool serial_out_t(uint8_t chByte);
或者省略形参的变量名:
typedef bool serial_out_t(uint8_t);
步骤2:使用新类型按照普通指针的使用方法来使用。
- 使用新的类型来定义指向该类型的指针——函数指针
serial_out_t *fnPutChar = NULL;
...
fnPutChar = &serial_out;
如果用传统的方法,上面的代码等效为:
bool (*)(uint8_t) fnPutChar = NULL;
...
fnPutChar = serial_out;
- 使用函数指针的来访问函数
...
if (NULL != fnPutChar) {
//! 调用函数指针所指向的函数
bResult = (*fnPutChar)('H'); //!< 这里的"*"可以省略,但最好保留哦
}
...
需要特别注意:
- 我们并不是通过typedef来直接定义指针类型,而是定义一个专门针对目标函数原型的新类型——这样在定义函数指针变量时就和普通变量类型一样需要使用“*”——任何时候都知道这是一个指针,不会迷惑。
- 虽然这里"&"在C语言语法上是可以省略的,但是为了简化规则(简化需要记忆的特殊情况),这里我们要遵守普通指针的使用规则——取地址的时候要使用取地址运算符“&”,访问指针所指向空间的时候,“*”也不能省略。
使用这种方法定义和使用函数指针好处非常明显:
- 极大的提高了代码的可读性——与函数指针有关的代码,任何时候一眼看就知道是一个指针;
- 极大的降低了函数指针的使用难度——通过typedef定义一个针对函数原型的类型,将函数指针的使用变得跟普通指针一摸一样,从而省区了额外的记忆负担;
- 允许轻松套娃
关于最后一点,我们不妨做一个极端一点的例子:
假设有一个函数,其输入参数是一个函数指针,其返回函数也是一个函数指针:
typedef struct task_cb_t task_cb_t;
typedef const char * get_err_string_t(task_cb_t *ptTask);
typedef void on_task_cpl_evt_t(task_cb_t *ptTask);
extern get_err_code_t *run_task(
task_cb_t *ptTask,
on_task_cpl_evt_t *fnTaskCPLEvtHandler);
为了让这个例子显得更为合理,我假想了一个调度器,而run_task就是这个调度器执行用户任务的函数。分析上面的代码容易清晰的获得以下信息:
- task_cb_t 是用户任务的控制块,具体内容未知,但我们可以用它来声明指针变量;
- 函数指针(get_err_code_t *)指向的函数可以返回指定任务的错误代码;
- 函数指针(on_task_cpl_evt_t *)所指向的函数是一个事件处理程序;
- 函数 run_task会执行指定的任务,“可能”会在任务执行完成的时候通过函数指针 fnTaskCPLEvtHandler调用一个用户指定的事件处理程序;
- 函数run_task在执行指定任务的时候,如果发生了错误,“可能”会返回一个非NULL的函数指针,类型是:(get_err_code_t *),用户可以通过这个函数指针获取任务ptTask专属的错误信息(字符串);
怎么样,是不是看起来一切都简单自然?那你考虑过,如果要做一个指向run_task的函数指针应该是什么样么?套娃开始:
typedef get_err_code_t *run_task_t(task_cb_t *, on_task_cpl_evt *);
【注意】run_task_t 前面的“*”是 (get_err_code_t *)的一部分。
我们可以用新类型run_task_t定义一个函数指针:
static run_task_t *s_fnDispatcher = NULL;
最后,作为一个挑战,我很怀疑有没有人能不借助typedef的方法,重新写出函数指针 s_fnDispatcher 的定义?
欢迎在评论区留言,写下你的答案。
【后记】
借助typedef,函数指针的使用可以极大的简化。与传统方式不同的是,这里typedef定义的不是函数指针本身,而是一个“函数原型的类型”——借助这一小技巧,我们成功的贯彻了“复杂的事情变简单、简单的事情变可靠”的原则。
- 使用CoreOs,Docker和Nirmata来部署微服务风格的应用程序
- 使用ACS和Kubernetes部署Red Hat JBoss Fuse
- 教你快速安装OpenShift容器平台3.6
- 面向开发者的Cloud Foundry
- 云数据库安全与农场和餐馆:知道来源的重要性
- 云数据库安全,农场和餐馆:知道你的来源的重要性
- NO.32 不堪重负:线程池拒绝策略
- 工厂模式进阶之Android中工厂模式源码分析
- C加加游戏编程,大神十年的绝技,正确的入门,这才叫学习
- 我们应该担心吗?人工智能现在可以通过交谈来学习新单词!
- 印度财政部:比特币是纯粹投机行为 区块链资产是“庞氏骗局”
- 法律人工智能实验室成立,法官和律师会丢饭碗吗?
- 让GridView中CheckBox列支持FireFox
- 在ASP.NET MVC中通过URL路由实现对多语言的支持
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 利用Python实现Excel的文件间的数据匹配功能
- PHP设计模式之简单工厂和工厂模式实例分析
- PHP实现数据四舍五入的方法小结【4种方法】
- 如何在Windows中安装多个python解释器
- PHP设计模式之抽象工厂模式实例分析
- 使用python matploblib库绘制准确率,损失率折线图
- Django REST Swagger实现指定api参数
- matplotlib.pyplot.matshow 矩阵可视化实例
- php+mysql开发的最简单在线题库(在线做题系统)完整案例
- python中元组的用法整理
- PHP错误提示It is not safe to rely on the system……的解决方法
- PHP使用mysqli同时执行多条sql查询语句的实例
- 在tensorflow下利用plt画论文中loss,acc等曲线图实例
- PHP生成短网址的思路以及实现方法的详解
- ThinkPHP 3.2.3实现加减乘除图片验证码