表驅動,又稱之為表驅動法、表驅動方法。
基本介紹
- 中文名:表驅動
- 外文名:Table drive
- 又稱:為表驅動法、表驅動方法
- 函式:函式指針在表驅動方法中的套用
- 致命缺點:無法對參數 和返回值的類型檢查
概念,假設,總結,
概念
一,什麼是表驅動(出處《代碼大全》,對軟體感興趣者,此書值得一看)
“表”是幾乎所有數據結構課本都要討論的非常有用的數據結構。表驅動方法出於特定的目的來使用表,程式設計師們經常談到“表驅動”方法,但是課本中卻從未提到過什麼是"表驅動"方法。表驅動方法是一種使你可以在表中查找信息,而不必用很多的邏輯語句(if或Case)來把它們找出來的方法。事實上,任何信息都可以通過表來挑選。在簡單的情況下,邏輯語句往往更簡單而且更直接。但隨著邏輯鏈的複雜,表就變得越來越富有吸引力了,通過下面的這個例子大家就能知道什麼是所謂的表驅動方法了。
假設
假設你需要一個可以返回每個月中天數的函式(為簡單起見不考慮閏年),
一個比較笨的方法是一個大的if語句:
intiGetMonthDays(intiMonth){intiDays;if(1==iMonth){iDays=31;}elseif(2==iMonth){iDays=28;}elseif(3==iMonth){iDays=31;}elseif(4==iMonth){iDays=30;}elseif(5==iMonth){iDays=31;}elseif(6==iMonth){iDays=30;}elseif(7==iMonth){iDays=31;}elseif(8==iMonth){iDays=31;}elseif(9==iMonth){iDays=30;}elseif(10==iMonth){iDays=31;}elseif(11==iMonth){iDays=30;}elseif(12==iMonth){iDays=31;}returniDays;}
可以看出本來應該很簡單的一件事情,代碼卻是這么冗餘,解決這個的辦法就可以用表驅動方法。
staticintaiMonthDays[12]={31,28,31,30,31,30,31,31,30,31,30,31};
// 我們可以先定義一個靜態數組,這個數組用來保存一年十二個月的天數
intiGetMonthDays(intiMonth){returnaiMonthDays[(iMonth-1)];}
接下來不用多說了,大家都能看出用這種表驅動的方法代替這種情邏輯性不強,但分支很多的代碼是多么令人"賞心悅目"的了。
函式:
函式指針在表驅動方法中的套用
在使用表驅動方法時需要說明的一個問題是,你將在表中存儲些什麼。
在某些情況下,表查尋的結果是數據。如果是這種情況,你可以把數據存儲在表中。
在其它情況下,表查尋的結果是動作。在這種情況下,你可以把描述這一動作的代碼存儲在表中。
#defineTASK_EVENT_BIT00(1<<0)#defineTASK_EVENT_BIT01(1<<1)#defineTASK_EVENT_BIT02(1<<2)#defineTASK_EVENT_BIT03(1<<3)#defineTASK_EVENT_BIT04(1<<4)#defineTASK_EVENT_BIT05(1<<5)#defineTASK_EVENT_BIT06(1<<6)#defineTASK_EVENT_BIT07(1<<7)#defineTASK_EVENT_BIT08(1<<8)#defineTASK_EVENT_BIT09(1<<9)voidvDoWithEvent00();voidvDoWithEvent01();voidvDoWithEvent02();voidvDoWithEvent03();voidvDoWithEvent04();voidvDoWithEvent05();voidvDoWithEvent06();voidvDoWithEvent07();voidvDoWithEvent08();voidvDoWithEvent09();
我們一般首先想到的寫法是
unsignedlongulEventBit;for(;;){xos_waitFlag(&ulEventBit);if(ulEventBit&TASK_EVENT_BIT00){vDoWithEvent00();}if(ulEventBit&TASK_EVENT_BIT01){vDoWithEvent01();}if(ulEventBit&TASK_EVENT_BIT02){vDoWithEvent02();}if(ulEventBit&TASK_EVENT_BIT03){vDoWithEvent03();}if(ulEventBit&TASK_EVENT_BIT04){vDoWithEvent04();}if(ulEventBit&TASK_EVENT_BIT05){vDoWithEvent05();}if(ulEventBit&TASK_EVENT_BIT06){vDoWithEvent06();}if(ulEventBit&TASK_EVENT_BIT07){vDoWithEvent07();}if(ulEventBit&TASK_EVENT_BIT08){vDoWithEvent08();}if(ulEventBit&TASK_EVENT_BIT09){vDoWithEvent09();}}
可以看出這樣寫是不是顯得程式太長了呢。
下面我們再看看同樣的一段代碼用函式指針和表驅動方法結合的方法寫出會是什麼樣子。
typedefstruct{unsignedlongulEventBit;void(*Func)(void);}EventDoWithTable_t;/*定義EventBit與相應處理函式關係的結構體*/staticconstEventDoWithTable_tastDoWithTable[]={{TASK_EVENT_BIT00,vDoWithEvent00},{TASK_EVENT_BIT01,vDoWithEvent01},{TASK_EVENT_BIT02,vDoWithEvent02},{TASK_EVENT_BIT03,vDoWithEvent03},{TASK_EVENT_BIT04,vDoWithEvent04},{TASK_EVENT_BIT05,vDoWithEvent05},{TASK_EVENT_BIT06,vDoWithEvent06},{TASK_EVENT_BIT07,vDoWithEvent07},{TASK_EVENT_BIT08,vDoWithEvent08},{TASK_EVENT_BIT09,vDoWithEvent09}};/*建立EventBit與相應處理函式的關係表*/ulongulEventBit;inti;for(;;){xos_waitFlag(&ulEventBit);for(i=0;i<sizeof(astDoWithTable)/sizeof(astDoWithTable[0]);i++){if((ulEventBit&astDoWithTable[i].ulEventBit)&&(astDoWithTable[i].Func!=NULL)){(*astDoWithTable[i].Func)();/*通過函式指針來調用相應的處理函式*/}}}
可以看出這種代碼的風格使代碼變得精緻得多了,並且使程式的靈活性大大加強了,如果我們還要再加入EventBit,只修改表中的內容就可以了。
總結
通過上面介紹的,相信大家已經對函式指針的使用方法有所了解了,但是需要提醒大家,凡事都要具體情況具體分析,使用函式指針的時候一定要多加小心,因為函式指針有它的一個致命的缺點。
函式指針的致命缺點是:無法對參數 (parameter) 和返回值 (return value) 的類型進行檢查,因為函式已經退化成指針,指針是不帶有這些類型信息的。少了類型檢查,當參數或者反回值不一致時,會造成嚴重的錯誤。有些編譯器並不會幫我們找出函式指針這樣的致命錯誤。所以,許多新的程式語言都不支持函式指針了,而改用其他方式。
從上面的例3中我們可以看到
int max(int x,int y){ return x>y?x:y; }
int min(int x,int y){ return x<y?x:y; }
int add(int x,int y){ return x+y; }
這三個函式都有兩個參數,而在後面卻把處理函式定義成
int process(int x,int y, int (*f)())
{
return (*f)(x,y);
}
其中第三個參數是一個函式的指針,從表面上看它是個沒有參數,並且返回int型的函式的指針,但是在後面卻用process(a,b,max)的方式進行調用,max帶有兩個參數,這段程式在C語言中就可以順利的編譯通過(但是在C++中卻編譯不通過),可以看出如果編譯器沒有檢查出錯誤,而我們又不小心寫錯的話,後果是很嚴重的,比如return (*f)(x,y);不小心寫成return (*f)(x);在C語言中可以正常的被編譯通過,但是運行結果一定不是我們想要的。
因此在C語言中使用函式指針的時候,一定要小心“類型陷阱”,小心地使用函式指針,只有這樣我們才可以從函式指針中獲益。