http://blog.csdn.net/arau_sh/article/details/7789102
转自 http://www.jsjgprjyyy.com/Html/?1260_5.html
摘要:本文深入数据结构中“表”的使用,探讨以表设计为中心来驱动各种复杂信息的处理,也就是表驱动方法;并进一步探讨表中数据的结构封装和表驱动方法的功能扩展。
关键词:数据结构;表驱动;接口封装;功能扩展
中图分类号:tp311.12 文献标识码:a 文章编号:1007-9599 (2010) 06-0000-02
discussing on table-driven
lan xiaojing
(fujian business school,fuzhou 350007,china)
abstract:this paper explores data structures,"table"the use of a table design center to drive a variety of complex information processing,
that is table-driven methods;and further explore the structure of data in the table table-driven methods of packaging and extensions.
keywords:data structures;table-driven;interface package;function expansion
一、表驱动方法简介
表是一种常见的数据结构,但是一般的程序员却几乎很少使用,或者是不会有意识地进行使用。本文探讨的“表驱动”方法是对表这种数据结构的一种理论提炼,通过理论上的把握,使得程序员能够在实践中以表设计为中心来驱动各种复杂信息的处理。
《代码大全》提到了表驱动的概念:表驱动方法是一种使你可以在表中查找信息,而不必用逻辑语句来把它们找出来的方法。这里的提到的信息可能是数据,也可能是动作。事实上,任何信息都可以通过表来挑选。
在简单的情况下,逻辑语句往往简单、直接(参见下面的示例代码)。但是,如果逻辑比较复杂,通常就是查找信息的输入、输出数据或动作很多,那么使用表驱动方法就是一个很好的选择了。
对于c语言初学者常常写出以下类似代码:
- int demo_tab_driver(int index,void*in,void *out)
- {
- int rc;
- switch(index)
- {
- case index_a:
- rc=processa(in,out);
- break;
- case index_b:
- rc=processb(in,out);
- break;
- case index_c:
- rc=processc(in,out);
- break;
- ..........
- default:
- rc=not_support;
- break;
- }
- return rc;
- }
int demo_tab_driver(int index,void*in,void *out)
{
int rc;
switch(index)
{
case index_a:
rc=processa(in,out);
break;
case index_b:
rc=processb(in,out);
break;
case index_c:
rc=processc(in,out);
break;
..........
default:
rc=not_support;
break;
}
return rc;
}
以上的代码直接使用了逻辑语句(switch-case),由于输出信息比较少,整个代码可读性还是比较好的,也比较简单、直接。但是,如果输出信息成倍增多,那么随着switch-case分支的增多、嵌套,代码可读性将迅速下降,代码的维护也会越来越困难。而且,如果预计输出信息可能会大量增加,这种代码也存在代码扩展问题。
在设计这种信息查找模块时候,应该考虑逻辑的复杂性,输入输出信息的数据量,以及可能的扩展。一般只有在逻辑简单、信息量少,而且基本不存在扩展性问题时,才使用逻辑语句(if-else或switch-case)直接处理。
二、表驱动的简单使用
表驱动方法示例如下:
#define dim_tab(x)(sizeof(x)/sizeof(x[0]))//求结构体数组的个数
- //表结构定义
- typedef struct
- {
- int index;//表索引
- int (*ptrfunc)(void *in, void *out);
- } dispatch_tab;
- int demo_tab_driver(int index , void *in, void *out)
- {
- // 表初始化
- dispatch_tab tab[] =
- {
- {index_a, processa},
- {index_b, processb},
- {index_c, processc},
- ......
- };
- int i;
- int rc=not_support;
- // 查表,并完成相关操作
- for( i=0; i<dim_tab(tab); i++ )
- {
- if( index == tab[i].index )
- {
- rc=(*tab[i].ptrfunc)(in,out);
- break;
- }
- }
- return rc;
- }
//表结构定义
typedef struct
{
int index;//表索引
int (*ptrfunc)(void *in, void *out);
} dispatch_tab;
int demo_tab_driver(int index , void *in, void *out)
{
// 表初始化
dispatch_tab tab[] =
{
{index_a, processa},
{index_b, processb},
{index_c, processc},
......
};
int i;
int rc=not_support;
// 查表,并完成相关操作
for( i=0; i<dim_tab(tab); i++ )
{
if( index == tab[i].index )
{
rc=(*tab[i].ptrfunc)(in,out);
break;
}
}
return rc;
}
使用表驱动的好处就是对表的操作部分的代码简短且基本上无需维护。如果需要添加、修改新功能,只需要维护驱动表tab[]就可以了,这就摆脱了冗长乏味的switch-case。
三、表驱动的数据结构封装
上一节例子中各个case分支中的动作,其参数都比较简单,而且类型相同,即都使用了相同的参数in和out,如果各个分支使用的参数类型各不相同,那该怎么办呢?
那就需要进行必要的封装,一般是封装一个比较复杂的struct类型,里面嵌套各种union对应不同的接口参数,通过不同的type,运行时从union中动态提取对应类型的数据。
具体方法如下:
- typedef struct strpara_in
- {
- …//此处声明所有的分支公用的数据
- int type;//数据类型
- union
- {
- struct {
- …
- } a_in; //processa分支使用的数据
- struct {
- …
- } b_in; //processb分支使用的数据
- struct {
- …
- } c_in; //processc分支使用的数据
- ......
- }para_in;
- }
- typedef struct strpara_out
- {
- …//此处声明所有的分支公用的数据
- int type; //数据类型
- union
- {
- struct {
- …
- } a_out; //processa分支使用的数据
- struct {
- …
- } b_out; //processb分支使用的数据
- struct {
- …
- } c_out; //processc分支使用的数据
- ......
- }para_out;
- // 表结构定义
- typedef struct
- {
- int index;
- int (*ptrfunc)(para_in *in, para_out *out);
- } dispatch_tab;
- int demo_tab_driver(int index , para_in *in, para_out *out)
- {
- … // 此处代码同上节示例
- }
- }
typedef struct strpara_in
{
…//此处声明所有的分支公用的数据
int type;//数据类型
union
{
struct {
…
} a_in; //processa分支使用的数据
struct {
…
} b_in; //processb分支使用的数据
struct {
…
} c_in; //processc分支使用的数据
......
}para_in;
}
typedef struct strpara_out
{
…//此处声明所有的分支公用的数据
int type; //数据类型
union
{
struct {
…
} a_out; //processa分支使用的数据
struct {
…
} b_out; //processb分支使用的数据
struct {
…
} c_out; //processc分支使用的数据
......
}para_out;
// 表结构定义
typedef struct
{
int index;
int (*ptrfunc)(para_in *in, para_out *out);
} dispatch_tab;
int demo_tab_driver(int index , para_in *in, para_out *out)
{
… // 此处代码同上节示例
}
}
四、表驱动的功能扩展
有时候case分支有如下特点:多个分支都有许多共同的处理。这就有必要进行进一步的改造,进行功能处理分级:
第一级:处理共通部分
第二级:处理该分支特有的功能
示例如下:
- // 表结构定义
- typedef struct
- {
- int index;
- int (*ptrcommonfunc)(para_in *in, para_out *out); //第一级共通部分
- int (*ptrfunc)(para_in *in, para_out *out); //特有功能部分
- } dispatch_tab;
- int demo_tab_driver(int index , para_in *in, para_out *out)
- {
- // 表初始化
- dispatch_tab tab[] =
- {
- { index_a, commonprocess1, processa },
- { index_b, commonprocess1, processb },
- { index_c, commonprocess2, processc },
- { index_d, commonprocess2, processd },
- { index_e, commonprocess2, processe },
- { index_f, commonprocess2, processf },
- { index_g, commonprocess2, processg },
- ......
- };
- int i;
- int rc = not_support;
- // 查表,并完成相关操作
- for( i=0; i<dim_tab(tab); i++ )
- {
- if( index == tab[i].index )
- {
- (*tab[i].ptrcommonfunc)(in,out); //共通功能处理
- rc = (*tab[i].ptrfunc)(in,out);
- break;
- }
- }
- return rc;
- }
// 表结构定义
typedef struct
{
int index;
int (*ptrcommonfunc)(para_in *in, para_out *out); //第一级共通部分
int (*ptrfunc)(para_in *in, para_out *out); //特有功能部分
} dispatch_tab;
int demo_tab_driver(int index , para_in *in, para_out *out)
{
// 表初始化
dispatch_tab tab[] =
{
{ index_a, commonprocess1, processa },
{ index_b, commonprocess1, processb },
{ index_c, commonprocess2, processc },
{ index_d, commonprocess2, processd },
{ index_e, commonprocess2, processe },
{ index_f, commonprocess2, processf },
{ index_g, commonprocess2, processg },
......
};
int i;
int rc = not_support;
// 查表,并完成相关操作
for( i=0; i<dim_tab(tab); i++ )
{
if( index == tab[i].index )
{
(*tab[i].ptrcommonfunc)(in,out); //共通功能处理
rc = (*tab[i].ptrfunc)(in,out);
break;
}
}
return rc;
}
实际运用中,表的构造多种多样,在设计阶段费点时间设计表结构,是很有必要的。
五、表驱动方法实例
下面以一个“菜单操作”例子来介绍表驱动方法的实际使用。
软件需求:使用键盘上的"←→"键切换菜单焦点,当焦点处于某菜单时,若敲击键盘上的ok、cancel键则调用该焦点菜单对应之处理函数。
注:以下代码关注于需求的后半部。
- /*将菜单的属性和操作"封装"在一起*/
- typedef struct strsysmenu
- {
- char*text; /* 菜单的文本 */
- int xpos; /* 菜单在lcd上的x坐标 */
- int ypos; /* 菜单在lcd上的y坐标 */
- void (*onokfun)(void); /* 在该菜单上按下ok键的处理函数指针 */
- void (*oncancelfun)(void); /* 在该菜单上按下cancel键的处理函数指针 */
- } sysmenu, *ptrsysmenu;
- // (菜单)表初始化
- static sysmenu menu[menu_num] =
- {
- {"menu1", 0, 48, menu1onok, menu1oncancel },
- {"menu2", 9, 48, menu2onok, menu2oncancel },
- {"menu3", 18, 48, menu3onok, menu3oncancel },
- {"menu4", 27, 48, menu4onok, menu4oncancel },
- ......
- };
- int currentfocusmenu; //当前光标聚焦于哪个菜单
- /* 按下ok键 */
- void onokkey(void)
- {
- menu[currentfocusmenu].onokfun();
- }
- /* 按下cancel键 */
- void oncancelkey(void)
- {
- menu[currentfocusmenu].oncancelfun();
- }
- void menu1onok()
- {
- ...
- }
- void menu1oncancel()
- {
- ...
- }
- ......
- void menu4onok()
- {
- ...
- }
- void menu4oncancel()
- {
- ...
- }
/*将菜单的属性和操作"封装"在一起*/
typedef struct strsysmenu
{
char*text; /* 菜单的文本 */
int xpos; /* 菜单在lcd上的x坐标 */
int ypos; /* 菜单在lcd上的y坐标 */
void (*onokfun)(void); /* 在该菜单上按下ok键的处理函数指针 */
void (*oncancelfun)(void); /* 在该菜单上按下cancel键的处理函数指针 */
} sysmenu, *ptrsysmenu;
// (菜单)表初始化
static sysmenu menu[menu_num] =
{
{"menu1", 0, 48, menu1onok, menu1oncancel },
{"menu2", 9, 48, menu2onok, menu2oncancel },
{"menu3", 18, 48, menu3onok, menu3oncancel },
{"menu4", 27, 48, menu4onok, menu4oncancel },
......
};
int currentfocusmenu; //当前光标聚焦于哪个菜单
/* 按下ok键 */
void onokkey(void)
{
menu[currentfocusmenu].onokfun();
}
/* 按下cancel键 */
void oncancelkey(void)
{
menu[currentfocusmenu].oncancelfun();
}
void menu1onok()
{
...
}
void menu1oncancel()
{
...
}
......
void menu4onok()
{
...
}
void menu4oncancel()
{
...
}
以上代码使用了表结构驱动和面向对象中的封装思想,可以看出程序结构相当清晰,维护也相当容易。
如果需要在系统中添加更多的菜单,只需修改表中的数据,也就是说:维护代码简化为维护一张表。
参考文献:
[1]steve mcconnell.代码大全[m].天奥.熊可宜.学苑出版社,1993,1
[2]严蔚敏,吴伟民.数据结构[m].清华大学出版社,1997,4,1
[3]arman danesh.linux从入门到精通[m].邱仲潘.电子工业出版社,1999,3,