模块的封装之C语言类的封装



[微知识]模块的封装(一):C语言类的封装



  

  是的,你没有看错,我们要讨论的是C语言而不是C++语言中类的封装。在展开知识点之前,我首先要

重申两点:

  1、面向对象是一种思想,基本与所用的语言是无关的。当你心怀面向对象时,即使使用QBasic也能写

    出符合面向对象思想的代码,更不要说C语言了。举一个反例,很多人初学C++的时候,并没有掌

    握面向对象的思想,活生生的把类当结构体来使用的也不在少数吧。

  2、面向对象的最基本的出发点是“将数据以及处理数据的方法封装在一起”,至于继承、派生、多态之类

    的则是后面扩展的东西。在C语言中,如果用结构体来保存数据,并将处理这些数据的函数与结构体

    的定义封装在同一个.c文件中,则该.c文件就可以视作一个类。如果将指向具体函数的函数指针与结

    构体的其他成员封装在同一个结构体中,则该“对象”的使用甚至与C++相差无几了。

  

  以上的内容是面向对象的C语言(Object-Oriented C Programming with ANSI-C)技术的基本出发

点。作为引子,在使用OOC技术的时候,我们会遇到这么一个问题:是的,我们可以用结构体模拟类,将所

有的成员变量都放在结构体中,并将这一结构体放在类模块的接口头文件中,但是问题是结构体里的成员变量

都是public的,如何保护他们使其拥有private的属性呢?解决的方法就是掩码结构体(Masked Structure)

  那么什么是掩码结构体呢?在回答这个问题前,我们先看下面的例子。已知我们定义了一下用于在C语言

里面进行类封装的宏,如下所示:

 1 #define EXTERN_CLASS(__NAME,...)  2     typedef union __NAME __NAME; 3     __VA_ARGS__ 4     union __NAME { 5         uint_fast8_t chMask[(sizeof(struct {
 6
 7 #define END_EXTERN_CLASS(__NAME)  8         }) + sizeof(uint_fast8_t) - 1) / sizeof(uint_fast8_t)]; 9     };
10
11 #define DEF_CLASS(__NAME,...)12     typedef union __NAME __NAME;13     __VA_ARGS__14     typedef struct __##__NAME __##__NAME;15     struct __##__NAME{
16
17 #define END_DEF_CLASS(__NAME) 18     };19     union __NAME {20         uint_fast8_t chMask[(sizeof(__##__NAME) + sizeof(uint_fast8_t) - 1) / sizeof(uint_fast8_t)];21     };
22
23 #define CLASS(__NAME)               __##__NAME

假设我要封装一个基于字节的队列类,不妨叫做Queue,因此我们建立了一个类文件queue.c和对应的接口头文件

queue.h。假设我们约定queue.c不包含queue.h(这么做的好处很多,在以后的内容里在讲解当然对掩码结构体

的技术来说,模块的实现是否包含模块的接口头文件并不是关键)。

我们首先想到是定义一个类来表示队列,他的一个可能的形式如下:

 1 //! \name byte queue
 2 //! @{
 3 typedef struct {
 4     uint8_t *pchBuffer;    //!< queue buffer
 5     uint16_t hwBufferSize; //!< buffer size
 6     uint16_t hwHead;       //!< head pointer
 7     uint16_t hwTail;       //!< tail pointer
 8     uint16_t hwCounter;    //!< byte counter
 9 }queue_t;
10 //! @}

目前为止一起都还OK,由于quue.c文件不包含queue.h,因此我们决定在两个文件中各放一个定义。由于.h文件包含了

数据队列的完整信息,使用该模块的人可能会因为种种原因直接访问甚至修改队列结构体中 的数据------也行在这个例子

中不是那么明显,但是在你某个其他应用模块的例子中,你放在结构体里面的某个信息可能对模块的使用者来说,直接操作

更为便利,因此悲剧发生了----原本你假设“所有操作都应该由queue.c来完成”的格局打破了,使用者可以轻而易举的修改

和访问结构体的内容-------而这些内容在面向对象的思想中原本应该是私有的,无法访问的(private)。原本测试完好的

系统,因为这种出乎意料的外界干涉而导致不稳定,甚至crash了。当你气冲冲的找到这么“非法”访问你结构体的人时,对方

居然推了推眼镜,一脸无辜的看着你说“根据接口的最小信息公开原则,难道你放在头文件里面的信息不是大家可以放心使用

的么?”

OTZ。。。。垭口无言,然后你会隐约觉得太阳穴微微的在跳动。。。

且慢,如果我们通过一开始提供的宏分别对queue.h和queue.c中的定义改写一番,也许就是另外一个局面了:

queue.h

 1 ...
 2 //! \name byte queue
 3 //! @{
 4 EXTERN_CLASS(queue_t)
 5     uint8_t *pchBuffer;    //!< queue buffer
 6     uint16_t hwBufferSize; //!< buffer size
 7     uint16_t hwHead;       //!< head pointer
 8     uint16_t hwTail;       //!< tail pointer
 9     uint16_t hwCounter;    //!< byte counter
10 END_EXTERN_CLASS(queue_t)
11 //! @}
12 ...
13 extern bool queue_init(queue_t *ptQueue, uint8_t *pchBuffer, uint16_t hwSize);
14 extern bool enqueue(queue_t *ptQueue, uint8_t chByte);
15 extern bool dequeue(queue_t *ptQueue, uint8_t *pchByte);
16 extern bool is_queue_empty(queue_t *ptQueue);
17 ...

queue.c

 1 ...
 2 //! \name byte queue
 3 //! @{
 4 EXTERN_CLASS(queue_t)
 5     uint8_t *pchBuffer;    //!< queue buffer
 6     uint16_t hwBufferSize; //!< buffer size
 7     uint16_t hwHead;       //!< head pointer
 8     uint16_t hwTail;       //!< tail pointer
 9     uint16_t hwCounter;    //!< byte counter
10 END_EXTERN_CLASS(queue_t)
11 //! @}
12 ...
13 extern bool queue_init(queue_t *ptQueue, uint8_t *pchBuffer, uint16_t hwSize);
14 extern bool enqueue(queue_t *ptQueue, uint8_t chByte);
15 extern bool dequeue(queue_t *ptQueue, uint8_t *pchByte);
16 extern bool is_queue_empty(queue_t *ptQueue);
17 ...

对照前面的宏,我们实际上可以手工将上面的内容展开,可以看到实际上类型queue_t是一个掩码结构体,

里面只有一个起到掩码作业的数组chMask,其大小和真正后台的类型_queue_t相同-----这就是掩码结

构体结构体实现私有成员保护的秘密。解决了私有成员的保护问题,剩下还有一个问题,对于queue.c的

函数来说queue_t只是一个数组,那么正常的功能如何实现呢?下面的代码片段为你解释一切:

 1 ...
 2 bool is_queue_empty(queue_t *ptQueue)
 3 {
 4     CLASS(queue_t) *ptQ = (CLASS(queue_t) *)ptQueue;
 5     if (NULL == ptQueue) {
 6         return true;
 7     }
 8     return ((ptQ->hwHead == ptQ->hwTail) && (0 == ptQ->Counter));
 9 }
10 ...

从编译器的角度来讲,这种从queue_t到_queue_t类型的转换是逻辑上的,并不会因此产生额外的代码,

简而言之,使用掩码结构体几乎是没有代价的----如果你找出了所谓的代价,一方面不妨告诉我,另一方

面不妨考虑这个代价和模块的封装相比是否是可以接受的。

时间: 12-21

模块的封装之C语言类的封装的相关文章

模块的封装之C语言类的继承和派生

[交流][微知识]模块的封装(二):C语言的继承和派生 在模块的封装(一):C语言的封装中,我们介绍了如何使用C语言的结构体来实现一个类的封装,并通过掩码结构体的方式实 现了类成员的保护.这一部分,我们将 在此的基础上介绍C语言类的继承和派生.其实继承和派生是一个动作的两种不同角度的表达 .当我们继承了一个基类而创造了一个新类时,派生的概念就诞生了.派生当然是从基类派生的.派生出来的类当然是继承了基类的 东西.继承和派生不是一对好基友,他们根本就是一个动作的两种不同的说法,强调动作的起始点的时候

022医疗项目-模块二:药品目录的导入导出-对XSSF导出excel类进行封装

资源全部来源于传智播客. 好的架构师写的程序,就算给刚入门的新手看,新手一看就知道怎么去用.所以我们要对XSSF导出excel类进行封装.这是架构师的工作,但我们也要知道. 我们写一个封装类: 这个类单独有自己的main函数. 我们进入main函数一步一步看: public static void main(String[] args) throws Exception { /** 导出文件存放物理路径 * @param fileWebPath * 导出文件web下载路径 * @param fi

025医疗项目-模块二:药品目录的导入导出-HSSF导入类的封装

上一篇文章提过,HSSF的用户模式会导致读取海量数据时很慢,所以我们采用的是事件驱动模式.这个模式类似于xml的sax解析.需要实现一个接口,HSSFListener接口. 原理:根据excel底层存储(07以版本采用xml存储,以下版本采用二进制)标签决定事件出发点. 目标:在解析完一行(row)数据时进行触发. 优点:读取大数据时,不会导致内存溢出 缺点:用户在解析数据时比较困难.读取数据时速度不快的,因为读取数据的同时根据每个标签进行事件触发. HSSF事件驱动读取文件的封装类解析. 主要

第三篇 :微信公众平台开发实战之请求消息,响应消息以及事件消息类的封装

微信服务器和第三方服务器之间究竟是通过什么方式进行对话的? 下面,我们先看下图: 其实我们可以简单的理解: (1)首先,用户向微信服务器发送消息: (2)微信服务器接收到用户的消息处理之后,通过开发者配置的URL和Token 来找到第三方服务器,并以XML形式向第三方服务器发送消息. (3)第三方服务器获取这些消息之后,需要按照微信服务器传过来的XML的语言进行解析,获取到信息之后,根据用户的需求,提供服务,然后封装成XML数据,传回到微信服务器上去. (4)微信服务器解析这些XML,并把相应的

OC语言类的本质和分类

OC语言类的深入和分类 一.分类 (一)分类的基本知识  概念:Category  分类是OC特有的语言,依赖于类. 分类的作用:在不改变原来的类内容的基础上,为类增加一些方法. 添加一个分类: 文件结构图: 在分类中添加一个方法 Study方法的实现 测试程序: (二)分类的使用注意 (1)分类只能增加方法(包括类方法和对象方法),不能增加成员变量 (2)在分类方法的实现中可以访问原来类中的成员变量: (3)分类中可以重新实现原来类中的方法,但是会覆盖掉原来的方法,导致原来的方法无法再使用(警

Java语言类的特性

一.内容的概述(主要内容) *类的私有成员和公共成员 *方法的重载 *构造方法 *类的静态方法 *对象的应用 二.类的私有成员和公有成员 私有成员:加private访问控制修饰符 公有成员:加public访问控制修饰符 如果没有一个机制来限制对类中成员的访问,则很有可能会造成错误的输入.为了防止这样的情况发生,Java语言提供了私有成员访问控制修饰符private..也就是说,如果在类的成员声明前加上修饰符private,则就无法从该类的外部访问到该类内部的成员,而只能通过该类自身访问和修改,而

PHP中的符号 -&gt;、=&gt; 和 :: 以及PDO类的封装

下文链接:http://www.95px.com/program/95px_program_3118.shtml 没关系,下面我们做一下详细的解释,如果你有C++,Perl基础,你会发现这些家伙和他们里面的一些符号功能是差不多的. '- >'符号是"插入式解引用操作符"(infix dereference operator) 换句话说,它是调用由引用传递参数的子程序的方法(当然,还有其它的作用).正如我们上面所提到的,在调用PHP的函数的时候,大部分参数都是通过引用传递的.PHP

VC++编程之第三课笔记——MFC窗口创建过程以及窗口类的封装

第三课 MFC窗口创建过程以及窗口类的封装 MFC的每一个类都是以C开头的,表明这是一个Class. 工程包含(单文档) 创建工程名为aaa的工程(单文档)时,在类视图中可看见五个类: CAboutDlg CMainFrame CAaaApp CAaaDoc CAaaView 其中: 类CAboutDlg继承自CDialog类,对话框的类 类CMainFrame继承自CFrameWnd类,创建整个程序的框架窗口 类CAaaApp继承自CWinApp类,创建唯一的应用程序对象 类CAaaDoc继承

【C语言学习】封装和模块化思想

刚学习完C后,做的关于C的课程设计是在一个源文件中放了几百行代码,而且各个功能之间都是相互依赖的,这样就会很麻烦.因为当我要修改某个地方的时候,就会牵连着要修改喝多的地方.而在实际的程序设计中,这也是不可取的.因此,模块化和封装的思想就显得很重要了!!! ★static变量 static变量的一个显著的作用就是可以实现一个模块的封装. static存储类别的特性决定了static声明的全局变量只能被本源文件的函数引用.当在一个源文件中定义一个static全局变量后,其他文件就不能通过使用"ext