用陣列代替if-else和switch-case語句
表驅動法(Table-Driven Approach),通過在表中查詢資訊,來代替很多複雜的if-else或者switch-case邏輯判斷。這是一種設計的技巧,可以應用很多的場合,不僅可以提高程式的效能,也能大大減少程式碼量,使得程式碼變得高效和優雅。下面將使用一個例子來展示這種方法的優點。
當我們需要寫一個函式用來獲取指定月份的天數時,我們可能會寫出以下的程式碼段:
//判斷是否閏年,若為閏年返回1,否則返回0 int IsLeapYear(unsigned int year) { if ((0 == year % 4 && 0 != year % 100) || (0 == year % 400)) return 1; return 0; } //獲取year年month月的天數 unsigned int GetMonthDays(unsigned int year, unsigned int month) { unsigned int days = 0; if (1 == month) {days = 31;} else if (2 == month) { if (IsLeapYear(year)) {days = 29;} else {days = 28;} } else if (3 == month) {days = 31;} else if (4 == month) {days = 30;} else if (5 == month) {days = 31;} else if (6 == month) {days = 30;} else if (7 == month) {days = 31;} else if (8 == month) {days = 31;} else if (9 == month) {days = 30;} else if (10 == month) {days = 31;} else if (11 == month) {days = 30;} else if (12 == month) {days = 31;} return days; }
上面的程式碼通過多個if-else語句,來獲取當前月份的總天數,程式碼看起來還算清晰,但是就是感覺有點冗長和重複,另外,需要進行多次比較才能得到具體的天數,月份越後,比較次數就越多。
編譯器在實現switch-case的時候,會用到跳錶來優化比較的效能,下面將上面程式碼的if-else語句換為switch-case的實現。
//獲取year年month月的天數 unsigned int GetMonthDays(unsigned int year, unsigned int month) { int days = 0; switch(month) { case 1: days = 31; break; case 2: { if (IsLeapYear(year)) {days = 29;} else {days = 28;} }break; case 3: days = 31; break; case 4: days = 30; break; case 5: days = 31; break; case 6: days = 30; break; case 7: days = 31; break; case 8: days = 31; break; case 9: days = 30; break; case 10: days = 31; break; case 11: days = 30; break; case 12: days = 31; break; default: break; } return days; }
對於上面的switch-case實現,先不論效率有多大提升,單純的看程式碼,感覺跟if-else沒多大區別,程式碼還是有點冗長和重複,還是不夠簡潔。若應用於其他場景,隨著case分支越來越多,case分支的程式碼量越來越大的時候,程式碼就更難維護了。其實可以看到,當分支越來越多的時候,每個case分支的邏輯其實還是一樣的,只是其中的資料改變了而已。若在加分支的時候不小心還有可能修改了邏輯的程式碼,容易導致出錯。
此時,通過表驅動法,可以很容易分離變化的資料和不變化的邏輯,程式碼如下:
//判斷是否閏年,若為閏年返回1,否則返回0 int IsLeapYear(unsigned int year) { if ((0 == year % 4 && 0 != year % 100) || (0 == year % 400)) return 1; return 0; } static unsigned int monthdays[2][12] = { {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; } //獲取year年month月的天數 unsigned int GetMonthDays(unsigned int year, unsigned int month) { return monthdyas[IsYeapYear(year)][month - 1]; }
這次程式碼夠整潔了,不僅程式碼量變少了,而且通過陣列地址偏移操作就可以快速得到資料,不需要一大段的比較邏輯,更加高效了。但是這種表驅動法也不是所有場合都能直接用到的,如果要判斷的資料不是連續的,如1、5、16、222,就需要做另外的轉換,譬如用一個map<int, int>來將原資料轉為一堆連續的數字,這樣就又可以用到表驅動法了。另外,也可以使用其他更加高階的資料結構來實現,不需要侷限於陣列,關鍵都是使用空間來換取時間。