1.1.1 利用查表调用函数通过之前介绍的范例可以看出,“打印的帮助信息”同样也不能做到动态绑定,同时还需要用手工添加case语句及处理函数。可想而知其扩展性很差,这是开发过程中最容易被忽略的问题。
1. 高级声明
或许,上面的示例都过于简单,下面不妨来一点刺激的。我们可以先声明一个结构体类型,并同时定义一个结构体变量,然后再定义一个结构体数组,其示例如下:
1 #define HELP_LEN 64 // 函数说明的最大长度
2 #define TABLE_LEN 10 // 函数表中的最大的函数个数
3
4 typedef struct CmdEntry{
5 void (*pfuncmd)(); // 定义函数指针,用于接收函数的入口地址
6 char cHelp[HELP_LEN];
7 }CmdEntry;
8
9 static CmdEntry cmdArray[TABLE_LEN] = { // 定义结构体数组(函数表)并初始化
10 {&CreateFile, "新建文件"}, // 取CreatFile()函数地址,帮助信息
11 {&OpenFile, "打开文件"}, // 取OpenFile()函数地址,帮助信息
12 {&SaveFile, "保存文件"}, // 取SaveFile()函数地址,帮助信息
13 // <标注1>在这里添加函数
14 {0, 0} // 退出
15 };
注意:在这里定义数组的长度是为了方便下一个版本添加调用函数。
2. 利用查表调用函数
根据上面的定义,即可用以下方式获得函数的入口地址。
cmdArray[iCmdNum].pfuncmd
然后用函数指针回调相应的功能函数,其示例如下:
cmdArray[iCmdNum].pfuncmd();
由此可见,如果采用回调函数法,且以动态绑定的方式,则程序的可扩展性得到了很大的提升,因为我们只需在“<标注> 1”处注册自定义的函数,无需多处修改代码,不仅可以很好地解决程序的可扩展性问题,而且还大大地降低程序的出错几率,详见程序清单1.1。
程序清单1.1 控制台菜单选项程序(V0.5)
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #define HELP_LEN 64 // 函数说明的最大长度
5 #define TABLE_LEN 10 // 函数表中最大的函数个数
6
7 typedef struct CmdEntry{ // 定义函数结构体
8 void (*pfuncmd)(); // 接收函数入口地址的函数指针
9 char cHelp[HELP_LEN];
10 }CmdEntry;
11
12 void ShowHelp();
13
14 // 此处省略CreateFile()函数代码 // “新建文件”菜单
15 // 此处省略OpenFile()函数代码 // “打开文件”菜单
16 // 此处省略SaveFile()函数代码 // “保存文件”菜单
17
18 static CmdEntry cmdArray[TABLE_LEN] = { // 定义函数表
19 {&CreateFile, "新建文件"},
20 {&OpenFile, "打开文件"},
21 {&SaveFile, "保存文件"},
22 // <标注1>在这里添加函数
23 {0, 0}
24 };
25
26 void ShowHelp() // 显示函数表中的内容
27 {
28 int i;
29
30 for (i = 0; (i < TABLE_LEN) && cmdArray.pfuncmd; i++) {
31 printf("%d\t%s\n", i, cmdArray.cHelp);
32 }
33 }
34
35 void CmdRunning()
36 {
37 int iCmdNum;
38
39 while (1){
40 ShowHelp(); // “帮助信息”显示初始化
41 printf("请选择!\n");
42 iCmdNum = getchar() - '0'; // 将字符转换为数字,转换失败也可以
43 fflush(stdin); // 清空缓冲区
44 if (iCmdNum >= 0 && iCmdNum < TABLE_LEN && cmdArray[iCmdNum].pfunCmd){
45 cmdArray[iCmdNum].pfunCmd();
46 }
47 else{
48 printf("对不起,你选择的数字不存在,请重新选择!\n");
49 }
50 }
51 }
52 void main()
53 {
54 CmdRunning();
55 }
这种方式相对来说其扩展性有了很大的提升,只需要在“<标注1>”处注册自定义的函数即可,帮助显示函数ShowHelp()会自动地显示新增加的函数帮助信息,因为修改一处比修改多处出错的可能性要低得多。
1.1.2 提供通用接口如果采用上述方式注册函数,则必须在这个文件的“<标注1>”处修改源代码。很多时候,当需要扩展菜单功能时,而又不允许随意修改源码,怎么办?唯一的解决方法就是为系统增加一个可动态扩展的接口函数,详见程序清单1.2。
程序清单1.2 动态扩展接口函数(V0.6)
1 void AddCmd(CmdEntry cmdentry)
2 {
3 int i;
4
5 for (i = 0; (i < TABLE_LEN) && cmdArray.pfuncmd; i++) {
6 ; // 找到空的功能条目位置
7 }
8 if (TABLE_LEN == i) {
9 printf("Sorry,table is full!");
10 }
11 else {
12 cmdArray = cmdentry;
13 }
14 }
当以后需要扩展菜单项时,那么只需要调用AddCmd接口即可。不过这里还是有一个不足之处,当菜单项的数量达到定义的TABLE_LEN长度后,就不能继续添加菜单项了,请读者思考一下怎样才能做到动态增长。 |