《我的第一本C++书》节选:11.1.3 用函数指针实现回调函数
11.1.3 用函数指针实现回调函数
除了可以使用函数指针简化函数的调用之外,函数指针更大的用途在于它可以作为函数参数传递给某个函数,从而实现函数的回调。所谓函数的回调,就是在某个函数中,通过函数指针调用另外一个函数,而这个函数指针,大多数情况下是通过函数参数传递进来的。如果把函数的指针作为参数传递给另一个函数,那么当这个函数指针用于调用它所指向的函数时,就说这个函数是回调函数。 函数指针已经够复杂了,为什么还要使用它来作为函数参数进行函数的回调?程序简单一点不是更好吗? 回调函数的使用,正是为了让程序变得更简单。因为回调函数可以把主调函数与被调函数分开,主调函数不必关心谁是被调函数,所有它需要知道的只是存在一个具有某种特定原型、某些限制条件的被调函数。这就像在主调函数中留下了一个插口,它定义了插口的规则,也就是函数的返回值和具体的参数。而被调函数就是插头,可以把任何符合这个插口规则的插头插入这个插口中,从而实现整个主调函数。也可以通过改变插入的插头来改变主调函数的功能,从而改变主调函数的实现。使用回调函数可以实现同一个算法框架、不同的算法实现,最终达到算法的通用。 还是回到上面的例子,看看如何使用回调函数让程序更具灵活性、更加通用。假设现在要求在每条打印消息的前后还要打印一些符号作为装饰,为了完成这项任务,可以定义一个统一的打印消息函数。
// 定义函数指针类型
typedef void (* PRINTFUNC )(int);
// 统一的打印消息函数
void PrintMessage( int nSocre , PRINTFUNC pFunc )
{
cout<<"============"<<endl;
// 通过函数指针回调函数
(*pFunc)(nSocre);
cout<<"++++++++++++"<<endl;
}
// 主函数
int _tmain(int argc, _TCHAR* argv[])
{
int nScore = 22;
PRINTFUNC pFunc;
// 根据不同分数给pFunc赋值
// …
// 使用不同函数指针作为参数调用PrintMessage()函数
PrintMessage( nScore, pFunc );
return 0;
}
在这里,实际上是通过PrintMessage()函数定义了一个通用算法框架:首先打印页眉;然后通过函数指针回调函数,打印具体的消息;最后打印页脚。PrintMessage()函数只完成最基本的页眉、页脚的打印,至于具体的消息,则留给回调函数负责,这就像留下一个插口,等待某个具体的回调函数插头的插入。在主函数中,通过给PrintMessage()函数传递不同的打印函数的指针,就如同将某个插头插入PrintMessage()函数所留下的插口中。不同函数指针插头的插入,可以改变PrintMessage()函数中的回调函数,进而改变PrintMessage()函数的行为,以达到对其行为进行自定义的效果。回调函数与插头理论的关系如图11-1所示。
clip_image002
通过回调函数改变一个函数的行为、对其行为进行自定义的特性被广泛应用在通用算法的设计中。假设有这样一种情况,要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序等,为了使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现具体的排序逻辑,或者让库用于多种数据类型。这时,回调函数就可以派上用场了。可以在通用算法中定义好算法的框架,至于其中核心的算法逻辑,则留待回调函数去完成。用户可以通过不同的回调函数,轻松简单地实现各种算法,对算法进行自定义。