之前在看一些算法和面试题目时经常会碰到类似于:请写一段程序,输出某个月份有多少天;某个班级成绩根据分数段划分为A,B,C,D,E之类的。之前都是不假思索,这类问题这么简单,最直接if-else解决就可以了,为什么会考。知道阅读《代码大全》中的表驱动法才发现,这类简单的问题中原来蕴藏着表驱动法这么高端的编程方法。下面具体介绍一下:
表驱动法是一种编程模式,从表里面查询信息而不使用逻辑语句
举个简单例子,需要输出某个月份的天数,那么就会有如下最基础的写法:
int getTotalDayInMonth(int month)
{
int totalDay = 0;
if(month == 2)
{
totalDay = 28;
}
else if(month == 4 || month == 6 || month == 9 || month == 11)
{
totalDay = 30;
}
else
{
totalDay = 31;
}
return totalDay;
}
这是一年只有12个月,所以分支不是很多,相对还简单,但是如果使用表驱动法,会更加简单易懂。如下面代码,只需要短短几行就可以,而且不用复杂的if-else分支。这就是表驱动法的精妙之处。
const int totalDayTable[12] =
{
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int getTotalDayInMonthFromTable(int month)
{
return totalDayTable[month - 1];
}
表驱动法的查表方式分为三类:
(1) 直接访问
(2) 索引访问
(3) 阶梯访问
(1)其中第一类很简单,如同上面讲的月份的例子。那么现在有个问题,万一“键”是不能直接用的呢?比如我想设计一个幼儿学习动物的软件,若小孩想查找牛的信息,这时屏幕上会打印出牛的特性,而若小孩想查找狗的信息,则屏幕上会打印出狗的信息。显然,这里的键是动物名,而值是相应的描述。下标必须是整数,但动物名是string,怎么办呢?数据结构中的hash表当然可以了,它就是计算string的hash值,通过hash值来索引表格的,但在这里我们不打算用hash值,而是由程序员自行设计string到int的映射,怎么做呢?很简单啊,自己做个菜单呗,让用户只能选择相应的数字,这样“键”就成int了哈。
class Animal
{
public:
virtual void print() = 0;
};
class Dog: public Animal
{
public:
void print()
{
cout << "This is Dog..." << endl;
}
};
class Cat: public Animal
{
public:
void print()
{
cout << "This is Cat..." << endl;
}
};
class Cow: public Animal
{
public:
void print()
{
cout << "This is Cow..." << endl;
}
};
Animal* animalTable[] = {
new Dog, new Cat, new Cow
};
int main()
{
cout << "想知道哪种动物的描述?" << endl;
cout << "1. 狗" << endl << "2. 猫" << endl << "3. 奶牛" << endl << endl;
int choiceIndex;
cout << "我选择:";
cin >> choiceIndex;
assert(choiceIndex >= 1 && choiceIndex <= 3);
animalTable[choiceIndex - 1]->print();
}
(2)索引访问
假设你经营一家商店,有100种商品,每种商品都有一个ID号,但很多商品的描述都差不多,所以只有30条不同的描述,现在的问题是建立商品与商品描述的表,如何建立?还是同上面做法来一一对应吗?那样描述会扩充到100的,会有70个描述是重复的!如何解决这个问题呢?方法是建立一个100长的索引,然后这些索引指向相应的描述,注意不同的索引可以指向相同的描述,这样就解决了表数据冗余的问题啦。
(3)阶梯访问
它适用于数据不是一个固定的值,而是一个范围的问题,比如将百分制成绩转成五级分制(我们用的优、良、中、合格、不合格,西方用的A、B、C、D和F),假定转换关系是当成绩在90-100区间,判为A,成绩在80-90区间,判为B,成绩在70-80区间,判为C,成绩在60-70区间,判为D,成绩在60以下,判为F(failure)。现在的问题是,怎么用表格对付这个范围问题?一种笨笨的方法是申请一个100长的表,然后在这个表中填充相应的等级就行了,但这样太浪费空间了,有没有更好的方法?
const char gradeTable[] = {
'A', 'B', 'C', 'D', 'F'
};
const int downLimit[] = {
90, 80, 70, 60
};
int main()
{
int score = 87;
int gradeLevel = 0;
while(gradeTable[gradeLevel] != 'F')
{
if(score < downLimit[gradeLevel])
{
++ gradeLevel;
}
else
{
break;
}
}
cout << "等级为 " << gradeTable[gradeLevel] << endl;
return 0;
}