C++ -- 回调函数

什么是回调函数,为什么要使用回调函数

函数的概念很好理解,就是把某个任务独立出来,封装在一起,然后给它取个名字,它可以有参数和返回值。
那么,回调函数是个什么呢?它和函数到底有何异同?既然已经有了函数,为啥还非要生出个回调函数来?我觉得要真正理解一个概念,必须要先理解它存在的意义,也就是它为什么要存在,它能带来什么方便之处。
首先,回调函数也是函数,就像白马也是马一样。它具有函数的所有特征,它可以有参数和返回值。其实,单独给出一个函数是看不出来它是不是回调函数的。回调函数区别于普通函数在于它的调用方式。

只有当某个函数(更确切的说是函数的指针)被作为参数,被另一个函数调用时,它才是回调函数。

为什么我们要把函数作为参数来调用呢,直接在函数体里面调用不好吗?
——这个问题问的好。在这个意义上,“把函数做成参数”和“把变量做成参数”目的是一致的,就是以不变应万变。形参是不变的,而实参是变的。唯一不同的是,普通的实参可以由计算机程序自动产生,而函数这种参数计算机程序是无法自己写出来的,因为函数本身就是程序(要是程序可以写程序的话那就是超级人工智能了),它必须由人来写。所以对于回调函数这种参数而言,它的“变”在于人有变或者人的需求有变。(函数作为参数,其实参是可变的)
C++ Primer里面举了个例子就是排序算法。为了使排序算法适应不同类型的数据,并且能够按各种要求进行排序,机智的人类把排序算法做成了一个模版(在标准模版库STL里),并且把判断两个数据之间的“大小”(也可以是“字节数”,或者其他某种可以比较的属性)这个任务(即函数)当成一个参数放在排序算法这个函数的参数列表里,而把它的具体实现就交给了使用排序算法的人。这个判断大小的函数就是一个回调函数。比如我们要给某个vector容器里面的单词进行排序,我们就可以声明一个排序算法:
[cpp] view plain copy
void stable_sort(vector::iterator iterBegin, vector::iterator iterEnd,bool(*isShorter)(const string&,const string&));
其中前面两个是普通参数,即迭代器(用于标记vector容器里面元素的位置),而第三个参数isShorter就是回调函数。根据不同需求isShorter可以有不同的实现,包括函数名。比如:

bool myIsShorter(const string &s1, const string &s2)  
{      
     return s1.size()<s2.size();  
}  
stable_sort(words.begin(),words.end(),myIsShorter);  

根据需求你也可以换一种方式来实现。注意,在传递myIsShorter这个参数时,只需写函数名,它代表函数指针。后面绝对不能加()和参数,绝对不能加()和参数,绝对不能加()和参数!
因为那样是调用函数的返回值!两者天壤之别!

在stable_sort运行时,当遇到需要比较两个单词的长短时,就会对myIsShorter进行调用,得到一个判断。在调用时,还必须把两个单词传递给isShorter供isShorter调用。所以说stable_sort调用了myIsShorter,而myIsShorter又调用了stable_sort给它的单词。它们相互调用。这就是“回调”这两个字的含义!

虽然说形参不变,实参可变,以不变应万变。但是作为实参有一点还是不能变的,那就是实参的数据类型不能变。比如void foo(int i)这个函数里的参数i可以取1也可以取2,但是它必须是整型的。 同样的,回调函数这种参数的类型也不能变。而函数的类型是由函数的参数类型和返回值类型决定的。比如前面提到的排序算法里面,isShorter这个回调函数的参数必须是两个const string类型,返回值必须是bool类型。所以在写回调函数时还是不能太任性,必须要查看一下调用该回调函数的函数的声明。

总之,所谓回调函数就是把函数当作参数使用。目的是使程序更加普适(正如活字印刷,把可能会“变”的字一个个分离开来,这样就可以任意组合,重复利用)。一般情况下,一个人的小规模程序用不着这种普适性,除非你想把它做成工具箱(比如游戏引擎),供他人使用。
其实实现这种普适性还有其他方法,比如对虚函数进行重写(或者用纯虚函数。Objective C里面所有函数都是虚函数,而协议相当于纯虚函数)。这样同一个函数就可以有不同的实现。不同的合作者之间就可以通过这种虚函数“协议”进行合作。

C/C++之回调函数

C++ 回调函数的那些事儿
1、基础知识
所谓回调,就是模块A要通过模块B的某个函数b()完成一定的功能,但是函数b()自己无法实现全部功能,需要反过头来调用模块A中的某个函数a()来完成,这个a()就是回调函数。如下图

①约定接口规范。
在模块B必须约定接口规范,也就是定义回调函数a()的函数原型
这里回调函数原型的定义最好遵循
typedef void (*SCT_XXX)(LPVOID lp ,const CBParamStruct & cbNode);
SCT_XXX是回调函数名称,lp是回调上下文,CBParamStruct是回调参数,一般由于要回调的参数不止一个,所以定义一个结构体比较方便。

②回调函数的注册。
为了让模块B知道自己将要使用的回调函数,必须有一个函数或语句来注册回调函数
注册回调函数的定义遵循void RCF_XXX(SCT_XXX pfn, LPVOID lp);
RCF_XXX是注册函数名,pfn是回调函数名称(是指针),lp是回调上下文。 一般在A模块初始化完B模块后调用,将A模块中定义的回调函数地址赋值给pfn,lp赋值为this。

③在模块A中要做的事情:
首先将回调函数声明成静态的,
static void CF_XXX(LPVOID lp, const CBParamStruct& cbNode);
函数的参数必须与B模块中回调函数原型的参数保持一致。
初始化B模块时,调用注册函数将模块A中声明的回调函数CF_XXX的地址传给pfn,即pfn=CF_XXX;(函数名称CF_XXX其实是个指针,指向回调函数的地址) 。

参考文献:
https://blog.csdn.net/llzhang_fly/article/details/80384734

猜你喜欢

转载自blog.csdn.net/xihuanzhi1854/article/details/90080926