回调函数技术广泛运用在动态库开发(或者类库)中,是使软件模块化的重要手段。回调函数可以看作是一种通知和实现机制,用于控制反转,即模块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. pwnable.kr-leg-witeup

    做过后其实知道,是很简单的一段代码,也很容易看懂,看懂后计算key1.key2.key3之和即可. main 汇编: 嗯,看来keyx的返回值是r0了,详细分析r0值. key1: 在arm状态下,r ...

  2. 从零开始学spring cloud(五) -------- 将服务注册到Eureka上

    一.开发前准备工作: 官方文档地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.0.RELEASE/mul ...

  3. trunk端口配置错误导致环路

    端口下 switchport mode trunk spannning-tree portfast 上述两个命令同时执行将导致环路

  4. uni-app

    1 路由的跳转 uni.navigateTo({ url:'/pages/home/search' }); //非tabBar页面跳转 uni.switchTab({ url:"/pages ...

  5. 2018-2019-2 20165315《网络攻防技术》Exp6 信息搜集与漏洞扫描

    2018-2019-2 20165315<网络攻防技术>Exp6 信息搜集与漏洞扫描 目录 一.实验内容 二.实验步骤 1.各种搜索技巧的应用 2.DNS IP注册信息的查询 3.基本的扫 ...

  6. 关于Android UI 优化

    之前项目为了同时兼容tv和手机端的UI,使用了百分比布局来动态计算控件的宽高,这种适配方案只关心屏幕的宽高(分辨率),与屏幕的像素密度无关. 在新的项目里也使用了这种方案.但是由于项目的运行硬件计算能 ...

  7. 处理ajax数据;数据渲染

    当我们用ajax把数据拿到前台,该如何渲染到页面,有以下几种方式: 一:使用字符串拼接的方法 声明一个空变量,然后拼接 var st=""; st+="<div&g ...

  8. NumPy学习_02 ndarray基本操作

    1.算术运算符 它们只用于位置相同的元素之间,即为元素级的运算. 所得到的运算结果组成一个新的数组. 不用编写循环即可对数据执行批量运算.(矢量化) import numpy as np # 创建一个 ...

  9. spring boot生成的war包运行时出现java.lang.NullPointerException: null

    最近写了一个数据库同步的程序,见之前的博客,没有用到spring框架来集成,用的时纯Java代码.然后,项目经理要我把程序合到spring boot框架中,因为涉及到多数据源,时间又比较紧,同意我直接 ...

  10. log4j.properties配置与将异常输出到Log日志文件实例

    将异常输出到 log日志文件 实际项目中的使用: <dependencies> <dependency> <groupId>org.slf4j</groupI ...