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

    HAL和LL库 HAL是ST为了实现代码在ST家族的MCU上的移植性,推出的一个库,称为硬件抽象层,很明显,这样做将会牺牲存储资源,所以项目最后的代码比较冗余,且运行效率大大降低,运行速度受制于fla ...

  2. Redux 检测状态树变更

    一 概述 Redux只是检测引用是否改变. 如果状态树的某个值是对象.数组等,在reducer中需要生成一个新对象.新数组,才能被Redux检测到变更. let fruits = ['apple',' ...

  3. python的requests模块参数详解

    import requests print(dir(requests)) # 1.方法 # ['ConnectTimeout', 'ConnectionError', 'DependencyWarni ...

  4. WPF中textBlock 变色功能

    <Window.Resources> <Storyboard x:Key="OnLoaded" RepeatBehavior="Forever" ...

  5. css与dom的渲染与解析

    js阻塞文档渲染与解析那么css呢? 结论一.css:阻塞渲染,不阻塞dom解析 <head> <script> document.addEventListener('DOMC ...

  6. 手工脱壳之FSG压缩壳-IAT表修复

    目录 一.工具及壳介绍 二.脱壳 2.1.单步跟踪脱壳 2.2.IAT修复 三.程序脱壳后运行截图 四.个人总结 五.附件 一.工具及壳介绍 使用工具:Ollydbg.PEID.ImportREC.L ...

  7. JavaSE基础知识(5)—面向对象(5.5 this和super关键字)

    一.this关键字 1.说明 this关键字代表当前类的对象,可以访问本类的属性.方法.构造器注意:谁调用该方法,则this就指谁 2.语法 访问属性: this.属性名 = 值; System.ou ...

  8. Django 的认识,面试题

    Django 的认识,面试题 1. 对Django的认识? #1.Django是走大而全的方向,它最出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构.以及全 ...

  9. 解决Jenkins用shell脚本部署后,Jenkins自动杀掉启衍生出来的守护进程

    Jenkins部署java项目遇到的问题: 1.Jenkins执行构建后,需要手动执行startup.sh,站点才能正常访问 产生原因: shell脚本发布时,会衍生进程,Jenkins默认会自动杀掉 ...

  10. c++11新标准for循环和lambda表达式

    :first-child { margin-top: 0px; } .markdown-preview:not([data-use-github-style]) h1, .markdown-previ ...