?
?
【說在前面的話】
?
“為什么要使用C語言來實現面向對象開發?” ?“直接用C++不就好了么?”?
想必很多人在第一次面對 OOPC(Object-Oriented-Programming-with-ANSI-C)的時候,都會情不自禁的發出類似的疑問。其實,任何針對上述問題的討論,其本身都是充滿爭議的——換句話說,無論我給出怎樣的答案,都無法令所有人滿意——正因如此,本文也無意去趟這攤渾水。 ? 我寫這篇文章的目的是為那些長期在MDK環境下從事C語言開發的朋友介紹一種方法:幫助大家在偶爾需要用到“面向對象”概念的時候,能簡便快捷的使用C語言“搞定”面向對象開發。 ? 在開始后續內容之前,我們需要約定和強調一些基本原則: ?-
“零消耗”原則:即,我們所要實現的所有面向對象的特性都應該是“零資源消耗”或至少是“極小資源消耗”。這里的原理是:能在編譯時刻(Compiletime)搞定的事情,絕不拖到運行時刻(Runtime)。
-
務實原則:即,我們不在形式上追求與C++類似,除非它的代價是零或者非常小。
-
“按需實現”原則:即,對任何類的實現來說,我們并不追求把所有的OO特性都實現出來——這完全沒有必要——我們僅根據實際應用的需求來實現最小的、必要的面向對象技術。
-
“傻瓜化”原則:即,類的建立和使用都必須足夠傻瓜化。最好所見即所得。
首先,我們要下載 PLOOC的 CMSIS-Pack,具體鏈接如下: ?
https://raw.githubusercontent.com/GorgonMeducer/PLOOC/master/cmsis-pack/GorgonMeducer.PLOOC.4.6.0.pack
? 當然,如果你因為某些原因無法訪問Github,也可以在關注【裸機思維】公眾號后發送關鍵字 “PLOOC” 來獲取網盤鏈接。 ? 下載成功后,直接雙擊安裝包即可。 ?
一般來說,部署會非常順利,但如果出現了安裝錯誤,比如下面這種:
?
?
則很可能是您所使用的MDK版本太低導致的——是時候更新下MDK啦。關注【裸機思維】公眾號后發送關鍵字"MDK",即可獲得最新的MDK網盤鏈接。
?
PLOOC 是?Protected-Low-overhead-Object-Oriented-programming-with-ansi-C 的縮寫,顧名思義,是一個強調地資源消耗且為私有類成員提供保護的一個面向對象模板。 ? 它是一個開源項目,如果你喜歡,還請多多Star哦! ?https://github.com/GorgonMeducer/PLOOC ?
?
?
【如何快速嘗鮮】
為了簡化用戶對 OOC 的學習成本,PLOOC提供了一個無需任何硬件就可以直接仿真執行的例子工程。該例子工程以隊列類為例子,展示了:
-
類的定義方式
-
如何實現類的方法(Method)
-
如何為類定義接口(Interface)
-
如何定義派生類
-
如何重載接口
-
如何在派生類中訪問基類受保護的成員(Protected Member)
-
……
?
很多時候千言萬語敵不過代碼幾行——學習OOC確是如此。 ? 例子工程的獲取非常簡單。首先打開 Pack-Installer,在Device列表中找到Arm,選擇任意一款Cortex-M內核(比如 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);
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
? 你看,同一個函數 LOG_OUT() 當我們給它不同數量和類型的參數時,居然可以實現不同的輸出效果,是不是特別神奇——這就是面向對象開發中多態的魅力所在。請記住: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));
-
此時我們仍然使用的是C語言,而不是C++;
-
在C99下,我們可以實現擁有不同參數個數的函數共享同一個名字;
-
在C11下,我們可以實現擁有相同參數個數但類型不同的函數共享同一個名字;
-
我們在運行時刻的開銷是0,一切在編譯時刻就已經塵埃落定了。我們并沒有為這項特性犧牲任何代碼空間。
PLOOC 模板其實是一套頭文件,既沒有庫(lib)也沒有C語言源代碼,更別提匯編了。 ? 在任意的MDK工程中,只要你已經安裝了此前我們提到過的CMSIS-Pack,就可以通過下述工具欄中標注的按鈕,打開RTE配置界面: ?
則我們需要在 C/C++選項中:
-
將Language C設置為 gnu11(或者最低c99):
-
(推薦,而不是必須)在Misc Controls中添加對微軟擴展的支持,并在 Define中添加一個宏定義 _MSC_VER。
- ?
-fms-extensions
?
?
如果你使用的是?Arm Compiler 5(armcc): ?
![287e39f8-3413-11ed-ba43-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/96/81/wKgZomTnIFSANaBAAAAsobQyAIc494.jpg)
-
類(class)
-
私有成員(private member)
-
公共成員(public member)
-
保護成員(protected member)
-
構造函數(constructor)
-
析構函數(destructor)
-
類的方法(method)
-
……
假設我們要創造一個新的類,叫做 my_class1
?
第一步:引入模板在工程管理器中,添加一個新的group,命名為 my_class1:
?
?
右鍵單擊 my_class1,并在彈出的菜單中選擇 "Add New Item to Group my_class1":?
?
在彈出的對話框中選擇 User Code Template:
?
![28d4d8bc-3413-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/81/wKgZomTnIFSAE9KSAACiOfx5ijU492.png)
展開 Language Extension,可以看到有兩個 PLOOC模板,分別對應:
- 基類和普通類(Base?Class Template)
- 派生類(Derived Class Template)
由于我們要創建的是一個普通類(未來也可以作為基類),因此選擇“Base Class Template”。單擊Location右邊的 "..." 按鈕,選擇一個保存代碼文件的路徑后,單擊“Add”。
?
此時我們可以看到,class_name.c 被添加到了 my_class1中,且MDK自動在編輯器中為我們打開了兩個模板文件: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 所在文件目錄:
?
打開 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 */
//! @}
很容易注意到:
-
類所對應的類型會自動在尾部添加?"_t"?以表示這是一個自定義類型,當然這不是強制的,當你熟悉模板后,如果確實看它不順眼,可以改成任何自己喜歡的類型名稱。這里,由于我們的類叫做 my_class1,因此對應的類型就是 my_class1_t。
?
-
declare_class(或者也可以寫成 dcl_class)用于類型的“前置聲明”,它的本質就是
- ?
typedef?struct my_class1_t my_class1_t;
因此并沒有什么特別神秘的地方。
?-
def_class用于定義類的成員。其中 public_member用于存放公共可見的成員變量;private_member用于存放私有成員;protected_member用于存放當前類以及派生類可見的成員。這三者的順序任意,可以缺失,也可以存在多個——非常靈活。
?
第四步:如何設計構造函數
?
找到 typedef struct my_class1_cfg_t 對應的代碼塊:- ?
- ?
- ?
- ?
- ?
typedef struct my_class1_cfg_t {
//! put your configuration members here
}?my_class1_cfg_t;
?
?
可以看到,這是個平平無奇的結構體。它用于向我們的構造函數傳遞初始化類時所需的參數。在類的頭文件中,你很容易找到構造函數的函數原型:
?
- ?
- ?
- ?
/*! rief the constructor of the class: my_class1 */
extern
my_class1_t * my_class1_init(my_class1_t *ptObj, my_class1_cfg_t *ptCFG);
?
可以看到,其第一個參數是指向類實例的指針,而第二個參數就是我們的配置結構體。在類的C源代碼文件中,可以找到構造函數的實體:
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
/*! 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;
}
?
此時,在構造函數中,我們可以通過 this.xxxx 的方式來訪問類的成員,以便根據配置結構體中傳進來的內容對類進行初始化。
?
也許你已經注意到了,我們的模板中并沒有任何為類申請空間的代碼。這是有意為之。原因如下:-
面向對象并非一定要使用動態內存分配,這是一種偏見
-
我們只提供構造函數,而類的用戶可以自由的決定如何為類的實例分配存儲空間。
-
由于我們創造的類(比如 my_class1_t)本質上是一個完整的結構體類型,因此可以由用戶像普通結構體那樣:
-
進行靜態分配:即定義靜態變量,或是全局變量
-
使用池分配:直接為目標類構建一個專用池,有效避免碎片化。
-
進行堆分配:使用普通的malloc()進行分配,類的大小可以通過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);
當然,如果你說我就是要那種形式主義,那你完全可以定義一個宏:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
({__name
__VA_ARGS__
};???????????????????????????????????????
__name
(__name
?????&tCFG);})
這可不就是一個根正苗紅的 new()方法么,比如:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
my_class1_t?*ptItem?=?new_class(my_class, <構造用的參數列表>);
if (NULL == ptItem) {
printf("Failed to new my_class1_t ");
}
...
free(ptItem);
怎么樣,是這個味道吧?析構函數類似,比如my_class1_depose()函數,同樣不負責資源的釋放——決定權還是在用戶的手里,當然你也可以做完一套:
- ?
- ?
- ?
- ?
- ?
????do?{?????????????????????????????
????????__name
????????free(__obj);
????} while(0)
形成組合拳,從分配資源、構造、析構到最后釋放資源一氣呵成:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
my_class1_t *ptItem = new_class(my_class, <構造用的參數列表>);
if (NULL == ptItem) {
printf("Failed to new my_class1_t ");
}
...
free_class(my_class, ptItem);
?
第五步:如何設計構類的方法(method)
我們開篇說過,實踐面向對象最重要的是功能,而非形式主義。假設有一個類的方法叫做 method1,理想中,大家一定覺得如下的使用方式是最“正統”的:- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
在C語言中,我們完全可以實現類似的效果——只要你在類的定義中加入函數指針就行了——其實很多OOC的模板都是這么做的(比如lw_oopc)。但你仔細思考一下,在類的結構體中加入函數指針究竟有何利弊: 先來說好處:my_class1_t *ptItem = new_class(my_class, <構造用的參數列表>);
if (NULL == ptItem) {
printf("Failed to new my_class1_t ");
}
ptItem.method1(<實參列表>);
free_class(my_class, ptItem);
-
可以用“優雅”的方式來完成方法的調用;
-
支持運行時刻的重載(Override);
?
再來說缺點:-
在嵌入式應用中,大部分類的方法都不需要重載,更別說是運行時刻的重載了;
-
函數指針會占用4個字節;
-
通過函數指針來實現的間接調用,其效率低于普通的函數直接調用。
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,my_class1_method1() 是 my_class1.h 提供聲明、my_class1.c 提供實現的一個函數。前綴 my_class1_ 用于防止命名空間污染。 ? 另外一個值得注意的細節是,OOPC中,任何類的方法,其函數的第一個參數一定是指向類實例的指針——也就是我們常說的 this 指針。以 my_class1_method1() 為例,它的形式為:my_class1_t *ptItem = new_class(my_class, <構造用的參數列表>);
if (NULL == ptItem) {
printf("Failed to new my_class1_t ");
}
my_class1_method1(ptItem,<實參列表>);
free_class(my_class, ptItem);
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,class_internal() 用于將 ptObj轉變成我們所需的 this指針(這里的ptThis),借助宏的幫助,我們就可以實現? this.xxxx 這樣無成本的形式主義了。 ?第六步:如何設計類的接口(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);
????
????...????
}
我們的模板還為每個類都提供了一個接口,并默認將構造和析構函數都包含在內,比如,我們可以較為優雅的對類進行構造和析構:
?
- ?
- ?
- ?
- ?
- ?
? 在 my_class1.h 中,我們可以找到這樣的結構:static?my_class1_t?s_tMyClass;
...
MY_CLASS.Init(&s_tMyClass,?...);
...
MY_CLASS.Depose(&s_tMyClass);
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
假設我們要加入一個新的方法,則只需要在 i_my_class1_t 的接口定義中添加對應的函數指針即可,比如://! 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 中添加對應方法的函數聲明://! 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 */
//! @}
- ?
- ?
這里,值得注意的是,習慣上函數的命名上與接口除大小寫歪,還有一個簡單的對應關系:即,所有的"."直接替換成"_",比如,使用上:extern
void?my_class1_method1(my_class1_t *ptObj, <形參列表>);
- ?
MY_CLASS1.Method1()
就對應為:- ?
my_class1_method1()
?
與此同時,我們需要在 my_class1.c 中添加 my_class1_method1() 函數的實體:- ?
- ?
- ?
- ?
- ?
并找到名為 MY_CLASS1 的接口實例:void my_class1_method1(my_class1_t *ptObj, <形參列表>)
{
class_internal(ptObj, ptThis, my_class1_t);
????...
}
- ?
- ?
- ?
- ?
- ?
- ?
在其中初始化我們的新方法(新函數指針) 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,
};
- ?
<類名大寫>.<接口中方法名>()
的形式來訪問類的操作函數了——這也算某種程度上的優雅了吧。
?第六步:如何設計派生類(Derived Class)派生類的創建在基本步驟上與普通類基本一致,除了在模板選擇階段使用對應的模板外,還需要在“格式化”階段額外添加以下兩個替換步驟:
-
將
替換為 基類的大寫名稱; -
將
替換為基類的小寫名稱;
在類的定義階段,我們注意到:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
派生類在原有的類定義基礎上多出了的結構,以"," 與類的類型名隔開://! 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() 其實是一個列表,它允許我們實現多重繼承。假設我們有多個基類,或是要繼承多個接口,則可以寫成如下的形式:- ?
- ?
- ?
- ?
- ?
- ?
需要注意的是,如果基類或是接口中存在名稱沖突(重名)的成員,則可以將 implement() 替換為? inherit() 來避免這種沖突。比如which(
implement(
_t) implement(
_t) implement(
_t) implement(
_t) )
- ?
- ?
- ?
- ?
- ?
- ?
? 就像這里所展示的那樣,PLOOC支持多繼承,這是C++和C#都不曾支持的——這也是 使用C語言來實現OO的魅力之一,具體方法,大家可以自行摸索,這里就不再贅述。 ?which(
inherit(
_t) implement(
_t) implement(
_t) implement(
_t) )
大家都知道,在面向對象中,有一類成員只有當前類和派生類能夠訪問——我們稱之為受保護成員(protected member)。在類的定義中,可以通過 protected_member() 將這些成員囊括起來,比如:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
這里,hwHead、hwTail和hwCount 都只有當前類和派生類能訪問。 ? 對于那些只允許派生類訪問的方法(函數)來說,我們一般會使用預編譯宏的形式將其有條件的保護起來://! 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 的保護,函數?byte_queue_buffer_get() 僅能夠允許類 byte_queue_t 自身極其派生類才能訪問了。 ? 在我們前面創建的 my_class1.h 中我們也有一個類似的例子:extern mem_t byte_queue_buffer_get(byte_queue_t *ptObj);
- ?
- ?
- ?
- ?
- ?
函數?my_class1_protected_method_example() 就是一個僅供 my_class1 極其派生類訪問的 受保護的方法。 ? 在派生類中,如果要訪問基類的受保護成員,則可以借助 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() 將其轉化為了名為 ptBase 的指針。在 base 宏的幫助下,我們得以通過? base.xxxx 來訪問基類的成員。在例子中,我們看到,base.hwTail 和 base.hwCount 正是前面所展示過的 byte_queue_t 的受保護成員。 ?【說在后面的話】
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來發的一個核心理念應該是“務實”,即:以最小的成本(最好是零成本),占最大的便宜(來自OO所帶來的好處)。
?
此前,我曾經在文章《真刀真槍模塊化(2.5)—— 君子協定》詳細介紹過PLOOC的原理和手動部署技術。借助CMSIS-Pack和MDK中RTE的幫助,原本繁瑣的手動部署和類的創建過程得到了空前的簡化,使用OOPC進行開發從未如此簡單過——幾乎與直接使用C++相差無幾了。
?
不知不覺間,從2年前第一次將其公開算起,PLOOC已經斬獲了一百多個Star——算是我倉庫中的明星工程了。從日志上來看,PLOOC相當穩定。距離我上一次“覺得其有必要更新”還是整整一年多前的事情,而加入CMSIS-Pack只是一件錦上添花的事情。
?
?
?
最后,感謝大家的支持——是你們的Star支撐著我一路對項目的持續更新。謝謝! ? ? ?
評論