傻孩子(GorgonMeducer) · 2020年06月17日

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

作者:GorgonMeducer 傻孩子
首发:裸机思维

640.png

【说在前面的话】


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

【正文】

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

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;

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

bool (*)(uint8_t) fnPutChar = NULL;
  • 使用函数指针的来访问函数
...

需要特别注意:

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

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

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

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

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

typedef struct task_cb_t task_cb_t;

为了让这个例子显得更为合理,我假想了一个调度器,而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定义的不是函数指针本身,而是一个“函数原型的类型”——借助这一小技巧,我们成功的贯彻了“复杂的事情变简单、简单的事情变可靠”的原则。



专栏推荐文章


如果你喜欢我的思维,欢迎订阅裸机思维
版权归裸机思维(傻孩子图书工作室旗下公众号)所有,
所有内容原创,严禁任何形式的转载。
推荐阅读
关注数
1480
内容数
118
探讨嵌入式系统开发的相关思维、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息