1.1 概念
表驱动方法是一种使你可以在表中查找信息,而不必用逻辑语句(if-else
或switch-case
)来把他们找出来的方法。事实上,任何信息都可以通过表来挑选。在简单的情况下,逻辑语句往往更简单而且更直接。但随着逻辑链的复杂,表就变得越来越富于吸引力了。表驱动编程的意义在于逻辑与数据的分离,表驱动的核心是key-handler,拿某个key去对应某个handler,只要条件符合key,那么就执行对应的handler。
可以简单的理解的就是用同样的方式处理不同的数据,表驱动方法具有以下特点:
-
可读性强,数据处理流程一目了然。
-
便于维护,只需要增、删数据索引和方法就可以实现功能。
-
精简代码,降低圈复杂度。减少 if-else、switch-case 使用。
-
在一定程度上可以提升程序运行速度。
2.1 查表方法
使用表驱动法的时候需要关注两个问题:一是如何查表
,二是表里面存放什么内容
对于如何查表:也就是有哪些查表方法,通常有直接查表法,索引查表法,分段查表法。
对于表中存放的内容:存放的要么是数值,要么是函数指针。
2.1.1 直接查表法
通常就是通过数组的下标直接获得数据。
示例:已知硬件版本ID:0-9,获取版本名称。
常见的方法:
Status GetHwversionID(uint8_t HWVersionID,char* HWVersionStr)
{
if(0 == HWVersionID)
{
sprintf(HWVersionStr,"V0.1");
}
else if (1 == HWVersionID)
{
sprintf(HWVersionStr,"V0.2");
}
//...
else
{
}
return OK;
}
使用直接查表法:
#define VERSIONMAP_NUM 10
/*
* 数组下标对应硬件版本ID,表中内容为硬件版本号
*
*/
const char* HW_Version_Map[VERSIONMAP_NUM] = \
{"V1.0","V1.1","V1.2","V1.3","V1.4","V1.5","V1.6","V1.7","V1.8","V1.9"};
Status GetHwversionID(uint8_t HWVersionID,char* HWVersionStr)
{
if(HWVersionID >= 0 && HWVersionID < VERSIONMAP_NUM)
{
sprintf(HWVersionStr,HW_Version_Map[HWVersionID]);
return OK;
}
else
{
return ERROR;
}
}
2.1.2 索引查表法
有时候,只用一个简单的数学运算还无法把数据转换为表键值。这类情况中的一部分可以通过索引访问的方法加以解决。
使用索引的时候,先用一个基本类型的数据从一张索引表中查出一个键值,然后再用这一个键值查出你需要的主数据。
如现有10件商品,2位编号,范围从00到99。此时只需要申请一个长度为10的数组,且对应1位键值。但将2位的编号转换为1位的键值,可能过于复杂或没有规律,最合适的方法是建立一个保存该转换关系的索引表。采用索引访问既节省内存,又方便维护
#include <stdio.h>
#include <stdlib.h>
#define Good_Num 10
#define Index_Num 100
typedef struct Goods_
{
unsigned int Good_ID;
char *Good_Name;
}Goods;
/*
* 查询表
* 共有10个商品,每个商品由商品ID和商品name组成
*/
Goods Good_Map[Good_Num] = {
{4, "Good_4"},
{33, "Good_33"},
{24, "Good_24"},
{35, "Good_35"},
{48, "Good_48"},
{60, "Good_60"},
{99, "Good_99"},
{50, "Good_50"},
{84, "Good_84"},
{92, "Good_92"},
};
/*
* 查询表的索引表
* 索引表有100个索引,可以索引ID:0-99的商品 0 2 1 3 4 7 5 8 9 6
*/
unsigned int Index_Map[Index_Num] = {
0xFFFF,0xFFFF,0xFFFF,0xFFFF,0x0,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
0xFFFF,0xFFFF,0xFFFF,0xFFFF,0x2,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
0xFFFF,0xFFFF,0xFFFF,0x1,0xFFFF,0x3,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0x4,0xFFFF,
0x7,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
0x5,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
0xFFFF,0xFFFF,0xFFFF,0xFFFF,0x8,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,
0xFFFF,0xFFFF,0x9,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0x6,
};
int main()
{
/* 通过控制台获取商品ID:0-99*/
int GoodID;
char* Good_Name;
unsigned int Good_ID;
scanf("%d",&GoodID);
/* 通过索引表获取查询表的数组ID后访问查询表 */
Good_Name = Good_Map[Index_Map[GoodID]].Good_Name;
Good_ID = Good_Map[Index_Map[GoodID]].Good_ID;
if(Good_Name != NULL)
{
printf("Good name: %s\n", Good_Name);
printf("Good ID: %d\n", Good_ID);
}
else
{
printf("Error! Can not find the good!\n");
}
return 0;
}
运行结果:
索引访问技术有两个主要优点:首先,如果主查询表中的每一条记录都很大,那么创建一个浪费了很多空间的索引数组所用的空间要比创建一个浪费了很多空间的主查询表所用的空间小的多。举例来说,假如有100种商品。每种商品都有一个4位数字的物品编号,其范围是0000到9999。如果如果主表中的每条记录需要占用100个字节,而索引表中的每条记录需要占用2字节。假设主表有100条记录,而用来访问它的数据有10 000种可能取值。这样一来,就是再10 000条索引记录和10 000条主数据成员记录之间做出选择。如果用的是索引查询,那么用掉的总内存是20 000字节。如果不使用索引结构,把空间耗费在主表里面,那么用掉的总内存就会是1000 000字节。其次,操作位于索引种的记录有时比操作位于主表中的记录更方便。最后,可维护性更好,编写到表里面的数据比嵌入代码中的数据更容易维护
2.1.3 分段查表法
通过确定数据所处的范围确定分类(下标)。有的数据可分成若干区间,即具有阶梯性,如分数等级。此时可将每个区间的上限(或下限)存到一个表中,将对应的值存到另一表中,通过第一个表确定所处的区段,再由区段下标在第二个表里读取相应数值。注意要留意端点,可用二分法查找,另外可考虑通过索引方法来代替。
如根据分数查绩效等级:
#include <stdio.h>
#include <stdlib.h>
#define MAX_GRADE_LEVEL 5u
typedef struct{
double RangeLimit;
char *Grade;
}GRADE_MAP;
GRADE_MAP GradeMap[MAX_GRADE_LEVEL] = {
{40.0, "Fail"},
{60.0, "Pass"},
{70.0, "Credit"},
{80.0, "Distinction"},
{100.0, "High Distinction"}
};
static char* EvaluateGrade(double Score)
{
unsigned char Level = 0;
for(; Level < MAX_GRADE_LEVEL; Level++)
{
if(Score < GradeMap[Level].RangeLimit)
return GradeMap[Level].Grade;
}
return GradeMap[0].Grade;
}
int main(int argc, char *argv[])
{
int Score;
const char* Grade;
scanf("%d",&Score);
Grade = EvaluateGrade(Score);
if(Grade != NULL)
{
printf("Grade: %s\n", Grade);
}
else
{
printf("Error! Can not find the Grade\n");
}
return 0;
}
2.1.4 函数指针
现在假设前级模块传给我二进制数据,输入参数为 char* buffer和 int length,buffer是数据的首地址,length表示这批数据的长度。数据的特点是:长度不定,类型不定,由第一个字节(buffer[0])标识该数据的类型,共有256(28 )种可能性。我的任务是必须对每一种可能出现的数据类型都要作处理,并且我的模块包含若干个函数,在每个函数里面都要作类似的处理。若按通常做法,会写出如下代码:
void MyFuntion( char* buffer, int length )
{
int8 nStreamType = buffer[0];
switch( nStreamType )
{
case 0:
function1();
break;
case 1:
......
case 255:
function255();
break;
}
}
如果按照这种方法写下去,那么在我的每一个函数里面,都必须作如此多的判断,写出的代码肯定很长,并且每一次处理,都要作许多次判断之后才找到正确的处理函数,代码的执行效率也不高。这是就要用到函数指针数组
void funtion0( void );
……..
void funtion255(void );
其次定义函数指针数组,并给数组赋值。
void (*fun[256])(void);
fun[0] = function0;
…….
fun[255] = function();
//最后,MyFunction()函数可以修改如下:
void MyFuntion( char* buffer, int length )
{
int8 nStreamType = buffer[0];
fun[nStreamType]();
}
参考文章链接:
https://mp.weixin.qq.com/s/RYUXbJ2K1Mc1xlaG_AvV3w
https://mp.weixin.qq.com/s/eBkB1MsfzJZcLHGxrf2MTw
https://mp.weixin.qq.com/s/g297RN5_QcKlm9WFxfNhuQ