转移表的使用
当许多处理函数的函数原型一样时,使用转移表,可以让代码更清晰,维护更方便。
大学时看过一本书《C和指针》,里面有一小节关于转移表的介绍,当时就觉得很有意思。现在在工作中又看到了,随手记录一下。
还是以《C和指针》中的例子说起吧。
需要实现一个简易计算器,能对两个数进行各种运算。现有以下代码,代码读入一个式子,在Switch-case语句中进行相应的运算。
/*声明一些运算符号*/
typedef enum{
ADD = '+',
SUB = '-',
MUL = '*',
DIV = '/',
//...其他运算法则
}ALG;
/*定义一些运算函数*/
double add(double a,double b){
return a+b;
}
double sub(double a,double b){
return a-b;
}
double mul(double a,double b){
return a*b;
}
double div_(double a,double b){
return a/b;
}
/*开始运算*/
int main(int argc, char *argv[]) {
double a;
double b;
double result = 0;
char c;
printf("input:");
scanf("%lf %c %lf",&a,&c,&b);
switch(c){
case ADD:
result = add(a,b);break;
case SUB:
result = sub(a,b);break;
case MUL:
result = mul(a,b);break;
case DIV:
result = div_(a,b);break;
//...其他case
default:break;
}
printf("result:%0.2lf",result);
return 0;
}
这里只有4种运算,且处理内容比较简单,所以这样处理也还好,但是如果要将其扩展为一个复杂计算器,处理几十种运算,这样做就不妥了,case会很多,这个处理函数会变得很长,非常不利于维护。
所以我们使用转移表的知识,将代码改成下面的的样子。
/*声明一些运算符号*/
typedef enum{
ADD = '+',
SUB = '-',
MUL = '*',
DIV = '/',
}ALG;
/*定义一些运算函数*/
double add(double a,double b){
return a+b;
}
double sub(double a,double b){
return a-b;
}
double mul(double a,double b){
return a*b;
}
double div_(double a,double b){
return a/b;
}
/*声明函数指针类型OPERFUN*/
typedef double (*OPERFUN)(double ,double);
/*定义运算符号列表 algorithm,定义运算函数列表 oper_fun */
ALG algorithm[] = {ADD,SUB,MUL,DIV};
OPERFUN oper_fun[] = {add,sub,mul,div_};
/*写一个处理函数,能够检查运算符号并调用相应的函数*/
double oper_handle(char c,double a, double b){
int i ;
int len = sizeof(algorithm)/sizeof(ALG);
for( i = 0; i < len; i ++ ){
if(c == algorithm[i]){
if(oper_fun[i] != NULL){
return oper_fun[i](a,b);
}
}
}
return 0;
}
int main(int argc, char *argv[]) {
double a;
double b;
double result = 0;
char c;
printf("input:");
scanf("%lf %c %lf",&a,&c,&b);
result = oper_handle(c,a,b); //在这里调用函数进行运算
printf("result:%0.2lf",result);
return 0;
}
代码改成这样就好多了,以后有了新的需求,就往algorithm和oper_fun列表里添加就好,代码长度基本不会增长多少。可能还有点小瑕疵,就是algorithm和oper_fun列表是分开的,一旦对应顺序出错了就糟糕了。
再改改。
/*声明一些运算符号*/
typedef enum{
ADD = '+',
SUB = '-',
MUL = '*',
DIV = '/',
}ALG;
/*定义一些运算函数*/
double add(double a,double b){
return a+b;
}
double sub(double a,double b){
return a-b;
}
double mul(double a,double b){
return a*b;
}
double div_(double a,double b){
return a/b;
}
/*声明函数指针类型OPERFUN*/
typedef double (*OPERFUN)(double ,double);
/*定义运算服务类型*/
typedef struct {
ALG algorithm;
OPERFUN oper_fun;
}OPER_SERVER;
/*运算服务列表,所有的运算符号和运算函数都添加在这个表里*/
OPER_SERVER oper_server[] = {
{ADD, add},
{SUB, sub},
{MUL, mul},
{DIV, div_},
};
double oper_handle(char c,double a, double b){
int i ;
int len = sizeof(oper_server)/sizeof(OPER_SERVER);
for( i = 0; i < len; i ++ ){
if(c == oper_server[i].algorithm){
if(oper_server[i].oper_fun != NULL){
return oper_server[i].oper_fun(a,b);
}
}
}
return 0;
}
int main(int argc, char *argv[]) {
double a;
double b;
double result = 0;
char c;
printf("input:");
scanf("%lf %c %lf",&a,&c,&b);
result = oper_handle(c,a,b); //调用函数进行运算
printf("result:%0.2lf",result);
return 0;
}
每次有新需求的时候,只需要oper_server中进行变动,algorithm和oper_fun一一对应,这样就不会把顺序搞乱了。
以上 用一个简单的例子演示了转移表的用法。