[微知識]模塊的封裝(一):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類型的轉換是邏輯上的,並不會所以產生額外的代碼,
簡而言之,使用掩碼結構體幾乎是沒有代價的----若是你找出了所謂的代價,一方面不妨告訴我,另外一方
面不妨考慮這個代價和模塊的封裝相比是不是能夠接受的。