前言
學(xué)習(xí)完《Test-Driven Development for Embedded C》后對C語言中的面向?qū)ο箝_發(fā)又多了一層理解,過兩天可能專門出個博客來說說新的理解。
而我已經(jīng)按照更面向?qū)ο蟮姆椒ù蟾牧嗽瓉淼哪莻€環(huán)形緩沖區(qū)模塊,考慮到整個結(jié)構(gòu)已經(jīng)完全不同了,所以直接棄用了原來那個模塊,新模塊直接重新開始記版本號。
Buffer模塊為了通用,定義了前后都可以進出,想當(dāng)成隊列來用比如可以入隊用BackIn,出隊用FrontOut;相當(dāng)成棧來用比如可以入棧用BackIn,出棧用BackOut。當(dāng)然,第三篇中我會給出Queue類,并把Buffer類適配為了Queue類,這樣可能用起來更舒服些,雖然當(dāng)然有額外的開銷。
緩沖區(qū)介紹
實際項目中我們常常會需要一小塊區(qū)域來暫存一些數(shù)據(jù),可能是用來緩存使用,可能是用來線程間通信,我們把其稱為緩沖區(qū)/Buffer。這塊區(qū)域可能是先入先出的(隊列)也可能是先入后出的(棧),但反正最后都可以抽象為一個可以在頭或尾存取數(shù)據(jù)的內(nèi)存區(qū)域。
緩沖區(qū)的具體實現(xiàn)方式一般有鏈表和數(shù)組,當(dāng)不能確定需要的緩沖區(qū)大小時使用鏈表較好,能確定時使用數(shù)組可以節(jié)省很多動態(tài)分配內(nèi)存的開銷。
而在具體實現(xiàn)上我們常常會使用環(huán)形緩沖區(qū),環(huán)形緩沖區(qū)就是一個邏輯上環(huán)形的區(qū)域,因為其(邏輯上)是環(huán)形的,所以不需要在內(nèi)部元素變動的時候需要移動內(nèi)部剩下的元素。這樣就使元素進出頭尾的時間復(fù)雜度只有O(1),效率十分的高,在通信等領(lǐng)域應(yīng)用頻繁。
模塊類圖
以下是目前整個模塊的類圖。
模塊設(shè)計思路簡介
緩沖區(qū)是個很常見的需求,即對一塊邏輯上環(huán)形的區(qū)域的頭尾進行In和Out操作,以緩存各種類型的數(shù)據(jù)。對調(diào)用者來說,并不需要知道其使用的模塊/類內(nèi)部實際是怎么實現(xiàn)的,只需要知道這個模塊/類實現(xiàn)哪幾個方法,這幾個方法是干什么用的就行(其實就是所謂的面向接口編程)。因此,需要為環(huán)形緩沖區(qū)定義通用的接口。
在一個略大的項目中我們常常需要在多處使用環(huán)形緩沖區(qū)。所以在這次的實現(xiàn)中我并沒有使用單例的方式來實現(xiàn)這個模塊,而是直接默認是多例的方式,不同的實現(xiàn)各自提供Create方法來返回對象引用,Destroy方法來銷毀。
實際開發(fā)中有時會需要混用多種實現(xiàn),比如有的你希望使用一個RAM數(shù)組,有的想用鏈表,甚至有的是使用Flash來存儲的等。而只要接口相同,調(diào)用者不需要知道具體的實現(xiàn)細節(jié),只需要你給他傳遞一個實現(xiàn)了這個接口的對象就行。這其實就實現(xiàn)了不同模塊間的解耦。而為了實現(xiàn)在同一個C工程中調(diào)用同一個接口能實際調(diào)用不同的實現(xiàn)(即多態(tài)),這就需要使用虛表技術(shù)。這里就不展開了。
再考慮一個很實際的需求,在CodeWarrior對S12X的編程中,為了節(jié)省非分頁的RAM,我常想要把這個環(huán)形緩沖區(qū)放在分頁的RAM中,這樣,兩種環(huán)形緩沖區(qū)可能唯一的差別就是具體訪問某個元素的方式是使用普通指針還是rptr指針,如果分別寫一個實現(xiàn),就會有大量的冗余代碼。出于程序員的自我修養(yǎng),肯定得把這些通用的部分給抽象出來。用繼承的方式實現(xiàn)代碼的復(fù)用。
好像前面說的有點混亂。簡而言之,就是按照面向?qū)ο蟮乃枷?,定義不同層次的接口,通過虛表實現(xiàn)多態(tài),通過類繼承盡可能復(fù)用代碼,最終實現(xiàn)這個完整的模塊。
我們可以照著類圖看看目前我的抽象方式。首先所有對象都會有個Destroy方法,所以object接口對其進行了定義。這里我沒有專門再定義一個Object虛類,后面可能會抽象出來。而最主要的一個基類叫做Container(容器),所有的容器都要實現(xiàn)getCapacity和getCount接口。而isEmpty和isFull其實是通過調(diào)用這兩個虛方法返回結(jié)果的。
而后虛類BufferTYPE繼承了Container虛類,并(虛)實現(xiàn)了環(huán)形緩沖區(qū)的7個通用方法,包括檢查頭/尾元素(Front和Back)、從頭/尾取出元素(FrontOut和BackOut)、往頭/尾放入元素(FrontOut和BackOut)以及清空緩沖區(qū)(Cleanup)。
TYPE只是一個代號,使用時要替換成實際類型。目前我實現(xiàn)了UINT8、UINT16、UINT32的。如果需要使用其他類型,如果類型的size為8、16、32,建議直接適配一下就好(其實就是對同樣size的進行強制類型轉(zhuǎn)換一下,BufferChar給出了一個示例),這樣可以盡可能地實現(xiàn)代碼復(fù)用,如果是其他特殊的,那就照著源代碼中的自己擴展一下吧,主要是C語言沒有模板功能,只好直接在名字中標(biāo)記上類型來區(qū)分。
然后繼承的BufferTYPEIndexed虛類要實現(xiàn)索引器接口,其是所有能通過索引直接訪問的Buffer的基類。
BufferArrayShare類繼承BufferTYPEIndexed類并實現(xiàn)了所有通過類數(shù)組操作來實現(xiàn)的Buffer類的通用部分,其調(diào)用索引器來訪問數(shù)組元素,這樣就實現(xiàn)了不同訪問方式的復(fù)用。與之相對的則是子類BufferTypeArray和BufferTypeArrayR分別實現(xiàn)了位于直接訪問區(qū)的數(shù)組和分頁區(qū)數(shù)組的索引器。
而末尾的3個實現(xiàn)類則主要負責(zé)內(nèi)存的管理部分,動態(tài)分配實例并實現(xiàn)對應(yīng)的Destroy方法,然后調(diào)用父類的Init方法來實現(xiàn)完整的類。項目中可以同時使用這幾個類。
當(dāng)然,因為高度的面向?qū)ο?,?dǎo)致有大量的小文件,這其實和面向?qū)ο笳Z言提供的各種類有一堆文件一個道理。好在使用起來還是很方便的。
如果只是使用的話建議不需要具體了解內(nèi)部的實現(xiàn),根據(jù)類圖了解下繼承關(guān)系,看看每個類的接口的描述。然后直接調(diào)用實例的方法就行。那一堆小文件就直接全部拖到項目中就好。(PS.沒有用到的類是不會鏈接進去占用內(nèi)存的,所以直接扔進項目就好了,沒用到就沒用到。)
現(xiàn)在會有這些文件。
編程約定
接口定義在.h頭文件中,如名字中帶有Private則代表其中的接口為private或protect的,非開發(fā)者不應(yīng)該使用,否則為public的,供給用戶使用。
由于C語言本身沒有OO這個概念,所以類的方法以這種方式命名,這樣可以很直觀地知道是哪個類的方法:
類名_方法();
另,對于多例的類,第一個參數(shù)為被調(diào)用方法的實例,其后跟其他參數(shù)。
如BufferUINT8的Back方法的簽名如下:
uint8_t BufferUINT8_Back (BufferUINT8 buf); 1
子類實例可以直接調(diào)用父類方法。如:
uint8_t arr[ARRSIZE]; BufferUINT8ArrayR buf; buf = BufferUINT8ExternalArray_Create(arr, ARRSIZE); // 緩沖區(qū)前面放入55 BufferUINT8_FrontIn((BufferUINT8)buf,55); // 現(xiàn)在緩沖區(qū)內(nèi)元素為[ 55 ]
示例程序
這里已隱去不重要的代碼:
#include#include "BufferExternalArrayR.h" #include "BufferExternalArray.h" #include "BufferMallocArray.h" #pragma push #pragma DATA_SEG __RPAGE_SEG PAGED_RAM static uint8_t PagedArray[300]; #pragma pop static uint8_t nonPagedArray[50]; static void BufferTest(BufferUINT8 buf){ int i; printf("sizeof buffer:%u FrontIn: 0 to 9 BackOut: ",BufferUINT8_getCapacity(buf)); for(i = 0; i < 10; i++) ? ?BufferUINT8_FrontIn(buf,i); ?for(i = 0; i < 10; i++) ? ?printf(" %u",BufferUINT8_BackOut(buf)); ?printf(" "); } void main(void) { ?BufferUINT8 buf1,buf2,buf3; ?buf1 = BufferUINT8ExternalArrayR_Create(PagedArray,sizeof(PagedArray)); ?buf2 = BufferUINT8ExternalArray_Create(nonPagedArray,sizeof(nonPagedArray)); ?buf3 = BufferUINT8MallocArray_Create(40); ?printf("buf1(BufferExternalArrayR) Test: "); ?BufferTest(buf1); ?printf("buf2(BufferExternalArray) Test: "); ?BufferTest(buf2); ?printf("buf3(BufferMallocArray) Test: "); ?BufferTest(buf3); ?for(;;) { } }
可以看到,上例中BufferTest并不知道傳遞給他的BufferUINT8的具體實現(xiàn),它只需要知道這個實例實現(xiàn)了BufferUINT8的方法就可以正確地對其進行操作,從而實現(xiàn)了解耦。
另,上例中的BufferUINT8_getCapacity其實是因為我在Buffer模塊的頭文件中用宏的方式給Container_getCapacity起了別名,這樣用起來就更順手了對吧。
審核編輯:湯梓紅
-
模塊
+關(guān)注
關(guān)注
7文章
2695瀏覽量
47431 -
緩沖區(qū)
+關(guān)注
關(guān)注
0文章
33瀏覽量
9107 -
C語言
+關(guān)注
關(guān)注
180文章
7604瀏覽量
136692 -
面向?qū)ο?/span>
+關(guān)注
關(guān)注
0文章
64瀏覽量
9983
原文標(biāo)題:循環(huán)隊列C語言面向?qū)ο髮崿F(xiàn)
文章出處:【微信號:技術(shù)讓夢想更偉大,微信公眾號:技術(shù)讓夢想更偉大】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論