[微知识]模块的封装(一):C语言类的封装数组
是的,你没有看错,咱们要讨论的是C语言而不是C++语言中类的封装。在展开知识点以前,我首先要函数
重申两点:测试
一、面向对象是一种思想,基本与所用的语言是无关的。当你心怀面向对象时,即便使用QBasic也能写ui
出符合面向对象思想的代码,更不要说C语言了。举一个反例,不少人初学C++的时候,并无掌spa
握面向对象的思想,活生生的把类当结构体来使用的也不在少数吧。指针
二、面向对象的最基本的出发点是“将数据以及处理数据的方法封装在一块儿”,至于继承、派生、多态之类code
的则是后面扩展的东西。在C语言中,若是用结构体来保存数据,并将处理这些数据的函数与结构体对象
的定义封装在同一个.c文件中,则该.c文件就能够视做一个类。若是将指向具体函数的函数指针与结blog
构体的其余成员封装在同一个结构体中,则该“对象”的使用甚至与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类型的转换是逻辑上的,并不会所以产生额外的代码,
简而言之,使用掩码结构体几乎是没有代价的----若是你找出了所谓的代价,一方面不妨告诉我,另外一方
面不妨考虑这个代价和模块的封装相比是不是能够接受的。