
- 意 思
- 通过函数指针调用的函数
- 作 用
- 对特定的事件或条件进行响应
- 中文名
- 回调函数
- 外文名
- Callback Functions
-
回调函数
2019-09-09 17:36:28回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定...回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
其实回调就是一种利用函数指针进行函数调用的过程.
为什么要用回调呢?比如我要写一个子模块给你用,来接收远程socket发来的命令.当我接收到命令后,需要调用你的主模块的函数, 来进行相应的处理.但是我不知道你要用哪个函数来处理这个命令,我也不知道你的主模块是什么.cpp或者.h,或者说,我根本不用关心你在主模块里怎么处理它,也不应该关心用什么函数处理它......怎么办呢?使用回调!
—— lone wolf
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。
—— 某专家
回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可以在回调函数里完成你要做的事。
—— 绿叶
回调函数是应用程序提供给Windows系统DLL或其它DLL调用的函数,一般用于截获消息、获取系统信息或处理异步事件。应用程序把回调函数的地址指针告诉DLL,而DLL在适当的时候会调用该函数。回调函数必须遵守事先规定好的参数格式和传递方式,否则DLL一调用它就会引起程序或系统的崩溃。通常情况下,回调函数采用标准WindowsAPI的调用方式,即__stdcall,当然,DLL编制者可以自己定义调用方式,但客户程序也必须遵守相同的规定。在__stdcall方式下,函数的参数按从右到左的顺序压入堆栈,除了明确指明是指针或引用外,参数都按值传递,函数返回之前自己负责把参数从堆栈中弹出。
—— jufengfeng
看了这么多的资料,我只将每位的定义总结一下就一句话:
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己写的一个函数(这个函数就是回调函数)的地址作为参数传递给那个函数。回调其实就是提供使用某模块的一种方法。回调函数就好比是一个中断处理函数。
3. 回调函数的作用?应该在什么情况下使用?
用过STL的人都知道,在STL中众多算法和程序都用到回调函数,这实现了一种策略。只要任何符合我的标准的函数和计算都可以用我这个公式。你可以实现各种各样的回调函数,只要符合我的格式就能用。就上面的程序来说,你只要函数格式符合cllback第二个参数的格式不论你给别人做饭、铺床叠被都可以正常工作。这就是回调的作用,把回调实现留给别人。这是一个用法。
有一位朋友用分层的概念来解释了回调机制:callback函数为B层,main函数和print函数为A层,A层调用了B层的回调函数callmeback,而B层的回调函数调用了A层的实现函数print。说白了B层就是一个接口。有一位朋友用分层的概念来解释了回调机制:callback函数为B层,main函数和print函数为A层,A层调用了B层的回调函数callmeback,而B层的回调函数调用了A层的实现函数print。说白了B层就是一个接口。
最后要注意的是:回调函数要么是全局函数,要么是静态函数!
#include <stdio.h> //回调函数 int ADD(int (*callback)(int,int), int a, int b){ return (*callback)(a,b);//此处回调add函数... } //普通函数 int add(int a, int b){ return a + b; } int main(void){ printf("%d\n",add(1,2)); printf("%d\n",ADD(add,1,2)); return 0; }
C回调函数
C++回调函数扩展自C回调函数,要想理解C++回调函数,先要理解C回调函数。我们通过一个实例来讲解C回调函数的使用方法。
//callbackTest.c //1.定义函数onHeight(回调函数) //@onHeight 函数名 //@height 参数 //@contex 上下文 void onHeight(double height, void* contex) { sprint("current height is %lf",height); } //2.定义onHeight函数的原型 //@CallbackFun 指向函数的指针类型 //@height 回调参数,当有多个参数时,可以定义一个结构体 //@contex 回调上下文,在C中一般传入nullptr,在C++中可传入对象指针 typedef void (*CallbackFun)(double height, void* contex); //3.定义注册回调函数 //@registHeightCallback 注册函数名 //@callback 回调函数原型 //@contex 回调上下文 void registHeightCallback(CallbackFun callback, void* contex) { double h=100; callback(h,nullptr); } //4.main函数 void main() { //注册onHeight函数,即通过registHeightCallback的参数将onHeight函数指针 //传入给registHeightCallback函数,在registHeightCallback函数中调用 //callback就相当于调用onHeight函数。 registHeightCallback(onHeight,nullptr); }
程序的运行结果是:
current height is 100
很多时候,注册的时候并不调用回调函数,而是在其他函数中调用,那我们可以定义一个CallbackFun全局指针变量,在注册的时候将函数指针赋给它,在要调用的调用它。如//定义全局指针变量 CallbackFun* m_pCallback; //定义注册回调函数 void registHeightCallback(CallbackFun callback, void* contex) { m_pCallback = callback; } //定义调用函数 void printHeightFun(double height) { m_pCallback(height,nullptr); } //main函数 void main() { //注册回调函数onHeight registHeightCallback(onHeight,nullptr); //打印height double h=99; printHeightFun(99); }
程序的运行结果是:
current height is 99C++回调函数
C++回调函数扩展自C,与C略有不同的是,C++可以使用全局函数和静态函数作为回调函数。考虑到全局函数会破坏封装性,所以一般都用静态成员函数。故除了理解函数指针,还要理解静态成员函数,具体一点是在静态成员函数中访问非静态成员函数的方法,因为我们很可能需要获取静态成员函数中的数据。
使用场景描述
比如说你使用了别人提供的sdk,这个sdk可能来自供应商,也有可能来自你的同事,他就提供给你一个注册回调函接口,比如就下面这个,你可以通过回调函数获取到height(某种传感器的实时返回的数据),你要怎么做?
C++回调函数定义
//CallbackFun类型 //@CallbackFun 指向函数的指针类型 //@height 回调参数,当有多个参数时,可以定义一个结构体 //@contex 回调上下文,在C中一般传入nullptr,在C++中可传入对象指针 typedef void (*CallbackFun)(double height, void* contex); //注册回调函数接口 //@registHeightCallback 注册函数名 //@callback 回调函数原型 //@contex 回调上下文 void registHeightCallback(CallbackFun callback, void* contex)
首先,你要定义一个静态成员函数并注册。
//sensorTest.cpp //接收数据类class Sensor class Sensor{ public: Sensor(){} ~Sensor(){} //定义回调函数onHeight static void onHeight(double height, void* contex) { cout << "current height is " << height << endl; } //定义注册回调函数 void registCallback() { registHeightCallback(onHeight, this); } } //main 函数 void main() { Sensor sens; sens.registCallback(); }
运行程序,我们发现控制台一直在打印
current height is **
说明我们的回调函数正确实现了。到这一步不难,只要掌握基本的回调函数概念都能实现。
现在我们有这样一种情况,我们有另外一个类,要在这个类里面实时打印获取的数据,要怎么做呢?静态成员函数访问非静态成员函数的方法
我们知道静态成员函数中是只能出现静态变量和静态函数的,但是有些时候真的需要访问非静态成员函数或变量,比如我上面说的那种情况。让我们先来实现对同一个类中的非静态成员函数的访问。
修改class Sensor如下//接收数据类class Sensor class Sensor{ public: Sensor(){} ~Sensor(){} //定义回调函数onHeight static void onHeight(double height, void* contex) { //cout << "current height is " << height << endl; Sensor* sen = (Sensor*)contex; if(sen) //注意判断sen是否有效 sen->getHeight(height); } //定义注册回调函数 void registCallback() { registHeightCallback(onHeight, this); } //新增的成员函数 void getHeight(double height) { cout << "current height is " << height << endl; } }
如此修改之后,得到与修改前一样的效果(实时打印height),关键点在于注册回调函数的时候将Sensor对象的指针传给了contex,在回调函数中又将contex转换为Sensor对象指针,所以能调用普通函数。
同理,如果注册时传入某一个对象的指针,就可以在回调函数中对该对象进行操作,这就是我们可以在一个对象中回调另一个对象的思想。回调对象
现在开始解决之前提出的问题,本质是不变的,回调是指针传递,可以是函数指针,也可以是对象指针。
//先定义一个类class DataPrint //打印数据类class DataPrint class DataPrint{ public: DataPrint(){} ~DataPrint(){} void printHeight(double height) { cout << "print height is " << height << endl; } } //要在类Sensor中增加DataPrint的指针和一个DataPrint指针赋值函数,class Sensor修改为 //接收数据类class Sensor class Sensor{ public: Sensor(){} ~Sensor(){} //定义回调函数onHeight static void onHeight(double height, void* contex) { DataPrint* dp = (DataPrint*)contex; if(dp) //注意判断dp是否有效 dp->printHeight(height); } //定义注册回调函数 void registCallback() { registHeightCallback(onHeight, m_pDataPrint ); } //新增的成员函数 void getHeight(double height) { //cout << "current height is " << height << endl; } void setDataPrint(DataPrint* dp) { m_pDataPrint = dp; } private: DataPrint* m_pDataPrint; } //main主函数 void main() { DataPrint* dp=new DataPrint(); Sensor* sens=new Sensor(); //注意这两句的顺序不能颠倒 sens->setDataPrint(dp); sens->registCallback(); }
这样就能实现在另一个类中取得回调函数的数据,如果无法保证DataPrint的实例化一定在Sensor之前,我们可以这样做
//先定义一个类class DataPrint //打印数据类class DataPrint class DataPrint{ public: DataPrint(){} ~DataPrint(){} void printHeight(double height) { cout << "print height is " << height << endl; } } //要在类Sensor中增加DataPrint的指针和一个DataPrint指针赋值函数,class Sensor修改为 //接收数据类class Sensor class Sensor{ public: Sensor(){} ~Sensor(){} //定义回调函数onHeight static void onHeight(double height, void* contex) { Sensor* sen= (Sensor*)contex; if(sen) //注意判断sen是否有效 sen->getHeight(height); } //定义注册回调函数 void registCallback() { registHeightCallback(onHeight, m_pDataPrint ); } //新增的成员函数 void getHeight(double height) { if(m_pDataPrint ) m_pDataPrint ->printHeight(height); } void setDataPrint(DataPrint* dp) { m_pDataPrint = dp; } private: DataPrint* m_pDataPrint; } //main主函数 void main() { DataPrint* dp=new DataPrint(); Sensor* sens=new Sensor(); //注意这两句的顺序可以颠倒 sens->setDataPrint(dp); sens->registCallback(); }
两个的区别是一个直接注册指定类的对象指针,另一个注册当前类的对象指针,间接调用另一个类的对象指针。
更复杂的讨论
刚才讨论的问题稍微复杂一点了,不过应该也容易理解,但是我们在实际项目中遇到的情况可能比这个复杂。比如在有层次的软件工程中,回调函数在底层,显示数据的类在上层,我们要如何把底层的数据显示到上层去?容易想到的是上层调用底层,如开个timer刷新,但这是不对的,你无法做到实时调用,你需要的是一个异步的机制,即底层一发生上层就能接收到。
怎么做呢?还是一样的道理,把上层的类的对象指针传给底层,这时底层需要包含上层的头文件,但这是不对的,上层已经包含了底层的头文件,它们不能互相包含,如何解决这个问题?那就要用到C++继承的特性,首先在底层定义一个基类,然后在上层继承它,在上层实例化这个继承类后,将其指针设置给底层,底层对该指针的操作就是对继承类对象的操作,以此实现数据的传递。参考:
[1] https://www.jianshu.com/p/26784d962f58
[2] https://blog.csdn.net/weixin_40237626/article/details/82801409
[3] https://blog.csdn.net/sinat_38183777/article/details/83958887
[4] https://blog.csdn.net/yidu_fanchen/article/details/80513359
-
JS中回调函数(callback)理解
2018-07-24 17:46:16今天有个刚入行的小兄弟问到了回调函数,讲解了一番以后觉得不能白讲,得把这些东西记下来,虽然很基础。。。 介绍 首先从英文介绍开始 A callback is a function that is passed as an argument to another ...前言
今天有个小兄弟问到了回调函数,讲解了一番以后觉得不能白讲,得把这些东西记下来,虽然很基础。。。
介绍
首先从英文介绍开始
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
这里只是js里的解释。
开始
字面上理解下来就是,回调就是一个函数的调用过程。那么就从理解这个调用过程开始吧。函数a有一个参数,这个参数是个函数b,当函数a执行完以后执行函数b。那么这个过程就叫回调。
其实中文也很好理解:回调,回调,就是回头调用的意思。函数a的事先干完,回头再调用函数b。
举个现实的例子:约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息,我很担心你。”对不,然后你女朋友回家以后还真给你发了条信息。小伙子,你有戏了。
其实这就是一个回调的过程。你留了个函数b(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是函数a。她必须先回到家以后,函数a的内容执行完了,再执行函数b,然后你就收到一条信息了。
这里必须清楚一点:函数b是你以参数形式传给函数a的,那么函数b就叫回调函数。
也许有人有疑问了:一定要以参数形式传过去吗,我不可以直接在函数a里面调用函数b吗?确实可以。求解中。
解惑:如果你直接在函数a里调用的话,那么这个回调函数就被限制死了。但是使用函数做参数就有下面的好处:当你a(b)的时候函数b就成了回调函数,而你还可以a(c)这个时候,函数c就成了回调函数。如果你写成了function a(){...;b();}就失去了变量的灵活性。
上一段代码大家可以跑一下:
function a(callback) { alert("我是parent函数a!"); alert("调用回调函数"); callback(); } function b(){ alert("我是回调函数b"); } function c(){ alert("我是回调函数c"); } function test() { a(b); a(c); }
根据代码示例加深理解
问题:
现在有函数如下定义:
function A(a,callback){
....
}
function B(){
....
}
则可以有如下调用
A(a,B);来实现回调。
现在我希望传给B一个参数c,即实现类似于: A(a,B(c)); 的效果,应该如何实现?function A(a,callback){ var b=callback; alert(a+b); } function B(c){ return (-c); } A(4,B(3))
根据jquery的ajax请求常见的场景再去写一个示例:
我包装了jQuery的ajax方法:
function doAjax(u,param,callback){ $.ajax({ type:'POST', url:u, data:param, success:callback }); } function showAlert(data){ alert(data); }
比如这样调用 doAjax("url","id=1",showAlert);
$.ajax 在success后,会返回一个data到showAlert中,显示出来 ,没有问题.
可是,当我想多传一个参数给showAlert时怎么写?
写成doAjax("server.php","id=12&type=1",showAlert("hi",data))、或者把上面的success:callback 写成success:callback(msg,data)显然都不行,如之奈何?其实很简单,很多方法都定义了回调函数,回调函数也是函数,就是说不管怎么传,只需要是个函数类型即可。写法如下:
方式1, doAjax(参数1,参数2,function(request,opts){ callback(request,opts,agrs); }); function callback(request,opts,args){ }; 方式2, var args=N; doAjax(参数1,参数2,function(request,opts){ var X=N; 回调函数代码块.. 和以上几乎一样,看个人编码方式选择。 });
再看一个从父函数传递给回调函数的示例
function e(m,n,Callback){ var d = m+n; alert("一个从父函数e 产生的参数将要被传递给回调函数 ,这个参数是:"+d); //这里才写你想调用的函数---参数要正确 Callback(d); } function callback(data){ alert("我是回调函数,我的名字叫:callback ,我接收到来自父函数的参数,参数是:"+data); } e(1,2,callback)
最后
感谢各位的阅读,如有问题麻烦及时指正出来。
-
回调函数
2006-12-04 18:02:00一,回调函数我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)...一,回调函数
我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。
而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALLBACK(相当于FAR PASCAL),这主要是说明该函数的调用方式。
至于钩子函数,只是回调函数的一个特例。习惯上把与SetWindowsHookEx函数一起使用的回调函数称为钩子函数。也有人把利用VirtualQueryEx安装的函数称为钩子函数,不过这种叫法不太流行。
也可以这样,更容易理解:回调函数就好像是一个中断处理函数,系统在符合你设定的条件时自动调用。为此,你需要做三件事:
1.声明;
2.定义;
3.设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。
声明和定义时应注意:回调函数由系统调用,所以可以认为它属于WINDOWS系统,不要把它当作你的某个类的成员函数。
二,回调函数、消息和事件例程
调用(calling)机制从汇编时代起已经大量使用:准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。借堆栈保护现场真是一项绝妙的发明,它使调用者和被调者可以互不相识,于是才有了后来的函数和构件。此调用机制并非完美。回调函数就是一例。函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。例如,写一个快速排序函数供他人调用,其中必包含比较大小。麻烦来了:此时并不知要比较的是何类数据--整数、浮点数、字符串?于是只好为每类数据制作一个不同的排序函数。更通行的办法是在函数参数中列一个回调函数地址,并通知调用者:君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值说明比较结果。排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。被调用者回头调用调用者的函数(够咬嘴的),故称其为回调(callback)。
回调函数使程序结构乱了许多。Windows API 函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。
无论何种事物,能以树形结构单向描述毕竟让人舒服些。如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。
Windows 系统还包含着另一种更为广泛的回调机制,即消息机制。消息本是 Windows 的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。消息所附带的 WParam 和 LParam 相当于函数的参数,只不过比普通参数更通用一些。应用程序可以主动发送消息,更多情况下是坐等 Windows 发送消息。一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。操作系统本是为应用程序服务,由应用程序来调用。而应用程序一旦启动,却要反过来等待操作系统的调用。这分明也是一种回调,或者说是一种广义回调。其实,应用程序之间也可以形成这种回调。假如进程 B 收到进程 A 发来的消息,启动了一段代码,其中又向进程 A 发送消息,这就形成了回调。这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。若是故意编写成此递归调用,并设好终止条件,倒是很有意思。但这种程序结构太隐蔽,除非十分必要,还是不用为好。
利用消息也可以构成狭义回调。上面所举排序函数一例,可以把回调函数地址换成窗口 handle。如此,当需要比较数据大小时,不是去调用回调函数,而是借 API 函数 SendMessage 向指定窗口发送消息。收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。所实现的功能与回调函数并无不同。当然,此例中改为消息纯属画蛇添脚,反倒把程序搞得很慢。但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。假如回调函数中包含文件处理之类的低速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。一个替代办法是借 API 函数 PostMessage 发送一个异步消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。
如今我们是活在一个 object 时代。只要与编程有关,无论何事都离不开 object。但 object 并未消除回调,反而把它发扬光大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。应用程序要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理例程,以备构件代码来调用。何谓事件?它不过是一个指向事件例程的地址,与回调函数地址没什么区别。
不过,此种回调方式比传统回调函数要高明许多。首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。再者,地址是一个危险的东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如 VB 和 Java),其代价是降低了程序效率。事件例程(?)使编程者无需直接操作地址,但并不会使程序减速。
(例程似乎是进程的台湾翻译。)
三,精妙比喻:回调函数还真有点像您随身带的BP机:告诉别人号码,在它有事情时Call您。回调用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调,例如作为一个驱动,是一个底层,他在收到一个数据时,除了完成本层的处理工作外,还将进行回调,将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。其实回调和API非常接近,他们的共性都是跨层调用的函数。但区别是API是低层提供给高层的调用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,对于低层他是未知的,必须由高层进行安装,这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这个回调,在需要调用时,只需引用这个函数指针和相关的参数指针。??? 其实:回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的触发下,低层通过该函数指针调用高层那个函数。
四
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础。
对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。
在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object Pascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。
Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。
对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。
下面我们集中比较具有代表性的语言(C、Object Pascal)和架构(CORBA)来分析回调的实现方式、具体作用等。
2 过程语言中的回调(C)
2.1 函数指针
回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子:void Func(char *s);// 函数原型
void (*pFunc) (char *);//函数指针可以看出,函数的定义和函数指针的定义非常类似。
一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。
typedef void(*pcb)(char *);回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。
被调函数的例子:
void GetCallBack(pcb callback)
{
/*do something*/
}
用户在调用上面的函数时,需要自己实现一个pcb类型的回调函数:
void fCallback(char *s)
{
/* do something */
}
然后,就可以直接把fCallback当作一个变量传递给GetCallBack,
GetCallBack(fCallback);如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。
2.2 参数传递规则
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:
// 被调用函数是以int为参数,以int为返回值
__stdcall int callee(int);// 调用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int));// 在p中企图存储被调用函数地址的非法操作
__cdecl int(*p)(int) = callee; // 出错指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列
2.3 应用举例
C语言的标准库函数中很多地方就采用了回调函数来让用户定制处理过程。如常用的快速排序函数、二分搜索函数等。快速排序函数原型:
void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
二分搜索函数原型:
void *bsearch(const void *key, const void *base, size_t nelem,
size_t width, int (_USERENTRY *fcmp)(const void *, const void *));其中fcmp就是一个回调函数的变量。
下面给出一个具体的例子:
#i nclude
#i ncludeint sort_function( const void *a, const void *b);
int list[5] = { 54, 21, 11, 67, 22 };int main(void)
{
int x;qsort((void *)list, 5, sizeof(list[0]), sort_function);
for (x = 0; x < 5; x++)
printf("%in", list[x]);
return 0;
}int sort_function( const void *a, const void *b)
{
return *(int*)a-*(int*)b;
}2.4 面向对象语言中的回调(Delphi)
Dephi与C++一样,为了保持与过程语言Pascal的兼容性,它在引入面向对象机制的同时,保留了以前的结构化特性。因此,对回调的实现,也有两种截然不同的模式,一种是结构化的函数回调模式,一种是面向对象的接口模式。
-
深入理解:回调函数
2019-06-21 15:36:09关于回调函数到底是什么,已经困扰了我很久了~ 在知乎上看到几位大神的帖子,才恍然大悟 作者:no.body 链接:https://www.zhihu.com/question/19801131/answer/27459821 来源:知乎 作者:常溪玲 链接...关于回调函数到底是什么,已经困扰了我很久了~
在知乎上看到几位大神的帖子,才恍然大悟
作者:no.body
链接:https://www.zhihu.com/question/19801131/answer/27459821
来源:知乎作者:常溪玲
链接:https://www.zhihu.com/question/19801131/answer/13005983
来源:知乎首先要明确的一点是,函数也可以作为函数的参数来传递
好了,有了这个概念我们来说明回调函数到底是怎么回事
首先至少要有 3 种类型的函数
-
主函数:相当于整个程序的引擎,调度各个函数按序执行
-
回调函数:一个独立的功能函数,如写文件函数
-
中间函数:一个介于主函数和回调函数之间的函数,登记回调函数,通知主函数,起到一个桥梁的作用
接下来我们一起来看下示例代码:
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- # 回调函数1 def callback1(x): return x * 2 # 回调函数2 def callback2(x): return x ** 2 # 中间函数 def middle(x, func): return 100 + func(x) # 主函数 def main(): x = 1 a = middle(x, callback1) print(a) b = middle(x, callback2) print(b) c = middle(x, lambda x: x + 2) print(c) main()
运行结果:
102 101 103
代码看懂以后我们接下来分析一下代码的逻辑
首先我们在主函数执行过程中需要用到一个功能
x * 2
,而callback1
函数就提供这个功能,我们就把这个函数称之为回调函数
(至于为什么要叫“回调函数”,不能叫别的呢?其实这只是人为规定的一个名字。你也可以叫“极客点儿专属函数”,但是到时候你又会问为什么要叫“极客点儿专属函数”,它特么的总的有个名字吧!所以叫“回调函数”就是王八的屁股:规定!)。这时候我们的
主函数
要调用它,但是有的时候在开发过程中遇到需要写硬盘的操作,这时候我们为了避免程序的阻塞,就需要用到异步 I/O。就是你自己先写着玩儿,爸爸去干别的事情去了,等你完事儿再来通知我。正是因为这种机制所以得有一个登记回调函数
和通知主函数执行完成
的“地方”,这个地方就是中间函数
。有上述内容我们就可以推导出回调函数执行的流程了:
-
主函数需要调用回调函数
-
中间函数登记回调函数
-
触发回调函数事件
-
调用回调函数
-
响应回调事件
回调实际上有两种:
阻塞式回调
和延迟式回调
也可以叫做同步回调
和异步回调
两者的区别在于:
在阻塞式回调里,回调函数的调用一定发生在主函数返回之前
在延迟式回调里,回调函数的调用有可能是在起始函数返回之后
上述示例均为 同步回调,异步需要用到多进程、多线程、协程这些概念,下次有机会再说
最后用一个例子说明一下到底说明是回调函数:
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。
在这个例子里,你的电话号码就叫
回调函数
,你把电话留给店员就叫登记回调函数
,店里后来有货了叫做触发回调事件
,店员给你打电话叫做调用回调函数
,你到店里去取货叫做响应回调事件
。 -
-
C++中回调函数使用详解(普通函数作为回调函数以及类的成员函数作为回调函数)
2018-11-06 16:17:06在C++编程,尤其时写Qt程序时,需要大量使用回调函数,在网上也有很多大牛对回调函数的使用进行了讲解,但是很多都是针对某一个或者两个特定的类型的。我在这篇文章中对网上关于回调函数的使用进行了归纳总结,并且...
-
C++ STL(十七):常用集合算法(set_intersection、set_union、set_difference)
-
SSH LINUX 免密码登录失败原因踩坑
-
希望一辈子只做一个专业
-
app软件测试全栈系列精品课程
-
vscode 通知: Error loading workspace: err: exit status 1: stderr: build flag -mod=readonly only valid
-
MySQL Router 实现高可用、负载均衡、读写分离
-
com.termux_102.apk
-
MATLAB最邻近插值算法的实现
-
应广105G雾化片驱动.rar
-
设计模式详解:Composite(组合模式)
-
DVD数码相册大师.rar
-
【干货】汇编修改,优先考虑简短的汇编字节, B0 08 mov al,0x8
-
sakura主题细节修改(3):主页技术栏类别添加
-
MaxScale 实现 MySQL 读写分离与负载均衡
-
第一章 工作原理、语言标准和规范(1.4~1.6)
-
fritzing.0.9.3b.32.pc.zip
-
2016通信中级互联网真题.pdf
-
迷你 Vue 原理.png
-
基于电商业务的全链路数据中台落地方案(全渠道、全环节、全流程)
-
计算机常见复试问题.pdf