把函数"封装"到结构体里的好处?

2019-12-22 13:50发布

我看很多人这么用,可不知道为什么,网上搜了,文章都是说可以这么用以及如何这么用,但是没有说明其中思想。

我只能猜是因为:1,封装起来后使写程序的人思路更清晰;2,不同的过程需要同一个函数,结构体可以迅速“复制”多个同样的函数?但是想想又不对,请高人解答。我觉得结构体里有变量、参数就足够方便了,想不出有什么必要把函数也放进去?
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
53条回答
moonspell
1楼-- · 2019-12-26 00:45
Andrewz 发表于 2014-3-26 11:34
以前还真没见过.
C语言的内容远远超出了课堂里老师涉及的内容.
学习了.

思路很清晰 厉害
tjpu__224
2楼-- · 2019-12-26 05:06
22楼的例子收藏了,启发不小。
error_dan
3楼-- · 2019-12-26 09:03
本帖最后由 error_dan 于 2014-3-27 10:06 编辑
kalo425 发表于 2014-3-27 01:16
你好,我可不可以理解为
extern ARM_UART_DRIVER uart2Driver 这种用法降低了 C文件之间的耦合性。使移植 ...


本身就是用来做模块化封装的技术。根据你的实际需求,按照某种方式划分的模块(比方说按照硬件划分,按照功能划分,甚至按照运行权限划分)最终采用资源结构体的方式对外提供一个接口。当接口固定时,即使需求发生变化也不用改动所有的代码,仅仅把变化的部分改掉就好,这个本来应该是编程习惯上的问题,但是显式的把接口写出来,并且把接口的说明写清楚,大家都依照这个接口来耦合上下层代码,不论是效率还是质量都会好于用习惯自律。
具体用的时候存在这样一个问题,本坛傻大师一直力主module.c里面不包含module.h,实际上本身.h就是给调用者使用模块的接口,我们这里讨论的例如ARM_UART_DRIVER这个结构体的定义应该就在这里。但是module.c不包含module.h的话又怎么获得这个结构体呢?傻大师给了一个掩码结构体的解决方案,你可以自己搜一下。至于成员函数,你包含到工程里面并且引用了这个函数就肯定会被编译并且链接啊。还是以extern ARM_UART_DRIVER uart2Driver为例子,实际上这只是个声明,告诉编译器外面有个形如ARM_UART_DRIVER的结构体,名字叫uart2Driver,至于在哪不知道,让编译器自己去找。但是你肯定要定义一个这样的结构体并且加入到工程当中去,否认编译器肯定要跟你抱怨找不到这个东西,找不到怎么用?找到的东西分两部分,一个是ARM_UART_DRIVER这是结构体定义,告诉编译器这个结构体长什么样子有哪些数据域,一个是uart2Driver是前面这个结构体的实例化,一般都会把每个数据域都填充有效的数据,这样直接拿来就可以用了:
举个栗子~

GPIO.h
里面有:
  1. typedef struct{
  2. u16 pinNumber;
  3. bool pinValue;
  4. GPIO_Type* port;
  5. (void)(*InterruptConfig)(void);
  6. (void) (*GPIO_IntertuptCallback)(bool enable);
  7. }GPIO_DRIVER;
复制代码
GPIO.c里面有:
  1. const GPIO_DRIVER GPIO_P00 = {
  2. 0x00,//pinNumber
  3. 0x00,//pinValue
  4. GPIO_P0,
  5. P00_InterruptConfig,
  6. P00_ISR
  7. };
复制代码

其中的GPIO_Type其实在这里根本用不上,只是为了说明一下,类似GPIO端口定义这样的内容一般会以
  1. typedef enum GPIO_Type
  2. {
  3. GPIO_P0,
  4. GPIO_P1
  5. 。。。
  6. };
复制代码
这样的形式出现。
管脚编号和管脚值就是普通成员变量,不多说。(我不会告诉你们在cortexM3上面可以通过位带计算直接把管脚位指针放到这里面,这样每次都等于直接读管脚)
下面的中断配置按照ARM的风格是编写GPIO_InterruptConfig(IntInfo arg,bool enable),然后P00_InterruptConfig实际上是就是:
  1. void P00_InterruptConfig(bool enable)
  2. {
  3.         GPIO_InterruptConfig(IntInfo_P00,enable);
  4. }
复制代码
反正他就是这么写的,好不好见仁见智,我反正不喜欢~反正有更粗暴的办法来实现,我觉得这样多包一层真是。。。
类似的还有GPIO_Init,里面可以传一组在GPIO.h里面定义好的枚举参数用于配置管脚(比方说GPIO_P0这是端口,然后还有个端口的参数比分说叫GPIO_ConfigInfo,把参数应用到端口上就行了),这个忘记写进去了~

接下来是就是最厉害的了,callback是什么怎么用,请大家自行百度,反正从配置上面来讲,这里的中断函数体本身也是不给用户自己写的,反正开放了callback接口,把你要做的事情写进去,然后驱动会自动帮你完成中断匹配时调用你的处理函数的任务。
所以呢,最终用的时候,在user.c里面:
#include “GPIO.h”//获得封装模块的结构体信息

extern GPIO_DRIVER GPIO_P00;//把定义好的资源拿来用

然后就爽啦,直接GPIO_P00.Init(管脚配置.浮空输入);//反正就是这个意思,你懂的
读取呢就是(伪代码)bool pinValue = GPIO_P00.pinValue;
配置中断:GPIO_P00.P00_InterruptConfig(true);//传个true进去就是开中断,传个false进去就是关中断,中断优先级神马,这里肯定不管啦~
安装callback:GPIO_P00.P00_ISR = 你的中断处理函数;
void 你的中断处理函数(void)
{
干点什么;
}
这里的顺序肯定是先安装回调函数然后再配置中断。

PS:大早上的还没醒,随手打的里面说不定有错,反正意思就是这个意思,看起来为了一个管脚写这么多不划算,但是在实际中经过测试的驱动写一次就够了,整个团队乃至你的客户都可以用这个驱动最大的简化开发流程,用半导体厂商的话说就是“专注于应用开发”
error_dan
4楼-- · 2019-12-26 09:18
gongxd 发表于 2014-3-27 08:42
呵呵 以为就在手边呢 谢谢 自己动手

看34L,顺便帮忙挑错~
kalo425
5楼-- · 2019-12-26 12:53
 精彩回答 2  元偷偷看……
mhw
6楼-- · 2019-12-26 17:14
以前关注过,现在不玩了……
小程序写多了,就跟吃饭喝水一样,自己趁手、舒服就行了,没必要麻烦的……面向对象不是任何地方都需要的:

我.喝水();
看起来简单,一行搞定,进去看看……里面还有喝水.厨房,喝水.茶,喝水.咖啡……厨房.插头,厨房.电水壶,厨房.杯子……茶.铁观音,茶.普洱……咖啡.雀巢,咖啡.蓝山……

所以我现在的做法就是先把项目做好整体规划,然后拷贝个模板,初始化代码一个函数里全搞定,方便检查,中断函数改一下,应用层也有不少跟硬件相关的,像读写IO口之类的顶多用宏封装一下……看似移植性比较差,但是现实中又有哪位尝试过把自己产品瞬间更换N种MCU……
100KB左右的bin(常量很少),尝试过在TI和ST的M3,以及芯唐的M0之间移植,速度还是很快的,摸索完新MCU的外设操作后,软件的移植也就两三天可以搞定。

一周热门 更多>