回调函数技术广泛运用在动态库开发(或者类库)中,是使软件模块化的重要手段。回调函数可以看作是一种通知和实现机制,用于控制反转,即模块A调用模块B时,模块B完成一定任务后反过头来调用模块A。在被调用方代码改变(功能变化)时,调用者代码保持不变。这种方式对应了一个经典的软件设计原则--开闭原则:软件模块对修改关闭,对添加新代码开放,也就是说,增加新功能时,增加新代码,但不修改老代码。由于可以动态加载dll,只要新的dll(接口与旧的dll相同)覆盖老dll,就实现了系统升级。

一般地,代码调用有三种方式,见图示:

同步调用最常见,它是单向调用,调用方A阻塞等待调用方B完成后返回。

回调是双向调用,被调用接口被调用时随后会调用调用方的接口。如果把调用方A称为高层,调用方B称为底层,回调就是高层调用底层,底层再回过头来调用高层的过程。这也就是回调得名的原因吧。从这个过程来看,回调接口由被调用方提供,调用方定义相同的接口原型和实现,并注册到被调用方提供的登记入口上,回调的真正实现在调用方。

异步调用:类似于消息或事件通知机制,在接口的服务收到被调用的消息或事件时,会主动调用调用者的接口,方向正好与同步调用相反。当然,实现异步调用的代码比同步调用要复杂的多。

当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function,中间函数)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。见下面的图示:

可以看到,回调函数由应用层提供,与应用处于同一抽象层。

中间函数与回调函数是回调的两个必要部分,不过人们常常忽略了回调里的第三位要角,就是中间函数的调用者。在一般简单的例子中,这个调用者可以和程序的主函数等同起来,但在模块化编程中,也许这个调用者是程序中的某个模块,模块中的某个函数调用了中间函数。为了表示区别,把它成为起始函数。

很多文章在解释回调概念时,都会提到这么一句话:“if you call me,i will call you back”。回调不是中间函数、回调函数两方的互动,而是起始函数、中间函数、回调函数的三方互动。给中间函数传入什么样的回调函数,是起始函数决定的。有了这层理解,在代码中实现回调时才不容易混淆出错。

回调技术最主要的用途是解耦,假设有两个模块A和B,如果模块A依赖模块B,在A中调用B,依赖是单方向的,即A依赖于B,如果B又要通知A,B就对A产生了依赖,而且是双向依赖。现在我们要解耦,让依赖只是单方向的,做法是A依赖B,B依赖一个函数指针,这个指针可以来自于任何地方。这样B对A的调用就变为隐式调用,B对A不依赖,只是依赖一个函数接口,这个接口就是回调。这样就做到了不依赖实现,依赖接口。

上面的表述过程太过抽象,用生活中的例子来比喻回调技术吧。

打个比方,有家酒店不仅提供住宿服务还提供叫醒服务,叫醒服务内容是客服在规定的时间打电话到客房。旅客即可以选择睡到自己醒来,也可以选择睡到客服打电话叫醒自己。前者是睡觉(高层调用底层实现)醒来(高层自身实现)两个步骤,后者是登记叫醒服务(高层调用底层注册回调的接口),睡觉(高层调用底层实现)呼叫(底层通知高层)醒来(高层自身实现)三个步骤。旅客要享受叫醒服务,需要先告诉酒店,这个告诉的动作,就叫登记回调函数。多出来的登记动作和通知动作就是回调与一般的函数调用最大的不同。

回调机制提供了巨大的灵活性,比如上边的叫醒服务,如果酒店不仅提供打客房电话,还可以是工作人员敲房门,登记了不同的服务内容,享受到的服务也不同,这就是灵活性的好处。举一个编程上的例子,Win32 SDK编程中,操作系统提供了注册窗口过程函数的接口,不同的软件实现自己不同的窗口过程,并注册到操作系统,软件运行的行为也就此不同了。

回调机制落地,代码实现

1.最简单的C语言实现

void callback(int a)
{
cout<<"callback called with para="<<a<<endl;
} typedef void (*pfunc)(int);
void caller(pfunc p)
{
(*p)(1);
} int main(int argc, char* argv[])
{
caller(&callback);
}

2. C++  静态成员函数方式实现(公司实际项目中大量使用)

3. Sink方式

参考:

被误读了千年的回调函数--写得实在太好了

C++回调机制实现(转)

回调函数之同步调用、回调、异步调用

异步消息的传递-回调机制

C++的回调机制

C++回调:利用Sink 

C++面试基础之回调的更多相关文章

  1. 快速掌握JavaScript面试基础知识(三)

    译者按: 总结了大量JavaScript基本知识点,很有用! 原文: The Definitive JavaScript Handbook for your next developer interv ...

  2. 一些iOS面试基础题总结

    一些iOS面试基础题总结 目录 多线程 AutoLayout objc_msgSend Runtime 消息转发 Category NSObject 与 objc_class Runloop Auto ...

  3. 10个经典的C语言面试基础算法及代码

    10个经典的C语言面试基础算法及代码作者:码农网 – 小峰 原文地址:http://www.codeceo.com/article/10-c-interview-algorithm.html 算法是一 ...

  4. iOS 面试基础题目

    转载: iOS 面试基础题目 题目来自博客:面试百度的记录,有些问题我能回答一下,不能回答的或有更好的回答我放个相关链接供参考. 1面 Objective C runtime library:Obje ...

  5. 快速掌握JavaScript面试基础知识(二)

    译者按: 总结了大量JavaScript基本知识点,很有用! 原文: The Definitive JavaScript Handbook for your next developer interv ...

  6. 前端读者 | 前端面试基础手册(HTML+CSS)

    本文来自@羯瑞:希望前端面试基础手册能帮助要找工作的前端小伙伴~~ HTML 前端需要注意哪些SEO? 合理的title.description.keywords:搜索对着三项的权重逐个减小,titl ...

  7. 前端面试基础题:Ajax原理

    Ajax 的原理简单来说是在⽤户和服务器之间加了—个中间层( AJAX 引擎),通过XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后⽤ javascrip t 来操作 D ...

  8. Java 笔试面试 基础篇 一

    1. Java 基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法, 线程的语法,集合的语法,io 的语法,虚拟机方面的语法. 1.一个".java& ...

  9. java面试基础题(三)

    程序员面试之九阴真经 谈谈final, finally, finalize的区别: final:::修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承.因此 ...

随机推荐

  1. JS-jquery对象和dom对象的属性操作区别

    <label class="">时间1</label> <label class="">时间2</label> ...

  2. centos 7 上Hive-2.1.1的安装与基本操作

    首先安装mysql 1.       在线安装mysql a)        yum install mysql b)        yum install mysql-devel c)        ...

  3. Redis分布式缓存

    Redis 主 slave 数据库优化-- 加inex, 分区 JVM调优--参数设置,比如偏向于计算的如何设置? 线程池:queue放满了之后,有什么方式能让他不拒绝掉?blockqueue就等在那 ...

  4. DOCKER学习 docker

    DOCKER只能安装到LIUX系列机器上 如果WINDOWS想安装必须通过虚拟机来完成. 比如用VM,VBOX等 安装之前需要用ROOT账户 su 安装DOCKER (CE是个人版本,EE是企业版本) ...

  5. 微信小程序发送ajax

    微信小程序通过 wx.request发送ajax请求 1. GET wx.request({ url: app.globalData.pubSiteUrl + 'user-information/ge ...

  6. Unable to resolve target 'android-XX' 类似的错误。

    解决办法: 打开项目代码中的Project target,将 复制代码 代码如下: # Project target.   target=android-7 修改为你当前支持的ADK版本即可. 参考: ...

  7. 使用QML绘制界面

    1 使用QML设计登录界面 https://www.cnblogs.com/bhlsheji/p/5324871.html 2 使用QML实现下拉列表框  https://blog.csdn.net/ ...

  8. java Quartz任务调度器

    1.quarz对java1.5实现的简单调度做了封装 /**     * quartz对任务调度进了高度抽象: 1调度器:2任务:3触发器     * Job接口(任务):定义需要调度的任务     ...

  9. Chapter5_初始化与清理_成员初始化

    在java中,成员初始化在使用之前应该都要保证已经完成初始化.对于在方法体中的局部变量,如果没有使用指定初始化的方法对成员变量进行初始化,编译器会提示一个错误.而对于类的数据成员,编译器会对这些成员赋 ...

  10. AHOI——Day1个人感悟

    今天,是个bilibili的日子.(嗯?什么意思?) 洛谷已经尽力了: 于是我带着洛谷的祝福,来到了AHOI的考场--合肥一中. 其实我是考完才签到的,我一大早五点多就起来了,到考场后,在肯德基吃了早 ...