andyjian的个人空间 https://bbs.21ic.com/?784696 [收藏] [复制] [RSS]

日志

如何实现C语言面向对象编程?

已有 23787 次阅读2020-7-15 13:36 |个人分类:软件编程|系统分类:DSP| C面向对象

如何用C语言实现面向对象思想的开发?



现状



目前各行各业中的软件工程师规模庞大,种类繁多,但是有一小众化的软件工程师往往受到忽视,那就是嵌入式软件工程师,对于小众化的嵌入式软件工程师其实门槛还是很高的,要有一定的硬件基础和硬件调试能力,特别是当项目产品不依赖与操作系统平台而进行裸奔的,对嵌入式软件工程师要求更高;



除了上面提到的门槛因素外,大多数嵌入式软件工程师用传统的C语言直面复杂的电路板,当代码量小于1W,软件功能复杂度较小的情况下,传统的C语言思维还能够指导单个软件工程师按部就班的写驱动->调驱动->写应用->调试主体功能;但是代码量突破1W功能较复杂的情况下,这就需要多人协调完成代码编写与功能调试,软件需求->软件架构->软件层次划分->软件模块划分->代码实现->模块级调试->功能模块调试->主功能调试;考虑到实现过程的环节,这就需要在代码实现时要进行必要的抽象与封装,对于传统的C语言是不支持面向对象的,这就让用C语言实现面向对象的封装与抽象的需求应运而生;



实现方法



对于传统的C编程,单个功能模块的实现需要多个C文件的支撑,功能模块间的数据传递是通过全局变量和接口函数的调用实现的;按照C++的类定义,可以用C中的struct 实现部分类的功能;



比如如何用C实现一个CAN驱动的类开发;



A、 struct 定义属性和方法



typedef struct CanbDriver



{



//属性



struct CanData m_strECanRxData;



struct
CanData m_strECanTxData;



struct CanConfigData m_strECanbConfigData;//配置数据



/方法



Uint32  (*Initialize)(void);



Uint32 (*SendData)(struct CanbDriver
*pThis,Uint16 * pTxAddr,Uint16 uiFrameTypeId);



Uint16 * (*ReceiveData)(struct CanbDriver
*pThis,Uint16 uiFrameTypeId);





}CCanDriver;



B、 CanDriver.c中实例化各个接口函数



即定义函数



//初始化



Uint32 CAND_Initialize(struct CanbDriver *pThis)



{        



           Uint32
ulErroCode = 0;



           ………



     //实现



 



    return ulErroCode;



}



//发送



TUint32 CAND_SendData(struct CanbDriver *pThis, ,Uint16 *
pTxAddr,Uint16 uiFrameTypeId)



{        



           Uint32
ulErroCode = 0;



           ………



     //实现



 



    return ulErroCode;



}



//接收



Uint16 * CAND_ReceiveData)(struct CanbDriver *pThis,Uint16
uiFrameTypeId)



{



         Uint16 * pAddr = NULL;



 



         ………



     //实现



 



return  pAddr;



}



C、 如何将.C文件中实例化的函数对接“类”中的方法呢?



这就要求我们的“构造函数”construct中对各个函数进行注册,也就是创造对象;



//构造函数



void
CCanaDriver_Cnst(CCanaDriver* pObj)



{                                    



pObj-> Initialize = CAND_Initialize;



    pObj-> SendData = CAND_SendData;



    pObj->ReceiveData = CAND_SendData;   



}



 



D、 当然为了更接近C++的风格我们需要宏定义



#define ConstructObject(c,o) c##_Cnst(o)



       
当然我们得定义一个实体CCanDriver CanDriverObj;



        



       
这样我们就能推导出:



        调用ConstructObjectCCanDriverCanDriverObj)等价于CCanaDriver_Cnst(&CanDriverObj)



E、  对于封装好的接口函数,我们如何访问呢,为此我们还定义了这个函数



CCanDriver *GetInstanceCCanDriver



{



         return
&CanDriverObj



}



F、  至此我们完成了整个CAN驱动的类封装,比如我现在想初始化一下这个CAN驱动则进行如下操作:



//获取入口



CCanDriver *Obj = GetInstance(CCanDriver);



//访问接口函数



Obj-> Initialize(Obj);



 



至此我们完成了一个驱动的类封装;



 



优点?



有人会问,我用之前的方法也能够实现,为啥还要烧脑实现这个?当然仁者见仁智者见智,以我个人的经历而言,这种实现代码的方式能够使代码更加清晰,接口访问清晰明了,杜绝了全局变量的使用,各个模块间通过接口访问;



 



 



 



 


路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)