?
?
【說(shuō)在前面的話】
?
“為什么要使用C語(yǔ)言來(lái)實(shí)現(xiàn)面向?qū)ο箝_(kāi)發(fā)?” ?“直接用C++不就好了么?”?
想必很多人在第一次面對(duì) OOPC(Object-Oriented-Programming-with-ANSI-C)的時(shí)候,都會(huì)情不自禁的發(fā)出類(lèi)似的疑問(wèn)。其實(shí),任何針對(duì)上述問(wèn)題的討論,其本身都是充滿爭(zhēng)議的——換句話說(shuō),無(wú)論我給出怎樣的答案,都無(wú)法令所有人滿意——正因如此,本文也無(wú)意去趟這攤渾水。 ? 我寫(xiě)這篇文章的目的是為那些長(zhǎng)期在MDK環(huán)境下從事C語(yǔ)言開(kāi)發(fā)的朋友介紹一種方法:幫助大家在偶爾需要用到“面向?qū)ο蟆备拍畹臅r(shí)候,能簡(jiǎn)便快捷的使用C語(yǔ)言“搞定”面向?qū)ο箝_(kāi)發(fā)。 ? 在開(kāi)始后續(xù)內(nèi)容之前,我們需要約定和強(qiáng)調(diào)一些基本原則: ?-
“零消耗”原則:即,我們所要實(shí)現(xiàn)的所有面向?qū)ο蟮奶匦远紤?yīng)該是“零資源消耗”或至少是“極小資源消耗”。這里的原理是:能在編譯時(shí)刻(Compiletime)搞定的事情,絕不拖到運(yùn)行時(shí)刻(Runtime)。
-
務(wù)實(shí)原則:即,我們不在形式上追求與C++類(lèi)似,除非它的代價(jià)是零或者非常小。
-
“按需實(shí)現(xiàn)”原則:即,對(duì)任何類(lèi)的實(shí)現(xiàn)來(lái)說(shuō),我們并不追求把所有的OO特性都實(shí)現(xiàn)出來(lái)——這完全沒(méi)有必要——我們僅根據(jù)實(shí)際應(yīng)用的需求來(lái)實(shí)現(xiàn)最小的、必要的面向?qū)ο蠹夹g(shù)。
-
“傻瓜化”原則:即,類(lèi)的建立和使用都必須足夠傻瓜化。最好所見(jiàn)即所得。
首先,我們要下載 PLOOC的 CMSIS-Pack,具體鏈接如下: ?
https://raw.githubusercontent.com/GorgonMeducer/PLOOC/master/cmsis-pack/GorgonMeducer.PLOOC.4.6.0.pack
? 當(dāng)然,如果你因?yàn)槟承┰驘o(wú)法訪問(wèn)Github,也可以在關(guān)注【裸機(jī)思維】公眾號(hào)后發(fā)送關(guān)鍵字 “PLOOC” 來(lái)獲取網(wǎng)盤(pán)鏈接。 ? 下載成功后,直接雙擊安裝包即可。 ?
一般來(lái)說(shuō),部署會(huì)非常順利,但如果出現(xiàn)了安裝錯(cuò)誤,比如下面這種:
?
?
則很可能是您所使用的MDK版本太低導(dǎo)致的——是時(shí)候更新下MDK啦。關(guān)注【裸機(jī)思維】公眾號(hào)后發(fā)送關(guān)鍵字"MDK",即可獲得最新的MDK網(wǎng)盤(pán)鏈接。
?
PLOOC 是?Protected-Low-overhead-Object-Oriented-programming-with-ansi-C 的縮寫(xiě),顧名思義,是一個(gè)強(qiáng)調(diào)地資源消耗且為私有類(lèi)成員提供保護(hù)的一個(gè)面向?qū)ο竽0濉? ? 它是一個(gè)開(kāi)源項(xiàng)目,如果你喜歡,還請(qǐng)多多Star哦! ?https://github.com/GorgonMeducer/PLOOC ?
?
?
【如何快速?lài)L鮮】
為了簡(jiǎn)化用戶對(duì) OOC 的學(xué)習(xí)成本,PLOOC提供了一個(gè)無(wú)需任何硬件就可以直接仿真執(zhí)行的例子工程。該例子工程以隊(duì)列類(lèi)為例子,展示了:
-
類(lèi)的定義方式
-
如何實(shí)現(xiàn)類(lèi)的方法(Method)
-
如何為類(lèi)定義接口(Interface)
-
如何定義派生類(lèi)
-
如何重載接口
-
如何在派生類(lèi)中訪問(wèn)基類(lèi)受保護(hù)的成員(Protected Member)
-
……
?
很多時(shí)候千言萬(wàn)語(yǔ)敵不過(guò)代碼幾行——學(xué)習(xí)OOC確是如此。 ? 例子工程的獲取非常簡(jiǎn)單。首先打開(kāi) Pack-Installer,在Device列表中找到Arm,選擇任意一款Cortex-M內(nèi)核(比如 Arm Cortex-M3)。在列表中選擇ARMCMx(比如下圖中的ARMCM3)。
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
? 其輸出為: ?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);
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
? 你看,同一個(gè)函數(shù) LOG_OUT() 當(dāng)我們給它不同數(shù)量和類(lèi)型的參數(shù)時(shí),居然可以實(shí)現(xiàn)不同的輸出效果,是不是特別神奇——這就是面向?qū)ο箝_(kāi)發(fā)中多態(tài)的魅力所在。請(qǐng)記?。?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)匀皇褂玫氖荂語(yǔ)言,而不是C++;
在C99下,我們可以實(shí)現(xiàn)擁有不同參數(shù)個(gè)數(shù)的函數(shù)共享同一個(gè)名字;
在C11下,我們可以實(shí)現(xiàn)擁有相同參數(shù)個(gè)數(shù)但類(lèi)型不同的函數(shù)共享同一個(gè)名字;
我們?cè)谶\(yùn)行時(shí)刻的開(kāi)銷(xiāo)是0,一切在編譯時(shí)刻就已經(jīng)塵埃落定了。我們并沒(méi)有為這項(xiàng)特性犧牲任何代碼空間。
PLOOC 模板其實(shí)是一套頭文件,既沒(méi)有庫(kù)(lib)也沒(méi)有C語(yǔ)言源代碼,更別提匯編了。 ? 在任意的MDK工程中,只要你已經(jīng)安裝了此前我們提到過(guò)的CMSIS-Pack,就可以通過(guò)下述工具欄中標(biāo)注的按鈕,打開(kāi)RTE配置界面: ?
則我們需要在 C/C++選項(xiàng)中:
-
將Language C設(shè)置為 gnu11(或者最低c99):
-
(推薦,而不是必須)在Misc Controls中添加對(duì)微軟擴(kuò)展的支持,并在 Define中添加一個(gè)宏定義 _MSC_VER。
- ?
-fms-extensions
?
?
如果你使用的是?Arm Compiler 5(armcc): ?

類(lèi)(class)
私有成員(private member)
公共成員(public member)
保護(hù)成員(protected member)
構(gòu)造函數(shù)(constructor)
析構(gòu)函數(shù)(destructor)
類(lèi)的方法(method)
……
假設(shè)我們要?jiǎng)?chuàng)造一個(gè)新的類(lèi),叫做 my_class1
?
第一步:引入模板在工程管理器中,添加一個(gè)新的group,命名為 my_class1:
?
?
右鍵單擊 my_class1,并在彈出的菜單中選擇 "Add New Item to Group my_class1":?
?
在彈出的對(duì)話框中選擇 User Code Template:
?

展開(kāi) Language Extension,可以看到有兩個(gè) PLOOC模板,分別對(duì)應(yīng):
- 基類(lèi)和普通類(lèi)(Base?Class Template)
- 派生類(lèi)(Derived Class Template)
由于我們要?jiǎng)?chuàng)建的是一個(gè)普通類(lèi)(未來(lái)也可以作為基類(lèi)),因此選擇“Base Class Template”。單擊Location右邊的 "..." 按鈕,選擇一個(gè)保存代碼文件的路徑后,單擊“Add”。
?
此時(shí)我們可以看到,class_name.c 被添加到了 my_class1中,且MDK自動(dòng)在編輯器中為我們打開(kāi)了兩個(gè)模板文件:class_name.h和class_name.c。
?
?
?
第二步:格式化在編輯器中打開(kāi)或者選中 class_name.c。通過(guò)快捷鍵CTRL+H打開(kāi)?替換窗口:
- 在Look in中選擇Current Document
- 去掉Find Opitons屬性框中的 Match?whold word前的勾選(這一步驟很重要)
接下來(lái),依次:
-
將小寫(xiě)的
-
將大寫(xiě)的
?替換為 MY_CLASS1
-
將小寫(xiě)的?
? 替換為?my_class1 -
將大寫(xiě)的?
?替換為?MY_CLASS1
在工程管理器中展開(kāi) my_class1,并將其中的 class_name.c 刪除:
?
打開(kāi)class_name.c 所在文件目錄:
?
打開(kāi) 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 */
//! @}
很容易注意到:
-
類(lèi)所對(duì)應(yīng)的類(lèi)型會(huì)自動(dòng)在尾部添加?"_t"?以表示這是一個(gè)自定義類(lèi)型,當(dāng)然這不是強(qiáng)制的,當(dāng)你熟悉模板后,如果確實(shí)看它不順眼,可以改成任何自己喜歡的類(lèi)型名稱(chēng)。這里,由于我們的類(lèi)叫做 my_class1,因此對(duì)應(yīng)的類(lèi)型就是 my_class1_t。
?
-
declare_class(或者也可以寫(xiě)成 dcl_class)用于類(lèi)型的“前置聲明”,它的本質(zhì)就是
- ?
typedef?struct my_class1_t my_class1_t;
因此并沒(méi)有什么特別神秘的地方。
?-
def_class用于定義類(lèi)的成員。其中 public_member用于存放公共可見(jiàn)的成員變量;private_member用于存放私有成員;protected_member用于存放當(dāng)前類(lèi)以及派生類(lèi)可見(jiàn)的成員。這三者的順序任意,可以缺失,也可以存在多個(gè)——非常靈活。
?
第四步:如何設(shè)計(jì)構(gòu)造函數(shù)
?
找到 typedef struct my_class1_cfg_t 對(duì)應(yīng)的代碼塊:- ?
- ?
- ?
- ?
- ?
typedef struct my_class1_cfg_t {
//! put your configuration members here
}?my_class1_cfg_t;
?
?
可以看到,這是個(gè)平平無(wú)奇的結(jié)構(gòu)體。它用于向我們的構(gòu)造函數(shù)傳遞初始化類(lèi)時(shí)所需的參數(shù)。在類(lèi)的頭文件中,你很容易找到構(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ù)是指向類(lèi)實(shí)例的指針,而第二個(gè)參數(shù)就是我們的配置結(jié)構(gòu)體。在類(lèi)的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ù)中,我們可以通過(guò) this.xxxx 的方式來(lái)訪問(wèn)類(lèi)的成員,以便根據(jù)配置結(jié)構(gòu)體中傳進(jìn)來(lái)的內(nèi)容對(duì)類(lèi)進(jìn)行初始化。
?
也許你已經(jīng)注意到了,我們的模板中并沒(méi)有任何為類(lèi)申請(qǐng)空間的代碼。這是有意為之。原因如下:-
面向?qū)ο蟛⒎且欢ㄒ褂脛?dòng)態(tài)內(nèi)存分配,這是一種偏見(jiàn)
-
我們只提供構(gòu)造函數(shù),而類(lèi)的用戶可以自由的決定如何為類(lèi)的實(shí)例分配存儲(chǔ)空間。
-
由于我們創(chuàng)造的類(lèi)(比如 my_class1_t)本質(zhì)上是一個(gè)完整的結(jié)構(gòu)體類(lèi)型,因此可以由用戶像普通結(jié)構(gòu)體那樣:
-
進(jìn)行靜態(tài)分配:即定義靜態(tài)變量,或是全局變量
-
使用池分配:直接為目標(biāo)類(lèi)構(gòu)建一個(gè)專(zhuān)用池,有效避免碎片化。
-
進(jìn)行堆分配:使用普通的malloc()進(jìn)行分配,類(lèi)的大小可以通過(guò)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)然,如果你說(shuō)我就是要那種形式主義,那你完全可以定義一個(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ù)類(lèi)似,比如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)類(lèi)的方法(method)
我們開(kāi)篇說(shuō)過(guò),實(shí)踐面向?qū)ο笞钪匾氖枪δ?,而非形式主義。假設(shè)有一個(gè)類(lèi)的方法叫做 method1,理想中,大家一定覺(jué)得如下的使用方式是最“正統(tǒng)”的:- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
在C語(yǔ)言中,我們完全可以實(shí)現(xiàn)類(lèi)似的效果——只要你在類(lèi)的定義中加入函數(shù)指針就行了——其實(shí)很多OOC的模板都是這么做的(比如lw_oopc)。但你仔細(xì)思考一下,在類(lèi)的結(jié)構(gòu)體中加入函數(shù)指針究竟有何利弊: 先來(lái)說(shuō)好處: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)雅”的方式來(lái)完成方法的調(diào)用;
-
支持運(yùn)行時(shí)刻的重載(Override);
?
再來(lái)說(shuō)缺點(diǎn):-
在嵌入式應(yīng)用中,大部分類(lèi)的方法都不需要重載,更別說(shuō)是運(yùn)行時(shí)刻的重載了;
-
函數(shù)指針會(huì)占用4個(gè)字節(jié);
-
通過(guò)函數(shù)指針來(lái)實(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中,任何類(lèi)的方法,其函數(shù)的第一個(gè)參數(shù)一定是指向類(lèi)實(shí)例的指針——也就是我們常說(shuō)的 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 這樣無(wú)成本的形式主義了。 ?第六步:如何設(shè)計(jì)類(lèi)的接口(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è)類(lèi)都提供了一個(gè)接口,并默認(rèn)將構(gòu)造和析構(gòu)函數(shù)都包含在內(nèi),比如,我們可以較為優(yōu)雅的對(duì)類(lèi)進(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 的接口定義中添加對(duì)應(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 */
//! @}
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
接下來(lái),我們要在 my_class1.h 中添加對(duì)應(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ù)的命名上與接口除大小寫(xiě)歪,還有一個(gè)簡(jiǎn)單的對(duì)應(yīng)關(guān)系:即,所有的"."直接替換成"_",比如,使用上:extern
void?my_class1_method1(my_class1_t *ptObj, <形參列表>);
- ?
MY_CLASS1.Method1()
就對(duì)應(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 */
};
- ?
- ?
- ?
- ?
- ?
- ?
- ?
至此,我們就完成了類(lèi)方法的添加和初始化。以后,在任何地方,都可以通過(guò)const i_my_class1_t MY_CLASS1 = {
.Init = &my_class1_init,
.Depose = &my_class1_depose,
/* other methods */
????.Method1?=??????????&my_class1_method1,
};
- ?
<類(lèi)名大寫(xiě)>.<接口中方法名>()
的形式來(lái)訪問(wèn)類(lèi)的操作函數(shù)了——這也算某種程度上的優(yōu)雅了吧。
?第六步:如何設(shè)計(jì)派生類(lèi)(Derived Class)派生類(lèi)的創(chuàng)建在基本步驟上與普通類(lèi)基本一致,除了在模板選擇階段使用對(duì)應(yīng)的模板外,還需要在“格式化”階段額外添加以下兩個(gè)替換步驟:
-
將
替換為 基類(lèi)的大寫(xiě)名稱(chēng); -
將
替換為基類(lèi)的小寫(xiě)名稱(chēng);
在類(lèi)的定義階段,我們注意到:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
派生類(lèi)在原有的類(lèi)定義基礎(chǔ)上多出了的結(jié)構(gòu),以"," 與類(lèi)的類(lèi)型名隔開(kāi)://! 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è)基類(lèi),或是要繼承多個(gè)接口,則可以寫(xiě)成如下的形式:- ?
- ?
- ?
- ?
- ?
- ?
需要注意的是,如果基類(lèi)或是接口中存在名稱(chēng)沖突(重名)的成員,則可以將 implement() 替換為? inherit() 來(lái)避免這種沖突。比如which(
implement(
_t) implement(
_t) implement(
_t) implement(
_t) )
- ?
- ?
- ?
- ?
- ?
- ?
? 就像這里所展示的那樣,PLOOC支持多繼承,這是C++和C#都不曾支持的——這也是 使用C語(yǔ)言來(lái)實(shí)現(xiàn)OO的魅力之一,具體方法,大家可以自行摸索,這里就不再贅述。 ?which(
inherit(
_t) implement(
_t) implement(
_t) implement(
_t) )
大家都知道,在面向?qū)ο笾校幸活?lèi)成員只有當(dāng)前類(lèi)和派生類(lèi)能夠訪問(wèn)——我們稱(chēng)之為受保護(hù)成員(protected member)。在類(lèi)的定義中,可以通過(guò) protected_member() 將這些成員囊括起來(lái),比如:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,hwHead、hwTail和hwCount 都只有當(dāng)前類(lèi)和派生類(lèi)能訪問(wèn)。 ? 對(duì)于那些只允許派生類(lèi)訪問(wèn)的方法(函數(shù))來(lái)說(shuō),我們一般會(huì)使用預(yù)編譯宏的形式將其有條件的保護(hù)起來(lái)://! 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() 僅能夠允許類(lèi) byte_queue_t 自身極其派生類(lèi)才能訪問(wèn)了。 ? 在我們前面創(chuàng)建的 my_class1.h 中我們也有一個(gè)類(lèi)似的例子:extern mem_t byte_queue_buffer_get(byte_queue_t *ptObj);
- ?
- ?
- ?
- ?
- ?
函數(shù)?my_class1_protected_method_example() 就是一個(gè)僅供 my_class1 極其派生類(lèi)訪問(wèn)的 受保護(hù)的方法。 ? 在派生類(lèi)中,如果要訪問(wèn)基類(lèi)的受保護(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);
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,派生類(lèi)借助 this.use_as__byte_queue_t 獲得了對(duì)基類(lèi)的“引用”,并借助? protected_internal() 將其轉(zhuǎn)化為了名為 ptBase 的指針。在 base 宏的幫助下,我們得以通過(guò)? base.xxxx 來(lái)訪問(wèn)基類(lèi)的成員。在例子中,我們看到,base.hwTail 和 base.hwCount 正是前面所展示過(guò)的 byte_queue_t 的受保護(hù)成員。 ?【說(shuō)在后面的話】
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---------------- */
}
?
無(wú)論使用何種模板,OOPC來(lái)發(fā)的一個(gè)核心理念應(yīng)該是“務(wù)實(shí)”,即:以最小的成本(最好是零成本),占最大的便宜(來(lái)自O(shè)O所帶來(lái)的好處)。
?
此前,我曾經(jīng)在文章《真刀真槍模塊化(2.5)—— 君子協(xié)定》詳細(xì)介紹過(guò)PLOOC的原理和手動(dòng)部署技術(shù)。借助CMSIS-Pack和MDK中RTE的幫助,原本繁瑣的手動(dòng)部署和類(lèi)的創(chuàng)建過(guò)程得到了空前的簡(jiǎn)化,使用OOPC進(jìn)行開(kāi)發(fā)從未如此簡(jiǎn)單過(guò)——幾乎與直接使用C++相差無(wú)幾了。
?
不知不覺(jué)間,從2年前第一次將其公開(kāi)算起,PLOOC已經(jīng)斬獲了一百多個(gè)Star——算是我倉(cāng)庫(kù)中的明星工程了。從日志上來(lái)看,PLOOC相當(dāng)穩(wěn)定。距離我上一次“覺(jué)得其有必要更新”還是整整一年多前的事情,而加入CMSIS-Pack只是一件錦上添花的事情。
?
?
?
最后,感謝大家的支持——是你們的Star支撐著我一路對(duì)項(xiàng)目的持續(xù)更新。謝謝! ? ? ?
評(píng)論