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

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

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

C語(yǔ)言實(shí)現(xiàn)狀態(tài)機(jī)的主要3種方法

STM32嵌入式開(kāi)發(fā) ? 來(lái)源:STM32嵌入式開(kāi)發(fā) ? 2023-11-06 12:20 ? 次閱讀

狀態(tài)機(jī)的實(shí)現(xiàn)無(wú)非就是 3 個(gè)要素:狀態(tài)、事件、響應(yīng)。轉(zhuǎn)換成具體的行為就 3 句話。

發(fā)生了什么事?

現(xiàn)在系統(tǒng)處在什么狀態(tài)?

在這樣的狀態(tài)下發(fā)生了這樣的事,系統(tǒng)要干什么?

用 C 語(yǔ)言實(shí)現(xiàn)狀態(tài)機(jī)主要有 3 種方法:switch—case 法、表格驅(qū)動(dòng)法、函數(shù)指針?lè)ā?/p>

switch—case 法

狀態(tài)用 switch—case 組織起來(lái), 將事件也用switch—case 組織起來(lái), 然后讓其中一個(gè) switch—case 整體插入到另一個(gè) switch—case 的每一個(gè) case 項(xiàng)中 。 「程序清單 List4 :」


switch(StateVal)
{
    case S0:
  switch(EvntID)
  {
   case E1:
    action_S0_E1(); /*S0 狀態(tài)下 E1 事件的響應(yīng)*/
    StateVal = new state value;/*狀態(tài)遷移,不遷移則沒(méi)有此行*/
    break;
   case E2:
    action_S0_E2(); /*S0 狀態(tài)下 E2 事件的響應(yīng)*/
    StateVal = new state value;
    break;
   ......
   case Em:
    action_S0_Em(); /*S0 狀態(tài)下 Em 事件的響應(yīng)*/
    StateVal = new state value;
    break;
   default:
    break;
  }
  break;
    case S1:
  ......
  break;
    ......
    case Sn:
  ......
  break;
    default:
  break;
}
上面的偽代碼示例只是通用的情況,實(shí)際應(yīng)用遠(yuǎn)沒(méi)有這么復(fù)雜。雖然一個(gè)系統(tǒng)中事件可能有很多種,但在實(shí)際應(yīng)用中,許多事件可能對(duì)某個(gè)狀態(tài)是沒(méi)有意義的。 例如在程序清單 List4中,如果 E2、······ Em 對(duì)處在 S0 狀態(tài)下的系統(tǒng)沒(méi)有意義,那么在 S0 的 case 下有關(guān)事件E2、······ Em 的代碼根本沒(méi)有必要寫,狀態(tài) S0 只需要考慮事件 E1 的處理就行了。 既然是兩個(gè) switch—case 之間的嵌套, 那么就有一個(gè)誰(shuí)嵌套誰(shuí)的問(wèn)題, 所以說(shuō) switch—case法有兩種寫法:狀態(tài)嵌套事件和事件嵌套狀態(tài)。這兩種寫法都可以, 各有利弊, 至于到底選用哪種方式就留給設(shè)計(jì)人員根據(jù)具體情況自行決斷吧。 關(guān)于 switch—case 法還有最后一點(diǎn)要說(shuō)明, 因?yàn)?switch—case 的原理是從上到下挨個(gè)比較,越靠后,查找耗費(fèi)的時(shí)間就越長(zhǎng),所以要注意狀態(tài)和事件在各自的 switch 語(yǔ)句中的安排順序,不推薦程序清單 List4 那樣按順序號(hào)排布的方式。出現(xiàn)頻率高或者實(shí)時(shí)性要求高的狀態(tài)和事件的位置應(yīng)該盡量靠前。

表格驅(qū)動(dòng)法

如果說(shuō) switch—case 法是線性的,那么表格驅(qū)動(dòng)法則是平面的。表格驅(qū)動(dòng)法的實(shí)質(zhì)就是將狀態(tài)和事件之間的關(guān)系固化到一張二維表格里, 把事件當(dāng)做縱軸,把狀態(tài)當(dāng)做橫軸,交點(diǎn)[Sn , Em]則是系統(tǒng)在 Sn 狀態(tài)下對(duì)事件 Em 的響應(yīng) 。 c07aa454-7c5a-11ee-939d-92fbcf53809c.png 如圖 4, 我把表格中的 Node_SnEm 叫做狀態(tài)機(jī)節(jié)點(diǎn), 狀態(tài)機(jī)節(jié)點(diǎn) Node_SnEm 是系統(tǒng)在 Sn狀態(tài)下對(duì)事件 Em 的響應(yīng)。這里所說(shuō)的響應(yīng)包含兩個(gè)方面:輸出動(dòng)作和狀態(tài)遷移。狀態(tài)機(jī)節(jié)點(diǎn)一般是一個(gè)類似程序清單 List5 中的結(jié)構(gòu)體變量 。


struct fsm_node
{
    void (*fpAction)(void* pEvnt);
    INT8U u8NxtStat;
};
程序清單 List5 中的這個(gè)結(jié)構(gòu)體有兩個(gè)成員:fpAction 和 u8NxtStat。fpAction 是一個(gè)函數(shù)指針, 指向一個(gè)形式為 void func(void * pEvnt)的函數(shù), func 這個(gè)函數(shù)是對(duì)狀態(tài)轉(zhuǎn)移中動(dòng)作序列的標(biāo)準(zhǔn)化封裝。 也就是說(shuō), 狀態(tài)機(jī)在狀態(tài)遷移的時(shí)候, 不管輸出多少個(gè)動(dòng)作、操作多少個(gè)變量、調(diào)用多少個(gè)函數(shù),這些行為統(tǒng)統(tǒng)放到函數(shù) func 中去做。 把動(dòng)作封裝好了之后,再把封裝函數(shù) func 的地址交給函數(shù)指針 fpAction,這樣,想要輸出動(dòng)作,只需要調(diào)用函數(shù)指針 fpAction 就行了。 再看看上面的 func 函數(shù),會(huì)發(fā)現(xiàn)函數(shù)有一個(gè)形參 pEvnt,這是一個(gè)類型為 void * 的指針, 在程序?qū)嶋H運(yùn)行時(shí)指向一個(gè)能存儲(chǔ)事件的變量,通過(guò)這個(gè)指針我們就能獲知關(guān)于事件的全部信息,這個(gè)形參是很有必要的。事件一般包括兩個(gè)屬性:事件的類型和事件的內(nèi)容。 例如一次按鍵事件,我們不僅要知道這是一個(gè)按鍵事件,還要知道按下的到底是哪個(gè)鍵。事件的類型和狀態(tài)機(jī)當(dāng)前的狀態(tài)可以讓我們?cè)趫D 4 的表格中迅速定位,確定該調(diào)用哪個(gè)動(dòng)作封裝函數(shù), 但是動(dòng)作封裝函數(shù)要正確響應(yīng)事件還需要知道事件的內(nèi)容是什么, 這也就是形參pEvnt 的意義。 由于事件的多樣性,存儲(chǔ)事件內(nèi)容的數(shù)據(jù)格式不一定一樣,所以就把 pEvnt 定義成了 void * 型,以增加靈活性。有關(guān) fpAction 的最后一個(gè)問(wèn)題:如果事件 Em 對(duì)狀態(tài) Sn 沒(méi)有意義,那么狀態(tài)機(jī)節(jié)點(diǎn)Node_SnEm 中的 fpAction 該怎么辦?我的答案是:那就讓它指向一個(gè)空函數(shù)唄!前面不是說(shuō)過(guò)么,什么也不干也叫響應(yīng)。 u8NxtStat 存儲(chǔ)的是狀態(tài)機(jī)的一個(gè)狀態(tài)值。我們知道, 狀態(tài)機(jī)響應(yīng)事件要輸出動(dòng)作, 也就是調(diào)用函數(shù)指針 fpAction 所指向的那個(gè)封裝函數(shù), 函數(shù)調(diào)用完畢后程序返回主調(diào)函數(shù), 狀態(tài)機(jī)對(duì)事件的響應(yīng)就算結(jié)束了, 下一步就要考慮狀態(tài)遷移的問(wèn)題了。 可能要保持本狀態(tài)不變, 也可能要遷移到一個(gè)新的狀態(tài),該如何抉擇呢?u8NxtStat 存儲(chǔ)的狀態(tài)就是狀態(tài)機(jī)想要的答案! 圖 4 的這張表格反映在 C 語(yǔ)言代碼里就是一個(gè)二維數(shù)組,第 1 維就是狀態(tài)機(jī)的狀態(tài),第 2維就是統(tǒng)一分類的事件,而數(shù)組的元素則是程序清單 List5 中的結(jié)構(gòu)體常量。如果程序中使用表格驅(qū)動(dòng)法,還需要注意一些特別的事項(xiàng)。要將狀態(tài)當(dāng)做表格的橫軸,那么就要求狀態(tài)值集合必須滿足以下條件: (1) 該集合是一個(gè)遞增的等差整數(shù)數(shù)列 (2) 該數(shù)列初值為 0 (3) 該數(shù)列等差值為 1 “事件” 作為縱軸,其特點(diǎn)和要求與用來(lái)做橫軸的“狀態(tài)” 完全一致。在 C 語(yǔ)言提供的數(shù)據(jù)類型中, 沒(méi)有比枚舉更符合以上要求的可選項(xiàng)了, 極力推薦將狀態(tài)集合和事件類型集合做成枚舉常量。表格驅(qū)動(dòng)法的優(yōu)點(diǎn):調(diào)用接口統(tǒng)一 ,定位快速。 表格驅(qū)動(dòng)法屏蔽了不同狀態(tài)下處理各個(gè)事件的差異性,因此可以將處理過(guò)程中的共性部分提煉出來(lái),做成標(biāo)準(zhǔn)統(tǒng)一的框架式代碼,形成統(tǒng)一的調(diào)用接口。根據(jù)程序清單 List5 中的狀態(tài)機(jī)節(jié)點(diǎn)結(jié)構(gòu)體,做成的框架代碼如程序清單 List6 所示。 表格驅(qū)動(dòng)法查找目標(biāo)實(shí)際上就是一次二維數(shù)組的尋址操作,所以它的平均效率要遠(yuǎn)高于switch—case 法。 「程序清單 List6 :」

extern struct fsm_node g_arFsmDrvTbl[][]; /*狀態(tài)機(jī)驅(qū)動(dòng)表格*/
INT8U u8CurStat = 0; /*狀態(tài)暫存*/
INT8U u8EvntTyp = 0; /*事件類型暫存*/
void* pEvnt = NULL; /*事件變量地址暫存*/
struct fsm_node stNodeTmp = {NULL, 0}; /*狀態(tài)機(jī)節(jié)點(diǎn)暫存*/
u8CurStat = get_cur_state(); /*讀取當(dāng)前狀態(tài)*/
u8EvntTyp = get_cur_evnt_typ(); /*讀取當(dāng)前觸發(fā)事件類型*/
pEvnt = (void*)get_cur_evnt_ptr(); /*讀取事件變量地址*/
stNodeTmp = g_arFsmDrvTbl[u8CurStat ][u8EvntTyp ];/*定位狀態(tài)機(jī)節(jié)點(diǎn)*/
stNodeTmp.fpAction(pEvnt ); /*動(dòng)作響應(yīng)*/
set_cur_state(stNodeTmp.u8NxtStat); /*狀態(tài)遷移*/
.....
表格驅(qū)動(dòng)法好則好矣,但用它寫出來(lái)的程序還有點(diǎn)兒小問(wèn)題,我們先來(lái)看看按照表格驅(qū)動(dòng)法寫出來(lái)的程序有什么特點(diǎn) 。 前面說(shuō)過(guò),表格驅(qū)動(dòng)法可以把狀態(tài)機(jī)調(diào)度的部分做成標(biāo)準(zhǔn)統(tǒng)一的框架代碼,這個(gè)框架適用性極強(qiáng), 不管用狀態(tài)機(jī)來(lái)實(shí)現(xiàn)什么樣的應(yīng)用, 框架代碼都不需要做改動(dòng), 我們只需要根據(jù)實(shí)際應(yīng)用場(chǎng)合規(guī)劃好狀態(tài)轉(zhuǎn)換圖,然后將圖中的各個(gè)要素(狀態(tài)、事件、動(dòng)作、遷移,有關(guān)“條件”要素一會(huì)兒再說(shuō))用代碼實(shí)現(xiàn)就行了,我把這部分代碼稱作應(yīng)用代碼。 在應(yīng)用代碼的.c 文件中, 你會(huì)看到一個(gè)聲明為 const 的二維數(shù)組, 也就是圖 4 所示的狀態(tài)驅(qū)動(dòng)表格, 還會(huì)看到許多彼此之間毫無(wú)關(guān)聯(lián)的函數(shù), 也就是前面提到的動(dòng)作封裝函數(shù)。這樣的一份代碼, 如果手頭上沒(méi)有一張狀態(tài)轉(zhuǎn)換圖, 讓誰(shuí)看了也會(huì)一頭霧水, 這樣的格式直接帶來(lái)了代碼可讀性差的問(wèn)題。 如果我們想給狀態(tài)機(jī)再添加一個(gè)狀態(tài),反映到代碼上就是給驅(qū)動(dòng)表格再加一列內(nèi)容,同時(shí)也要新添加若干個(gè)動(dòng)作封裝函數(shù)。如果驅(qū)動(dòng)表格很大, 做這些工作是很費(fèi)事兒的, 而且容易出錯(cuò)。如果不小心在數(shù)組中填錯(cuò)了位置, 那么程序跑起來(lái)就和設(shè)計(jì)者的意圖南轅北轍了, 遠(yuǎn)沒(méi)有在 switch—case 法中改動(dòng)來(lái)得方便、安全。Extended State Machine 的最大特點(diǎn)就是狀態(tài)機(jī)響應(yīng)事件之前先判斷條件,根據(jù)判定結(jié)果選擇執(zhí)行哪些動(dòng)作,轉(zhuǎn)向哪個(gè)狀態(tài)。 也就是說(shuō),系統(tǒng)在狀態(tài) Sn 下發(fā)生了事件 Em 后,轉(zhuǎn)向的狀態(tài)不一定是唯一的,這種靈活性是 Extended State Machine 的最有價(jià)值的優(yōu)點(diǎn)。 回過(guò)頭來(lái)看看程序清單 List5 中給出的狀態(tài)機(jī)節(jié)點(diǎn)結(jié)構(gòu)體,如果系統(tǒng)在狀態(tài) Sn 下發(fā)生了事件 Em, 狀態(tài)機(jī)執(zhí)行完 fpAction 所給出的動(dòng)作響應(yīng)之后, 必須轉(zhuǎn)到 u8NxtStat 指定的狀態(tài)。 表格驅(qū)動(dòng)法的這個(gè)特性直接杜絕了 Extended State Machine 在表格驅(qū)動(dòng)法中應(yīng)用的可能性, 所以表格驅(qū)動(dòng)法的代碼實(shí)現(xiàn)中不存在“條件” 這個(gè)狀態(tài)機(jī)要素。ESM,你是如此的優(yōu)秀,我怎么舍得拋棄你 ?! 再看圖 4 所示的表格驅(qū)動(dòng)法示例圖,如果我們把表格中的代表事件的縱軸去掉,只留下代表狀態(tài)的橫軸,將一列合并成一格,前文提到的問(wèn)題是不是能得到解決呢?不錯(cuò)!這就是失傳江湖多年的《葵花寶典》 ——閹割版表格驅(qū)動(dòng)法 ?。?閹割版表格驅(qū)動(dòng)法,又名壓縮表格驅(qū)動(dòng)法,一維狀態(tài)表格與事件 switch—case 的合體。壓縮表格驅(qū)動(dòng)法使用了一維數(shù)組作為驅(qū)動(dòng)表格,數(shù)組的下標(biāo)即是狀態(tài)機(jī)的各個(gè)狀態(tài)。 表格中的元素叫做壓縮狀態(tài)機(jī)節(jié)點(diǎn), 節(jié)點(diǎn)的主要內(nèi)容還是一個(gè)指向動(dòng)作封裝函數(shù)的函數(shù)指針, 只不過(guò)這個(gè)動(dòng)作封裝函數(shù)不是為某個(gè)特定事件準(zhǔn)備的, 而是對(duì)所有的事件都有效的。 節(jié)點(diǎn)中不再?gòu)?qiáng)制指定狀態(tài)機(jī)輸出動(dòng)作完畢后所轉(zhuǎn)向的狀態(tài), 而是讓動(dòng)作封裝函數(shù)返回一個(gè)狀態(tài), 并把這個(gè)狀態(tài)作為狀態(tài)機(jī)新的狀態(tài)。 壓縮表格驅(qū)動(dòng)法的這個(gè)特點(diǎn), 完美的解決了 Extended State Machine 不能在表格驅(qū)動(dòng)法中使用的問(wèn)題 。 程序清單 List7 中的示例代碼包含了壓縮狀態(tài)機(jī)節(jié)點(diǎn)結(jié)構(gòu)體和狀態(tài)機(jī)調(diào)用的框架代碼。 「程序清單 List7:」

struct fsm_node /*壓縮狀態(tài)機(jī)節(jié)點(diǎn)結(jié)構(gòu)體*/
{
 INT8U (*fpAction)(void* pEvnt); /*事件處理函數(shù)指針*/
 INT8U u8StatChk; /*狀態(tài)校驗(yàn)*/
};
......
u8CurStat = get_cur_state(); /*讀取當(dāng)前狀態(tài)*/
......
if(stNodeTmp.u8StatChk == u8CurStat )
{
 u8CurStat = stNodeTmp.fpAction(pEvnt ); /*事件處理*/
 set_cur_state(u8CurStat ); /*狀態(tài)遷移*/
}
else
{
 state_crash(u8CurStat ); /*非法狀態(tài)處理*/
}
.....
對(duì)照程序清單 List5,就會(huì)發(fā)現(xiàn)程序清單 List7 中 struct fsm_node 結(jié)構(gòu)體的改動(dòng)之處。首先, fpAction 所指向函數(shù)的函數(shù)形式變了,動(dòng)作封裝函數(shù) func 的模樣成了這樣的了:

INT8U func(void * pEvnt);
現(xiàn)在的動(dòng)作封裝函數(shù) func 是要返回類型為 INT8U 的返回值的,這個(gè)返回值就是狀態(tài)機(jī)要轉(zhuǎn)向的狀態(tài), 也就是說(shuō), 壓縮表格驅(qū)動(dòng)法中的狀態(tài)機(jī)節(jié)點(diǎn)不負(fù)責(zé)狀態(tài)機(jī)新?tīng)顟B(tài)的確定, 而把這項(xiàng)任務(wù)交給了動(dòng)作封裝函數(shù) func, func 返回哪個(gè)狀態(tài), 狀態(tài)機(jī)就轉(zhuǎn)向哪個(gè)狀態(tài)。 新?tīng)顟B(tài)由原來(lái)的常量變成了現(xiàn)在的變量,自然要靈活許多。上面說(shuō)到現(xiàn)在的動(dòng)作封裝函數(shù) func 要對(duì)當(dāng)前發(fā)生的所有的事件都要負(fù)責(zé), 那么 func 怎么會(huì)知道到底是哪個(gè)事件觸發(fā)了它呢?看一下 func 的形參 void * pEvnt 。 在程序清單 List5 中我們提到過(guò),這個(gè)形參是用來(lái)向動(dòng)作封裝函數(shù)傳遞事件內(nèi)容的,但是從前文的敘述中我們知道, pEvnt 所指向的內(nèi)存包含了事件的所有信息, 包括事件類型和事件內(nèi)容 , 所以通過(guò)形參 pEvnt , 動(dòng)作封裝函數(shù) func 照樣可以知道事件的類型。 程序清單 List7 中 struct fsm_node 結(jié)構(gòu)體還有一個(gè)成員 u8StatChk , 這里面存儲(chǔ)的是狀態(tài)機(jī) 的一個(gè)狀態(tài),干什么用的呢?玩 C 語(yǔ)言數(shù)組的人都知道,要嚴(yán)防數(shù)組尋址越界。 要知道,壓縮表格驅(qū)動(dòng)法的驅(qū)動(dòng)表格是一個(gè)以狀態(tài)值為下標(biāo)的一維數(shù)組, 數(shù)組元素里面最重要的部分就是一個(gè)個(gè)動(dòng)作封裝函數(shù)的地址。 函數(shù)地址在單片機(jī)看來(lái)無(wú)非就是一段二進(jìn)制數(shù)據(jù), 和內(nèi)存中其它的二進(jìn)制數(shù)據(jù)沒(méi)什么兩樣,不管程序往單片機(jī) PC 寄存器里塞什么值,單片機(jī)都沒(méi)意見(jiàn)。假設(shè)程序由于某種意外而改動(dòng)了存儲(chǔ)狀態(tài)機(jī)當(dāng)前狀態(tài)的變量,使變量值變成了一個(gè)非法狀態(tài)。 再發(fā)生事件時(shí), 程序就會(huì)用這個(gè)非法的狀態(tài)值在驅(qū)動(dòng)表格中尋址, 這時(shí)候就會(huì)發(fā)生內(nèi)存泄露,程序拿泄露內(nèi)存中的未知數(shù)據(jù)當(dāng)函數(shù)地址跳轉(zhuǎn),不跑飛才怪! 為了防止這種現(xiàn)象的發(fā)生, 壓縮狀態(tài)機(jī)節(jié)點(diǎn)結(jié)構(gòu)體中又添加了成員 u8StatChk 。u8StatChk中存儲(chǔ)的是壓縮狀態(tài)機(jī)節(jié)點(diǎn)在一維驅(qū)動(dòng)表格的位置, 例如某節(jié)點(diǎn)是表格中的第 7 個(gè)元素, 那么這個(gè)節(jié)點(diǎn)的成員 u8StatChk 值就是 6。 看一下程序清單 List7 中的框架代碼示例, 程序在引用函數(shù)指針 fpAction 之前, 先檢查當(dāng)前狀態(tài)和當(dāng)前節(jié)點(diǎn)成員 u8CurStat 的值是否一致,一致則認(rèn)為狀態(tài)合法,事件正常響應(yīng),如果不一致,則認(rèn)為當(dāng)前狀態(tài)非法,轉(zhuǎn)至意外處理,最大限度保證程序運(yùn)行的安全。當(dāng)然,如果泄露內(nèi)存中的數(shù)據(jù)恰好和 u8CurStat 一致,那么這種方法真的就回天乏力了。 還有一個(gè)方法也可以防止?fàn)顟B(tài)機(jī)跑飛,如果狀態(tài)變量是枚舉,那么框架代碼就可以獲知狀態(tài)值的最大值, 在調(diào)用動(dòng)作封裝函數(shù)之前判斷一下當(dāng)前狀態(tài)值是否在合法的范圍之內(nèi), 同樣能保證狀態(tài)機(jī)的安全運(yùn)行。 壓縮表格驅(qū)動(dòng)法中動(dòng)作封裝函數(shù)的定義形式我們已經(jīng)知道了,函數(shù)里面到底是什么樣子的呢?程序清單 List8 是一個(gè)標(biāo)準(zhǔn)的示例。 「程序清單List8:」

INT8U action_S0(void* pEvnt)
{
 INT8U u8NxtStat = 0;
 INT8U u8EvntTyp = get_evnt_typ(pEvnt);
 switch(u8EvntTyp )
 {
  case E1:
   action_S0_E1(); /*事件 E1 的動(dòng)作響應(yīng)*/
   u8NxtStat = new state value; /*狀態(tài)遷移,不遷移也必須有本行*/
   break;
   ......
  case Em:
   action_S0_Em(); /*事件 Em 的動(dòng)作響應(yīng)*/
   u8NxtStat = new state value; /*狀態(tài)遷移,不遷移也必須有本行*/
   break;
  default:
   ; /*不相關(guān)事件處理*/
   break;
 }
 return u8NxtStat ; /*返回新?tīng)顟B(tài)*/
}
從程序清單 List8 可以看出, 動(dòng)作封裝函數(shù)其實(shí)就是事件 switch—case 的具體實(shí)現(xiàn)。函數(shù)根據(jù)形參 pEvnt 獲知事件類型, 并根據(jù)事件類型選擇動(dòng)作響應(yīng), 確定狀態(tài)機(jī)遷移狀態(tài), 最后將新的狀態(tài)作為執(zhí)行結(jié)果返回給框架代碼。 有了這樣的動(dòng)作封裝函數(shù), Extended State Machine 的應(yīng)用就可以完全不受限制了!到此,有關(guān)壓縮表格驅(qū)動(dòng)法的介紹就結(jié)束了。 個(gè)人認(rèn)為壓縮表格驅(qū)動(dòng)法是相當(dāng)優(yōu)秀的,它既有表格驅(qū)動(dòng)法的簡(jiǎn)潔、高效、標(biāo)準(zhǔn),又有 switch—case 法的直白、靈活、多變,相互取長(zhǎng)補(bǔ)短,相得益彰。

函數(shù)指針?lè)?/strong>

上面說(shuō)過(guò),用 C 語(yǔ)言實(shí)現(xiàn)狀態(tài)機(jī)主要有 3 種方法(switch—case 法、表格驅(qū)動(dòng)法、函數(shù)指針?lè)?, 其中函數(shù)指針?lè)ㄊ亲铍y理解的, 它的實(shí)質(zhì)就是把動(dòng)作封裝函數(shù)的函數(shù)地址作為狀態(tài)來(lái)看待。不過(guò),有了之前壓縮表格驅(qū)動(dòng)法的鋪墊,函數(shù)指針?lè)ň妥兊煤美斫饬?,因?yàn)閮烧弑举|(zhì)上是相同的。 壓縮表格驅(qū)動(dòng)法的實(shí)質(zhì)就是一個(gè)整數(shù)值(狀態(tài)機(jī)的一個(gè)狀態(tài))到一個(gè)函數(shù)地址(動(dòng)作封裝函數(shù))的一對(duì)一映射, 壓縮表格驅(qū)動(dòng)法的驅(qū)動(dòng)表格就是全部映射關(guān)系的直接載體。在驅(qū)動(dòng)表格中通過(guò)狀態(tài)值就能找到函數(shù)地址,通過(guò)函數(shù)地址同樣能反向找到狀態(tài)值。 我們用一個(gè)全局的整型變量來(lái)記錄狀態(tài)值,然后再查驅(qū)動(dòng)表格找函數(shù)地址,那干脆直接用一個(gè)全局的函數(shù)指針來(lái)記錄狀態(tài)得了,還費(fèi)那勞什子勁干嗎?!這就是函數(shù)指針?lè)ǖ那笆澜裆?用函數(shù)指針?lè)▽懗鰜?lái)的動(dòng)作封裝函數(shù)和程序清單 List8 的示例函數(shù)是很相近的, 只不過(guò)函數(shù)的返回值不再是整型的狀態(tài)值, 而是下一個(gè)動(dòng)作封裝函數(shù)的函數(shù)地址, 函數(shù)返回后, 框架代碼再把這個(gè)函數(shù)地址存儲(chǔ)到全局函數(shù)指針變量中。 相比壓縮表格驅(qū)動(dòng)法,在函數(shù)指針?lè)ㄖ袪顟B(tài)機(jī)的安全運(yùn)行是個(gè)大問(wèn)題,我們很難找出一種機(jī)制來(lái)檢查全局函數(shù)指針變量中的函數(shù)地址是不是合法值。如果放任不管, 一旦函數(shù)指針變量中的數(shù)據(jù)被篡改,程序跑飛幾乎就不可避免了。

總結(jié)

有關(guān)狀態(tài)機(jī)的東西說(shuō)了那么多,相信大家都已經(jīng)感受到了這種工具的優(yōu)越性,狀態(tài)機(jī)真的是太好用了!其實(shí)我們至始至終講的都是有限狀態(tài)機(jī)(Finite State Machine 現(xiàn)在知道為什么前面的代碼中老是有 fsm 這個(gè)縮寫了吧!), 還有一種比有限狀態(tài)機(jī)更 NB 更復(fù)雜的狀態(tài)機(jī), 那就是層次狀態(tài)機(jī)(Hierarchical State Machine 一般簡(jiǎn)寫為 HSM)。 通俗的說(shuō),系統(tǒng)中只存在一個(gè)狀態(tài)機(jī)的叫做有限狀態(tài)機(jī),同時(shí)存在多個(gè)狀態(tài)機(jī)的叫做層次狀態(tài)機(jī)(其實(shí)這樣解釋層次狀態(tài)機(jī)有些不嚴(yán)謹(jǐn), 并行狀態(tài)機(jī)也有多個(gè)狀態(tài)機(jī), 但層次狀態(tài)機(jī)各個(gè)狀態(tài)機(jī)之間是上下級(jí)關(guān)系,而并行狀態(tài)機(jī)各個(gè)狀態(tài)機(jī)之間是平級(jí)關(guān)系)。 層次狀態(tài)機(jī)是一種父狀態(tài)機(jī)包含子狀態(tài)機(jī)的多狀態(tài)機(jī)結(jié)構(gòu),里面包含了許多與面向?qū)ο笙嗨频乃枷耄?所以它的功能也要比有限狀態(tài)機(jī)更加強(qiáng)大, 當(dāng)一個(gè)問(wèn)題用有限狀態(tài)機(jī)解決起來(lái)有些吃力的時(shí)候, 就需要層次狀態(tài)機(jī)出馬了。 層次狀態(tài)機(jī)理論我理解得也不透徹, 就不在這里班門弄斧了,大家可以找一些有關(guān)狀態(tài)機(jī)理論的專業(yè)書籍來(lái)讀一讀。要掌握狀態(tài)機(jī)編程,理解狀態(tài)機(jī)(主要指有限狀態(tài)機(jī))只是第一步,也是最簡(jiǎn)單的一步,更重要的技能是能用狀態(tài)機(jī)這個(gè)工具去分析解剖實(shí)際問(wèn)題:劃分狀態(tài)、 提取事件、 確定轉(zhuǎn)換關(guān)系、規(guī)定動(dòng)作等等,形成一張完整的狀態(tài)轉(zhuǎn)換圖,最后還要對(duì)轉(zhuǎn)換圖進(jìn)行優(yōu)化,達(dá)到最佳。 把實(shí)際問(wèn)題變成了狀態(tài)轉(zhuǎn)換圖, 工作的一大半就算完成了, 這個(gè)是具有架構(gòu)師氣質(zhì)的任務(wù),剩下的問(wèn)題就是按照狀態(tài)圖編程寫代碼了,這個(gè)是具有代碼工特色的工作。

編輯:黃飛

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(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)投訴
  • Switch
    +關(guān)注

    關(guān)注

    1

    文章

    532

    瀏覽量

    58070
  • C語(yǔ)言
    +關(guān)注

    關(guān)注

    180

    文章

    7581

    瀏覽量

    135626
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4262

    瀏覽量

    62243
  • 指針
    +關(guān)注

    關(guān)注

    1

    文章

    476

    瀏覽量

    70483
  • 狀態(tài)機(jī)
    +關(guān)注

    關(guān)注

    2

    文章

    490

    瀏覽量

    27438

原文標(biāo)題:C語(yǔ)言實(shí)現(xiàn)狀態(tài)機(jī)的三種方法

文章出處:【微信號(hào):c-stm32,微信公眾號(hào):STM32嵌入式開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    C語(yǔ)言實(shí)現(xiàn)狀態(tài)機(jī)設(shè)計(jì)模式

    狀態(tài)機(jī)模式是一行為模式,在《設(shè)計(jì)模式》這本書中對(duì)其有詳細(xì)的描述,通過(guò)多態(tài)實(shí)現(xiàn)不同狀態(tài)的調(diào)轉(zhuǎn)行為的確是一很好的
    發(fā)表于 12-14 13:38 ?2780次閱讀

    狀態(tài)機(jī)編程實(shí)例-面向?qū)ο蟮?b class='flag-5'>狀態(tài)設(shè)計(jì)模式

    本編介紹了狀態(tài)機(jī)編程的第3種方法——面向?qū)ο蟮?b class='flag-5'>狀態(tài)設(shè)計(jì)模式,通過(guò)C++的繼承特性,以及類指針,實(shí)現(xiàn)
    的頭像 發(fā)表于 06-28 09:04 ?1341次閱讀
    <b class='flag-5'>狀態(tài)機(jī)</b>編程實(shí)例-面向?qū)ο蟮?b class='flag-5'>狀態(tài)</b>設(shè)計(jì)模式

    C語(yǔ)言實(shí)現(xiàn)嵌入式狀態(tài)機(jī)的三種方法

    狀態(tài)機(jī)實(shí)現(xiàn)無(wú)非就是三個(gè)要素:狀態(tài)、事件、響應(yīng)。
    發(fā)表于 07-02 11:00 ?1663次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言實(shí)現(xiàn)</b>嵌入式<b class='flag-5'>狀態(tài)機(jī)</b>的三<b class='flag-5'>種方法</b>

    基于C語(yǔ)言狀態(tài)機(jī)實(shí)現(xiàn)方案

    關(guān)于狀態(tài)機(jī),基礎(chǔ)的知識(shí)點(diǎn)可以自行理解。本文主要講解的是一個(gè)有限狀態(tài)機(jī)FSM通用的寫法,目的在于更好理解,移植,節(jié)省代碼閱讀與調(diào)試時(shí)間,體現(xiàn)出編程之美。
    發(fā)表于 09-13 09:28 ?719次閱讀
    基于<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>的<b class='flag-5'>狀態(tài)機(jī)</b><b class='flag-5'>實(shí)現(xiàn)</b>方案

    如何在微型計(jì)算機(jī)中實(shí)現(xiàn)狀態(tài)機(jī)?

    我不是C語(yǔ)言,我是為18F的微型計(jì)算機(jī)組裝的。我一直在考慮用微控制器來(lái)控制電梯。在閱讀了幾篇文章和參考文獻(xiàn)之后,我發(fā)現(xiàn)狀態(tài)機(jī)經(jīng)常(如果不總是)被提及,我所理解的是考慮問(wèn)題的方法。然后,
    發(fā)表于 09-30 09:18

    【工程源碼】基于FPGA在Modelsim仿真中顯示狀態(tài)機(jī)名稱的3種方法

    經(jīng)過(guò)網(wǎng)上的搜索及自己的實(shí)驗(yàn),總結(jié)在Modelsim仿真中顯示狀態(tài)機(jī)名稱的三種方法。下面以一個(gè)具體的實(shí)例進(jìn)行講解。實(shí)例功能:引入狀態(tài)機(jī)實(shí)現(xiàn)2分頻,這里使用
    發(fā)表于 02-21 16:48

    有限狀態(tài)機(jī)的硬件描述語(yǔ)言設(shè)計(jì)方法

    實(shí)驗(yàn)?zāi)康? 1、 熟悉用硬件描述語(yǔ)言(VHDL)設(shè)計(jì)一般狀態(tài)機(jī)所包含的幾個(gè)基本部分;2、 掌握用硬件描述語(yǔ)言(VHDL)設(shè)計(jì)Moore型和Mealy型有限狀態(tài)機(jī)
    發(fā)表于 09-03 09:48 ?0次下載

    基于FPGA實(shí)現(xiàn)狀態(tài)機(jī)的設(shè)計(jì)

    狀態(tài)機(jī)有三描述方式:一段式狀態(tài)機(jī)、兩段式狀態(tài)機(jī)、三段式狀態(tài)機(jī)。下面就用一個(gè)小例子來(lái)看看三方式
    的頭像 發(fā)表于 08-29 06:09 ?2767次閱讀
    基于FPGA<b class='flag-5'>實(shí)現(xiàn)狀態(tài)機(jī)</b>的設(shè)計(jì)

    什么是狀態(tài)機(jī) 狀態(tài)機(jī)的描述三種方法

    狀態(tài)機(jī) 1、狀態(tài)機(jī)是許多數(shù)字系統(tǒng)的核心部件,是一類重要的時(shí)序邏輯電路。通常包括三個(gè)部分:一是下一個(gè)狀態(tài)的邏輯電路,二是存儲(chǔ)狀態(tài)機(jī)當(dāng)前狀態(tài)的時(shí)
    的頭像 發(fā)表于 11-16 17:39 ?2.6w次閱讀

    使用函數(shù)指針的方法實(shí)現(xiàn)狀態(tài)機(jī)

    之前寫過(guò)一篇狀態(tài)機(jī)的實(shí)用文章,很多朋友說(shuō)有幾個(gè)地方有點(diǎn)難度不易理解,今天給大家換簡(jiǎn)單寫法,使用函數(shù)指針的方法實(shí)現(xiàn)狀態(tài)機(jī)狀態(tài)機(jī)簡(jiǎn)介 有限
    的頭像 發(fā)表于 10-19 09:36 ?2349次閱讀
    使用函數(shù)指針的<b class='flag-5'>方法</b><b class='flag-5'>實(shí)現(xiàn)狀態(tài)機(jī)</b>

    使用單片機(jī)實(shí)現(xiàn)24C02存儲(chǔ)上次使用中狀態(tài)C語(yǔ)言實(shí)例免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是使用單片機(jī)實(shí)現(xiàn)24C02存儲(chǔ)上次使用中狀態(tài)C
    發(fā)表于 03-24 13:51 ?13次下載

    單片機(jī)實(shí)現(xiàn)24C02存儲(chǔ)上次使用中狀態(tài)C語(yǔ)言實(shí)

    單片機(jī)實(shí)現(xiàn)24C02存儲(chǔ)上次使用中狀態(tài)C語(yǔ)言實(shí)
    發(fā)表于 03-15 14:19 ?9次下載

    如何在FPGA中實(shí)現(xiàn)狀態(tài)機(jī)

    狀態(tài)機(jī)往往是FPGA 開(kāi)發(fā)的主力。選擇合適的架構(gòu)和實(shí)現(xiàn)方法將確保您獲得一款最佳解決方案。 FPGA 常常用于執(zhí)行基于序列和控制的行動(dòng), 比如實(shí)現(xiàn)一個(gè)簡(jiǎn)單的通信協(xié)議。對(duì)于設(shè)計(jì)人員來(lái)說(shuō),滿
    的頭像 發(fā)表于 07-18 16:05 ?987次閱讀
    如何在FPGA中<b class='flag-5'>實(shí)現(xiàn)狀態(tài)機(jī)</b>

    狀態(tài)機(jī)的三實(shí)現(xiàn)模式(C語(yǔ)言實(shí)現(xiàn)狀態(tài)機(jī)的三種方法

    壓縮表格驅(qū)動(dòng)法的實(shí)質(zhì)就是一個(gè)整數(shù)值(狀態(tài)機(jī)的一個(gè)狀態(tài))到一個(gè)函數(shù)地址(動(dòng)作封裝函數(shù))的一對(duì)一映射, 壓縮表格驅(qū)動(dòng)法的驅(qū)動(dòng)表格就是全部映射關(guān)系的直接載體。在驅(qū)動(dòng)表格中通過(guò)狀態(tài)值就能找到函數(shù)地址,通過(guò)函數(shù)地址同樣能反向找到
    發(fā)表于 07-25 10:27 ?2813次閱讀
    <b class='flag-5'>狀態(tài)機(jī)</b>的三<b class='flag-5'>種</b><b class='flag-5'>實(shí)現(xiàn)</b>模式(<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言實(shí)現(xiàn)狀態(tài)機(jī)</b>的三<b class='flag-5'>種方法</b>)

    如何在FPGA中實(shí)現(xiàn)狀態(tài)機(jī)

    在FPGA(現(xiàn)場(chǎng)可編程門陣列)中實(shí)現(xiàn)狀態(tài)機(jī)是一常見(jiàn)的做法,用于控制復(fù)雜的數(shù)字系統(tǒng)行為。狀態(tài)機(jī)能夠根據(jù)當(dāng)前的輸入和系統(tǒng)狀態(tài),決定下一步的動(dòng)作和新的
    的頭像 發(fā)表于 07-18 15:57 ?369次閱讀