表驅動,又稱之為表驅動法、表驅動方法。
“表”是幾乎所有數據結構課本都要討論的非常有用的數據結構。表驅動方法出於特定的目的來使用表,程式設計師們經常談到“表驅動”方法,但是課本中卻從未提到過什麼是"表驅動"方法。表驅動方法是一種使你可以在表中查找信息,而不必用很多的邏輯語句(if或Case)來把它們找出來的方法。事實上,任何信息都可以通過表來挑選。在簡單的情況下,邏輯語句往往更簡單而且更直接。但隨著邏輯鏈的複雜,表就變得越來越富有吸引力了,通過下面的這個例子大家就能知道什麼是所謂的表驅動方法了。
假設你需要一個可以返回每個月中天數的函式(為簡單起見不考慮閏年),
一個比較笨的方法是一個大的if語句:
int iGetMonthDays(int iMonth)
{
int iDays;
if(1 == iMonth) {iDays = 31;}
else if(2 == iMonth) {iDays = 28;}
else if(3 == iMonth) {iDays = 31;}
else if(4 == iMonth) {iDays = 30;}
else if(5 == iMonth) {iDays = 31;}
else if(6 == iMonth) {iDays = 30;}
else if(7 == iMonth) {iDays = 31;}
else if(8 == iMonth) {iDays = 31;}
else if(9 == iMonth) {iDays = 30;}
else if(10 == iMonth) {iDays = 31;}
else if(11 == iMonth) {iDays = 30;}
else if(12 == iMonth) {iDays = 31;}
return iDays;
}
可以看出本來應該很簡單的一件事情,代碼卻是這么冗餘,解決這個的辦法就可以用表驅動方法。
static int aiMonthDays[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
// 我們可以先定義一個靜態數組,這個數組用來保存一年十二個月的天數
int iGetMonthDays(int iMonth)
{
return aiMonthDays[(iMonth - 1)];
}
接下來不用多說了,大家都能看出用這種表驅動的方法代替這種情邏輯性不強,但分支很多的代碼是多么令人"賞心悅目"的了。
函式:
函式指針在表驅動方法中的套用
在使用表驅動方法時需要說明的一個問題是,你將在表中存儲些什麼。
在某些情況下,表查尋的結果是數據。如果是這種情況,你可以把數據存儲在表中。
在其它情況下,表查尋的結果是動作。在這種情況下,你可以把描述這一動作的代碼存儲在表中。
在某些語言中,也可以把實現這一動作的子程式的調用存儲在表中,也就是將函式的指針保存在表中,當查找到這項時,讓程式用這個函式指針來調用相應的程式代碼,這個就是函式指針在表驅動方法中的套用。
其實說到這已經說了很多表驅動方法的相關問題了,現在要把函式指針也套用進去,很多人應該已經想到會是個什麼樣子了,其實也很簡單,通過下面這兩段偽代碼的例子就可以充分體現函式指針在表驅動方法中套用會使代碼更加精緻。
我們在寫一段程式的過程中會經常遇到這樣的問題,我們在寫一個Task的主函式中有時會要等待不同的Event通知,並且處理不同的分支,首先有如下的Event Bit的宏定義和相應的處理函式的聲明。
#define TASK_EVENT_BIT00 (1 << 0)
#define TASK_EVENT_BIT01 (1 << 1)
#define TASK_EVENT_BIT02 (1 << 2)
#define TASK_EVENT_BIT03 (1 << 3)
#define TASK_EVENT_BIT04 (1 << 4)
#define TASK_EVENT_BIT05 (1 << 5)
#define TASK_EVENT_BIT06 (1 << 6)
#define TASK_EVENT_BIT07 (1 << 7)
#define TASK_EVENT_BIT08 (1 << 8)
#define TASK_EVENT_BIT09 (1 << 9)
void vDoWithEvent00();
void vDoWithEvent01();
void vDoWithEvent02();
void vDoWithEvent03();
void vDoWithEvent04();
void vDoWithEvent05();
void vDoWithEvent06();
void vDoWithEvent07();
void vDoWithEvent08();
void vDoWithEvent09();
我們一般首先想到的寫法是
unsigned long ulEventBit;
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();
}
}
可以看出這樣寫是不是顯得程式太長了呢。
下面我們再看看同樣的一段代碼用函式指針和表驅動方法結合的方法寫出會是什麼樣子。
typedef struct {
unsigned long ulEventBit;
void (*Func)(void);
} EventDoWithTable_t;
/* 定義EventBit 與相應處理函式關係的結構體 */
static const EventDoWithTable_t astDoWithTable[] = {
{ 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與相應處理函式的關係表 */
ulong ulEventBit;
int i;
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語言中使用函式指針的時候,一定要小心“類型陷阱”,小心地使用函式指針,只有這樣我們才可以從函式指針中獲益。
相關詞條
-
判定表驅動法
動作項:列出在條件項的各種取值情況下應該採取的動作 規則:任何一個條件組合的特定取值及其相應要執行的操作 第二步:列出所有的條件樁和動作樁
定義 判定表組成 判定表的建立(步驟) 使用判定表設計測試用例的條件 -
創新驅動型經濟
創新驅動型經濟指那些從個人的創造力、技能和天分中獲取發展動力的企業,以及那些通過對智慧財產權的開發可創造潛在財富和就業機會的活動。
概念延伸 概念內涵 要素特徵 理論簡評 經濟外延 -
驅動市場戰略
傳統的市場驅動理論認為消費者能夠清楚地表達自身的需要。因此,企業只要深入調研消費者需要和為界定明確的細分市場提供差異化產品或服務就能取得成功。企業相應的...
含義 案例 分析 競爭 條件 -
驅動市場型行銷
驅動市場型行銷(Market-driven-type Marketing)是一種全新的企業行銷理念和行銷行為。是一種生產者積極地開發產品、引導消費,推動...
簡介 特點 關鍵 存在意義 內涵 -
腕表
手錶,或稱為腕錶,是指戴在手腕上,用以計時/顯示時間的儀器。 手錶通常是利用皮革、橡膠、尼龍布、不鏽鋼等材料,製成錶帶,將顯示時間的“表頭”束在手腕上。
含義 工作原理 歷史發展 分類與鑑定 行業發展 -
減數分裂驅動
配子或雜交子代的分離比偏離孟德爾定律的現象。這一現象往往是受兩個親體的一方中的特定基因或染色體的驅使而造成的,又稱分離偏離。
減數分裂驅動 正文 配圖 相關連線 -
萬國表
一百三十六年來,萬國表於瑞士的沙夫豪森當地潛心鑽研製表工藝。打造出永恆經典的作品,其設計與技術在同行業中的領先一直是萬國表締造眾多輝煌成就的動力。源自萬...
簡介 創始人 創始發展 公司歷史 工藝特色 -
IGBT驅動
柵極驅動電壓 的驅動偏壓應比 1200
淺析IGBT驅動 IGBT驅動器正常輸出波形的測試 IGBT驅動器短路保護功能的測試