?
?
【說在前面的話】
?
“為什么要使用C語言來實(shí)現(xiàn)面向?qū)ο箝_發(fā)?” ?“直接用C++不就好了么?”?
想必很多人在第一次面對 OOPC(Object-Oriented-Programming-with-ANSI-C)的時(shí)候,都會(huì)情不自禁的發(fā)出類似的疑問。其實(shí),任何針對上述問題的討論,其本身都是充滿爭議的——換句話說,無論我給出怎樣的答案,都無法令所有人滿意——正因如此,本文也無意去趟這攤渾水。 ? 我寫這篇文章的目的是為那些長期在MDK環(huán)境下從事C語言開發(fā)的朋友介紹一種方法:幫助大家在偶爾需要用到“面向?qū)ο蟆备拍畹臅r(shí)候,能簡便快捷的使用C語言“搞定”面向?qū)ο箝_發(fā)。 ? 在開始后續(xù)內(nèi)容之前,我們需要約定和強(qiáng)調(diào)一些基本原則: ?-
“零消耗”原則:即,我們所要實(shí)現(xiàn)的所有面向?qū)ο蟮奶匦远紤?yīng)該是“零資源消耗”或至少是“極小資源消耗”。這里的原理是:能在編譯時(shí)刻(Compiletime)搞定的事情,絕不拖到運(yùn)行時(shí)刻(Runtime)。
-
務(wù)實(shí)原則:即,我們不在形式上追求與C++類似,除非它的代價(jià)是零或者非常小。
-
“按需實(shí)現(xiàn)”原則:即,對任何類的實(shí)現(xiàn)來說,我們并不追求把所有的OO特性都實(shí)現(xiàn)出來——這完全沒有必要——我們僅根據(jù)實(shí)際應(yīng)用的需求來實(shí)現(xiàn)最小的、必要的面向?qū)ο蠹夹g(shù)。
-
“傻瓜化”原則:即,類的建立和使用都必須足夠傻瓜化。最好所見即所得。
首先,我們要下載 PLOOC的 CMSIS-Pack,具體鏈接如下: ?
https://raw.githubusercontent.com/GorgonMeducer/PLOOC/master/cmsis-pack/GorgonMeducer.PLOOC.4.6.0.pack
? 當(dāng)然,如果你因?yàn)槟承┰驘o法訪問Github,也可以在關(guān)注【裸機(jī)思維】公眾號后發(fā)送關(guān)鍵字 “PLOOC” 來獲取網(wǎng)盤鏈接。 ? 下載成功后,直接雙擊安裝包即可。 ?
一般來說,部署會(huì)非常順利,但如果出現(xiàn)了安裝錯(cuò)誤,比如下面這種:
?
?
則很可能是您所使用的MDK版本太低導(dǎo)致的——是時(shí)候更新下MDK啦。關(guān)注【裸機(jī)思維】公眾號后發(fā)送關(guān)鍵字"MDK",即可獲得最新的MDK網(wǎng)盤鏈接。
?
PLOOC 是?Protected-Low-overhead-Object-Oriented-programming-with-ansi-C 的縮寫,顧名思義,是一個(gè)強(qiáng)調(diào)地資源消耗且為私有類成員提供保護(hù)的一個(gè)面向?qū)ο竽0濉? ? 它是一個(gè)開源項(xiàng)目,如果你喜歡,還請多多Star哦! ?https://github.com/GorgonMeducer/PLOOC ?
?
?
【如何快速嘗鮮】
為了簡化用戶對 OOC 的學(xué)習(xí)成本,PLOOC提供了一個(gè)無需任何硬件就可以直接仿真執(zhí)行的例子工程。該例子工程以隊(duì)列類為例子,展示了:
-
類的定義方式
-
如何實(shí)現(xiàn)類的方法(Method)
-
如何為類定義接口(Interface)
-
如何定義派生類
-
如何重載接口
-
如何在派生類中訪問基類受保護(hù)的成員(Protected Member)
-
……
?
很多時(shí)候千言萬語敵不過代碼幾行——學(xué)習(xí)OOC確是如此。 ? 例子工程的獲取非常簡單。首先打開 Pack-Installer,在Device列表中找到Arm,選擇任意一款Cortex-M內(nèi)核(比如 Arm Cortex-M3)。在列表中選擇ARMCMx(比如下圖中的ARMCM3)。此時(shí),在右邊的Example選項(xiàng)卡中,就可以看到最底部出現(xiàn)了一個(gè)名為 plooc_example (uVision Simulator)的例子工程。單擊Copy,在彈出窗口中選擇一個(gè)目錄位置來保存工程: ?
單擊OK后將打開自動(dòng)打開如下所示的 MDK 界面: ?
直接單擊編譯,如果一切順利,應(yīng)該沒有任何編譯錯(cuò)誤: ?
? 此時(shí),我們可以直接進(jìn)入調(diào)試模式: ?
? 可以看到,調(diào)試指針停在了 main() 函數(shù)的起始位置。我們先不著急開始全速運(yùn)行。通過菜單打開 "Debug (printf) Viewer" 窗口: ?
? 一開始該窗口會(huì)出現(xiàn)在屏幕下方的窗體中,通過拖動(dòng)的方式,我們可以將其挪到醒目的位置。此時(shí),全速運(yùn)行就可以看到例子工程所要展示的效果了: ?
該例子只展示了C99模式下使用PLOOC所構(gòu)建的隊(duì)列類(enhanced_byte_queue_t)的效果:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
? 其輸出為: ?static enhanced_byte_queue_t s_tQueue;
printf("Hello PLOOC! ");
do {
static uint8_t s_chQueueBuffer[QUEUE_BUFFER_SIZE];
const enhanced_byte_queue_cfg_t tCFG = {
s_chQueueBuffer,
sizeof(s_chQueueBuffer),
};
ENHANCED_BYTE_QUEUE.Init(&s_tQueue, (enhanced_byte_queue_cfg_t *)&tCFG);
} while(0);
//! you can enqueue
ENHANCED_BYTE_QUEUE.Enqueue(&s_tQueue, 'p');
ENHANCED_BYTE_QUEUE.Enqueue(&s_tQueue, 'L');
ENHANCED_BYTE_QUEUE.Enqueue(&s_tQueue, 'O');
ENHANCED_BYTE_QUEUE.Enqueue(&s_tQueue, 'O');
ENHANCED_BYTE_QUEUE.Enqueue(&s_tQueue, 'C');
ENHANCED_BYTE_QUEUE.use_as__i_byte_queue_t.Enqueue(&s_tQueue.use_as__byte_queue_t, '.');
ENHANCED_BYTE_QUEUE.use_as__i_byte_queue_t.Enqueue(&s_tQueue.use_as__byte_queue_t, '.');
ENHANCED_BYTE_QUEUE.use_as__i_byte_queue_t.Enqueue(&s_tQueue.use_as__byte_queue_t, '.');
//! you can dequeue
do {
uint_fast16_t n = ENHANCED_BYTE_QUEUE.Count(&s_tQueue);
uint8_t chByte;
printf("There are %d byte in the queue! ", n);
printf("let's peek! ");
while(ENHANCED_BYTE_QUEUE.Peek.PeekByte(&s_tQueue, &chByte)) {
printf("%c ", chByte);
}
printf("There are %d byte(s) in the queue! ",
ENHANCED_BYTE_QUEUE.Count(&s_tQueue));
printf("Let's remove all peeked byte(s) from queue... ");
ENHANCED_BYTE_QUEUE.Peek.GetAllPeeked(&s_tQueue);
printf("Now there are %d byte(s) in the queue! ",
ENHANCED_BYTE_QUEUE.Count(&s_tQueue));
} while(0);
? 類 enhanced_byte_queue_t 實(shí)際上是從基類 byte_queue_t 基礎(chǔ)上派生出來的,并添加了一個(gè)非常有用的功能:可以連續(xù)的偷看(Peek)隊(duì)列里的內(nèi)容,并可以在需要的時(shí)候,要么1)將已經(jīng)偷看的內(nèi)容實(shí)際都取出來;要么2)從頭開始偷看——上述代碼就展示了這一功能。 ? PLOOC 相較普通的OOC模板來說,除了可以隱藏類的私有成員(private member)以外,還能夠以零運(yùn)行時(shí)成本實(shí)現(xiàn)多肽(Polymorphism)——用通俗的話說就是:PLOOC允許擁有不同參數(shù)數(shù)量、不同參數(shù)類型的多個(gè)函數(shù)擁有相同的名字。 ? 要獲得這樣的功能,就要打開 C11(最好是GNU11)的支持。當(dāng)我們打開工程配置,在“C/C++”選項(xiàng)卡中將 Language C 設(shè)置為 c11(最好是gnu11): ?
重新編譯后,進(jìn)入調(diào)試模式,將在輸出窗口中看到額外的信息: ?
? 這些信息實(shí)際上對應(yīng)如下的代碼: ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
? 你看,同一個(gè)函數(shù) LOG_OUT() 當(dāng)我們給它不同數(shù)量和類型的參數(shù)時(shí),居然可以實(shí)現(xiàn)不同的輸出效果,是不是特別神奇——這就是面向?qū)ο箝_發(fā)中多態(tài)的魅力所在。請記?。?ul class="list-paddingleft-1" style="list-style-type:disc;">LOG_OUT(" -[Demo of overload]------------------------------ ");
LOG_OUT((uint32_t) 0x12345678);
LOG_OUT(" ");
LOG_OUT(0x12345678);
LOG_OUT(" ");
LOG_OUT("PI is ");
LOG_OUT(3.1415926f);
LOG_OUT(" ");
LOG_OUT(" Show BYTE Array: ");
LOG_OUT((uint8_t *)main, 100);
LOG_OUT(" Show Half-WORD Array: ");
LOG_OUT((uint16_t *)(((intptr_t)&main) & ~0x1), 100/sizeof(uint16_t));
LOG_OUT(" Show WORD Array: ");
LOG_OUT((uint32_t *)(((intptr_t)&main) & ~0x3), 100/sizeof(uint32_t));
此時(shí)我們?nèi)匀皇褂玫氖荂語言,而不是C++;
在C99下,我們可以實(shí)現(xiàn)擁有不同參數(shù)個(gè)數(shù)的函數(shù)共享同一個(gè)名字;
在C11下,我們可以實(shí)現(xiàn)擁有相同參數(shù)個(gè)數(shù)但類型不同的函數(shù)共享同一個(gè)名字;
我們在運(yùn)行時(shí)刻的開銷是0,一切在編譯時(shí)刻就已經(jīng)塵埃落定了。我們并沒有為這項(xiàng)特性犧牲任何代碼空間。
PLOOC 模板其實(shí)是一套頭文件,既沒有庫(lib)也沒有C語言源代碼,更別提匯編了。 ? 在任意的MDK工程中,只要你已經(jīng)安裝了此前我們提到過的CMSIS-Pack,就可以通過下述工具欄中標(biāo)注的按鈕,打開RTE配置界面: ?
?找到 Language Extension選項(xiàng),將其展開后勾選PLOOC,單擊OK關(guān)閉窗口。
此時(shí),我們就可以在工程管理器中看到一個(gè)新的列表項(xiàng)“Language Extension”: ?
? 它是不可展開的,別擔(dān)心,這就足夠了。打開工程配置,如果你使用的是 Arm Compiler 6(armclang): ?
則我們需要在 C/C++選項(xiàng)中:
-
將Language C設(shè)置為 gnu11(或者最低c99):
-
(推薦,而不是必須)在Misc Controls中添加對微軟擴(kuò)展的支持,并在 Define中添加一個(gè)宏定義 _MSC_VER。
- ?
-fms-extensions
?
?
如果你使用的是?Arm Compiler 5(armcc): ?則需要在 C/C++?選項(xiàng)卡中開啟對 GNU Extension 和 C99的支持: ?
遺憾的是作為一款已經(jīng)停止更新的編譯器,Arm Compiler 5 既不支持C11,也不支持微軟擴(kuò)展(-fms-extensions),這意味著PLOOC中的多態(tài)特性無法發(fā)揮最大潛能,著實(shí)有點(diǎn)遺憾(但擁有不同參數(shù)數(shù)量的函數(shù)還是允許共享同一個(gè)名稱的)。 ? 至此,我們就完成了PLOOC在一個(gè)工程中的部署。是不是特別簡單? ? ? 也許文章到了一半我才問,已經(jīng)有點(diǎn)遲了——大家都熟悉基本的面向?qū)ο蟾拍畎??比如?ul class="list-paddingleft-1" style="list-style-type:disc;">
類(class)
私有成員(private member)
公共成員(public member)
保護(hù)成員(protected member)
構(gòu)造函數(shù)(constructor)
析構(gòu)函數(shù)(destructor)
類的方法(method)
……
假設(shè)我們要?jiǎng)?chuàng)造一個(gè)新的類,叫做 my_class1
?
第一步:引入模板在工程管理器中,添加一個(gè)新的group,命名為 my_class1:
?
?
右鍵單擊 my_class1,并在彈出的菜單中選擇 "Add New Item to Group my_class1":?
?
在彈出的對話框中選擇 User Code Template:
?
展開 Language Extension,可以看到有兩個(gè) PLOOC模板,分別對應(yīng):
- 基類和普通類(Base?Class Template)
- 派生類(Derived Class Template)
由于我們要?jiǎng)?chuàng)建的是一個(gè)普通類(未來也可以作為基類),因此選擇“Base Class Template”。單擊Location右邊的 "..." 按鈕,選擇一個(gè)保存代碼文件的路徑后,單擊“Add”。
?
此時(shí)我們可以看到,class_name.c 被添加到了 my_class1中,且MDK自動(dòng)在編輯器中為我們打開了兩個(gè)模板文件:class_name.h和class_name.c。
?
?
?
第二步:格式化在編輯器中打開或者選中 class_name.c。通過快捷鍵CTRL+H打開?替換窗口:
- 在Look in中選擇Current Document
- 去掉Find Opitons屬性框中的 Match?whold word前的勾選(這一步驟很重要)
接下來,依次:
-
將小寫的
-
將大寫的
?替換為 MY_CLASS1
-
將小寫的?
? 替換為?my_class1 -
將大寫的?
?替換為?MY_CLASS1
在工程管理器中展開 my_class1,并將其中的 class_name.c 刪除:
?
?
打開class_name.c 所在文件目錄:
?
? 找到我們剛剛編輯好的兩個(gè)文件?class_name.c 和 class_name.h: ?
? 用我們的類為這兩個(gè)文件命名:my_class1.c 和 my_class1.h ?
? 在MDK工程管理器中,將這兩個(gè)文件加入 my_class1?下: ?
?如果此前你的工程就是可以正常編譯的話,在加入了上述文件后,應(yīng)該依然可以正常編譯:
?第四步:如何設(shè)計(jì)你的類成員變量
打開 my_class1.h,找到?def_class 所在的代碼片斷:
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//! ame class my_class1_t
//! @{
declare_class(my_class1_t)
def_class(my_class1_t,
public_member(
//!< place your public members here
)
private_member(
//!< place your private members here
)
protected_member(
//!< place your private members here
)
)
end_def_class(my_class1_t) /* do not remove this for forward compatibility */
//! @}
很容易注意到:
-
類所對應(yīng)的類型會(huì)自動(dòng)在尾部添加?"_t"?以表示這是一個(gè)自定義類型,當(dāng)然這不是強(qiáng)制的,當(dāng)你熟悉模板后,如果確實(shí)看它不順眼,可以改成任何自己喜歡的類型名稱。這里,由于我們的類叫做 my_class1,因此對應(yīng)的類型就是 my_class1_t。
?
-
declare_class(或者也可以寫成 dcl_class)用于類型的“前置聲明”,它的本質(zhì)就是
- ?
typedef?struct my_class1_t my_class1_t;
因此并沒有什么特別神秘的地方。
?-
def_class用于定義類的成員。其中 public_member用于存放公共可見的成員變量;private_member用于存放私有成員;protected_member用于存放當(dāng)前類以及派生類可見的成員。這三者的順序任意,可以缺失,也可以存在多個(gè)——非常靈活。
?
第四步:如何設(shè)計(jì)構(gòu)造函數(shù)
?
找到 typedef struct my_class1_cfg_t 對應(yīng)的代碼塊:- ?
- ?
- ?
- ?
- ?
typedef struct my_class1_cfg_t {
//! put your configuration members here
}?my_class1_cfg_t;
?
?
可以看到,這是個(gè)平平無奇的結(jié)構(gòu)體。它用于向我們的構(gòu)造函數(shù)傳遞初始化類時(shí)所需的參數(shù)。在類的頭文件中,你很容易找到構(gòu)造函數(shù)的函數(shù)原型:
?
- ?
- ?
- ?
/*! rief the constructor of the class: my_class1 */
extern
my_class1_t * my_class1_init(my_class1_t *ptObj, my_class1_cfg_t *ptCFG);
?
可以看到,其第一個(gè)參數(shù)是指向類實(shí)例的指針,而第二個(gè)參數(shù)就是我們的配置結(jié)構(gòu)體。在類的C源代碼文件中,可以找到構(gòu)造函數(shù)的實(shí)體:
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
/*! rief the constructor of the class: my_class1 */
my_class1_t * my_class1_init(my_class1_t *ptObj, my_class1_cfg_t *ptCFG)
{
/* initialise "this" (i.e. ptThis) to access class members */
????class_internal(ptObj,?ptThis,?my_class1_t);
ASSERT(NULL != ptObj && NULL != ptCFG);
return ptObj;
}
?
此時(shí),在構(gòu)造函數(shù)中,我們可以通過 this.xxxx 的方式來訪問類的成員,以便根據(jù)配置結(jié)構(gòu)體中傳進(jìn)來的內(nèi)容對類進(jìn)行初始化。
?
也許你已經(jīng)注意到了,我們的模板中并沒有任何為類申請空間的代碼。這是有意為之。原因如下:-
面向?qū)ο蟛⒎且欢ㄒ褂脛?dòng)態(tài)內(nèi)存分配,這是一種偏見
-
我們只提供構(gòu)造函數(shù),而類的用戶可以自由的決定如何為類的實(shí)例分配存儲(chǔ)空間。
-
由于我們創(chuàng)造的類(比如 my_class1_t)本質(zhì)上是一個(gè)完整的結(jié)構(gòu)體類型,因此可以由用戶像普通結(jié)構(gòu)體那樣:
-
進(jìn)行靜態(tài)分配:即定義靜態(tài)變量,或是全局變量
-
使用池分配:直接為目標(biāo)類構(gòu)建一個(gè)專用池,有效避免碎片化。
-
進(jìn)行堆分配:使用普通的malloc()進(jìn)行分配,類的大小可以通過sizeof() 獲得,比如:
-
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
my_class1_cfg_t tCFG = {
...
};
my_class1_t?*ptNewItem?=?my_class1_init(
???? (my_class1_t *)malloc(sizeof(my_class1_t),
?????&tCFG);
if?(NULL?==?ptNewItem) {
????printf("Failed?to?new my_class1_t ");
}
...
free(ptNewItem);
當(dāng)然,如果你說我就是要那種形式主義,那你完全可以定義一個(gè)宏:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
({__name
__VA_ARGS__
};???????????????????????????????????????
__name
(__name
?????&tCFG);})
這可不就是一個(gè)根正苗紅的 new()方法么,比如:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
my_class1_t?*ptItem?=?new_class(my_class, <構(gòu)造用的參數(shù)列表>);
if (NULL == ptItem) {
printf("Failed to new my_class1_t ");
}
...
free(ptItem);
怎么樣,是這個(gè)味道吧?析構(gòu)函數(shù)類似,比如my_class1_depose()函數(shù),同樣不負(fù)責(zé)資源的釋放——決定權(quán)還是在用戶的手里,當(dāng)然你也可以做完一套:
- ?
- ?
- ?
- ?
- ?
????do?{?????????????????????????????
????????__name
????????free(__obj);
????} while(0)
形成組合拳,從分配資源、構(gòu)造、析構(gòu)到最后釋放資源一氣呵成:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
my_class1_t *ptItem = new_class(my_class, <構(gòu)造用的參數(shù)列表>);
if (NULL == ptItem) {
printf("Failed to new my_class1_t ");
}
...
free_class(my_class, ptItem);
?
第五步:如何設(shè)計(jì)構(gòu)類的方法(method)
我們開篇說過,實(shí)踐面向?qū)ο笞钪匾氖枪δ埽切问街髁x。假設(shè)有一個(gè)類的方法叫做 method1,理想中,大家一定覺得如下的使用方式是最“正統(tǒng)”的:- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
在C語言中,我們完全可以實(shí)現(xiàn)類似的效果——只要你在類的定義中加入函數(shù)指針就行了——其實(shí)很多OOC的模板都是這么做的(比如lw_oopc)。但你仔細(xì)思考一下,在類的結(jié)構(gòu)體中加入函數(shù)指針究竟有何利弊: 先來說好處:my_class1_t *ptItem = new_class(my_class, <構(gòu)造用的參數(shù)列表>);
if (NULL == ptItem) {
printf("Failed to new my_class1_t ");
}
ptItem.method1(<實(shí)參列表>);
free_class(my_class, ptItem);
-
可以用“優(yōu)雅”的方式來完成方法的調(diào)用;
-
支持運(yùn)行時(shí)刻的重載(Override);
?
再來說缺點(diǎn):-
在嵌入式應(yīng)用中,大部分類的方法都不需要重載,更別說是運(yùn)行時(shí)刻的重載了;
-
函數(shù)指針會(huì)占用4個(gè)字節(jié);
-
通過函數(shù)指針來實(shí)現(xiàn)的間接調(diào)用,其效率低于普通的函數(shù)直接調(diào)用。
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,my_class1_method1() 是 my_class1.h 提供聲明、my_class1.c 提供實(shí)現(xiàn)的一個(gè)函數(shù)。前綴 my_class1_ 用于防止命名空間污染。 ? 另外一個(gè)值得注意的細(xì)節(jié)是,OOPC中,任何類的方法,其函數(shù)的第一個(gè)參數(shù)一定是指向類實(shí)例的指針——也就是我們常說的 this 指針。以 my_class1_method1() 為例,它的形式為:my_class1_t *ptItem = new_class(my_class, <構(gòu)造用的參數(shù)列表>);
if (NULL == ptItem) {
printf("Failed to new my_class1_t ");
}
my_class1_method1(ptItem,<實(shí)參列表>);
free_class(my_class, ptItem);
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,class_internal() 用于將 ptObj轉(zhuǎn)變成我們所需的 this指針(這里的ptThis),借助宏的幫助,我們就可以實(shí)現(xiàn)? this.xxxx 這樣無成本的形式主義了。 ?第六步:如何設(shè)計(jì)類的接口(Interface)#undef this
#define this (*ptThis)
void?my_class1_method(my_class1_t?*ptObj,?<形參列表>)
{
/* initialise "this" (i.e. ptThis) to access class members */
????class_internal(ptObj,?ptThis,?my_class1_t);
????
????...????
}
我們的模板還為每個(gè)類都提供了一個(gè)接口,并默認(rèn)將構(gòu)造和析構(gòu)函數(shù)都包含在內(nèi),比如,我們可以較為優(yōu)雅的對類進(jìn)行構(gòu)造和析構(gòu):
?
- ?
- ?
- ?
- ?
- ?
? 在 my_class1.h 中,我們可以找到這樣的結(jié)構(gòu):static?my_class1_t?s_tMyClass;
...
MY_CLASS.Init(&s_tMyClass,?...);
...
MY_CLASS.Depose(&s_tMyClass);
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
假設(shè)我們要加入一個(gè)新的方法,則只需要在 i_my_class1_t 的接口定義中添加對應(yīng)的函數(shù)指針即可,比如://! ame interface i_my_class1_t
//! @{
def_interface(i_my_class1_t)
my_class1_t * (*Init) (my_class1_t *ptObj, my_class1_cfg_t *ptCFG);
void (*Depose) (my_class1_t *ptObj);
/* other methods */
end_def_interface(i_my_class1_t) /*do not remove this for forward compatibility */
//! @}
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
接下來,我們要在 my_class1.h 中添加對應(yīng)方法的函數(shù)聲明://! ame interface i_my_class1_t
//! @{
def_interface(i_my_class1_t)
my_class1_t * (*Init) (my_class1_t *ptObj, my_class1_cfg_t *ptCFG);
void (*Depose) (my_class1_t *ptObj);
/* other methods */
????void???????????(*Method1)????(my_class1_t?*ptObj,?<形參列表>);
end_def_interface(i_my_class1_t) /*do not remove this for forward compatibility */
//! @}
- ?
- ?
這里,值得注意的是,習(xí)慣上函數(shù)的命名上與接口除大小寫歪,還有一個(gè)簡單的對應(yīng)關(guān)系:即,所有的"."直接替換成"_",比如,使用上:extern
void?my_class1_method1(my_class1_t *ptObj, <形參列表>);
- ?
MY_CLASS1.Method1()
就對應(yīng)為:- ?
my_class1_method1()
?
與此同時(shí),我們需要在 my_class1.c 中添加 my_class1_method1() 函數(shù)的實(shí)體:- ?
- ?
- ?
- ?
- ?
并找到名為 MY_CLASS1 的接口實(shí)例:void my_class1_method1(my_class1_t *ptObj, <形參列表>)
{
class_internal(ptObj, ptThis, my_class1_t);
????...
}
- ?
- ?
- ?
- ?
- ?
- ?
在其中初始化我們的新方法(新函數(shù)指針) Method1:const i_my_class1_t MY_CLASS1 = {
.Init = &my_class1_init,
.Depose = &my_class1_depose,
/* other methods */
};
- ?
- ?
- ?
- ?
- ?
- ?
- ?
至此,我們就完成了類方法的添加和初始化。以后,在任何地方,都可以通過const i_my_class1_t MY_CLASS1 = {
.Init = &my_class1_init,
.Depose = &my_class1_depose,
/* other methods */
????.Method1?=??????????&my_class1_method1,
};
- ?
<類名大寫>.<接口中方法名>()
的形式來訪問類的操作函數(shù)了——這也算某種程度上的優(yōu)雅了吧。
?第六步:如何設(shè)計(jì)派生類(Derived Class)派生類的創(chuàng)建在基本步驟上與普通類基本一致,除了在模板選擇階段使用對應(yīng)的模板外,還需要在“格式化”階段額外添加以下兩個(gè)替換步驟:
-
將
替換為 基類的大寫名稱; -
將
替換為基類的小寫名稱;
在類的定義階段,我們注意到:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
派生類在原有的類定義基礎(chǔ)上多出了的結(jié)構(gòu),以"," 與類的類型名隔開://! ame class
_t //! @{
declare_class(
_t) def_class(
_t, which(implement( _t)) ????...
)
end_def_class(
_t) /* do not remove this for forward compatibility */ //! @}
- ?
which(implement(<base_class_name>_t))
這里,which() 其實(shí)是一個(gè)列表,它允許我們實(shí)現(xiàn)多重繼承。假設(shè)我們有多個(gè)基類,或是要繼承多個(gè)接口,則可以寫成如下的形式:- ?
- ?
- ?
- ?
- ?
- ?
需要注意的是,如果基類或是接口中存在名稱沖突(重名)的成員,則可以將 implement() 替換為? inherit() 來避免這種沖突。比如which(
implement(
_t) implement(
_t) implement(
_t) implement(
_t) )
- ?
- ?
- ?
- ?
- ?
- ?
? 就像這里所展示的那樣,PLOOC支持多繼承,這是C++和C#都不曾支持的——這也是 使用C語言來實(shí)現(xiàn)OO的魅力之一,具體方法,大家可以自行摸索,這里就不再贅述。 ?which(
inherit(
_t) implement(
_t) implement(
_t) implement(
_t) )
大家都知道,在面向?qū)ο笾?,有一類成員只有當(dāng)前類和派生類能夠訪問——我們稱之為受保護(hù)成員(protected member)。在類的定義中,可以通過 protected_member() 將這些成員囊括起來,比如:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,hwHead、hwTail和hwCount 都只有當(dāng)前類和派生類能訪問。 ? 對于那些只允許派生類訪問的方法(函數(shù))來說,我們一般會(huì)使用預(yù)編譯宏的形式將其有條件的保護(hù)起來://! ame class byte_queue_t
//! @{
declare_class(byte_queue_t)
def_class(byte_queue_t,
private_member(
implement(mem_t) //!< queue buffer
void *pTarget; //!< user target
)
protected_member(
uint16_t hwHead; //!< head pointer
uint16_t hwTail; //!< tail pointer
uint16_t hwCount; //!< byte count
)
)
end_def_class(byte_queue_t) /* do not remove this for forward compatibility */
//! @}
- ?
- ?
- ?
這里,受到宏 __BYTE_QUEUE_CLASS_IMPLEMENT 和 __BYTE_QUEUE_CLASS_INHERIT 的保護(hù),函數(shù)?byte_queue_buffer_get() 僅能夠允許類 byte_queue_t 自身極其派生類才能訪問了。 ? 在我們前面創(chuàng)建的 my_class1.h 中我們也有一個(gè)類似的例子:extern mem_t byte_queue_buffer_get(byte_queue_t *ptObj);
- ?
- ?
- ?
- ?
- ?
函數(shù)?my_class1_protected_method_example() 就是一個(gè)僅供 my_class1 極其派生類訪問的 受保護(hù)的方法。 ? 在派生類中,如果要訪問基類的受保護(hù)成員,則可以借助 protected_internal() 的幫助,例如: ?/*! rief a method only visible for current class and derived class */
extern void my_class1_protected_method_example(my_class1_t *ptObj);
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,派生類借助 this.use_as__byte_queue_t 獲得了對基類的“引用”,并借助? protected_internal() 將其轉(zhuǎn)化為了名為 ptBase 的指針。在 base 宏的幫助下,我們得以通過? base.xxxx 來訪問基類的成員。在例子中,我們看到,base.hwTail 和 base.hwCount 正是前面所展示過的 byte_queue_t 的受保護(hù)成員。 ?【說在后面的話】
void enhanced_byte_queue_peek_reset(enhanced_byte_queue_t *ptObj)
{
/* initialise "this" (i.e. ptThis) to access class members */
class_internal(ptObj, ptThis, enhanced_byte_queue_t);
/* initialise "base" (i.e. ptBase) to access protected members */
protected_internal(&this.use_as__byte_queue_t, ptBase, byte_queue_t);
ASSERT(NULL != ptObj);
/* ------------------atomicity sensitive start---------------- */
this.hwPeek = base.hwTail;
this.hwPeekCount = base.hwCount;
/* ------------------atomicity sensitive end---------------- */
}
?
無論使用何種模板,OOPC來發(fā)的一個(gè)核心理念應(yīng)該是“務(wù)實(shí)”,即:以最小的成本(最好是零成本),占最大的便宜(來自O(shè)O所帶來的好處)。
?
此前,我曾經(jīng)在文章《真刀真槍模塊化(2.5)—— 君子協(xié)定》詳細(xì)介紹過PLOOC的原理和手動(dòng)部署技術(shù)。借助CMSIS-Pack和MDK中RTE的幫助,原本繁瑣的手動(dòng)部署和類的創(chuàng)建過程得到了空前的簡化,使用OOPC進(jìn)行開發(fā)從未如此簡單過——幾乎與直接使用C++相差無幾了。
?
不知不覺間,從2年前第一次將其公開算起,PLOOC已經(jīng)斬獲了一百多個(gè)Star——算是我倉庫中的明星工程了。從日志上來看,PLOOC相當(dāng)穩(wěn)定。距離我上一次“覺得其有必要更新”還是整整一年多前的事情,而加入CMSIS-Pack只是一件錦上添花的事情。
?
?
?
最后,感謝大家的支持——是你們的Star支撐著我一路對項(xiàng)目的持續(xù)更新。謝謝! ? ? ?
評論
查看更多