前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >求求你,不要再纠结指针了(2)——函数指针

求求你,不要再纠结指针了(2)——函数指针

作者头像
GorgonMeducer 傻孩子
发布2020-07-28 09:49:51
2940
发布2020-07-28 09:49:51
举报
文章被收录于专栏:裸机思维裸机思维

【说在前面的话】


如果说指针在一些人心中是导致代码“极其不稳定的奇技淫巧”,那么“函数指针”则是导致代码跑飞和艰涩难懂的罪魁祸首。然而,函数指针的定义和使用其实非可以非常简单——请暂时忘记原本你从课本上所学的知识,让我们来看一种函数指针的正确打开方式。

【正文】

假设有一个目标函数,其函数原型是这样的:

代码语言:javascript
复制
extern bool serial_out(uint8_t chByte);

那么如何定义指向该函数原型的函数指针呢?

步骤1:用typedef定义一个函数原型类型:

代码语言:javascript
复制
typedef bool serial_out_t(uint8_t chByte);

或者省略形参的变量名:

代码语言:javascript
复制
typedef bool serial_out_t(uint8_t);

步骤2:使用新类型按照普通指针的使用方法来使用。

  • 使用新的类型来定义指向该类型的指针——函数指针
代码语言:javascript
复制
serial_out_t *fnPutChar = NULL;
...
fnPutChar = &serial_out;

如果用传统的方法,上面的代码等效为:

代码语言:javascript
复制
bool (*)(uint8_t) fnPutChar = NULL;
...
fnPutChar = serial_out;
  • 使用函数指针的来访问函数
代码语言:javascript
复制
...
if (NULL != fnPutChar) {
    //! 调用函数指针所指向的函数
    bResult = (*fnPutChar)('H');     //!< 这里的"*"可以省略,但最好保留哦
}
...

需要特别注意:

  • 我们并不是通过typedef来直接定义指针类型,而是定义一个专门针对目标函数原型的新类型——这样在定义函数指针变量时就和普通变量类型一样需要使用“*”——任何时候都知道这是一个指针,不会迷惑。
  • 虽然这里"&"在C语言语法上是可以省略的,但是为了简化规则(简化需要记忆的特殊情况),这里我们要遵守普通指针的使用规则——取地址的时候要使用取地址运算符“&”,访问指针所指向空间的时候,“*”也不能省略。

使用这种方法定义和使用函数指针好处非常明显:

  • 极大的提高了代码的可读性——与函数指针有关的代码,任何时候一眼看就知道是一个指针;
  • 极大的降低了函数指针的使用难度——通过typedef定义一个针对函数原型的类型,将函数指针的使用变得跟普通指针一摸一样,从而省区了额外的记忆负担;
  • 允许轻松套娃

关于最后一点,我们不妨做一个极端一点的例子:

假设有一个函数,其输入参数是一个函数指针,其返回函数也是一个函数指针:

代码语言:javascript
复制
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的函数指针应该是什么样么?套娃开始:

代码语言:javascript
复制
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定义一个函数指针:

代码语言:javascript
复制
static run_task_t *s_fnDispatcher = NULL;

最后,作为一个挑战,我很怀疑有没有人能不借助typedef的方法,重新写出函数指针 s_fnDispatcher 的定义?

欢迎在评论区留言,写下你的答案。

【后记】


借助typedef,函数指针的使用可以极大的简化。与传统方式不同的是,这里typedef定义的不是函数指针本身,而是一个“函数原型的类型”——借助这一小技巧,我们成功的贯彻了“复杂的事情变简单、简单的事情变可靠”的原则。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 裸机思维 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档