RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一文了解嵌入式軟件開(kāi)發(fā)的對(duì)象

strongerHuang ? 來(lái)源:strongerHuang ? 2024-11-15 10:31 ? 次閱讀

以前應(yīng)用場(chǎng)景很單一,嵌入式開(kāi)發(fā)可能談不上面向?qū)ο箝_(kāi)發(fā)。但現(xiàn)在,做嵌入式開(kāi)發(fā),沒(méi)有面向?qū)ο箝_(kāi)發(fā),你就有點(diǎn)落伍了。

本文結(jié)合個(gè)人經(jīng)驗(yàn)和周立功《抽象接口技術(shù)和組件開(kāi)發(fā)規(guī)范及其思想》,循序漸進(jìn)的用代碼范例說(shuō)明嵌入式軟件開(kāi)發(fā)的對(duì)象,前提你最好有一點(diǎn)點(diǎn)C++基礎(chǔ)。間接說(shuō)明理論指導(dǎo)實(shí)踐的意義。

紙上得來(lái)終覺(jué)淺,絕知此事要躬行。

1 面向?qū)ο?a href="http://hljzzgx.com/v/tag/1315/" target="_blank">編程基礎(chǔ)

面向?qū)ο缶幊躺婕暗饺齻€(gè)重要的特性:封裝、繼承與多態(tài)。部分 C 程序員,特別是嵌入式 C 程序員有一種誤解,C 語(yǔ)言不是面向?qū)ο蟮木幊陶Z(yǔ)言,C++、Java、Python 等更高級(jí)的才是,使用 C 語(yǔ)言無(wú)法實(shí)現(xiàn)面向?qū)ο缶幊獭_@種誤解致使他們沒(méi)有動(dòng)力學(xué)習(xí)一些優(yōu)秀的面向?qū)ο缶幊谭椒?,例如設(shè)計(jì)模式、設(shè)計(jì)原則、軟件架構(gòu)設(shè)計(jì)等等,進(jìn)而很難開(kāi)發(fā)出易維護(hù)、易部署、易重用、易管理的軟件,很難面對(duì)項(xiàng)目需求的變更、擴(kuò)展,很難開(kāi)發(fā)和維護(hù)大型的復(fù)雜項(xiàng)目。

1.1 對(duì)象

面向?qū)ο缶幊?,“?duì)象”是整個(gè)編程過(guò)程的關(guān)鍵。其常見(jiàn)的解釋是“數(shù)據(jù)與函數(shù)的組合”。每個(gè)對(duì)象都是由一組數(shù)據(jù)(用以描述對(duì)象的狀態(tài))和一組函數(shù)(對(duì)象支持的操作,用以描述對(duì)象的行為)組成的。對(duì)象實(shí)現(xiàn)了數(shù)據(jù)和操作的結(jié)合,使數(shù)據(jù)和操作可以封裝于“對(duì)象”這個(gè)統(tǒng)一體中。

在面向過(guò)程編程中,程序設(shè)計(jì)注重的是“過(guò)程”,先做什么,后做什么;在外界看來(lái),整個(gè)程序由一系列散亂的數(shù)據(jù)和函數(shù)組合而成。而在面向?qū)ο缶幊讨校绦蛟O(shè)計(jì)注重的是“對(duì)象”,在外界看來(lái),整個(gè)程序由一系列“對(duì)象”塊組合而成,數(shù)據(jù)和函數(shù)封裝到了對(duì)象內(nèi)部。

1.2 類(lèi)

對(duì)象是有“類(lèi)型”的,即類(lèi)?!邦?lèi)”是對(duì)一組對(duì)象共性的抽象,表示一類(lèi)對(duì)象,而對(duì)象是某個(gè)類(lèi)的一個(gè)具體化的個(gè)例,通常稱之為類(lèi)的實(shí)例。對(duì)象通常是由數(shù)據(jù)和函數(shù)組成的,相應(yīng)的類(lèi)也具有兩部分內(nèi)容:屬性(數(shù)據(jù)的抽象)和方法(對(duì)象行為的抽象)。

除了封裝屬性和操作外,類(lèi)還具有訪問(wèn)控制的能力,如某些屬性和方法是私有的,不能被外界訪問(wèn)。通過(guò)訪問(wèn)控制,能夠?qū)?nèi)部數(shù)據(jù)提供不同級(jí)別的保護(hù),以防止外界意外地改變或使用私有部分。

1. 屬性

類(lèi)具有屬性,它是對(duì)數(shù)據(jù)(對(duì)象的狀態(tài))的抽象。在 C 程序設(shè)計(jì)時(shí),通常使用結(jié)構(gòu)體類(lèi)型來(lái)表示一個(gè)類(lèi),相關(guān)屬性即包含在相應(yīng)的結(jié)構(gòu)體類(lèi)型中。例如學(xué)生具有屬性:姓名、學(xué)號(hào)、性別、身高、體重等信息,可以使用如下結(jié)構(gòu)體類(lèi)型表示“學(xué)生類(lèi)”:

//微信公眾號(hào)【嵌入式系統(tǒng)】
structstudent
{
charname[10];/*姓名(假定最長(zhǎng)10字符)*/
unsignedintid;/*學(xué)號(hào)*/
charsex;/*性別:'M',男;'F',女*/
floatheight;/*身高*/
floatweight;/*體重*/
};
//提示,關(guān)于結(jié)構(gòu)體、枚舉等復(fù)雜類(lèi)型定義推薦使用關(guān)鍵字typedef

提示,關(guān)于結(jié)構(gòu)體、枚舉等復(fù)雜類(lèi)型定義推薦使用關(guān)鍵字 typedef,更多C關(guān)鍵字了解可以參考《C語(yǔ)言關(guān)鍵字應(yīng)用技巧》、《高質(zhì)量嵌入式軟件的開(kāi)發(fā)技巧》。

2. 方法

類(lèi)具有方法,它是對(duì)象行為的抽象,在 C 程序中,方法可以看作普通函數(shù),不過(guò)其通常有一個(gè)特點(diǎn) ,函數(shù)的第一個(gè)參數(shù)為類(lèi)型的指針,指向了一個(gè)確定的對(duì)象,用以表明此次操作針對(duì)哪個(gè)對(duì)象,在方法實(shí)現(xiàn)時(shí),即可通過(guò)該指針訪問(wèn)到對(duì)象中的各個(gè)屬性。(微信公眾號(hào)【嵌入式系統(tǒng)】這是C面向?qū)ο蟊仨毜模?lèi)似C++的this)

針對(duì)學(xué)生對(duì)象,為了對(duì)外展現(xiàn)學(xué)生自身的信息,自我介紹的格式是對(duì)外輸出一個(gè)固定格式的字符串:

"Hi! My name is xxx, I'm a (boy/girl). My school number is xxx. My height is xxxcm and weight is xxxkg . "

其中的 xxx 對(duì)應(yīng)學(xué)生實(shí)際的信息,基于此,可以為學(xué)生類(lèi)定義并實(shí)現(xiàn)一個(gè)“自我介紹”的方法:

voidstudent_self_introduction(structstudent*p_this)
{
printf("Hi!Mynameis%s,I'ma%s.Myschoolnumberis%d.Myheightis%fcmandweightis%fkg",
p_this->name,
(p_this->sex=='M')?"boy":"girl",
p_this->id,
p_this->height,
p_this->weight);
}

對(duì)于外界來(lái)講,調(diào)用學(xué)生的“自我介紹”方法可以獲知學(xué)生的全部信息。基于該類(lèi)的定義,一個(gè)簡(jiǎn)易的應(yīng)用程序范例詳如下:

voidmain(void)
{
structstudentchengj={"chengj",2024001,'M',173,68};
structstudenthehe={"hehe",2024002,'M',150,45};
student_self_introduction(&chengj);
student_self_introduction(&hehe);
//...
}

類(lèi)中的方法 student_self_introduction 可以作用于任一學(xué)生類(lèi)對(duì)象,對(duì)于程序員來(lái)講,編寫(xiě)的代碼將適用于一組對(duì)象,而非特定的某一個(gè)對(duì)象,提高了代碼利用率。

在實(shí)際應(yīng)用中,對(duì)比代碼《嵌入式算法14---數(shù)據(jù)流與環(huán)形隊(duì)列》,不少程序員都喜歡編寫(xiě)出一堆非常類(lèi)似的接口,它們僅通過(guò)某一個(gè)數(shù)字后綴(0、1、2……)來(lái)區(qū)分,如系統(tǒng)使用到 3 個(gè)棧,初級(jí)程序員可能實(shí)現(xiàn) 3 個(gè)入棧函數(shù),不良示意代碼如下:

//三個(gè)棧入棧的不良范例,引以為戒
intpush_stack0(intdata)
{
//...
}

intpush_stack1(intdata)
{
//...
}

intpush_stack2(intdata)
{
//...
}

三個(gè)操作可能除了極小部分的差異外,其它處理完全相同,這就是沒(méi)有面向?qū)ο缶幊痰乃季S,沒(méi)有定義對(duì)象類(lèi)型的概念,將操作直接針對(duì)每個(gè)具體對(duì)象(棧 0、棧 1、棧 2),而不是一組同類(lèi)的對(duì)象(所有棧對(duì)象)。顯然,3個(gè)棧的特性和行為都基本類(lèi)似,因而可以定義一個(gè)“棧類(lèi)型”,如此一來(lái),入棧操作將屬于棧類(lèi)型中的一個(gè)方法,適用于所有棧對(duì)象。例如:

//數(shù)據(jù)壓入棧,p_stack指向具體的棧對(duì)象
intpush_stack(stack*p_stack,intdata);

//微信公眾號(hào):嵌入式系統(tǒng)
//三個(gè)棧的入棧操作均可使用同一個(gè)方法
push_stack(p_stack0,1);
push_stack(p_stack1,2);
push_stack(p_stack2,3);

這只是示意性代碼,說(shuō)明使用“類(lèi)”的設(shè)計(jì)解決問(wèn)題所帶來(lái)的優(yōu)勢(shì)。

1.3 UML 類(lèi)圖

在面向?qū)ο蟮脑O(shè)計(jì)和開(kāi)發(fā)過(guò)程中,通常使用 UML 工具來(lái)進(jìn)行分析與設(shè)計(jì)。最基本的就是使用 UML 類(lèi)圖來(lái)表示類(lèi)以及描述類(lèi)之間的關(guān)系。

在 UML 類(lèi)圖中,一個(gè)矩形框表示一個(gè)類(lèi),矩形框內(nèi)部被分隔為上、中、下三部分,上部為類(lèi)的名字,中部為類(lèi)的屬性,下面部分為類(lèi)的方法。對(duì)于屬性和方法,還可以使用“+”、“-”修飾符來(lái)表示訪問(wèn)權(quán)限,“+”為公有屬性、“-”為私有屬性。如前面的學(xué)生類(lèi),其類(lèi)名為 student,屬性包括姓名、學(xué)號(hào)、性別、身高、體重,方法有“自我介紹”方法,則其對(duì)應(yīng)的類(lèi)圖如下:

3ff7fd9a-906d-11ef-a511-92fbcf53809c.png

通常情況下,類(lèi)中的所有屬性均為私有屬性,不建議直接訪問(wèn),所有屬性的訪問(wèn)都通過(guò)類(lèi)提供的方法?;诖耍俣藢W(xué)生類(lèi)中的所有屬性均為私有屬性,因而在所有屬性前都增加了“-”修飾符。

UML 類(lèi)圖主要用于輔助分析和設(shè)計(jì),設(shè)計(jì)類(lèi)時(shí)應(yīng)聚焦在與當(dāng)前問(wèn)題有關(guān)的重要屬性和行為,無(wú)關(guān)的屬性和方法可去掉,確保簡(jiǎn)潔。由于私有屬性僅在內(nèi)部使用,外界無(wú)需關(guān)心,因此UML 類(lèi)圖中通常不體現(xiàn)私有屬性和方法,除非某些特殊的私有屬性和方法影響到問(wèn)題的理解或者類(lèi)的實(shí)現(xiàn)。基于此可以簡(jiǎn)化。

3ffc0656-906d-11ef-a511-92fbcf53809c.png

2 封裝

類(lèi)是對(duì)一組對(duì)象共性的抽象,封裝了屬性和方法;即把一組關(guān)聯(lián)的數(shù)據(jù)和函數(shù)圈起來(lái),使圈外的代碼只能看見(jiàn)部分函數(shù),數(shù)據(jù)則完全不可見(jiàn)(微信公眾號(hào)【嵌入式系統(tǒng)】一般建議數(shù)據(jù)的訪問(wèn)都應(yīng)通過(guò)類(lèi)提供的方法,而不是全局變量滿天飛)。

2.1 “封裝”示例

在C語(yǔ)言中,可使用一個(gè) C 文件(*.c 文件)和 H 文件(*.h 文件)完成“類(lèi)”的定義,將所有需要封裝的東西都存于 C 文件中,H 文件中只展現(xiàn)“對(duì)外可見(jiàn)、無(wú)需封裝”的內(nèi)容。

以棧的實(shí)現(xiàn)為例,將所有實(shí)現(xiàn)代碼都存于 C 文件中,H 文件只包含與棧相關(guān)接口的聲明,比如入棧和出棧等。頭文件和源文件的示意內(nèi)容分別詳見(jiàn)如下:

stack.h文件
#ifndef__STACK_H
#define__STACK_H
//微信公眾號(hào):嵌入式系統(tǒng)所有頭文件都必須防止重復(fù)引用

/*類(lèi)型聲明,無(wú)需關(guān)心類(lèi)定義的具體細(xì)節(jié)*/
structstack;

/*創(chuàng)建棧,并指定棧空間的大小*/
structstack*stack_create(intsize);

/*入棧*/
intstack_push(structstack*p_stack,intval);

/*出棧*/
intstack_pop(structstack*p_stack,int*p_val);

/*刪除棧*/
intstack_delete(structstack*p_stack);

#endif
stack.c文件
//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack.h"
#include"stdlib.h"

structstack
{
inttop;/*棧頂*/
int*p_buf;/*棧緩存*/
unsignedintsize;/*棧緩存的大小*/
};

unsignedintsize;/*棧緩存的大小*/

structstack*stack_create(intsize)
{
structstack*p_stack=(structstack*)malloc(sizeof(structstack));
if(p_stack!=NULL)
{
p_stack->top=0;
p_stack->size=size;

p_stack->p_buf=(int*)malloc(sizeof(int)*size);
if(p_stack->p_buf!=NULL)
{
returnp_stack;
}
free(p_stack);/*分配棧內(nèi)存失敗*/
}
returnNULL;/*創(chuàng)建棧失敗,返回NULL*/
}

intstack_push(structstack*p_stack,intval)
{
if(p_stack->top!=p_stack->size)//未滿可入棧
{
p_stack->p_buf[p_stack->top++]=val;
return0;
}
return-1;
}

intstack_pop(structstack*p_stack,int*p_val)
{
if(p_stack->top!=0)//非空可出棧
{
*p_val=p_stack->p_buf[--p_stack->top];
return0;
}
return-1;
}

intstack_delete(structstack*p_stack)
{
if(p_stack==NULL)
{
return-1;
}

if(p_stack->p_buf!=NULL)
{
free(p_stack->p_buf);
}
free(p_stack);
return0;
}

使用 stack.h 的程序沒(méi)有 struct stack 結(jié)構(gòu)體成員的訪問(wèn)權(quán)限的,只能調(diào)用stack.h 文件中聲明的方法。對(duì)于外界用戶來(lái)說(shuō),struct stack 結(jié)構(gòu)體的內(nèi)部細(xì)節(jié),以及各個(gè)函數(shù)的具體實(shí)現(xiàn)方式都是不可見(jiàn)的。這正是完美的封裝!

由于所有細(xì)節(jié)都封裝到了 C 文件內(nèi)部,用戶通過(guò) stack.h 文件并不能看到 struct stack 結(jié)構(gòu)體的具體定義,因此也無(wú)法訪問(wèn) stack 結(jié)構(gòu)體中的成員。若用戶嘗試訪問(wèn) struct stack結(jié)構(gòu)體中的成員,將會(huì)編譯報(bào)錯(cuò)。(微信公眾號(hào)【嵌入式系統(tǒng)】C 語(yǔ)言不是面向?qū)ο蟮木幊陶Z(yǔ)言,實(shí)現(xiàn)封裝有擴(kuò)展性的犧牲)。

C語(yǔ)言實(shí)現(xiàn)封裝的一般做法為:在頭文件中進(jìn)行數(shù)據(jù)結(jié)構(gòu)以及函數(shù)定義的前置聲明,在源文件中完成各函數(shù)的具體實(shí)現(xiàn)以及數(shù)據(jù)結(jié)構(gòu)的定義。這樣所有函數(shù)實(shí)現(xiàn)及定義細(xì)節(jié)均封裝到了源文件中,對(duì)使用者來(lái)說(shuō)是完全不可見(jiàn)的。

2.2 創(chuàng)建對(duì)象

2.2.1 內(nèi)存分配的問(wèn)題

基于前面創(chuàng)建棧方法,可以創(chuàng)建多個(gè)棧對(duì)象,例如:

structstack*p_stack1=stack_create(20);
structstack*p_stack2=stack_create(30);
structstack*p_stack3=stack_create(50);

每個(gè)棧對(duì)象需要兩部分內(nèi)存:

一是棧對(duì)象本身的內(nèi)存(內(nèi)存大小為 sizeof(struct stack));

二是該棧對(duì)象用于存儲(chǔ)數(shù)據(jù)的緩存(內(nèi)存大小為 sizeof(int) * size,其中,size 由用戶在創(chuàng)建 棧時(shí)通過(guò)參數(shù)指定)。

在棧對(duì)象的創(chuàng)建函數(shù)中,使用 malloc()分配了該對(duì)象所需的內(nèi)存空間,使用 malloc()分配內(nèi)存空間非常方便,但這種做法也限制了對(duì)象內(nèi)存的來(lái)源——必須使用動(dòng)態(tài)內(nèi)存。但對(duì)于嵌入式系統(tǒng),內(nèi)存往往是很大的瓶頸,很多應(yīng)用場(chǎng)合可能并不太適合使用動(dòng)態(tài)內(nèi)存,主要有以下幾個(gè)因素:

1)內(nèi)存資源不足。運(yùn)行嵌入式軟件的硬件平臺(tái)普遍內(nèi)存小甚至只有幾k RAM。這種條件下管理使用動(dòng)態(tài)內(nèi)存是比較浪費(fèi)的行為,可能產(chǎn)生內(nèi)存碎片,且內(nèi)存分配的軟件算法本身也會(huì)占用一定的內(nèi)存空間。

2)實(shí)時(shí)性要求高。部分嵌入式應(yīng)用對(duì)實(shí)時(shí)性要求很高,但由于資源的限制,集成的動(dòng)態(tài)內(nèi)存分配算法不是很完善,使得很難確保動(dòng)態(tài)內(nèi)存分配的實(shí)時(shí)性。

3)內(nèi)存泄漏。動(dòng)態(tài)內(nèi)存分配可能出現(xiàn)內(nèi)存泄漏。

4)軟件編程復(fù)雜。在可靠的設(shè)計(jì)中,必須考慮內(nèi)存分配失敗的情況并對(duì)其進(jìn)行異常處理,如果存在大量的動(dòng)態(tài)內(nèi)存分配,則處處都需考慮分配失敗的情況。

將對(duì)象內(nèi)存的來(lái)源限制為動(dòng)態(tài)內(nèi)存分配,限制了該類(lèi)的應(yīng)用場(chǎng)合,致使部分應(yīng)用場(chǎng)合因?yàn)閮?nèi)存來(lái)源的問(wèn)題不得不放棄該類(lèi)的使用。

2.2.2 內(nèi)存來(lái)源的探索

在 C 程序開(kāi)發(fā)中,除了使用 malloc()得到一段內(nèi)存空間外,還可以使用“直接定義變量”的形式分配一段內(nèi)存。直接定義變量的形式,內(nèi)存在編譯階段由編譯器負(fù)責(zé)分配,無(wú)需用戶作任何干預(yù)。根據(jù)變量定義位置的不同,實(shí)際內(nèi)存的開(kāi)辟位置存在一定的區(qū)別,主要有兩類(lèi):

局部變量:內(nèi)存開(kāi)辟在棧中;
靜態(tài)變量(static 修飾的變量)或全局變量:內(nèi)存開(kāi)辟在全局靜態(tài)存儲(chǔ)區(qū)。

兩種變量主要是生命周期的不同:局部變量在退出當(dāng)前作用域后(比如函數(shù)返回),內(nèi)存自動(dòng)釋放;靜態(tài)變量或全局變量?jī)?nèi)存開(kāi)辟在全局靜態(tài)存儲(chǔ)區(qū),它們?cè)诔绦虻恼麄€(gè)生命周期均有效。

內(nèi)存可以有 3 種來(lái)源,它們的優(yōu)缺點(diǎn)對(duì)比詳見(jiàn)下表:

內(nèi)存類(lèi)別 內(nèi)存位置 生命周期 優(yōu)點(diǎn) 缺點(diǎn)
動(dòng)態(tài)內(nèi)存 系統(tǒng)堆 Heap 直到調(diào)用free()釋放內(nèi)存 靈活,可以隨時(shí)按需分配和釋放 內(nèi)存分配可能失敗,花費(fèi)的時(shí)間可能不確定;需要處理內(nèi)存分配失敗的情況,增加程序的復(fù)雜性
靜態(tài)內(nèi)存 全局靜態(tài)存儲(chǔ)區(qū)(.data、.bss存儲(chǔ)段) 程序的整個(gè)運(yùn)行周期 確定性好,只要程序能夠編譯、鏈接成功,內(nèi)存一定能夠分配成功 需要編程時(shí)確定內(nèi)存的大小;一直占用內(nèi)存,無(wú)法釋放
棧內(nèi)存 系統(tǒng)棧(或任務(wù)棧) 函數(shù)調(diào)用周期 自動(dòng)完成內(nèi)存的分配和回收 內(nèi)存太大會(huì)導(dǎo)致棧溢出
微信公眾號(hào):嵌入式系統(tǒng)

不同來(lái)源的內(nèi)存各有優(yōu)劣。前面提到,stack_create()函數(shù)將內(nèi)存的來(lái)源限制為僅動(dòng)態(tài)內(nèi)存不太合理。為了避免內(nèi)存來(lái)源受限,“內(nèi)存的分配”這一步交由用戶實(shí)現(xiàn),以便用戶根據(jù)實(shí)際需要自由選擇內(nèi)存的來(lái)源。基于此,可以將對(duì)象的創(chuàng)建拆分為兩個(gè)獨(dú)立的步驟,分配對(duì)象所需的內(nèi)存和初始化對(duì)象。

2.2.3 分配對(duì)象所需的內(nèi)存

內(nèi)存分配的工作交由用戶完成,以便用戶根據(jù)實(shí)際需要自由選擇。用戶能夠完成內(nèi)存分配的前提是:用戶知道應(yīng)該分配的內(nèi)存大小。前面提到,每個(gè)棧對(duì)象需要兩部分內(nèi)存:一是棧對(duì)象本身的內(nèi)存(內(nèi)存大小為:sizeof(struct stack));二是該棧對(duì)象用于存儲(chǔ)數(shù)據(jù)的緩存(內(nèi)存大小為 sizeof(int) * size,其中,size 由用戶在創(chuàng)建棧時(shí)通過(guò)參數(shù)指定)。

1、棧對(duì)象本身的內(nèi)存

棧對(duì)象本身的內(nèi)存大小為 sizeof(struct stack),若用戶直接采用靜態(tài)內(nèi)存分配的方式(直接定義一個(gè)變量),則形式如下:

structstackmy_stack;

也可以繼續(xù)采用動(dòng)態(tài)內(nèi)存的分配方式,例如:

structstack*p_stack=(structstack*)malloc(sizeof(structstack));

但是,若將這兩行代碼直接放到主程序中會(huì)無(wú)法編譯,因?yàn)橹懊枋龅摹胺庋b”特性,使外界看不到 struct stack 的具體定義,也就是說(shuō),對(duì)于外界而言,該類(lèi)型僅僅只是聲明并未定義,該類(lèi)型對(duì)應(yīng)變量的大小對(duì)外也是未知的。

在 C 語(yǔ)言中定義一個(gè)變量時(shí),編譯器將負(fù)責(zé)該變量所占用內(nèi)存的分配。內(nèi)存的大小與類(lèi)型相關(guān),要完成變量?jī)?nèi)存的分配,編譯器必須知道變量所占用的存儲(chǔ)空間大小。當(dāng)一個(gè)變量的類(lèi)型未定義時(shí),無(wú)法完成該類(lèi)型對(duì)應(yīng)變量的定義,因此,如下語(yǔ)句在編譯時(shí)會(huì)出錯(cuò):

structstackmy_stack;

同理,sizeof 語(yǔ)句用于獲得相應(yīng)類(lèi)型數(shù)據(jù)的大小,而未定義的類(lèi)型顯然是不知道其大小的,動(dòng)態(tài)內(nèi)存分配中所使用的 sizeof(struct stack)語(yǔ)句也是錯(cuò)誤的。

也許部分人會(huì)有疑問(wèn),既然該類(lèi)型未定義,為什么在主程序中定義該類(lèi)型的指針變量卻可以呢?

structstack*p_stack=//...

雖然 struct stack 類(lèi)型未定義,但在之前已經(jīng)聲明,因此,編譯器知道它是一個(gè)“合法的結(jié)構(gòu)體類(lèi)型”。此外,這里定義的是一個(gè)指針變量,在特定系統(tǒng)中,指針變量所占用的內(nèi)存大小是確定的,例如,在 32 位系統(tǒng)中,指針通常占用 4 個(gè)字節(jié)。即指針變量所占用的內(nèi)存空間大小與其指向的數(shù)據(jù)類(lèi)型無(wú)關(guān),編譯器無(wú)需知道其指向的數(shù)據(jù)類(lèi)型,就可完成指針變量?jī)?nèi)存的分配。因此,一個(gè)類(lèi)型未定義,只要其聲明了,就可以定義該類(lèi)型的指針變量。但需要注意的是,在完成該類(lèi)型的定義之前,不得嘗試訪問(wèn)該指針?biāo)赶虻膬?nèi)容。

完成內(nèi)存的分配,提供三種方案。

(1) 將類(lèi)的具體定義放到 H 文件中

為了使用戶知道對(duì)象內(nèi)存的大小,一種最簡(jiǎn)單的辦法是直接將類(lèi)型的定義放在 H 文件中。更新后的 H 文件示意代碼如下:

stack.h文件
#ifndef__STACK_H
#define__STACK_H

/*類(lèi)型定義*/
structstack
{
inttop;/*棧頂*/
int*p_buf;/*棧緩存*/
unsignedintsize;/*棧緩存的大小*/
};

//......其它函數(shù)聲明
#endif

此時(shí),對(duì)于外界,類(lèi)型已經(jīng)定義,如下語(yǔ)句均可正常使用:

structstackmy_stack;//靜態(tài)內(nèi)存分配
structstack*p_stack=(structstack*)malloc(sizeof(structstack));//動(dòng)態(tài)內(nèi)存分配

由于類(lèi)型的定義存放到了 H 文件中,暴露了類(lèi)中的成員,在一定程度上破壞了類(lèi)的“封裝”性。此時(shí)外界可以直接訪問(wèn)類(lèi)中的數(shù)據(jù)成員。犧牲一定的封裝性,換來(lái)內(nèi)存分配的靈活性,這也是在嵌入式系統(tǒng)中,基于 C 語(yǔ)言實(shí)現(xiàn)面向?qū)ο缶幊痰囊话阕龇ǎ〝?shù)據(jù)結(jié)構(gòu)定義存放在 H 文件中更加符合程序員的編程風(fēng)格)。嵌入式軟件大多數(shù)類(lèi)定義在 H文件中,并沒(méi)有封裝在 C 文件中。

雖然類(lèi)的定義存放在 H 文件中,但出于封裝性考慮,外界任何時(shí)候都不應(yīng)直接訪問(wèn)對(duì)象中的數(shù)據(jù),應(yīng)該將其視為使用 C 語(yǔ)言實(shí)現(xiàn)面向?qū)ο缶幊痰囊粭l準(zhǔn)則。軟件開(kāi)發(fā)需要遵守兩個(gè)規(guī)則:一是在設(shè)計(jì)類(lèi)時(shí),應(yīng)考慮到用戶可能訪問(wèn)的數(shù)據(jù),并為這些數(shù)據(jù)提供相應(yīng)的訪問(wèn)接口;二是在使用別人提供的類(lèi)時(shí),除非有特殊說(shuō)明,否則都不應(yīng)該嘗試直接訪問(wèn)類(lèi)中的數(shù)據(jù)。

這種方法是目前嵌入式系統(tǒng)中使用得最為廣泛的一種方法,因此后文使用這種方法討論。

(2) 在 H 文件中定義一個(gè)新的結(jié)構(gòu)體類(lèi)型

為了繼續(xù)保持類(lèi)的封裝性,類(lèi)的定義依然保留在 C 文件中。只不過(guò)與此同時(shí),在 H 文件中定義一個(gè)新的結(jié)構(gòu)體類(lèi)型。在該結(jié)構(gòu)體類(lèi)型中,各個(gè)成員的順序和類(lèi)型與類(lèi)定義完全一致,僅命名不同。

structstack_mem
{
intdummy1;
int*dummy2;
unsignedintdummy3;
};

各成員的順序和類(lèi)型均與 struct stack 的定義完全相同,以此保證兩個(gè)類(lèi)型數(shù)據(jù)所需要的內(nèi)存空間完全一致。同時(shí),為了屏蔽各個(gè)成員的具體含義,所有成員均以 dummy 開(kāi)頭進(jìn)行命名。對(duì)于外界來(lái)講,可以基于 struct stack_mem 類(lèi)型完成內(nèi)存的分配,例如:

structstack_memmy_stack;//靜態(tài)內(nèi)存分配
structstack*p_stack=(structstack*)malloc(sizeof(structstack_mem));//動(dòng)態(tài)內(nèi)存分配

使用這種方案,類(lèi)的實(shí)際定義依然沒(méi)有暴露給外界,繼續(xù)保持了良好的封裝。(微信公眾號(hào)【嵌入式系統(tǒng)】實(shí)際上FreeRTOS中,很多地方都采用了這種方法)。但這里定義了一個(gè)新的類(lèi)型,給用戶理解上造成了一定的困擾,此外,為確保兩個(gè)類(lèi)型完全一致,就要求類(lèi)的設(shè)計(jì)者在修改類(lèi)的定義時(shí),必須確保 struct stack_mem 類(lèi)型也同步修改,這給類(lèi)的維護(hù)工作帶來(lái)了挑戰(zhàn);稍有不慎,某一個(gè)類(lèi)型沒(méi)有同步修改就可能造成嚴(yán)重的錯(cuò)誤,且這種錯(cuò)誤編譯器不會(huì)給出任何提示,非常隱蔽。關(guān)于代碼審查可以參考《代碼審查那些事》、《代碼的保養(yǎng)》。

(3) 使用宏的形式告知對(duì)象所需的內(nèi)存大小

既然外界只需要知道對(duì)象內(nèi)存的大小,可以在開(kāi)發(fā)過(guò)程中使用 sizoeof()獲得struct stack 類(lèi)型的大小,然后將其以宏的形式定義在 H 文件中。例如在 32 位系統(tǒng)中,使用 sizeof()獲知 struct stack 類(lèi)型的長(zhǎng)度為 12,則可以在 H 文件中定義一個(gè)宏,例如:

#defineSTACK_MEM_SIZE12

用戶使用該宏完成內(nèi)存分配,例如:

unsignedcharstack_mem[STACK_MEM_SIZE];

這種做法僅僅在頭文件中新增了一個(gè)宏定義,類(lèi)的定義依然保持的 C 文件中,“封裝”完全沒(méi)有被破壞,看起來(lái)也非常完美。但這種做法也存在一些問(wèn)題,因而很少采用。

a)對(duì)于同一個(gè)類(lèi)型,不同系統(tǒng)中 sizeof()的結(jié)果可能不同。類(lèi)型的長(zhǎng)度與系統(tǒng)和編譯器均相關(guān)。以 int 類(lèi)型為例,在 32 位系統(tǒng)中為 32 位(4 字節(jié)),但 16 位系統(tǒng)中,其位寬可能為 16 位(2 字節(jié))。因此,同樣是 sizeof(int),結(jié)果可能為 4,也可能為 2。使用 sizeof()獲取類(lèi)型的長(zhǎng)度時(shí),不同系統(tǒng)中獲取的結(jié)果可能并不相同。這就導(dǎo)致 H 文件中的宏定義,切換平臺(tái)需要重新測(cè)試驗(yàn)證。同時(shí),由于類(lèi)型的定義封裝到了 C 文件中,因此修改過(guò)程只能有類(lèi)的開(kāi)發(fā)者完成,一般用戶還無(wú)法完成,這就使得該類(lèi)的跨平臺(tái)特性很差,移植有風(fēng)險(xiǎn)。

b)內(nèi)存不僅有大小的要求,還有內(nèi)存對(duì)齊的要求。

因此,通過(guò)一個(gè)宏告知用戶需要分配的內(nèi)存空間大小并不是十分合適,會(huì)遇到跨平臺(tái)、內(nèi)存對(duì)齊等多個(gè)注意事項(xiàng),用戶可能在不經(jīng)意間出錯(cuò)。在實(shí)際嵌入式系統(tǒng)中很少使用。一些編碼技能可以參考《高質(zhì)量嵌入式軟件的開(kāi)發(fā)技巧》。

2、存儲(chǔ)數(shù)據(jù)的緩存

存儲(chǔ)數(shù)據(jù)的緩存大小為 sizeof(int) *size,其中的 size 本身就是由用戶指定的,這部分內(nèi)存的大小用戶很容易得知,進(jìn)而完成內(nèi)存的分配。可以采用靜態(tài)內(nèi)存分配的方式(直接定義一個(gè)變量)完成內(nèi)存的分配:

intbuf[20];

也可以采用動(dòng)態(tài)內(nèi)存分配的方式完成內(nèi)存的分配:

int*p_buf=(int*)malloc(sizeof(int)*20);

2.2.4 內(nèi)存小曲

內(nèi)存的來(lái)源主要有三種:動(dòng)態(tài)內(nèi)存、靜態(tài)內(nèi)存和棧內(nèi)存,具體如何選擇按實(shí)際情況。

對(duì)象類(lèi)別 應(yīng)用場(chǎng)合
動(dòng)態(tài)對(duì)象 不會(huì)頻繁創(chuàng)建、銷(xiāo)毀對(duì)象的應(yīng)用;內(nèi)存占用太大的對(duì)象
靜態(tài)對(duì)象 確定性要求較高,長(zhǎng)生命周期的對(duì)象
棧對(duì)象 函數(shù)內(nèi)部使用的臨時(shí)對(duì)象;對(duì)象內(nèi)存占用較小的對(duì)象

一些入式應(yīng)用對(duì)確定性要求較高,建議優(yōu)先使用靜態(tài)對(duì)象。如此一來(lái)只要能夠編譯(包含鏈接)成功,應(yīng)用程序往往就可以按照確定的流程正確執(zhí)行;若使用動(dòng)態(tài)對(duì)象,則必須考慮對(duì)象創(chuàng)建失敗的情況。偶爾使用的大塊內(nèi)存則建議使用動(dòng)態(tài)內(nèi)存,使用注意和防范可參考《動(dòng)態(tài)內(nèi)存管理及防御性編程》。

2.3 初始化對(duì)象

初始化對(duì)象的具體細(xì)節(jié)用戶不需要關(guān)心,指定棧對(duì)象的地址、緩存地址及緩存大小,基于此,可以定義初始化函數(shù)的原型為:

intstack_init(structstack*p_stack,int*p_buf,intsize);

對(duì)于棧來(lái)講,棧頂索引(top)的初始值恒為 0,因此該值無(wú)需通過(guò)初始化函數(shù)的參數(shù)傳遞。int 類(lèi)型的返回值常用于表示執(zhí)行的結(jié)果(微信公眾號(hào)【嵌入式系統(tǒng)】建議非指針類(lèi)型的返回值,以0表示成功,負(fù)數(shù)表示失?。?。該函數(shù)的實(shí)現(xiàn)示意如下:

//微信公眾號(hào):嵌入式系統(tǒng)
intstack_init(structstack*p_stack,int*p_buf,intsize)
{
p_stack->top=0;
p_stack->size=size;
p_stack->p_buf=p_buf;
return0;
}

該初始化函數(shù)的實(shí)現(xiàn)僅作為原理性展示,沒(méi)有做過(guò)多的錯(cuò)誤處理或參數(shù)檢查,實(shí)際應(yīng)用中,p_stack 為 NULL 或 p_buf 為 NULL 等情況都是錯(cuò)誤情況,后續(xù)范例也會(huì)省去部分參數(shù)校驗(yàn))。

至此,完成了將創(chuàng)建對(duì)象分離為“分配對(duì)象所需的內(nèi)存”和“初始化對(duì)象”兩個(gè)步驟,對(duì)象內(nèi)存的來(lái)源交由用戶決定,用戶根據(jù)需要獲得內(nèi)存后,再將相關(guān)內(nèi)存的首地址傳遞給初始化函數(shù)。

2.4 銷(xiāo)毀對(duì)象

實(shí)現(xiàn) stack_create()以及對(duì)應(yīng)的stack_delete(),設(shè)計(jì)該函數(shù)的初衷是當(dāng)一個(gè)棧對(duì)象不會(huì)再被使用時(shí),可以通過(guò)該函數(shù)釋放棧占用的資源,比如釋放在 stack_create()函數(shù)中使用 malloc()分配的內(nèi)存資源。

當(dāng)將 stack_create()拆分為兩步后,內(nèi)存的分配將由用戶決定,對(duì)應(yīng)地內(nèi)存的釋放也應(yīng)由用戶決定?;仡?stack_delete()函數(shù)的實(shí)現(xiàn),該函數(shù)目前只做了內(nèi)存釋放相關(guān)的操作,當(dāng)不需要釋放內(nèi)存時(shí),該函數(shù)看起來(lái)沒(méi)有存在的必要。實(shí)際上,stack_delete()和 stack_create()函數(shù)是對(duì)應(yīng)的,當(dāng)將 stack_create()拆分為“分配對(duì)象所需的內(nèi)存”和“初始化對(duì)象”兩個(gè)步驟后,stack_delete()也應(yīng)該相應(yīng)的拆分為兩個(gè)步驟:“釋放對(duì)象占用的內(nèi)存”和“解初始化對(duì)象”(微信公眾號(hào)【嵌入式系統(tǒng)】解初始化或者反初始化,不用太在意這個(gè)操作的名稱,只要理解表達(dá)的意思是初始化的逆過(guò)程即可,init:deinit,關(guān)于命名的英文集客參考《嵌入式軟件命名常用英文集》)。

1. 釋放對(duì)象占用的內(nèi)存

前面已經(jīng)提到,釋放內(nèi)存交由用戶處理,釋放方法與內(nèi)存的來(lái)源相關(guān)。

動(dòng)態(tài)內(nèi)存的釋放動(dòng)態(tài)內(nèi)存分配應(yīng)使用相應(yīng)的釋放內(nèi)存函數(shù)(如 free())進(jìn)行釋放。在釋放時(shí)應(yīng)確保分配的內(nèi)存全部被有效釋放。若某一部分內(nèi)存被遺漏,將造成內(nèi)存泄漏。隨著程序的長(zhǎng)期運(yùn)行,內(nèi)存不斷泄漏可能導(dǎo)致系統(tǒng)崩潰。

靜態(tài)內(nèi)存的釋放使用靜態(tài)內(nèi)存(定義變量的形式),則內(nèi)存的釋放是系統(tǒng)自動(dòng)完成的。若將對(duì)象定義為局部變量,內(nèi)存開(kāi)辟在系統(tǒng)棧中,則退出當(dāng)前作用域后(函數(shù)返回)自動(dòng)釋放;若將對(duì)象定義為靜態(tài)變量(static)或全局變量,則內(nèi)存開(kāi)辟在全局靜態(tài)區(qū),該區(qū)域的內(nèi)存在應(yīng)用程序的整個(gè)生命周期均有效,無(wú)法釋放。

2. 解初始化對(duì)象

釋放內(nèi)存已交由用戶處理,對(duì)于類(lèi)的設(shè)計(jì)來(lái)講,重點(diǎn)是設(shè)計(jì)“解初始化對(duì)象”對(duì)應(yīng)的函數(shù),該函數(shù)與 stack_init()函數(shù)對(duì)應(yīng),通常命名為“*_deinit”,即:stack_deinit()。該函數(shù)通常用于釋放在初始化對(duì)象時(shí)占用的其它資源。

對(duì)于純軟件對(duì)象(與硬件無(wú)關(guān)的軟件),通常其只會(huì)占用內(nèi)存資源,不會(huì)額外占用其它資源,對(duì)這類(lèi)對(duì)象解初始化時(shí)可能無(wú)需做任何事情。例如前面關(guān)于棧的實(shí)現(xiàn),在stack_init()函數(shù)中僅對(duì)幾個(gè)屬性進(jìn)行了賦值,沒(méi)有額外占用其它任何資源,此時(shí),stack_deinit()可能無(wú)需做任何事情,成為一個(gè)空函數(shù)。

intstack_deinit(structstack*p_stack)
{
return0;
}

在嵌入式系統(tǒng)中,經(jīng)常會(huì)遇到與硬件相關(guān)的對(duì)象,其初始化時(shí)往往會(huì)占用一定的硬件資源:I/O 引腳、系統(tǒng)中斷、系統(tǒng)總線。在解初始化這種對(duì)象時(shí),應(yīng)同時(shí)釋放占用的資源??芍攸c(diǎn)關(guān)注對(duì)象的初始化函數(shù),查看其中是否分配、占用了某些資源。若有,則在解初始化函數(shù)中作相應(yīng)的釋放操作;若無(wú),則解初始化函數(shù)留空。為了提高軟件的簡(jiǎn)潔性,也可刪除了空的解初始化函數(shù),但這里為了展示軟件結(jié)構(gòu),依然保留了解初始化函數(shù)。

將原 H 文件中的創(chuàng)建接口更新為初始化接口,刪除接口更新為解初始化接口,更新后的 H 文件內(nèi)容和 C 文件如下:

stack.h文件
//微信公眾號(hào):嵌入式系統(tǒng)
#ifndef__STACK_H
#define__STACK_H

/*類(lèi)型定義*/
structstack
{
inttop;/*棧頂*/
int*p_buf;/*棧緩存*/
unsignedintsize;/*棧緩存的大小*/
};

/*初始化*/
intstack_init(structstack*p_stack,int*p_buf,intsize);

/*入棧*/
intstack_push(structstack*p_stack,intval);

/*出棧*/
intstack_pop(structstack*p_stack,int*p_val);

/*解初始化*/
intstack_deinit(structstack*p_stack);

#endif
stack.c文件
//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack.h"

intstack_init(structstack*p_stack,int*p_buf,intsize)
{
p_stack->top=0;
p_stack->size=size;
p_stack->p_buf=p_buf;
return0;
}

intstack_push(structstack*p_stack,intval)
{
if(p_stack->top!=p_stack->size)
{
p_stack->p_buf[p_stack->top++]=val;
return0;
}
return-1;
}

intstack_pop(structstack*p_stack,int*p_val)
{
if(p_stack->top!=0)
{
*p_val=p_stack->p_buf[--p_stack->top];
return0;
}
return-1;
}

intstack_deinit(structstack*p_stack)
{
return0;
}

3. 銷(xiāo)毀對(duì)象的順序

創(chuàng)建對(duì)象時(shí)是先分配對(duì)象所需內(nèi)存,再初始化對(duì)象,因?yàn)樵诔跏蓟瘜?duì)象時(shí),需要傳遞相應(yīng)內(nèi)存空間的首地址作為初始化函數(shù)的參數(shù)。這就保證了在初始化對(duì)象之前,必須完成相關(guān)內(nèi)存的分配。而銷(xiāo)毀一個(gè)對(duì)象時(shí),釋放內(nèi)存與調(diào)用解初始化函數(shù)并不能通過(guò)接口進(jìn)行制約,銷(xiāo)毀過(guò)程與創(chuàng)建恰恰相反,應(yīng)先解初始化對(duì)象,再釋放對(duì)象占用的內(nèi)存。因?yàn)樵诮獬跏蓟瘜?duì)象時(shí),還會(huì)使用到對(duì)象中的數(shù)據(jù),若先釋放對(duì)象占用的內(nèi)存,則對(duì)象在被解初始化之前,就被徹底銷(xiāo)毀了,對(duì)象已經(jīng)不存在了,顯然無(wú)法再進(jìn)行解初始化操作。

若內(nèi)存來(lái)源于動(dòng)態(tài)內(nèi)存分配,則完整的應(yīng)用程序范例如下:

//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack.h"
#include"stdio.h"
#include"stdlib.h"
intmain()
{
intval;
structstack*p_stack=(structstack*)malloc(sizeof(structstack));
int*p_buf=(int*)malloc(sizeof(int)*20);

//初始化
stack_init(p_stack,buf,20);

//依次壓入數(shù)據(jù):2、4、5、8
stack_push(p_stack,2);
stack_push(p_stack,4);
stack_push(p_stack,5);
stack_push(p_stack,8);

//依次彈出各個(gè)數(shù)據(jù),并打印
stack_pop(p_stack,&val);
printf("%d",val);
stack_pop(p_stack,&val);
printf("%d",val);
stack_pop(p_stack,&val);
printf("%d",val);
stack_pop(p_stack,&val);
printf("%d",val);

//解初始化
stack_deinit(p_stack);

//釋放內(nèi)存
free(p_stack);
free(p_buf);

return0;
}

若內(nèi)存來(lái)源于靜態(tài)內(nèi)存分配,則內(nèi)存的分配和釋放完全由系統(tǒng)自行完成,如內(nèi)存以“局部變量”的形式分配,范例程序如下:

//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack.h"
#include"stdio.h"
intmain()
{
intval;
intbuf[20];
structstackstack;
structstack*p_stack=&stack;

stack_init(p_stack,buf,20);

//依次壓入數(shù)據(jù):2、4、5、8
stack_push(p_stack,2);
stack_push(p_stack,4);
stack_push(p_stack,5);
stack_push(p_stack,8);

//依次彈出各個(gè)數(shù)據(jù),并打印
stack_pop(p_stack,&val);
printf("%d",val);
stack_pop(p_stack,&val);
printf("%d",val);
stack_pop(p_stack,&val);
printf("%d",val);
stack_pop(p_stack,&val);
printf("%d",val);

stack_deinit(p_stack);
return0;
}

從形式上看,雖然棧類(lèi)的代碼變得復(fù)雜了一些,但對(duì)象內(nèi)存的來(lái)源更具有靈活性,使得棧的適用范圍更加廣泛。在部分系統(tǒng)中,在保證對(duì)象內(nèi)存來(lái)源不受限制的同時(shí),為了特殊情況下的便利性,往往還保留了基于動(dòng)態(tài)內(nèi)存分配創(chuàng)建對(duì)象的方法,在這種情況下,將同時(shí)提供create 和 init 兩套接口。

以 FreeRTOS 為例,其提供了兩套創(chuàng)建任務(wù)的接口:xTaskCreate()和 xTaskCreateStatic()。其中,xTaskCreate()函數(shù)中采用動(dòng)態(tài)內(nèi)存分配的方法獲得了任務(wù)相關(guān)內(nèi)存;而 xTaskCreateStatic()函數(shù)即用于以“靜態(tài)”的方式創(chuàng)建任務(wù),任務(wù)相關(guān)的內(nèi)存需要用戶通過(guò)函數(shù)的參數(shù)傳遞(實(shí)際上該函數(shù)的作用就類(lèi)似于 init 初始化函數(shù),只不過(guò)其命名為了 Create)。freeRTOS可以作為RTOS開(kāi)發(fā)入門(mén)的基礎(chǔ),具體可參考《FreeRTOS及其應(yīng)用,萬(wàn)字長(zhǎng)文,基礎(chǔ)入門(mén)》、《基于RTOS的軟件開(kāi)發(fā)理論》。

在絕大部分面向?qū)ο缶幊陶Z(yǔ)言中,也有類(lèi)似于初始化和解初始化的接口,以C++為例,在定義類(lèi)時(shí),每個(gè)類(lèi)都有構(gòu)造函數(shù)和析構(gòu)函數(shù)兩個(gè)特殊的函數(shù)。構(gòu)造函數(shù)就相當(dāng)于這里的初始化函數(shù),其在創(chuàng)建對(duì)象時(shí)自動(dòng)調(diào)用;析構(gòu)函數(shù)就相當(dāng)于這里的解初始化函數(shù),其在銷(xiāo)毀對(duì)象時(shí)自動(dòng)調(diào)用。例如,以局部變量的形式定義一個(gè)對(duì)象,則在定義對(duì)象時(shí),會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù);在退出當(dāng)前作用域(函數(shù)返回)時(shí),會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)。高級(jí)的面向?qū)ο缶幊陶Z(yǔ)言,為很多操作提供了語(yǔ)法特性上的原生支持,給實(shí)際編程帶來(lái)了極大的便利。

3 繼承

繼承表示了一種類(lèi)與類(lèi)之間的特殊關(guān)系,即 is-a 關(guān)系,例如蘋(píng)果是一種水果。A is-a B,表明了 A 只是 B 的一個(gè)特例,并不是 B 的全部,A(蘋(píng)果)是子類(lèi),B(水果)是父類(lèi)(又稱基類(lèi)、超類(lèi))。

子類(lèi)是父類(lèi)的一個(gè)特例,可以看作是在父類(lèi)的基礎(chǔ)上作了一些屬性或方法的擴(kuò)展,子類(lèi)依然具有父類(lèi)的屬性和方法。使用繼承關(guān)系在一個(gè)已經(jīng)存在的類(lèi)的基礎(chǔ)上,定義一個(gè)新類(lèi)。新類(lèi)將自動(dòng)繼承已存在類(lèi)的屬性和方法,并可根據(jù)需要添加新的屬性和方法。繼承使子類(lèi)可以重用父類(lèi)中已經(jīng)實(shí)現(xiàn)的屬性和方法,無(wú)需再重復(fù)設(shè)計(jì)和編程,以此實(shí)現(xiàn)代碼最大限度的復(fù)用。

3.1 “繼承”示例

在 C 語(yǔ)言編程中,在定義子類(lèi)(子類(lèi)結(jié)構(gòu)體類(lèi)型)時(shí),通過(guò)將父類(lèi)作為子類(lèi)的第一個(gè)成員實(shí)現(xiàn)繼承。之所以這樣做,是因?yàn)樵?C 語(yǔ)言結(jié)構(gòu)體中,第一個(gè)成員(父類(lèi))的地址和結(jié)構(gòu)體自身(子類(lèi))的地址相同,當(dāng)子類(lèi)需要復(fù)用父類(lèi)的方法時(shí),子類(lèi)的地址也可以作為父類(lèi)的地址使用(微信公眾號(hào)【嵌入式系統(tǒng)】這是后續(xù)繼承操作取巧的基礎(chǔ))。

例如在一個(gè)系統(tǒng)中具有多個(gè)棧,為便于區(qū)分,每個(gè)??梢跃哂胁煌拿Q(系統(tǒng)棧、數(shù)據(jù)棧、符號(hào)?!;谠撔枨螅梢詫?shí)現(xiàn)一個(gè)帶名稱的棧(為便于和前文普通棧區(qū)分,后文將其稱為“命名?!保丛谄胀5幕A(chǔ)上,增加一個(gè)“名稱”屬性,該屬性使每個(gè)棧都具有一個(gè)可供識(shí)別的名稱,該棧類(lèi)型的定義及接口聲明如下:

stack_named.h文件
//微信公眾號(hào):嵌入式系統(tǒng)
#ifndef__STACK_NAMED_H
#define__STACK_NAMED_H

#include"stack.h"/*包含基類(lèi)頭文件*/

structstack_named
{
structstacksuper;/*基類(lèi)(超類(lèi))*/
constchar*p_name;/*棧名*/
};

/*初始化*/
intstack_named_init(structstack_named*p_stack,int*p_buf,intsize,constchar*p_name);

/*設(shè)置名稱*/
intstack_named_set(structstack_named*p_stack,constchar*p_name);

/*獲取名稱*/
constchar*stack_named_get(structstack_named*p_stack);

/*解初始化*/
intstack_named_deinit(structstack_named*p_stack);

#endif
stack_named.c文件
//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack_named.h"

intstack_named_init(structstack_named*p_stack,int*p_buf,intsize,constchar*p_name)
{
stack_init(&p_stack->super,p_buf,size);/*初始化基類(lèi)*/
p_stack->p_name=p_name;/*初始化子類(lèi)成員*/
return0;
}

intstack_named_set(structstack_named*p_stack,constchar*p_name)
{
p_stack->p_name=p_name;
return0;
}

constchar*stack_named_get(structstack_named*p_stack)
{
returnp_stack->p_name;
}

intstack_named_deinit(structstack_named*p_stack)
{
returnstack_deinit(&p_stack->super);/*解初始化基類(lèi)*/
}

實(shí)現(xiàn)“命名?!睍r(shí),除初始化函數(shù)和解初始化函數(shù)外,僅為新增的屬性p_name 提供了設(shè)置和獲取方法,棧的核心邏輯相關(guān)函數(shù)(入棧、出棧)無(wú)需重復(fù)實(shí)現(xiàn),入棧和出棧方法作為“命名棧”父類(lèi)的方法,可以被復(fù)用。使用“命名?!钡膽?yīng)用程序范例如下:

//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack_named.h"
#include"stdio.h"
intmain()
{
intval;
intbuf[20];
structstack_namedstack_named;
structstack_named*p_stack_named=&stack_named;

stack_named_init(p_stack_named,buf,20,"chengj");
printf("Thestacknameis%s!
",stack_named_get(p_stack_named));

//依次壓入數(shù)據(jù):2、4、5、8
stack_push((structstack*)p_stack_named,2);//強(qiáng)制棧類(lèi)型轉(zhuǎn)換
stack_push((structstack*)p_stack_named,4);
stack_push((structstack*)p_stack_named,5);
stack_push((structstack*)p_stack_named,8);

//依次彈出各個(gè)數(shù)據(jù),并打印
stack_pop((structstack*)p_stack_named,&val);//強(qiáng)制棧類(lèi)型轉(zhuǎn)換
printf("%d",val);
stack_pop((structstack*)p_stack_named,&val);
printf("%d",val);
stack_pop((structstack*)p_stack_named,&val);
printf("%d",val);
stack_pop((structstack*)p_stack_named,&val);
printf("%d",val);

stack_named_deinit(p_stack_named);
return0;
}

程序中,因?yàn)楦割?lèi)(struct stack)和子類(lèi)(struct stack_named)對(duì)應(yīng)的類(lèi)型并不相同,所以當(dāng)父類(lèi)方法(stack_push()、stack_pop())作用于子類(lèi)對(duì)象(stack_named)時(shí),為了避免編譯器輸出“類(lèi)型不匹配”的警告,必須對(duì)類(lèi)型進(jìn)行強(qiáng)制轉(zhuǎn)換。

在 C 語(yǔ)言中,大量的使用類(lèi)型強(qiáng)制轉(zhuǎn)換存在一定的風(fēng)險(xiǎn),如兩個(gè)類(lèi)之間沒(méi)有繼承關(guān)系,使用強(qiáng)制轉(zhuǎn)換將屏蔽編譯器輸出的警告信息,導(dǎo)致這類(lèi)錯(cuò)誤在編譯階段無(wú)法發(fā)現(xiàn)。為了避免使用強(qiáng)制類(lèi)型轉(zhuǎn)換,可以多做一步操作,從子類(lèi)中取出父類(lèi)的地址進(jìn)行傳遞,保證參數(shù)類(lèi)型一致:

stack_push((structstack*)p_stack_named,2);
//改為
stack_push(&p_stack_named->super,2);

但無(wú)論使用哪種方法,看起來(lái)都不是很完美。這類(lèi)問(wèn)題的存在主要是因?yàn)?C語(yǔ)言并非真正的面向?qū)ο缶幊陶Z(yǔ)言,使用 C 語(yǔ)言實(shí)現(xiàn)面向?qū)ο缶幊虝r(shí),需要使用到一些看似“投機(jī)取巧”的手段。在真正的面向?qū)ο缶幊陶Z(yǔ)言中,編譯器可以識(shí)別繼承關(guān)系,無(wú)需任何強(qiáng)制轉(zhuǎn)換語(yǔ)句,父類(lèi)的方法可以直接作用于子類(lèi)。

3.2 初始化函數(shù)

回顧前面命名棧初始化函數(shù):

intstack_named_init(structstack_named*p_stack,int*p_buf,intsize,constchar*p_name)
{
stack_init(&p_stack->super,p_buf,size);/*初始化基類(lèi)*/
p_stack->p_name=p_name;/*初始化子類(lèi)成員*/
return0;
}

先調(diào)用了父類(lèi)的初始化函數(shù)(stack_init()),再初始化命名棧特有的 p_name 屬性。這里指出了一個(gè)隱含的規(guī)則:先初始化基類(lèi)的成員,再初始化派生類(lèi)特有的成員。該規(guī)則與面向?qū)ο缶幊陶Z(yǔ)言中構(gòu)造函數(shù)的調(diào)用順序是一致的:在建立一個(gè)對(duì)象時(shí),首先調(diào)用基類(lèi)的構(gòu)造函數(shù),然后再調(diào)用派生類(lèi)的構(gòu)造函數(shù)。

3.3 解初始化函數(shù)

解初始化的順序與初始化的順序是恰好相反的,應(yīng)先對(duì)派生類(lèi)中特有的數(shù)據(jù)“解初始化”,再對(duì)基類(lèi)作解初始化操作。解初始化函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序如下:

intstack_deinit(structstack*p_stack)
{
p_stack->top=0;
return0;
}

intstack_named_deinit(structstack_named*p_stack)
{
p_stack->p_name=NULL;
returnstack_deinit(&p_stack->super);/*解初始化基類(lèi)在后*/
}

3.4 最少知識(shí)原則

所謂 “最少知識(shí)原則”就是,對(duì)使用者而言,不管類(lèi)的內(nèi)部如何,只調(diào)用提供的方法,其他的一概不管。(微信公眾號(hào)【嵌入式系統(tǒng)】更多編碼原則可以參考《嵌入式軟件設(shè)計(jì)原則隨想》)顯然前面的“命名?!辈⒎侨绱耍瑢?duì)于命名棧的使用者,其必須知道命名棧與普通棧之間的繼承關(guān)系,進(jìn)而才可以正確的使用普通棧的入棧方法,操作命名棧,例如:

stack_push((structstack*)p_stack_named,2);//類(lèi)型轉(zhuǎn)換關(guān)系

這對(duì)用戶來(lái)說(shuō)并不友好,因?yàn)槠涫褂玫氖恰懊麠!鳖?lèi)(stack_named.h),卻還要關(guān)心“普通?!鳖?lèi)(stack.h)。為滿足“最少知識(shí)原則”,命名棧也可以提供入棧和出棧方法,使用戶僅需關(guān)心命名棧的公共接口就可以完成命名棧的所有操作。

stack_named.h文件
#ifndef__STACK_NAMED_H
#define__STACK_NAMED_H

#include"stack.h"

/*包含基類(lèi)頭文件*/

structstack_named
{
structstacksuper;/*基類(lèi)(超類(lèi))*/
constchar*p_name;/*棧名*/
};

/*初始化*/
intstack_named_init(structstack_named*p_stack,int*p_buf,intsize,constchar*p_name);

/*設(shè)置名稱*/
intstack_named_set(structstack_named*p_stack,constchar*p_name);

/*獲取名稱*/
constchar*stack_named_get(structstack_named*p_stack);

//微信公眾號(hào):嵌入式系統(tǒng)
staticinlineintstack_named_push(structstack_named*p_stack,intval)
{
returnstack_push(&p_stack->super,val);
}
staticinlineintstack_named_pop(structstack_named*p_stack,int*p_val)
{
returnstack_pop(&p_stack->super,p_val);
}

/*解初始化*/
intstack_named_deinit(structstack_named*p_stack);

#endif

頭文件中增加了兩個(gè)方法:stack_named_push()和 stack_named_pop(),由于這兩個(gè)函數(shù)非常簡(jiǎn)單,只是調(diào)用了其父類(lèi)中相應(yīng)的方法,僅一行代碼,因而使用了內(nèi)聯(lián)函數(shù)的形式,如此可以優(yōu)化代碼大小和執(zhí)行速度。經(jīng)過(guò)簡(jiǎn)單的包裝后,用戶使用的所有方法都是作用于“命名?!睂?duì)象的,無(wú)需再使用類(lèi)型強(qiáng)制轉(zhuǎn)換等特殊的方法。更新后的“命名?!笔褂梅独稳缦拢?/p>

//壓入數(shù)據(jù)
stack_named_push(p_stack_named,2);

//彈出數(shù)據(jù)并打印
stack_named_pop(p_stack_named,&val);printf("%d",val);

從用戶角度看,包裝后的“命名?!睂?duì)用戶來(lái)講更加友好(無(wú)需類(lèi)型強(qiáng)制轉(zhuǎn)換)。但在實(shí)際開(kāi)發(fā)過(guò)程中,若所有繼承關(guān)系都再次封裝一遍會(huì)顯得累贅。因此,只對(duì)用戶開(kāi)放的類(lèi)才需要這樣做,如果某些類(lèi)無(wú)需對(duì)用戶開(kāi)放,僅在內(nèi)部使用,則可以酌情省略包裝過(guò)程。

4 多態(tài)

多態(tài)字面含義就是具有“多種形式”。從調(diào)用者的角度看對(duì)象,會(huì)發(fā)現(xiàn)它們非常相似,但內(nèi)部處理實(shí)際上卻各不相同。換句話說(shuō),各對(duì)象雖然內(nèi)部處理不同,但對(duì)于使用者(調(diào)用者)來(lái)講,它們卻是相同的。

4.1 學(xué)生的“自我介紹”

在前面提到的學(xué)生類(lèi),包含姓名、學(xué)號(hào)、性別、身高、體重等屬性,并對(duì)外提供了一個(gè)“自我介紹”方法。

voidstudent_self_introduction(structstudent*p_this)
{
printf("Hi!Mynameis%s,I'ma%s.Myschoolnumberis%d.Myheightis%fcmandweightis%fkg",
p_this->name,
(p_this->sex=='M')?"boy":"girl",
p_this->id,
p_this->height,
p_this->weight);
}

假設(shè)一個(gè)場(chǎng)景,開(kāi)學(xué)第一課所有同學(xué)依次作一個(gè)簡(jiǎn)單的自我介紹,調(diào)用所有同學(xué)的自我介紹方法即可,范例程序如下:

voidfirst_class(structstudent*p_students,intnum)
{
inti;
for(i=0;i

調(diào)用該函數(shù)前,需要將所有學(xué)生對(duì)象創(chuàng)建好,并存于一個(gè)數(shù)組中,假定一個(gè)班級(jí)有 50個(gè)學(xué)生,則調(diào)用示意代碼如下:

intmain()
{
structstudentstudent[50];

/*根據(jù)每個(gè)學(xué)生的信息,依次創(chuàng)建各個(gè)學(xué)生對(duì)象*/
student_init(&student[0],"zhangsan",2024001,'M',173,60);
student_init(&student[1],"lisi",2024002,'F',168,65);
//...

/*上第一節(jié)課*/
first_class(student,50);
}

上面的實(shí)現(xiàn)代碼,假定了學(xué)生的“自我介紹”格式是完全相同的,都是將個(gè)人信息陳述一遍,顯然,這樣的自我介紹無(wú)法體現(xiàn)每個(gè)學(xué)生的個(gè)性和差異。例如,一個(gè)名叫張三的學(xué)生,其期望這樣介紹自己:

“親愛(ài)的老師,同學(xué)們!我叫張三,來(lái)自湖北仙桃,是一個(gè)自信開(kāi)朗,積極向上的人,我有著廣泛的興趣愛(ài)好,喜歡打籃球、看書(shū)、下棋、聽(tīng)音樂(lè)……”

每個(gè)學(xué)生自我介紹的內(nèi)容并不期望千篇一律。若不基于多態(tài)的思想,最簡(jiǎn)單粗暴的方式是每個(gè)學(xué)生都提供一個(gè)自我介紹方法,例如 student_zhangsan_introduction()。這種情況下每個(gè)學(xué)生提供的方法都不相同(函數(shù)名不同),根本無(wú)法統(tǒng)一調(diào)用,此時(shí),第一節(jié)課的調(diào)用將會(huì)大改,需要依次調(diào)用每個(gè)學(xué)生提供的不同的自我介紹方法,例如:

voidfirst_class()
{
student_zhangsan_introduction(&zhangshan);//張三自我介紹
student_lisi_introduction(&lisi);//李四自我介紹
//….
}

無(wú)法使用同樣的調(diào)用形式(函數(shù))完成不同對(duì)象的“自我介紹”。對(duì)于調(diào)用者來(lái)講,需要關(guān)注每個(gè)對(duì)象提供的特殊方法,復(fù)雜度將提升。

使用多態(tài)的思想即可很好的解決這個(gè)問(wèn)題,進(jìn)而保證 firstt_class()的內(nèi)容不變,雖然每個(gè)對(duì)象方法的實(shí)現(xiàn)不同,但可以使用同樣的形式調(diào)用它。在 C 語(yǔ)言中,函數(shù)指針就是解決這個(gè)問(wèn)題的“利器”。

函數(shù)指針的原型決定了調(diào)用方法,例如定義函數(shù)指針:

int(*student_self_introduction)(structstudent*p_student);

無(wú)論該函數(shù)指針指向何處,都表示該函數(shù)指針指向的是 int 類(lèi)型返回值,具有一個(gè)*p_student 參數(shù)的函數(shù),其調(diào)用形式如下:

student_self_introduction(p_student);

函數(shù)指針的指向代表了函數(shù)的實(shí)現(xiàn),指向不同的函數(shù)就代表了不同的實(shí)現(xiàn)。基于此,為了使每個(gè)學(xué)生對(duì)象可以有自己獨(dú)特的介紹方式,在學(xué)生類(lèi)的定義中,可以不實(shí)現(xiàn)自我介紹方法,但可以通過(guò)函數(shù)指針約定自我介紹方法的調(diào)用形式。更新學(xué)生類(lèi)的定義:

student.h文件```

```c
//微信公眾號(hào):嵌入式系統(tǒng)
#ifndef__STUDENT_H
#define__STUDENT_H

structstudent
{
int(*student_self_introduction)(structstudent*p_student);/*新增個(gè)性化自我介紹*/
charname[10];/*姓名(假定最長(zhǎng)10字符)*/
unsignedintid;/*學(xué)號(hào)*/
charsex;/*性別:'M',男;'F',女*/
floatheight;/*身高*/
floatweight;/*體重*/
};

intstudent_init(structstudent*p_student,
char*p_name,
unsignedintid,
charsex,
floatheight,
floatweight,
int(*student_self_introduction)(structstudent*));

/*學(xué)生類(lèi)提供的自我介紹方法*/
staticinlineintstudent_self_introduction(structstudent*p_student)
{
returnp_student->student_self_introduction(p_student);
}

#endif

此時(shí),對(duì)于外界來(lái)講,學(xué)生類(lèi)“自我介紹方法”的調(diào)用形式并未發(fā)生任何改變,函數(shù)原型還是一樣的(由于只有一行代碼,因而以內(nèi)聯(lián)函數(shù)的形式存放到了頭文件中)?;诖?,“第一節(jié)課的內(nèi)容”可以保持完全不變(for循環(huán)調(diào)用全部)。在這種方式下,每個(gè)對(duì)象在初始化時(shí),需要指定自己特殊的自我介紹方,例如張三對(duì)象的創(chuàng)建過(guò)程為:

intstudent_zhangsan_introduction(structstudent*p_student)
{
constchar*str="親愛(ài)的老師,同學(xué)們!我叫張三,來(lái)自湖北仙桃,是一個(gè)自信開(kāi)朗,積極向上的人,我有著廣泛的興趣愛(ài)好,喜歡打籃球、看書(shū)、下棋、聽(tīng)音樂(lè)……";

printf("%s
",str);
return0;
}

intmain()
{
structstudentstudent[50];

/*根據(jù)每個(gè)學(xué)生的信息,依次創(chuàng)建各個(gè)學(xué)生對(duì)象*/
student_init(&student[0],"zhangsan",2024001,'M',173,60,student_zhangsan_introduction);

//...

/*上第一節(jié)課*/
first_class(student,50);
}

多態(tài)的核心是:對(duì)于上層調(diào)用者,不同的對(duì)象可以使用完全相同的操作方法,但是每個(gè)對(duì)象可以有各自不同的實(shí)現(xiàn)方式。多態(tài)是面向?qū)ο缶幊谭浅V匾奶匦裕珻 語(yǔ)言依賴指針實(shí)現(xiàn)多態(tài)。

很多設(shè)計(jì)模式或硬件多型號(hào)適配都是基于這個(gè)基礎(chǔ),可以參考《嵌入式軟件的設(shè)計(jì)模式(上)》)。

4.2 I/O 設(shè)備驅(qū)動(dòng)

C 程序使用 printf()打印日志信息,在 PC 上運(yùn)行時(shí),日志信息可能輸出到控制臺(tái),而在嵌入式系統(tǒng)中,信息可能通過(guò)某個(gè)串口輸出。printf()函數(shù)的解釋是輸出信息至 STDOUT(標(biāo)準(zhǔn)輸出)。顯然printf()函數(shù)就具有多態(tài)性,對(duì)于用戶來(lái)講,其調(diào)用形式是確定的,但內(nèi)部具體輸出信息到哪里,卻會(huì)隨著 STDOUT 的不同而不同。

在一些操作系統(tǒng)中(如Linux),硬件設(shè)備(例串口、ADC 等)的操作方法都和文件操作方法類(lèi)似(一切皆文件),都可以通過(guò) open()、close()、read()、write()等幾個(gè)標(biāo)準(zhǔn)函數(shù)進(jìn)行操作。為統(tǒng)一 I/O 設(shè)備的使用方法,要求每個(gè) I/O 設(shè)備都提供 open、close、read、write 這幾個(gè)標(biāo)準(zhǔn)函數(shù)的實(shí)現(xiàn),即每個(gè) I/O設(shè)備的驅(qū)動(dòng)程序,對(duì)這些標(biāo)準(zhǔn)函數(shù)的實(shí)現(xiàn)在函數(shù)調(diào)用上必須保持一致。這本質(zhì)上就是一個(gè)多態(tài)問(wèn)題,即以同樣的方法使用不同的 I/O 設(shè)備。

通過(guò)函數(shù)指針解決這個(gè)問(wèn)題,首先定義file_ops結(jié)構(gòu)體,包含了相對(duì)應(yīng)的函數(shù)指針,指向I/O 設(shè)備針對(duì)操作的實(shí)現(xiàn)函數(shù)。

file_ops.h文件
//微信公眾號(hào):嵌入式系統(tǒng)
//代碼片段只是原理性展示
structfile_ops
{
void(*open)(char*name,intmode);
void(*close)();
int(*read)();
void(*write)();
};

對(duì)于 I/O設(shè)備,其驅(qū)動(dòng)程序提供這 4個(gè)函數(shù)的實(shí)現(xiàn),并將 file_ops結(jié)構(gòu)體的函數(shù)指針指向?qū)?yīng)的函數(shù)。

#include"file_ops.h"

staticvoidopen(char*name,intmode)
{
//...
}

staticvoidclose()
{
//...
}

staticintread()
{
//...
}

staticvoidwrite()
{
//...
}

structfile_opsmy_console={open,close,read,write};

所有的函數(shù)都使用 static修飾符,避免與外部的函數(shù)產(chǎn)生命名沖突。對(duì)于該設(shè)備,僅對(duì)外提供了一個(gè)可以使用的 file_ops 對(duì)象 my_console。

上面展示了設(shè)備 I/O 的一般管理方法,其中的編程方法或技巧正是面向?qū)ο缶幊讨卸鄳B(tài)的基礎(chǔ),也再一次展現(xiàn)了函數(shù)指針在多態(tài)中的重要地位,多態(tài)可以視為函數(shù)指針的一種典型應(yīng)用。(微信公眾號(hào)【嵌入式系統(tǒng)】類(lèi)似使用是Linux設(shè)備驅(qū)動(dòng)的基礎(chǔ))。

4.3 帶檢查功能的棧

前面范例實(shí)現(xiàn)了棧的核心邏輯(入棧和出棧),假設(shè)現(xiàn)在增加需求,實(shí)現(xiàn)“帶檢查功能的棧”,即在數(shù)據(jù)入棧之前,必須進(jìn)行特定的檢查,“檢查通過(guò)”后才能壓人棧中。檢查方式有多種:

范圍檢查:必須在特定的范圍之內(nèi),比如1 ~ 9,才視為檢查通過(guò);
奇偶檢查:必須是奇數(shù)或者偶數(shù),才視為檢查通過(guò);
變化檢查:值必須增加(比上一次的值大),才視為檢查通過(guò)。

4.3.1 基于繼承實(shí)現(xiàn)“帶范圍檢查功能”的棧

先不考慮多種檢查方式,僅實(shí)現(xiàn)范圍檢查。參照“命名?!钡膶?shí)現(xiàn),使用繼承方式,在普通棧的基礎(chǔ)上實(shí)現(xiàn)一個(gè)新類(lèi),范例程序如下:

stack_with_range_check.h帶范圍檢查的棧
#ifndef__STACK_WITH_RANGE_CHECK_H
#define__STACK_WITH_RANGE_CHECK_H

#include"stack.h"/*包含基類(lèi)頭文件*/

structstack_with_range_check
{
structstacksuper;/*基類(lèi)(超類(lèi))*/
intmin;/*最小值*/
intmax;/*最大值*/
};

intstack_with_range_check_init(structstack_with_range_check*p_stack,
int*p_buf,
intsize,
intmin,intmax);

/*入棧*/
intstack_with_range_check_push(structstack_with_range_check*p_stack,intval);

/*出棧*/
intstack_with_range_check_pop(structstack_with_range_check*p_stack,int*p_val);

#endif

帶范圍檢查的棧 C 文件 stack_with_range_check.c

#include"stack_with_range_check.h"

intstack_with_range_check_init(structstack_with_range_check*p_stack,
int*p_buf,
intsize,
intmin,intmax)
{
/*初始化基類(lèi)*/
stack_init(&p_stack->super,p_buf,size);

/*初始化子類(lèi)成員*/
p_stack->min=min;
p_stack->max=max;
return0;
}

intstack_with_range_check_push(structstack_with_range_check*p_stack,intval)
{
if((val>=p_stack->min)&&(val<=?p_stack->max))//差異點(diǎn)
{
returnstack_push(&p_stack->super,val);
}
return-1;
}

intstack_with_range_check_pop(structstack_with_range_check*p_stack,int*p_val)
{
returnstack_pop(&p_stack->super,p_val);
}

為了接口的簡(jiǎn)潔性,沒(méi)有再展示解初始化等函數(shù)的定義。新增入棧時(shí)作檢查,出棧和普通棧是完全相同的,但基于最小知識(shí)原則也封裝了一個(gè) pop 接口,使該類(lèi)的用戶完全不需要關(guān)心普通棧。

依照這個(gè)方法,可以實(shí)現(xiàn)其它檢查方式的棧。核心是實(shí)現(xiàn)帶檢查功能的入棧函數(shù),因而僅簡(jiǎn)單展示另外兩種檢查方式下入棧函數(shù)的實(shí)現(xiàn),分別如下:

//奇偶檢查入棧函數(shù)
intstack_with_oddeven_check_push(structstack_with_oddeven_check*p_stack,intval)
{
if(((p_stack->iseven)&&((val%2)==0))||((!p_stack->iseven)&&((val%2)!=0)))
{
returnstack_push(&p_stack->super,val);//檢查通過(guò):偶校驗(yàn)且為偶數(shù),或奇校驗(yàn)且為奇數(shù)
}
return-1;
}

//變化檢查入棧函數(shù)
intstack_with_change_check_push(structstack_with_change_check*p_stack,intval)
{
if(p_stack->pre_valuepre_value=val;
returnstack_push(&p_stack->super,val);//檢查通過(guò):本次入棧值大于上一次的值
}
return-1;
}

由此可見(jiàn),這種實(shí)現(xiàn)方式存在一定的缺陷,不同檢查方法對(duì)應(yīng)的入棧函數(shù)不相同,對(duì)于用戶來(lái)講,使用不同的檢查功能,就必須調(diào)用不同的入棧函數(shù)。即操作不同的棧使用不同的接口。但觀察幾個(gè)入棧函數(shù),其入棧方法類(lèi)似,示意代碼如下:

intstack_XXX_push(structstack_XXX*p_stack,intval)
{
if(檢查通過(guò))//不同棧的差異僅是檢測(cè)條件不同
{
returnstack_push(&p_stack->super,val);
}
return-1;
}

可使用多態(tài)思想,將“檢查”函數(shù)的調(diào)用形式標(biāo)準(zhǔn)化編寫(xiě)一個(gè)通用的、與具體檢查方式無(wú)關(guān)的入棧函數(shù)。

4.3.2 基于多態(tài)實(shí)現(xiàn)通用的“帶檢查功能的?!?/p>

使用函數(shù)指針表示“檢查功能”,指向不同的檢查函數(shù)??梢远x一個(gè)包含函數(shù)指針的類(lèi):

structstack_with_validate
{
structstacksuper;/*基類(lèi)(超類(lèi))*/
int(*validate)(structstack_with_validate*p_this,intval);/*檢查函數(shù)*/
};

和其它普通方法一樣,類(lèi)中抽象方法(函數(shù)指針)的第一個(gè)成員同樣是指向該類(lèi)對(duì)象的指針。此時(shí),數(shù)據(jù)入棧前的檢查工作交給 validate 指針?biāo)赶虻暮瘮?shù)實(shí)現(xiàn)。假定其指向的函數(shù)在檢查數(shù)據(jù)時(shí),返回 0 表示檢查通過(guò)可入棧,其它值表示檢查未通過(guò)。完整的帶檢查功能的棧實(shí)現(xiàn)范例如下:

帶檢查功能的棧 H 文件(stack_with_validate.h)


#ifndef__STACK_WITH_VALIDATE_H
#define__STACK_WITH_VALIDATE_H

#include"stack.h"/*包含基類(lèi)頭文件*/
structstack_with_validate
{
structstacksuper;/*基類(lèi)(超類(lèi))*/
int(*validate)(structstack_with_validate*p_this,intval);/*檢查函數(shù)*/
};


intstack_with_validate_init(structstack_with_validate*p_stack,
int*p_buf,
intsize,
int(*validate)(structstack_with_validate*,int));

/*入棧*/
intstack_with_validate_push(structstack_with_validate*p_stack,intval);

/*出棧*/
intstack_with_validate_pop(structstack_with_validate*p_stack,int*p_val);

#endif

帶檢查功能的棧 C 文件(stack_with_validate.c)

#include"stack_with_validate.h"
#include"stdio.h"

intstack_with_validate_init(structstack_with_validate*p_stack,
int*p_buf,
intsize,
int(*validate)(structstack_with_validate*,int))

{
/*初始化基類(lèi)*/
stack_init(&p_stack->super,p_buf,size);
p_stack->validate=validate;//檢查條件,上層說(shuō)了算
return0;
}

intstack_with_validate_push(structstack_with_validate*p_stack,intval)
{
if((p_stack->validate==NULL)||
((p_stack->validate!=NULL)&&(p_stack->validate(p_stack,val)==0)))
{
returnstack_push(&p_stack->super,val);
}
return-1;
}

intstack_with_validate_pop(structstack_with_validate*p_stack,int*p_val)
{
returnstack_pop(&p_stack->super,p_val);
}

帶某種檢查功能的棧,重點(diǎn)是實(shí)現(xiàn)其中的 validate 方法?;趲z查的棧,實(shí)現(xiàn)帶范圍檢查的棧,程序詳見(jiàn)如下:

帶范圍檢查的棧 H 文件更新(stack_with_range_check.h)

#ifndef__STACK_WITH_RANGE_CHECK_H
#define__STACK_WITH_RANGE_CHECK_H

#include"stack_with_validate.h"/*包含基類(lèi)頭文件*/

structstack_with_range_check
{
structstack_with_validatesuper;/*基類(lèi)(超類(lèi))*/
intmin;/*最小值*/
intmax;/*最大值*/
};

structstack_with_validate*stack_with_range_check_init(structstack_with_range_check*p_stack,
int*p_buf,
intsize,
intmin,
intmax);

#endif

帶范圍檢查的棧 C 文件更新(stack_with_range_check.c)

#include"stack_with_range_check.h"

staticint_validate(structstack_with_validate*p_this,intval)
{
structstack_with_range_check*p_stack=(structstack_with_range_check*)p_this;

if((val>=p_stack->min)&&(val<=?p_stack->max))
{
return0;/*檢查通過(guò)*/
}

return-1;
}

structstack_with_validate*stack_with_range_check_init(structstack_with_range_check*p_stack,
int*p_buf,
intsize,
intmin,
intmax)
{
/*初始化基類(lèi)*/
stack_with_validate_init(&p_stack->super,p_buf,size,_validate);

/*初始化子類(lèi)成員*/
p_stack->min=min;
p_stack->max=max;
return0;
}

帶范圍檢查的棧,主要目的就是實(shí)現(xiàn)“檢查功能”對(duì)應(yīng)的函數(shù):_validate,并將其作為 validate 函數(shù)指針(抽象方法)的值。

在面向?qū)ο缶幊讨校橄蠓椒ǖ念?lèi)通常稱之為抽象類(lèi),抽象類(lèi)不能直接實(shí)例化(因?yàn)槠溥€有方法未實(shí)現(xiàn)),抽象類(lèi)只能被繼承,且由子類(lèi)實(shí)現(xiàn)其中定義的抽象方法。在 UML 類(lèi)圖中,抽象類(lèi)的類(lèi)名和其中的抽象方法均使用斜體表示,普通棧、帶檢查功能的棧和帶范圍檢查的棧,它們之間的關(guān)系詳見(jiàn)圖。

400669c0-906d-11ef-a511-92fbcf53809c.png

帶范圍檢查的棧,其主要作用是實(shí)現(xiàn)其父類(lèi)中定義的抽象方法,進(jìn)而創(chuàng)建一個(gè)真正的“帶檢查功能”的棧對(duì)象(此時(shí)的抽象方法已實(shí)現(xiàn)),該對(duì)象即可提交給外部使用。帶范圍檢查的棧并沒(méi)有其他特殊的方法,因而在其初始化完成后,通過(guò)初始化函數(shù)的返回值向外界提供了一個(gè)“帶檢查功能”的棧對(duì)象,后續(xù)用戶即可使用 stack_with_validate.h 文件中的push 和 pop 方法操作該對(duì)象。

帶范圍檢查的棧使用范例如下:

//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack_with_range_check.h"
#include"stdio.h"

intmain()
{
intval;
intbuf[20];
inti;
inttest_data[5]={2,4,5,3,10};

structstack_with_range_checkstack;

structstack_with_validate*p_stack=stack_with_range_check_init(&stack,buf,20,1,9);

for(i=0;i

無(wú)論何種檢查方式,其主要目的都是創(chuàng)建“帶檢查功能”的棧對(duì)象(完成抽象方法的實(shí)現(xiàn))。創(chuàng)建完畢后,對(duì)于用戶操作方法都是完全相同的 stack_with_validate_push 和 stack_with_validate_pop ,與檢查方式無(wú)關(guān)。為避免贅述,這里不再實(shí)現(xiàn)另外兩種檢查功能的棧,僅展示出他們的類(lèi)圖。

40265a32-906d-11ef-a511-92fbcf53809c.png

在這里插入圖片描述

在一些大型項(xiàng)目中,初始化過(guò)程往往和應(yīng)用程序是分離的(即stack_with_range_check_init 內(nèi)部封閉不可見(jiàn)),也就是說(shuō),對(duì)于用戶來(lái)講,其僅會(huì)獲取到一個(gè) struct stack_with_validate *類(lèi)型的指針,其指向某個(gè)“帶檢查功能的?!?,實(shí)際檢查什么,用戶可能并不關(guān)心,應(yīng)用程序基于該類(lèi)型指針編程,將使應(yīng)用程序與具體檢查功能無(wú)關(guān),即使后續(xù)更換為其它檢查方式,應(yīng)用程序也不需要做任何改動(dòng)。

4.4 抽象分離

如果是硬件資源有限,功能單一或大概率無(wú)需擴(kuò)展的嵌入式軟件開(kāi)發(fā),進(jìn)行到這基本可以滿足需求;如果是復(fù)雜應(yīng)用,且硬件資源充足還可繼續(xù)優(yōu)化。

4.4.1 檢查功能抽象

前面的實(shí)現(xiàn)中,將檢查功能視為棧的一種擴(kuò)展(使用繼承),檢查邏輯直接在相應(yīng)的擴(kuò)展類(lèi)中實(shí)現(xiàn)。這就使檢查功能與棧綁定在一起,檢查功能的實(shí)現(xiàn)無(wú)法獨(dú)立復(fù)用。如果要實(shí)現(xiàn)一個(gè)“帶檢查功能的隊(duì)列”,同樣是上述的 3 種檢查邏輯,期望能夠復(fù)用檢查邏輯相關(guān)的代碼。顯然,由于當(dāng)前檢查邏輯的實(shí)現(xiàn)與棧捆綁在一起,無(wú)法單獨(dú)提取出來(lái)復(fù)用。

檢查功能與棧的綁定,主要在“帶檢查功能的?!敝畜w現(xiàn),該類(lèi)的定義如下:

structstack_with_validate
{
structstacksuper;/*基類(lèi)(超類(lèi))*/
int(*validate)(structstack_with_validate*p_this,intval);/*檢查函數(shù)*/
};

super 用于繼承自普通棧,validate 表示一個(gè)抽象的數(shù)據(jù)檢查方法,不同的檢查方法,通過(guò)該指針?biāo)赶虻暮瘮?shù)體現(xiàn)。由于檢查方法validate是該類(lèi)的一個(gè)方法,檢查邏輯與棧綁定。為了解綁分離,可以將檢查邏輯放到獨(dú)立的與棧無(wú)關(guān)的類(lèi)中,額外定義一個(gè)抽象的校驗(yàn)器類(lèi),專(zhuān)門(mén)表示數(shù)據(jù)檢查邏輯:

structvalidator
{
int(*validate)(structvalidator*p_this,intval);/*檢查函數(shù)*/
};

雖然該類(lèi)僅包含 validate 函數(shù)指針,但需注意該函數(shù)指針類(lèi)型的變化,其第一個(gè)參數(shù)為指向校驗(yàn)器的指針,而在“帶檢查功能的?!敝?,其第一個(gè)參數(shù)是指向“帶檢查功能的棧”的指針。通過(guò)該類(lèi)的定義,明確的將檢查邏輯封裝到獨(dú)立的校驗(yàn)器類(lèi)中,與棧再無(wú)任何關(guān)聯(lián)。不同的檢查邏輯,可以在其子類(lèi)中實(shí)現(xiàn),校驗(yàn)器類(lèi)和各個(gè)子類(lèi)之間的關(guān)系如下:

405549d2-906d-11ef-a511-92fbcf53809c.png

由于校驗(yàn)器類(lèi)僅包含一個(gè)函數(shù)指針,因此其只需要在頭文件中定義出類(lèi)即可,程序如下:

校驗(yàn)器類(lèi)定義(validator.h)

#ifndef__VALIDATOR_H
#define__VALIDATOR_H

structvalidator
{
int(*validate)(structvalidator*p_this,intval);
};

staticinlineintvalidator_init(structvalidator*p_validator,
int(*validate)(structvalidator*,int))
{
p_validator->validate=validate;
return0;
}

staticinlineintvalidator_validate(structvalidator*p_validator,intval)/*校驗(yàn)函數(shù)*/
{
if(p_validator->validate==NULL)/*校驗(yàn)函數(shù)為空,視為無(wú)需校驗(yàn)*/
{
return0;
}
returnp_validator->validate(p_validator,val);
}

#endif

初始化函數(shù)負(fù)責(zé)為 validate 賦值,validator_validate 函數(shù)是校驗(yàn)器對(duì)外提供的校驗(yàn)函數(shù),在其實(shí)現(xiàn)中僅調(diào)用了 validate 函數(shù)指針指向的函數(shù)。由于函數(shù)都比較簡(jiǎn)單,因而直接使用了內(nèi)聯(lián)函數(shù)的形式進(jìn)行了定義。接下來(lái)以范圍校驗(yàn)為例,實(shí)現(xiàn)一個(gè)范圍校驗(yàn)器。

范圍校驗(yàn)器 H 文件內(nèi)容(validator_range_check.h)

#ifndef__VALIDATOR_RANGE_CHECK_H
#define__VALIDATOR_RANGE_CHECK_H

#include"validator.h"
structvalidator_range_check
{
structvalidatorsuper;
intmin;
intmax;
};

structvalidator*validator_range_check_init(structvalidator_range_check*p_validator,intmin,intmax);

#endif

范圍校驗(yàn)器 C 文件內(nèi)容(validator_range_check.c)

//微信公眾號(hào):嵌入式系統(tǒng)
#include"validator_range_check.h"

staticint_validate(structvalidator*p_this,intval)
{
structvalidator_range_check*p_stack=(structvalidator_range_check*)p_this;
if((val>=p_stack->min)&&(val<=?p_stack->max))
{
return0;/*檢查通過(guò)*/
}
return-1;
}

structvalidator*validator_range_check_init(structvalidator_range_check*p_validator,intmin,intmax)
{
validator_init(&p_validator->super,_validate);
p_validator->min=min;
p_validator->max=max;
return&p_validator->super;
}

由于 validator_range_check 類(lèi)僅用于實(shí)現(xiàn) validator 抽象類(lèi)中定義的抽象方法,其初始化函數(shù)可以直接對(duì)外返回一個(gè)標(biāo)準(zhǔn)的校驗(yàn)器(其中的抽象方法已實(shí)現(xiàn))。按照同樣的方法,可以實(shí)現(xiàn)validator_oddeven_check 類(lèi)和 validator_change_check 類(lèi)。將檢查功能從“帶檢查功能的?!敝蟹蛛x出來(lái)之后,“帶檢查功能的?!敝芯蜔o(wú)需再維護(hù)檢查功能對(duì)應(yīng)的抽象方法。其可以通過(guò)依賴的方式使用檢查功能,即依賴一個(gè)校驗(yàn)器。在類(lèi)圖中,依賴關(guān)系可以使用一個(gè)虛線箭頭表示,箭頭指向被依賴的類(lèi),示意圖如下:

406bfaf6-906d-11ef-a511-92fbcf53809c.png

“帶檢查功能的?!鳖?lèi)定義如下:

structstack_with_validate
{
structstacksuper;/*基類(lèi)(超類(lèi))*/
structvalidator*p_validator;/*依賴的校驗(yàn)器*/
};

與先前相比,其核心變化是由一個(gè) validate 函數(shù)指針(指向具體的檢查方法)變更為 p_validator 指針(指向抽象的檢查方法),變化雖小,但是兩種截然不同的設(shè)計(jì)理念。之前的方式是定義了一個(gè)抽象方法,而現(xiàn)在的方式是依賴于一個(gè)校驗(yàn)器對(duì)象。

基于此更新“帶檢查功能的?!鳖?lèi)的實(shí)現(xiàn)如下:

帶檢查功能的棧 H 文件更新(stack_with_validate.h)

#ifndef__STACK_WITH_VALIDATE_H
#define__STACK_WITH_VALIDATE_H

#include"stack.h"/*包含基類(lèi)頭文件*/
#include"validator.h"

structstack_with_validate
{
structstacksuper;/*基類(lèi)(超類(lèi))*/
structvalidator*p_validator;
};

intstack_with_validate_init(structstack_with_validate*p_stack,
int*p_buf,
intsize,
structvalidator*p_validator);

intstack_with_validate_push(structstack_with_validate*p_stack,intval);
intstack_with_validate_pop(structstack_with_validate*p_stack,int*p_val);

#endif

帶檢查功能的棧 C 文件更新(stack_with_validate.c)

//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack_with_validate.h"
#include"stdio.h"

intstack_with_validate_init(structstack_with_validate*p_stack,
int*p_buf,
intsize,
structvalidator*p_validator)
{
stack_init(&p_stack->super,p_buf,size);
p_stack->p_validator=p_validator;
return0;
}

intstack_with_validate_push(structstack_with_validate*p_stack,intval)
{
if((p_stack->p_validator==NULL)||(validator_validate(p_stack->p_validator,val)==0))//注意差別
{
returnstack_push(&p_stack->super,val);
}
return-1;
}

intstack_with_validate_pop(structstack_with_validate*p_stack,int*p_val)
{
returnstack_pop(&p_stack->super,p_val);
}

“帶檢查功能的棧”的應(yīng)用接口(push 和 pop)并沒(méi)有發(fā)生任何改變,應(yīng)用程序可以被復(fù)用,測(cè)試更新后的帶檢查功能的棧:

#include"stack_with_validate.h"
#include"validator_range_check.h"
#include"stdio.h"
intmain()
{
intbuf[20];
structstack_with_validatestack;
structvalidator_range_checkvalidator_range_check;

/*獲取范圍檢查校驗(yàn)器*/
structvalidator*p_validator=validator_range_check_init(&validator_range_check,1,9);
stack_with_validate_init(&stack,buf,20,p_validator);

stack_validate_application(&stack);//使用和先前繼承方式一樣,實(shí)現(xiàn)忽略

return0;
}

4.4.2 定義抽象棧

定義校驗(yàn)器類(lèi)后,整個(gè)系統(tǒng)實(shí)現(xiàn)了兩種棧:普通棧和“帶檢查功能的?!保瑹o(wú)論什么棧,對(duì)于用戶來(lái)講都是實(shí)現(xiàn)入棧和出棧兩個(gè)核心邏輯。兩種棧提供兩種入棧和出棧方法。

普通棧提供的方法為:

intstack_push(structstack*p_stack,intval);/*入棧*/
intstack_pop(structstack*p_stack,int*p_val);/*出棧*/

“帶檢查功能的?!碧峁┑姆椒椋?/p>

intstack_with_validate_push(structstack_with_validate*p_stack,intval);/*入棧*/
intstack_with_validate_pop(structstack_with_validate*p_stack,int*p_val);/*出棧*/

用戶執(zhí)行入棧和出棧操作,使用不同類(lèi)的棧,調(diào)用的函數(shù)不同。通過(guò)多態(tài)思想,將入棧和出棧定義為抽象方法(函數(shù)指針),則可以達(dá)到這樣的效果:無(wú)論使用何種棧,都可以使用相同的方法來(lái)實(shí)現(xiàn)入棧和出棧?;诖硕x抽象棧。

抽象棧類(lèi)定義(stack.h)

#ifndef__STACK_H
#define__STACK_H

structstack
{
int(*push)(structstack*p_stack,intval);
int(*pop)(structstack*p_stack,int*p_val);
};

staticinlineintstack_init(structstack*p_stack,
int(*push)(structstack*,int),
int(*pop)(structstack*,int*))
{
p_stack->push=push;
p_stack->pop=pop;
}

staticinlineintstack_push(structstack*p_stack,intval)
{
returnp_stack->push(p_stack,val);
}

staticinlineintstack_pop(structstack*p_stack,int*p_val)
{
returnp_stack->pop(p_stack,p_val);
}

#endif

基于抽象棧的定義,使用抽象棧提供的接口實(shí)現(xiàn)一個(gè)通用的應(yīng)用程序,該應(yīng)用程序與底層細(xì)節(jié)無(wú)關(guān),任何棧都可以使用該應(yīng)用程序進(jìn)行測(cè)試。

基于抽象棧實(shí)現(xiàn)的應(yīng)用程序:

#include"stack.h"
#include"stdio.h"
intstack_application(structstack*p_stack)
{
inti;
intval;
inttest_data[5]={2,4,5,3,10};

for(i=0;i

先有應(yīng)用層代碼再有底層代碼。在實(shí)現(xiàn)具體棧之前,就可以開(kāi)始編寫(xiě)應(yīng)用程序(微信公眾號(hào)【嵌入式系統(tǒng)】這就是依賴倒置原則,可參考《嵌入式軟件設(shè)計(jì)原則隨想》)。實(shí)現(xiàn)普通棧:

普通棧 H 文件內(nèi)容(stack_normal.h)

#ifndef__STACK_NORMAL_H
#define__STACK_NORMAL_H

#include"stack.h"
structstack_normal
{
structstacksuper;
inttop;/*棧頂*/
int*p_buf;/*棧緩存*/
unsignedintsize;/*棧緩存的大小*/
};

structstack*stack_normal_init(structstack_normal*p_stack,int*p_buf,intsize);

#endif

普通棧 C 文件內(nèi)容(stack_normal.c)

//微信公眾號(hào):嵌入式系統(tǒng)
#include"stack_normal.h"

staticint_push(structstack*p_this,intval)
{
structstack_normal*p_stack=(structstack_normal*)p_this;
if(p_stack->top!=p_stack->size)
{
p_stack->p_buf[p_stack->top++]=val;
return0;
}
return-1;
}

staticint_pop(structstack*p_this,int*p_val)
{
structstack_normal*p_stack=(structstack_normal*)p_this;
if(p_stack->top!=0)
{
*p_val=p_stack->p_buf[--p_stack->top];
return0;
}
return-1;
}

structstack*stack_normal_init(structstack_normal*p_stack,int*p_buf,intsize)
{
p_stack->top=0;
p_stack->size=size;

p_stack->p_buf=p_buf;
stack_init(&p_stack->super,_push,_pop);
return&p_stack->super;
}

基于普通類(lèi)的實(shí)現(xiàn),測(cè)試普通棧類(lèi):

#include"stack_normal.h"
intmain()
{
intbuf[20];

structstack_normalstack;
structstack*p_stack=stack_normal_init(&stack,buf,20);
stack_application(p_stack);
return0;
}

“帶檢查功能的?!笔窃谄胀5幕A(chǔ)上,增加了檢查功能,實(shí)現(xiàn)范例程序如下:

帶檢查功能的棧 H 文件更新(stack_with_validate.h)

#ifndef__STACK_WITH_VALIDATE_H
#define__STACK_WITH_VALIDATE_H

#include"stack.h"/*包含基類(lèi)頭文件*/
#include"validator.h"


structstack_with_validate
{

structstacksuper;/*基類(lèi)(超類(lèi))*/
structstack*p_normal_stack;/*依賴于普通棧的實(shí)現(xiàn)*/
structvalidator*p_validator;
};

structstack*stack_with_validate_init(structstack_with_validate*p_stack,
structstack*p_normal_stack,
structvalidator*p_validator);

#endif

檢查功能的棧 C 文件更新(stack_with_validate.c)

#include"stack_with_validate.h"
#include"stdio.h"
staticint_push(structstack*p_this,intval)
{
structstack_with_validate*p_stack=(structstack_with_validate*)p_this;
if((p_stack->p_validator==NULL)||(validator_validate(p_stack->p_validator,val)==0))
{
returnstack_push(p_stack->p_normal_stack,val);
}
return-1;
}

staticint_pop(structstack*p_this,int*p_val)
{
structstack_with_validate*p_stack=(structstack_with_validate*)p_this;
returnstack_pop(p_stack->p_normal_stack,p_val);
}

structstack*stack_with_validate_init(structstack_with_validate*p_stack,
structstack*p_normal_stack,
structvalidator*p_validator)
{
stack_init(&p_stack->super,_push,_pop);

p_stack->p_validator=p_validator;
p_stack->p_normal_stack=p_normal_stack;
return&p_stack->super;
}

基于“帶檢查功能的?!钡膶?shí)現(xiàn),測(cè)試范例如下:

#include"stack_normal.h"
#include"stack_with_validate.h"
#include"validator_range_check.h"

intmain()
{
intbuf[20];
structstack_normalstack;
structstack_with_validatestack_with_validate;
structvalidator_range_checkvalidator_range_check;

structstack*p_stack_normal=stack_normal_init(&stack,buf,20);

structvalidator*p_validator=validator_range_check_init(&validator_range_check,1,9);
structstack*p_stack=stack_with_validate_init(&stack_with_validate,
p_stack_normal,
p_validator);

stack_application(p_stack);
return0;
}

由此可見(jiàn),無(wú)論底層的各種棧如何實(shí)現(xiàn),對(duì)于上層應(yīng)用來(lái)講,其可以使用同一套接口stack_application操作各種各樣不同的棧。

多種多態(tài)示例的核心解決方案都是相同的,即:定義抽象方法(函數(shù)指針),使上層應(yīng)用可以使用同一套接口訪問(wèn)不同的對(duì)象。從類(lèi)的角度看,每個(gè)類(lèi)中操作的規(guī)約都是相同的,而這些類(lèi)可以用不同的方式實(shí)現(xiàn)這些同名的操作,從而使得擁有相同接口的對(duì)象可以在運(yùn)行時(shí)相互替換。

同樣的應(yīng)用程序,可以在多個(gè)硬件平臺(tái)上運(yùn)行,更換硬件時(shí)應(yīng)用程序無(wú)需作任何改動(dòng)。在嵌入式系統(tǒng)中,相同功能芯片的更新替換,也是多態(tài)應(yīng)用最多的場(chǎng)景,根據(jù)硬件差異多態(tài)封裝,應(yīng)用層無(wú)感使用相同接口?;诙鄳B(tài)的思想實(shí)現(xiàn)“與硬件無(wú)關(guān)”的應(yīng)用程序,還可以衍生出兩個(gè)概念:抽象接口與依賴倒置,它們的核心都是多態(tài)。更多編碼原則可參考《嵌入式軟件設(shè)計(jì)原則隨想》、《Unix哲學(xué)之編程原則》,分層架構(gòu)《嵌入式軟件分層隔離的典范》。

5 小節(jié)

學(xué)會(huì)了屠龍技,但是沒(méi)有龍,怎么辦?有些東西只是一種思維模式,作為日常開(kāi)發(fā)工作中潛移默化的一種偏愛(ài)。所以嵌入式軟件開(kāi)發(fā)究竟有沒(méi)對(duì)象呢?有但少。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3614

    瀏覽量

    93685
  • 嵌入式軟件
    +關(guān)注

    關(guān)注

    4

    文章

    240

    瀏覽量

    26641

原文標(biāo)題:一文了解嵌入式軟件開(kāi)發(fā)的“對(duì)象”

文章出處:【微信號(hào):strongerHuang,微信公眾號(hào):strongerHuang】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    嵌入式軟件開(kāi)發(fā)與非嵌入式軟件開(kāi)發(fā)的區(qū)別?

    嵌入式軟件開(kāi)發(fā)與非嵌入式軟件開(kāi)發(fā)區(qū)別?設(shè)備驅(qū)動(dòng)開(kāi)發(fā)與裸機(jī)驅(qū)動(dòng)開(kāi)發(fā)區(qū)別?
    發(fā)表于 04-02 06:29

    嵌入式軟件開(kāi)發(fā)工具

    翻譯自這篇博不同物理設(shè)備之間的數(shù)字轉(zhuǎn)換可以說(shuō)是隨處可見(jiàn),讓其智能又強(qiáng)大。驅(qū)動(dòng)設(shè)備的引擎是嵌入式軟件,它是快速發(fā)展的IoT生態(tài)系統(tǒng)中不可或缺的部分。本篇博客主要談
    發(fā)表于 10-28 08:21

    如何學(xué)習(xí)嵌入式軟件開(kāi)發(fā)

    1. 如何學(xué)習(xí)嵌入式軟件開(kāi)發(fā)嵌入式軟件開(kāi)發(fā)定要結(jié)合開(kāi)發(fā)板來(lái)學(xué)習(xí),所有的知識(shí)都可以在
    發(fā)表于 02-11 07:33

    嵌入式軟件開(kāi)發(fā)是做什么的?

    ,C語(yǔ)言作為門(mén)基礎(chǔ)語(yǔ)言,無(wú)論對(duì)嵌入式軟件還是硬件開(kāi)發(fā)編程,都會(huì)有很大的幫助。 2.了解操作系統(tǒng),對(duì)操作系統(tǒng)
    發(fā)表于 12-15 16:39

    什么是嵌入式軟件開(kāi)發(fā)

    嵌入式軟件開(kāi)發(fā)又是指什么?   隨著嵌入式軟件系統(tǒng)結(jié)構(gòu)越來(lái)越復(fù)雜,嵌入式軟件
    發(fā)表于 04-20 08:43 ?8773次閱讀

    嵌入式軟件開(kāi)發(fā)與Embedded-GIS

    1.嵌入式軟件開(kāi)發(fā) 市場(chǎng)現(xiàn)狀 嵌入式操作系統(tǒng) 嵌入式軟件開(kāi)發(fā)特點(diǎn) 嵌入式
    發(fā)表于 02-28 10:39 ?36次下載

    Eclipse嵌入式軟件開(kāi)發(fā)平臺(tái)

    隨著嵌入式系統(tǒng)在通信、網(wǎng)絡(luò)設(shè)施、航空、航海和航天等領(lǐng)域的大范圍應(yīng)用,需要功能強(qiáng)大的軟件開(kāi)發(fā)平臺(tái)輔助嵌入式應(yīng)用軟件開(kāi)發(fā)。
    發(fā)表于 12-01 15:37 ?55次下載
    Eclipse<b class='flag-5'>嵌入式</b><b class='flag-5'>軟件開(kāi)發(fā)</b>平臺(tái)

    ARM嵌入式軟件開(kāi)發(fā)

    ARM嵌入式軟件開(kāi)發(fā)ARM嵌入式軟件開(kāi)發(fā)ARM嵌入式軟件開(kāi)發(fā)
    發(fā)表于 01-15 17:29 ?65次下載

    嵌入式軟件開(kāi)發(fā)的優(yōu)勢(shì)分析

    為什么當(dāng)今嵌入式行業(yè)會(huì)如此受歡迎呢?我們從事嵌入式軟件開(kāi)發(fā)有什么好處嗎?小編就來(lái)說(shuō)說(shuō)我的看法吧,嵌入式軟件開(kāi)發(fā)的優(yōu)勢(shì)我們可以從幾點(diǎn)來(lái)看。
    的頭像 發(fā)表于 12-21 15:23 ?5225次閱讀

    嵌入式軟件開(kāi)發(fā)環(huán)境

    嵌入式軟件開(kāi)發(fā)環(huán)境1 簡(jiǎn)介嵌入式軟件定義嵌入式系統(tǒng)構(gòu)成2 環(huán)境搭建2.1 Qt2.2 CLion2.3 Source Insight + v
    發(fā)表于 10-21 12:21 ?7次下載
    <b class='flag-5'>嵌入式</b><b class='flag-5'>軟件開(kāi)發(fā)</b>環(huán)境

    嵌入式軟件開(kāi)發(fā)做什么?嵌入式開(kāi)發(fā)培訓(xùn)學(xué)哪些

    嵌入式行業(yè)可以說(shuō)從幕后走到前臺(tái),大家對(duì)他都很熟悉了,我國(guó)嵌入式人才缺口每年在50萬(wàn)左右,相關(guān)調(diào)查報(bào)告稱嵌入式軟件開(kāi)發(fā)是未來(lái)幾年最熱門(mén)和最受歡迎的職業(yè)之
    發(fā)表于 11-03 10:36 ?19次下載
    <b class='flag-5'>嵌入式</b><b class='flag-5'>軟件開(kāi)發(fā)</b>做什么?<b class='flag-5'>嵌入式開(kāi)發(fā)</b>培訓(xùn)學(xué)哪些

    嵌入式軟件開(kāi)發(fā)的特點(diǎn)、設(shè)計(jì)流程、嵌入式軟件的結(jié)構(gòu)

    ? ? ? ?嵌入式軟件開(kāi)發(fā)的特點(diǎn)、設(shè)計(jì)流程、嵌入式軟件的結(jié)構(gòu)?嵌入式
    發(fā)表于 11-03 15:21 ?37次下載
    <b class='flag-5'>嵌入式</b><b class='flag-5'>軟件開(kāi)發(fā)</b>的特點(diǎn)、設(shè)計(jì)流程、<b class='flag-5'>嵌入式</b><b class='flag-5'>軟件</b>的結(jié)構(gòu)

    嵌入式軟件開(kāi)發(fā)入門(mén)

    1. 如何學(xué)習(xí)嵌入式軟件開(kāi)發(fā)嵌入式軟件開(kāi)發(fā)定要結(jié)合開(kāi)發(fā)板來(lái)學(xué)習(xí),所有的知識(shí)都可以在
    發(fā)表于 12-07 17:21 ?17次下載
    <b class='flag-5'>嵌入式</b><b class='flag-5'>軟件開(kāi)發(fā)</b>入門(mén)

    嵌入式軟件開(kāi)發(fā)流程

    電子發(fā)燒友網(wǎng)站提供《嵌入式軟件開(kāi)發(fā)流程.ppt》資料免費(fèi)下載
    發(fā)表于 11-17 14:37 ?3次下載
    <b class='flag-5'>嵌入式</b><b class='flag-5'>軟件開(kāi)發(fā)</b>流程

    嵌入式軟件開(kāi)發(fā)軟件開(kāi)發(fā)的區(qū)別

    嵌入式軟件開(kāi)發(fā)軟件開(kāi)發(fā)是兩個(gè)不同的概念,它們?cè)?b class='flag-5'>一些關(guān)鍵方面有著明顯的區(qū)別。嵌入式軟件開(kāi)發(fā)是指
    的頭像 發(fā)表于 01-22 15:27 ?2267次閱讀
    RM新时代网站-首页