[微知识]模块的封装(一):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语言

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

 #define EXTERN_CLASS(__NAME,...) \
typedef union __NAME __NAME;\
__VA_ARGS__\
union __NAME {\
uint_fast8_t chMask[(sizeof(struct { #define END_EXTERN_CLASS(__NAME) \
}) + sizeof(uint_fast8_t) - ) / sizeof(uint_fast8_t)];\
}; #define DEF_CLASS(__NAME,...)\
typedef union __NAME __NAME;\
__VA_ARGS__\
typedef struct __##__NAME __##__NAME;\
struct __##__NAME{ #define END_DEF_CLASS(__NAME) \
};\
union __NAME {\
uint_fast8_t chMask[(sizeof(__##__NAME) + sizeof(uint_fast8_t) - ) / sizeof(uint_fast8_t)];\
}; #define CLASS(__NAME) __##__NAME

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

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

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

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

 //! \name byte queue
//! @{
typedef struct {
uint8_t *pchBuffer; //!< queue buffer
uint16_t hwBufferSize; //!< buffer size
uint16_t hwHead; //!< head pointer
uint16_t hwTail; //!< tail pointer
uint16_t hwCounter; //!< byte counter
}queue_t;
//! @}

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

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

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

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

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

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

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

的么?”

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

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

queue.h

 ...
//! \name byte queue
//! @{
EXTERN_CLASS(queue_t)
uint8_t *pchBuffer; //!< queue buffer
uint16_t hwBufferSize; //!< buffer size
uint16_t hwHead; //!< head pointer
uint16_t hwTail; //!< tail pointer
uint16_t hwCounter; //!< byte counter
END_EXTERN_CLASS(queue_t)
//! @}
...
extern bool queue_init(queue_t *ptQueue, uint8_t *pchBuffer, uint16_t hwSize);
extern bool enqueue(queue_t *ptQueue, uint8_t chByte);
extern bool dequeue(queue_t *ptQueue, uint8_t *pchByte);
extern bool is_queue_empty(queue_t *ptQueue);
...

queue.c

 ...
//! \name byte queue
//! @{
EXTERN_CLASS(queue_t)
uint8_t *pchBuffer; //!< queue buffer
uint16_t hwBufferSize; //!< buffer size
uint16_t hwHead; //!< head pointer
uint16_t hwTail; //!< tail pointer
uint16_t hwCounter; //!< byte counter
END_EXTERN_CLASS(queue_t)
//! @}
...
extern bool queue_init(queue_t *ptQueue, uint8_t *pchBuffer, uint16_t hwSize);
extern bool enqueue(queue_t *ptQueue, uint8_t chByte);
extern bool dequeue(queue_t *ptQueue, uint8_t *pchByte);
extern bool is_queue_empty(queue_t *ptQueue);
...

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

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

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

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

 ...
bool is_queue_empty(queue_t *ptQueue)
{
CLASS(queue_t) *ptQ = (CLASS(queue_t) *)ptQueue;
if (NULL == ptQueue) {
return true;
}
return ((ptQ->hwHead == ptQ->hwTail) && ( == ptQ->Counter));
}
...

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

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

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

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

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

    [交流][微知识]模块的封装(二):C语言的继承和派生 在模块的封装(一):C语言的封装中,我们介绍了如何使用C语言的结构体来实现一个类的封装,并通过掩码结构体的方式实 现了类成员的保护.这一部分,我 ...

  2. Learn day6 模块pickle\json\random\os\zipfile\面对对象(类的封装 操作 __init__)

    1.模块 1.1 pickle模块 # ### pickle 序列化模块 import pickle """ 序列化: 把不能够直接存储的数据变得可存储 反序列化: 把数 ...

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

    上一篇文章提过,HSSF的用户模式会导致读取海量数据时很慢,所以我们采用的是事件驱动模式.这个模式类似于xml的sax解析.需要实现一个接口,HSSFListener接口. 原理:根据excel底层存 ...

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

    资源全部来源于传智播客. 好的架构师写的程序,就算给刚入门的新手看,新手一看就知道怎么去用.所以我们要对XSSF导出excel类进行封装.这是架构师的工作,但我们也要知道. 我们写一个封装类: 这个类 ...

  5. python面向对象 : 抽象类(接口类),多态,封装(私有制封装)

    一. 抽象类(接口类) 与java一样, python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类, 它的特殊之处在于只能被继承, 不能被实例化. 从设计角度去看, 如果类是从现实对 ...

  6. Python面向对象之:三大特性:继承,封装,多态以及类的约束

    前言: python面向对象的三大特性:继承,封装,多态. 1. 封装: 把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情 ...

  7. OC语言类的本质和分类

    OC语言类的深入和分类 一.分类 (一)分类的基本知识  概念:Category  分类是OC特有的语言,依赖于类. 分类的作用:在不改变原来的类内容的基础上,为类增加一些方法. 添加一个分类: 文件 ...

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

    微信服务器和第三方服务器之间究竟是通过什么方式进行对话的? 下面,我们先看下图: 其实我们可以简单的理解: (1)首先,用户向微信服务器发送消息: (2)微信服务器接收到用户的消息处理之后,通过开发者 ...

  9. 李洪强iOS开发之OC语言类的深入和分类

    OC语言类的深入和分类 一.分类 (一)分类的基本知识  概念:Category  分类是OC特有的语言,依赖于类. 分类的作用:在不改变原来的类内容的基础上,为类增加一些方法. 添加一个分类: 文件 ...

随机推荐

  1. 香港主机Squid+Stunnel代理搭建

    1.说明 Squid,代理软件 Stunnel,数据包加密(貌似如果数据不加密,客户端的数据流无法传到squid服务端,原因你懂的!) 2.Squid安装略 3.安装完squid后需要以下操作 a.生 ...

  2. vue - webpack、babel

    一.webpack 在这里我仅仅的是对webpack做个讲解,webpack这个工具非常强大,解决了我们前端很繁琐的一些工具流程繁琐的事情.如果感兴趣的同学,建议还是看官网吧. 中文链接地址:http ...

  3. PHP面向对象详解:继承、封装与多态

    首先,在解释面向对象之前先解释下什么是面向对象? [面向对象]1.什么是类? 具有相同属性(特征)和方法(行为)的一系列个体的集合,类是一个抽象的概念2.什么是对象?从类中拿到的具有具体属性值得个体, ...

  4. Dolls---hdu4160(最大匹配)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4160 有n个长方体形的娃娃:当长宽高都小于另一个的时候可以放进去,每一个里面最多放一个,问最优的套法下 ...

  5. Git学习-->关于Jenkins编译时候,如何获取Git分支的当前分支名?

    一.背景 因为代码都迁移到了Gitlab,所以Jenkins编译的时候我们都需要将之前的SVN信息换成现在的Git信息.最近编译一个Lib库的时候,因为团队规定上传Release版本的AAR到Mave ...

  6. ssh 配置文件讲解大全 ssh调试模式 sftp scp strace进行调试 特权分离

    ssh 配置文件讲解大全  ssh调试模式  sftp scp strace进行调试  特权分离 http://blog.chinaunix.net/uid-16728139-id-3265394.h ...

  7. HDFS的工作流程分析

    HDFS的工作机制 概述 HDFS集群分为两大角色:NameNode.DataNode NameNode负责管理整个文件系统的元数据 DataNode 负责管理用户的文件数据块 文件会按照固定的大小( ...

  8. Alpine Linux配置使用技巧【一个只有5M的操作系统(转)】

    Alpine Linux是一个面向安全应用的轻量级Linux发行版.它采用了musl libc和busybox以减小系统的体积和运行时资源消耗,同时还提供了自己的包管理工具apk. Alpine Li ...

  9. SpringData_JpaRepository接口

    该接口提供了JPA的相关功能 List<T> findAll(); //查找所有实体 List<T> findAll(Sort sort); //排序.查找所有实体 List& ...

  10. (16)Cocos2d-x 多分辨率适配完全解析

    Overview 从Cocos2d-x 2.0.4开始,Cocos2d-x提出了自己的多分辨率支持方案,废弃了之前的retina相关设置接口,提出了design resolution概念. 3.0中有 ...