嵌入式程序中比较让人头疼的是回调函数的运用,下面说一下回调函数的意义,以帮助程序理解。
我们写了两个用于向用户输出信息的函数,它们的功能基本相同,只是使用了不同的输出设备,假设为:
void Printer(char a)
{
使用打印机做输出;
}
void Screen(char b)
{
使用显示器做输出;
}
我们检查自己的编程设备后发现它只连接了打印机,那么要做输出就只能使用打印机,假设有两个程序要这样做输出,就应该这样调用它:
void Program1(void)
{
Printer (“开始输出”);
}
void Program2 (void)
{
Printer (“开始输出”);
}
这样看起来也挺好用的。但是,如果换了一个设备,我们发现它只有显示器,怎么办?你是不是觉得把这两个函数替换成Screen(“开始输出”)就可以了?没错,这样很有效。但是就是觉得有点不舒服,心中肯定惦记,如果下次又换个设备,并且有很多的输出调用,那岂不是很麻烦。
于是需求出现了,有没有比较省事的办法,让我不用改整个程序而是只改一处就搞定。这可能首先想到的是使用#define,但它的局限性是可想而之的,一两个还行,一套程序下来全篇都是#define难于书写和扩展,而且也不professional。与#define类似的,C为我们提供了一个更高级的关键字typedef,它可以用来定义我们自己的类型,这有什么意义呢?这意义可大了,你觉得C给你提供的类型太少,不够用?没问题,给你自己定义的自由,随便去定义吧。通常我们用它来定义结构体类型,实现我们希望的各种封装。但是这里,我们用它来定义一个函数类型:
typedef void (*OutputFunc) (char c)
这个OutputFunc代表的是void XX (char c)这种形式的一类函数,typedef的强大之处正在于此。
但是只有类型是不够的,就像只有一个int,你依然没法做什么。下面我们要定义一个OutputFunc类型的变量,并且为它赋初值:
OutputFunc Myout = Printer;
现在这个变量指向的就是Printer函数了。这样我们就通过OutputFunc这种函数类型统一了这一类函数的入口,我们以后再调用void XX (char c)这种形式的一类函数,只需要定义一个OutputFunc类型的变量并赋值给它,再调用这个变量就可以了,甚至这个变量还可以被用于参数传递,拥有极大的灵活性。刚才那个示例程序相应的要做一下修改:
void Program1(void)
{
Myout (“开始输出”);
}
void Program2 (void)
{
Myout (“开始输出”);
}
这样无论有多少地方要怎么改,我们只要把Myout的赋值给改一下,就好了。甚至于它可以被用于参数传递,例如:
OutputFunc Myout = Screen;
void Program3(OutputFunc f)
{
f (“开始输出”);
}
Program3(Myout);
这样的调用简洁明了,极大的降低了耦合性,是程序猿们一直苦苦追求的境界。这就是回调函数的精髓,函数调用与被调用者通过中间变量被分隔开,互不关心。
而这只是初级用法,更高级的用法是写成如下的形式:
typedef struct {
void (*Output) (char c);
void (*Parse) (char c);
char (*Input) (void);
void (*Algorithm) (void);
} DeviceFunc;
它定义了一个新的结构体类型DeviceFunc,它是一堆函数类型的集合,打包了设备全部的处理功能。接下来定义这种类型的两个变量:FuncA,FuncB,FuncC,并分别赋值:
DeviceFunc FuncA;
FuncA. Parse =协议解析函数;
FuncA. Input =输入函数;
DeviceFunc FuncB;
FuncB. Output =输出函数;
FuncB. Input =输入函数;
DeviceFunc FuncC;
FuncC. Output =输出函数;
FuncC. Algorithm =算法处理函数;
在用户的程序中,这样定义要传入的形参:
void Program1 (DeviceFunc IO)
{
IO. Parse (“c”);
char c = IO. Input ();
}
void Program2 (DeviceFunc IO)
{
IO. Output(“c”);
char c = IO. Input();
}
void Program3 (DeviceFunc IO)
{
IO. Output(“c”);
IO. Algorithm ();
}
接下来就是设备调用:
Program1(FuncA); /*使用A功能的全部函数*/
Program2(FuncB); /*使用B功能的全部函数*/
Program3(FuncC); /*使用C功能的全部函数*/
注意新定义的DeviceFunc结构体类型,它是一堆函数类型的集合,它不但打包了设备全部的处理功能,而且实现了统一的调用入口。它让调用者只需要操作传入的FuncA、FuncB、FuncC变量就可以使用它们各自所指向的设备的全部功能了,而且还可以定义其他变量,让更多的调用者去使用,并且调用者之间,调用者和被使用的设备之间都互不关心。
其实回调函数就是一种封装,它把功能封闭在一个小圈子里,你只能看到它的两个接口:一个是类型,一个是它所指向的功能,去调用就行了,它会按照所指向的功能函数去执行动作,而用户不用再管具体执行的事。这在大型系统级程序中尤其有用,能够把用户与底层分隔开来,例如程序库的API很多都是这样给出来的,只给出了类和方法名,底层代码是怎么实现的谁都不知道。下面来张图看一下它的好处吧: