|
概念+ M) k7 p) i* f+ \- e
所謂表驅(qū)動(dòng)法(Table-Driven Approach)簡(jiǎn)而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫(kù)的一種體現(xiàn)。根據(jù)字典中的部首檢字表查找讀音未知的漢字就是典型的表驅(qū)動(dòng)法,即以每個(gè)字的字形為依據(jù),計(jì)算出一個(gè)索引值,并映射到對(duì)應(yīng)的頁(yè)數(shù)。相比一頁(yè)一頁(yè)地順序翻字典查字,部首檢字法效率極高。具體到編程方面,在數(shù)據(jù)不多時(shí)可用邏輯判斷語(yǔ)句(if…else或switch…case)來(lái)獲取值;但隨著數(shù)據(jù)的增多,邏輯語(yǔ)句會(huì)越來(lái)越長(zhǎng),此時(shí)表驅(qū)動(dòng)法的優(yōu)勢(shì)就開始顯現(xiàn)。
; _5 F/ ]; F; i簡(jiǎn)單示例: r5 A& f4 M& n. u0 z1 Q2 ]) }( I
上面講概念總是枯燥的,我們簡(jiǎn)單寫一個(gè)C語(yǔ)言的例子。下面例子功能:傳入不同的數(shù)字打印不同字符串。使用if…else逐級(jí)判斷的寫法如下7 G( a$ W! a8 f
void fun(int day){ if (day == 1) { printf("Monday2 V! [, a7 o; `, _2 ]/ Y+ p X
"); } else if (day == 2) { printf("Tuesday
; P/ G4 }& f/ s: }7 |( S. K"); } else if (day == 3) { printf("Wednesday. i0 _9 P. W; @7 L, t% S6 J4 s
"); } else if (day == 4) { printf("Thursday
/ F- c+ U" y) ^/ w: l" v7 C; _"); } else if (day == 5) { printf("Friday
! k5 A! g; [) C9 N) q7 w"); } else if (day == 6) { printf("Saturday" j& n/ c3 F3 p2 s0 a( Y
"); } else if (day == 7) { printf("Sunday
1 X" T4 H& o6 c. W9 G9 i7 ["); }}使用switch…case的方法寫6 O/ S! N$ `5 W z' H& c
void fun(int day){ switch (day) { case 1: printf("Monday* K4 m% d8 I0 z4 B" g& @- q
"); break; case 2: printf("Tuesday
' Z% c. o0 w0 N3 o"); break; case 3: printf("Wednesday3 L; c6 m6 K; V2 q$ ^: U( T: v
"); break; case 4; printf("Thursday$ g0 l. k6 T" l; b8 ^
"); break; case 5: printf("Friday/ D. D) X6 X6 K/ A4 k" U
"); break; case 6: printf("Saturday) F( _7 j9 W& I' G
"); break; case 7:printf("Sunday
# p# ]) q& n0 p, K"); break; default: break; }}使用表驅(qū)動(dòng)法實(shí)現(xiàn)( G* d0 _$ Y3 y; f$ i
char weekDay[] = {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};void fun(int day){ printf("%s+ g8 x j) y6 }- v
",weekDay[day]);}看完示例,可能“恍然大悟”,一拍大腿,原來(lái)表驅(qū)動(dòng)法就是這么簡(jiǎn)單啊。是的,它的核心原理就是這個(gè)簡(jiǎn)單,如上面例子一樣。
6 \2 G) |1 x1 p, v4 O' C如果上面的例子還沒(méi)get這種用法的好處,那么再舉一個(gè)栗子。
4 d$ ~$ z' I! [7 O A5 u統(tǒng)計(jì)用戶輸入的一串?dāng)?shù)字中每個(gè)數(shù)字出現(xiàn)的次數(shù)。! k7 L' b% @% P5 G* ^# ~* Z
常規(guī)寫法int32_t aDigitCharNum[10] = {0}; /* 輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù) */int32_t dwStrLen = strlen(szDigits);
8 s/ ?! z9 R" S0 ~& Vint32_t dwStrIdx = 0;for (; dwStrIdx { switch (szDigits[dwStrIdx]) { case '1': aDigitCharNum[0]++; break; case '2': aDigitCharNum[1]++; break; //... ... case '9': aDigitCharNum[8]++; break; }}表驅(qū)動(dòng)法
$ n F3 x) n, T }; K+ }* Pfor(; dwStrIdx { aDigitCharNum[szDigits[dwStrIdx] - '0']++;}偶爾在一些開源項(xiàng)目中看到類似的操作,驚呼“騷操作”,其實(shí)他們有規(guī)范的叫法:表驅(qū)動(dòng)法。- {0 j3 O" v" a) r7 W5 S9 o
在MCU中應(yīng)用. x4 b9 I% j$ \+ k& Z0 M
在MCU中的應(yīng)用示例,怎么少的了點(diǎn)燈大師操作呢?首先來(lái)點(diǎn)一下流水LED燈吧。
& q8 ]" ~6 K) h/ S" F常規(guī)寫法/ x0 r! G! t5 E
void LED_Ctrl(void){ static uint32_t sta = 0;1 p" j X- k4 x7 M3 K6 c( m
if (0 == sta) { LED1_On(); } else { LED1_Off(); }
6 C0 B8 }) D; q5 X+ I if (1 == sta) { LED2_On(); } else { LED2_Off(); }1 Y( E* e% A+ D9 v
/* 兩個(gè)燈,最大不超過(guò)2 */ sta = (sta + 1) % 2;}
& q$ Y5 b! ?. _) V* M, t/* 主函數(shù)運(yùn)行 */int main(void){ while (1) { LED_Ctrl(); os_delay(200); }}表驅(qū)動(dòng)法
+ f' }1 W5 }* K( v; hextern void LED1_On(void);extern void LED1_Off(void);extern void LED2_On(void);extern void LED2_Off(void);& \) v1 g4 S6 B
/* 把同一個(gè)燈的操作封裝起來(lái) */struct tagLEDFuncCB{ void (*LedOn)(void); void (*LedOff)(void);};5 \& t! S+ ]3 r
/* 定義需要操作到的燈的表 */const static struct tagLEDFuncCB LedOpTable[] ={ {LED1_On, LED1_Off}, {LED2_On, LED2_Off},};9 c2 v' j" D# n/ q6 K+ _( P
void LED_Ctrl(void){ static uint32_t sta = 0; uint8_t i;8 ?- f! t5 U" v
for (i = 0; i sizeof(LedOpTable) / sizeof(LedOpTable[0]); i++) { (sta == i) ? (LedOpTable.LED_On()) : (LedOpTable.LED_Off()); }
, D- n7 `: |' F& L /* 跑下個(gè)燈 */ sta = (sta + 1) % (sizeof(LedOpTable) / sizeof(LedOpTable[0]));}
" I$ l* c. l* Nint main(void){ while (1) { LED_Ctrl(); os_delay(200); }}這樣的代碼結(jié)構(gòu)緊湊,因?yàn)楹徒Y(jié)構(gòu)體結(jié)合起來(lái)了,方便添加下一個(gè)LED燈到流水燈序列中,這其中涉及到函數(shù)指針,詳細(xì)請(qǐng)看《回調(diào)函數(shù)》,只需要修改LedOpTable如下
; ?/ B- e7 S1 ^2 N5 B5 h8 V( Vconst static struct tagLEDFuncCB LedOpTable[] ={ {LED1_On, LED1_Off}, {LED2_On, LED2_Off}, {LED3_On, LED3_Off},};這年頭誰(shuí)還把流水燈搞的這么花里胡哨的啊,那么就舉例在串口解析中的應(yīng)用,之前的文章推送過(guò)《回調(diào)函數(shù)在命令解析中的應(yīng)用》,下面只貼一下代碼typedef struct{ rt_uint8_t CMD; rt_uint8_t (*callback_func)(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len);} _FUNCCALLBACK;
@9 r0 G! h: s0 n) O& T_FUNCCALLBACK callback_list[] ={ {cmd1, func_callback1}, {cmd2, func_callback2}, {cmd3, func_callback3}, {cmd4, func_callback41}, ...};: i: O6 }& ]5 _0 }0 g' [
void poll_task(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len){ int cmd_indexmax = sizeof(callback_list) / sizeof(_FUNCCALLBACK); int cmd_index = 0;
& ]$ q6 r3 r$ {. L$ [) L for (cmd_index = 0; cmd_index { if (callback_list[cmd_index].CMD == cmd) { if (callback_list[cmd_index]) { /* 處理邏輯 */ callback_list[cmd_index].callback_func(cmd, msg, len); } } }}除上述例子,表驅(qū)動(dòng)法在UI界面中也有良好的應(yīng)用,如下+ @& ]) J Y8 p$ b' V" l
結(jié)構(gòu)體封裝typedef enum{ stage1 = 0, stage2, stage3, stage4, stage5, stage6, stage7, stage8, stage9,} SCENE;typedef struct{ void (*current_operate)(); //當(dāng)前場(chǎng)景的處理函數(shù) SCENE Index; //當(dāng)前場(chǎng)景的標(biāo)簽 SCENE Up; //按下Up鍵跳轉(zhuǎn)的場(chǎng)景 SCENE Down; //按下Down鍵跳轉(zhuǎn)的場(chǎng)景 SCENE Right; //按下Left鍵跳轉(zhuǎn)的場(chǎng)景 SCENE Left; //按下Right鍵跳轉(zhuǎn)的場(chǎng)景} STAGE_TAB;函數(shù)映射表
: a# _7 f& C* n& H% ySTAGE_TAB stage_tab[] = { //operate Index Up Down Left Right {Stage1_Handler, stage1, stage4, stage7, stage3, stage2}, {Stage2_Handler, stage2, stage5, stage8, stage1, stage3}, {Stage3_Handler, stage3, stage6, stage9, stage2, stage1}, {Stage4_Handler, stage4, stage7, stage1, stage6, stage5}, {Stage5_Handler, stage5, stage8, stage2, stage4, stage6}, {Stage6_Handler, stage6, stage9, stage3, stage5, stage4}, {Stage7_Handler, stage7, stage1, stage4, stage9, stage8}, {Stage8_Handler, stage8, stage2, stage5, stage7, stage9}, {Stage9_Handler, stage9, stage3, stage6, stage8, stage7},};定義兩個(gè)變量保存當(dāng)前場(chǎng)景和上一個(gè)場(chǎng)景
! t; T- i/ l+ V0 Vchar current_stage=stage1;char prev_stage=current_stage;按下Up按鍵 跳轉(zhuǎn)到指定場(chǎng)景current_stage的值根據(jù)映射表改變) b/ m: `! J; s+ T2 k
current_stage =stage_tab[current_stage].Up;場(chǎng)景改變后 根據(jù)映射表執(zhí)行相應(yīng)的函數(shù)Handler9 |3 C6 r7 J8 r& V7 }
if(current_stage!=prev_stage){ stage_tab[current_stage].current_operate(); prev_stage=current_stage;}這是一個(gè)簡(jiǎn)單的菜單操作,結(jié)合了表驅(qū)動(dòng)法。在MCU中表驅(qū)動(dòng)法有很多很多用處,本文的例子已經(jīng)過(guò)多了,如果在通勤路上用手機(jī)看到這里,已經(jīng)很難了。關(guān)于UI操作,大神figght在github開源了zBitsView倉(cāng)庫(kù),單片機(jī)實(shí)現(xiàn)屏幕界面,多層菜單。很牛,很優(yōu)秀的代碼,有興趣的同學(xué)可以學(xué)習(xí)一下。https://github.com/figght/zBitsView
" S# f* A3 O$ G
! Y( [% X8 z' w {- ]4 q# X B后記: V7 @% Z& w: L# e7 N1 U0 U
這篇文章我也看到網(wǎng)上一遍表驅(qū)動(dòng)法的后總結(jié)的筆記,可能也有很多同學(xué)和我一樣,在自己的項(xiàng)目中熟練應(yīng)用了這種“技巧”,但今天才知道名字:表驅(qū)動(dòng)法。
~9 l4 a3 v7 F1 G. _5 D這篇文章多數(shù)都是代碼示例,實(shí)在因?yàn)楸眚?qū)動(dòng)法大家應(yīng)該都熟練應(yīng)用了,這篇文章算是總結(jié)一下吧。
7 G" d! \# x/ q5 ~* Q學(xué)習(xí)知識(shí),可以像在學(xué)校從概念一點(diǎn)點(diǎn)學(xué)習(xí),也可以在工作中慢慢積累,然后總結(jié)記錄,回歸最初的概念,豐富自己的知識(shí)框架。& \! p: u% v- y" u$ {
祝大家變得更強(qiáng)!END
" r, I; c! v: p" ~( F) K. J6 R
2uhazf4qq1t64014187353.gif (170.56 KB, 下載次數(shù): 0)
下載附件
保存到相冊(cè)
2uhazf4qq1t64014187353.gif
2024-9-1 11:48 上傳
$ f/ P4 Y% [7 y3 d' T% y$ Z
t0h4nm5vm1i64014187453.gif (66.66 KB, 下載次數(shù): 1)
下載附件
保存到相冊(cè)
t0h4nm5vm1i64014187453.gif
2024-9-1 11:48 上傳
2 x& g. Z K' `2 J?測(cè)量代碼運(yùn)行時(shí)間 必讀
! p$ X8 i' N0 a$ t% D% L5 ]) A0 J?聊一聊const關(guān)鍵字
& i D( e N8 B$ h?聯(lián)合體在單片機(jī)中的應(yīng)用 必讀4 ]! f( r8 f. a' C' u: I! x. r! k
?教你如何在STM32中使用DSP指令
1 w1 L M. P; I3 f* D5 L" f?STM32單片機(jī)中結(jié)構(gòu)體和枚舉的結(jié)合 |
|