【背景】
数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。
——Rob Pike
上面是这个名人说过的话,那么c语言之父 丹尼斯·麦卡利斯泰尔·里奇 的《c程序设计》里曾经也有这样一句话: 程序 = 算法 + 数据(原文是:program = algorithm + data structure)
从以上两个人的思想当中我们可以知道:算法和数据对于程序来说是至关重要的,如果把程序比作一个人的话,算法就是他的灵魂,数据结构就是他的肉体(个人理解,不喜勿喷),我们在普遍的编程里,一直追求的是算法的高效性和安全性,往往忽略了数据结构的重要性和编程用途,其实,数据结构也可以作为编程的一种思想,这便是:表驱动编程!
所谓表驱动法(Table-Driven Approach)简而言之就是用查表的方法获取数据。此处的“表”通常为数组,但可视为数据库的一种体现。
根据字典中的部首检字表查找读音未知的汉字就是典型的表驱动法,即以每个字的字形为依据,计算出一个索引值,并映射到对应的页数。相比一页一页地顺序翻字典查字,部首检字法效率极高。
具体到编程方面,在数据不多时可用逻辑判断语句(if…else或switch…case)来获取值;但随着数据的增多,逻辑语句会越来越长,此时表驱动法的优势就开始显现
表驱动是一种在C语言里常见的编程模式,从表里面查找信息而不使用逻辑语句(if和case)。核心操作是将输入因素作为直接或者间接的索引,到数组里找到直接的结果或者对应的处理(通常是函数指针)。
在传统的23种面向对象设计模式里,并没有表驱动这种模式。这种模式是强烈依赖数组或者多维数组的一种设计模式,不涉及类,继承等关系,所以在C语言等非面向对象编程里得到了广泛的应用。
表驱动是一种在C语言里常见的编程模式,从表里面查找信息而不使用逻辑语句(if和case)。核心操作是将输入因素作为直接或者间接的索引,到数组里找到直接的结果或者对应的处理(通常是函数指针)。
表驱动实质上把逻辑和数据进行了分离。因素和结果之间的映射关系能够全部存放到数组里,而不是混杂在if,else的流程代码里。当映射关系发生改变的时候,只需要改变数组就可以,不需要修改代码。管理和维护起来非常方便。甚至可以把数据作为配置文件存放到硬盘上,需要的时候读取进来,避免了代码重新编译
看完上面这写内容,可能你对表驱动编程思想有些粗略的认识,脑袋也许会懵,很正常,我们举个现实生活中的例子看看,如下:
一家水果超市有苹果,香蕉,橘子这三种水果售卖,你要去买之前,想要在app上查下苹果还有没有?价格多少?人们的评价如何?这时你输入apple,就会出现对应的这些问题的答案,这个怎么实现呢?
【分析】
看了上面这个问题后,我相信大多数人都会想到if...else...结构或者switch...case...结构来实现此功能,完全可以,但是我们不妨再往深想一层,目前只是三类水果,当然可以这么做,而且也不费力,但是如果要实现100个水果类别呢?是不是脑袋瞬间嗡嗡的了?那么我们就可以用表驱动编程思想,去处理这100个不同的数据,注意:我们把重心放在了让数据去选择程序,就是把数据和程序对应起来,这次数据作为主导,去搞事情,而不是逻辑作为主导!是不是有点懵或者一时半会儿转不过这个弯儿?没事,你不妨用笔画画,试试看,再分析分析哦,这个想通了,你就知道在大型结构数据面前,表驱动编程是多么的666了,接下来,我们看看具体的testcode的实现
【实现_TestCode】
/****************************
*表驱动编程(数据结构思想)
****************************/
/*定义打印宏函数*/
#define PFS(a, b, c) printf("state: %s --- price: %.1lf --- \
evaluate: %s \n", a, b, c )
/*1.基本数据结构*/
typedef struct Bs /*水果属性数据结构*/
{
char* state; /*状态:是否还有该水果*/
double price; /*价格:该水果多钱一斤*/
char* evaluate; /*评价:受到的顾客评价good, general, bad*/
}base;
typedef struct fruit /*水果类别数据结构*/
{
base apple;
base banana;
base orange;
}fs;
/*2.函数指针类型声明*/
typedef fs* (*funcp)(fs* tp);
/*3.函数指针具体实现*/
fs* apples(fs *tmp) /*苹果函数实现*/
{
char* state = "have";
double price = 2.1;
char* evaluate = "good";
LOGS("welcome to zll fruit shop...");
LOGS("I am is apples");
tmp->apple.state = state;
//tmp.apple.state = state;
tmp->apple.price = price;
tmp->apple.evaluate = evaluate;
return tmp;
};
fs* bananas(fs* tmp) /*香蕉函数实现*/
{
char* state = "have";
double price = 3.0;
char* evaluate = "general";
LOGS("welcome to zll fruit shop...");
LOGS("I am is banana");
tmp->banana.state = state;
tmp->banana.price = price;
tmp->banana.evaluate = evaluate;
return tmp;
};
fs* oranges(fs* tmp) /*橘子函数实现*/
{
char* state = "have";
double price = 1.2;
char* evaluate = "bad";
LOGS("welcome to zll fruit shop...");
LOGS("I am is orange");
tmp->orange.state = state;
tmp->orange.price = price;
tmp->orange.evaluate = evaluate;
return tmp;
};
/*4.数据结构组合*/
typedef struct map /*表驱动结构:名字+对应函数指针*/
{
char* name;
funcp handle;
}Map_node;
/*5.表编程实现*/
static const Map_node g_fruit_handle[] = {
/*对应的数组结构:根据名字回调对应的函数*/
{"apple", apples},
{"banana", bananas},
{"orange", oranges}
};
/*6.回调函数实现*/
fs mycallback(char* name, int len)
{
int i;
fs ttm = {0};
for (i = 0; i < len; i++) {
if (!strcmp(g_fruit_handle[i].name, name)) {
if (g_fruit_handle[i].handle) {
(*(g_fruit_handle[i].handle))(&ttm);
return ttm;
}
}
}
};
/*7.调用*/
void test_38_biaoprogram()
{
char* name = "apple";
int len = sizeof(Map_node);
fs gtm = {0};
gtm = mycallback(name, len);
PFS(gtm.apple.state, gtm.apple.price, gtm.apple.evaluate);
};
我们测试的是apple,结果如下:
那么,以后只要有新的水果需要添加,我们只需要实现本水果函数并把name和handle添加到g_fruit_handle数组中便可,是不是有种焕然一新的感觉?完全和if...else..以及switch...case...的编程思想是两种感觉,一起加油,伙伴们^-^