|
概念6 q! J4 e; X0 l& w* L2 F [" m
所謂表驅(qū)動法(Table-Driven Approach)簡而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫的一種體現(xiàn)。根據(jù)字典中的部首檢字表查找讀音未知的漢字就是典型的表驅(qū)動法,即以每個字的字形為依據(jù),計算出一個索引值,并映射到對應(yīng)的頁數(shù)。相比一頁一頁地順序翻字典查字,部首檢字法效率極高。具體到編程方面,在數(shù)據(jù)不多時可用邏輯判斷語句(if…else或switch…case)來獲取值;但隨著數(shù)據(jù)的增多,邏輯語句會越來越長,此時表驅(qū)動法的優(yōu)勢就開始顯現(xiàn)。: j5 e; |7 C6 {5 Z G. x
簡單示例
7 x( |: }' N% S# f2 ]% \1 Q5 `2 k" \上面講概念總是枯燥的,我們簡單寫一個C語言的例子。下面例子功能:傳入不同的數(shù)字打印不同字符串。使用if…else逐級判斷的寫法如下/ | Y+ c9 x% L
void fun(int day){ if (day == 1) { printf("Monday7 F$ t* n4 _" j/ z! ?9 {5 u' {' @* k
"); } else if (day == 2) { printf("Tuesday
. V; x% y0 q( y' A/ D1 z+ }"); } else if (day == 3) { printf("Wednesday1 ^& ^) J! g s9 d2 t: r$ ?' w# ]
"); } else if (day == 4) { printf("Thursday* l: I% n. x! d1 `: S
"); } else if (day == 5) { printf("Friday1 J0 i5 l' \1 N
"); } else if (day == 6) { printf("Saturday
. I$ P, }/ p' T" I2 N6 D2 @; P) B2 a# a"); } else if (day == 7) { printf("Sunday; J) b1 [1 G! q
"); }}使用switch…case的方法寫
3 k8 D# A) p, o* z G( r+ avoid fun(int day){ switch (day) { case 1: printf("Monday1 q3 p* k: G1 P4 Q" |- D t
"); break; case 2: printf("Tuesday
; g( C- H/ p0 j- c: f/ x; l; \ }"); break; case 3: printf("Wednesday
3 X* q4 r0 t4 y2 c"); break; case 4; printf("Thursday* f& k) Z7 V' R. v; J; t# ^
"); break; case 5: printf("Friday
0 k: X8 p2 r1 s"); break; case 6: printf("Saturday
' I% ^& a/ ?. H4 i! A; E, c" ?" n"); break; case 7:printf("Sunday
5 a% i G, h, W2 b2 K) V"); break; default: break; }}使用表驅(qū)動法實現(xiàn)
- o$ }( m3 r3 m J3 |9 n$ ochar weekDay[] = {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};void fun(int day){ printf("%s
4 O5 A. ^. g% ]$ S$ y5 O4 @& t* ^",weekDay[day]);}看完示例,可能“恍然大悟”,一拍大腿,原來表驅(qū)動法就是這么簡單啊。是的,它的核心原理就是這個簡單,如上面例子一樣。
9 i" _2 M2 f' v& I9 ]# E如果上面的例子還沒get這種用法的好處,那么再舉一個栗子。! a ^# [4 Q4 h; i% }
統(tǒng)計用戶輸入的一串?dāng)?shù)字中每個數(shù)字出現(xiàn)的次數(shù)。
% k) L: n, j$ v9 _常規(guī)寫法int32_t aDigitCharNum[10] = {0}; /* 輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù) */int32_t dwStrLen = strlen(szDigits);7 M" a6 F+ R6 e. }8 f
int32_t dwStrIdx = 0;for (; dwStrIdx { switch (szDigits[dwStrIdx]) { case '1': aDigitCharNum[0]++; break; case '2': aDigitCharNum[1]++; break; //... ... case '9': aDigitCharNum[8]++; break; }}表驅(qū)動法
6 r- S1 |7 n" _5 v" _for(; dwStrIdx { aDigitCharNum[szDigits[dwStrIdx] - '0']++;}偶爾在一些開源項目中看到類似的操作,驚呼“騷操作”,其實他們有規(guī)范的叫法:表驅(qū)動法。
' r T- N' y" D- m* D0 X在MCU中應(yīng)用. m; p6 D9 g; L2 F
在MCU中的應(yīng)用示例,怎么少的了點燈大師操作呢?首先來點一下流水LED燈吧。
8 g- v, g8 T; R& V/ T/ X0 i8 H: h常規(guī)寫法4 V4 L! [% v. I9 o, n9 q9 Y; B
void LED_Ctrl(void){ static uint32_t sta = 0;, g5 j6 S: t8 B/ K
if (0 == sta) { LED1_On(); } else { LED1_Off(); }
3 _2 R* f9 b. @+ n' Z6 o. j0 X G! M if (1 == sta) { LED2_On(); } else { LED2_Off(); }
2 f% V! N) F0 {4 A+ W; Y" Z /* 兩個燈,最大不超過2 */ sta = (sta + 1) % 2;}8 D* k9 [# e9 A9 r
/* 主函數(shù)運行 */int main(void){ while (1) { LED_Ctrl(); os_delay(200); }}表驅(qū)動法+ i0 S2 _) o* S/ s1 W
extern void LED1_On(void);extern void LED1_Off(void);extern void LED2_On(void);extern void LED2_Off(void);, e4 p! e" _( C) ]4 y: e/ p
/* 把同一個燈的操作封裝起來 */struct tagLEDFuncCB{ void (*LedOn)(void); void (*LedOff)(void);};1 K( k$ o! C" i- u1 i' F- Y" Y+ a
/* 定義需要操作到的燈的表 */const static struct tagLEDFuncCB LedOpTable[] ={ {LED1_On, LED1_Off}, {LED2_On, LED2_Off},};+ H: }9 d4 q2 _: u
void LED_Ctrl(void){ static uint32_t sta = 0; uint8_t i;
; s& \$ J; A/ g for (i = 0; i sizeof(LedOpTable) / sizeof(LedOpTable[0]); i++) { (sta == i) ? (LedOpTable.LED_On()) : (LedOpTable.LED_Off()); }
% r( Y& B- Q3 q /* 跑下個燈 */ sta = (sta + 1) % (sizeof(LedOpTable) / sizeof(LedOpTable[0]));}' h7 l/ o" A6 }7 m3 h% m# D
int main(void){ while (1) { LED_Ctrl(); os_delay(200); }}這樣的代碼結(jié)構(gòu)緊湊,因為和結(jié)構(gòu)體結(jié)合起來了,方便添加下一個LED燈到流水燈序列中,這其中涉及到函數(shù)指針,詳細(xì)請看《回調(diào)函數(shù)》,只需要修改LedOpTable如下
' z% Y4 m C8 L! [const static struct tagLEDFuncCB LedOpTable[] ={ {LED1_On, LED1_Off}, {LED2_On, LED2_Off}, {LED3_On, LED3_Off},};這年頭誰還把流水燈搞的這么花里胡哨的啊,那么就舉例在串口解析中的應(yīng)用,之前的文章推送過《回調(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;, `$ p9 X4 s- i; h
_FUNCCALLBACK callback_list[] ={ {cmd1, func_callback1}, {cmd2, func_callback2}, {cmd3, func_callback3}, {cmd4, func_callback41}, ...};
" P, k. O- {+ T8 Qvoid 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;
6 x. y% G: P" C0 M" {5 s8 ^5 h 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ū)動法在UI界面中也有良好的應(yīng)用,如下
0 S: H, g6 n' x; s/ [0 ]8 u) v結(jié)構(gòu)體封裝typedef enum{ stage1 = 0, stage2, stage3, stage4, stage5, stage6, stage7, stage8, stage9,} SCENE;typedef struct{ void (*current_operate)(); //當(dāng)前場景的處理函數(shù) SCENE Index; //當(dāng)前場景的標(biāo)簽 SCENE Up; //按下Up鍵跳轉(zhuǎn)的場景 SCENE Down; //按下Down鍵跳轉(zhuǎn)的場景 SCENE Right; //按下Left鍵跳轉(zhuǎn)的場景 SCENE Left; //按下Right鍵跳轉(zhuǎn)的場景} STAGE_TAB;函數(shù)映射表5 d+ S" S1 a6 O( J% v
STAGE_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},};定義兩個變量保存當(dāng)前場景和上一個場景3 I4 N* A: D7 @
char current_stage=stage1;char prev_stage=current_stage;按下Up按鍵 跳轉(zhuǎn)到指定場景current_stage的值根據(jù)映射表改變
7 w6 E8 W/ P4 S: Ycurrent_stage =stage_tab[current_stage].Up;場景改變后 根據(jù)映射表執(zhí)行相應(yīng)的函數(shù)Handler
/ X) D0 S% T$ x, o: r" _% D8 ?if(current_stage!=prev_stage){ stage_tab[current_stage].current_operate(); prev_stage=current_stage;}這是一個簡單的菜單操作,結(jié)合了表驅(qū)動法。在MCU中表驅(qū)動法有很多很多用處,本文的例子已經(jīng)過多了,如果在通勤路上用手機看到這里,已經(jīng)很難了。關(guān)于UI操作,大神figght在github開源了zBitsView倉庫,單片機實現(xiàn)屏幕界面,多層菜單。很牛,很優(yōu)秀的代碼,有興趣的同學(xué)可以學(xué)習(xí)一下。https://github.com/figght/zBitsView: @1 o- G6 T% Q0 Y
) L) d6 O3 ^' a后記 t3 [1 L5 T0 \# H3 y
這篇文章我也看到網(wǎng)上一遍表驅(qū)動法的后總結(jié)的筆記,可能也有很多同學(xué)和我一樣,在自己的項目中熟練應(yīng)用了這種“技巧”,但今天才知道名字:表驅(qū)動法。* F5 z" b) c: M4 M- p
這篇文章多數(shù)都是代碼示例,實在因為表驅(qū)動法大家應(yīng)該都熟練應(yīng)用了,這篇文章算是總結(jié)一下吧。
( y1 ~8 ^ @8 V6 v3 m7 h* h學(xué)習(xí)知識,可以像在學(xué)校從概念一點點學(xué)習(xí),也可以在工作中慢慢積累,然后總結(jié)記錄,回歸最初的概念,豐富自己的知識框架。
, [" b; r1 `: `' f6 t祝大家變得更強!END& A' E- I- n& ]4 _. m. \" V- m
2uhazf4qq1t64014187353.gif (170.56 KB, 下載次數(shù): 0)
下載附件
保存到相冊
2uhazf4qq1t64014187353.gif
2024-9-1 11:48 上傳
* _" a6 v2 K! r+ q5 R) ]; T0 u" o+ F
t0h4nm5vm1i64014187453.gif (66.66 KB, 下載次數(shù): 0)
下載附件
保存到相冊
t0h4nm5vm1i64014187453.gif
2024-9-1 11:48 上傳
& R! \" J1 f: R0 t8 r1 `# K?測量代碼運行時間 必讀7 j; e- p; c4 a% V: Y$ V
?聊一聊const關(guān)鍵字
' g; X* k& O2 L& P0 u2 a?聯(lián)合體在單片機中的應(yīng)用 必讀
$ Z' F( r: x3 B$ v9 a?教你如何在STM32中使用DSP指令
4 z. P" _5 Y/ E# {4 q+ R, R?STM32單片機中結(jié)構(gòu)體和枚舉的結(jié)合 |
|