表驱动法(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>来将原数据转为一堆连续的数字,这样就又可以用到表驱动法了。另外,也可以使用其他更加高级的数据结构来实现,不需要局限于数组,关键都是使用空间来换取时间。