C++ 回调函数详解

1、什么是回调函数
回调函数本质上也是普通函数,只是调用机制有所区别——首先通过传参的形式将该函数的地址传递给其他函数,然后在其他函数中通过函数指针调用该函数。在其他函数中通过函数指针调用该函数的过程称为回调,而作为被调用的该函数则被称为回调函数。有人可能会疑惑何必多此一举,直接在其他函数中调用该函数就好,为何一定要通过指针中转。
2、为什么需要回调函数
这就不得不提到项目联合开发带来的必然后果——接口的兼容性问题。举个超级简单的例子:程序员小A和程序员小B联合开发一个项目,要求小A开发的函数必须为小B开发的函数提供灵活可用的接口。 假如小A小B是好朋友,他们天天在一起,而且也不考虑开发效率问题,那就串行方式一起开发好了。如下例所示,在同一个文件中,小A先负责开发Add()函数,小B后负责开发add()函数,小A需要调用小B的函数,即小A为小B提供了一个接口,通过这个接口小B可以随意通过更改add()函数,间接更改了Add()函数。 由于小A在使用小B的函数前小B还没有实现,所以他们需要先一起商量一下小B需要实现的函数名称、函数返回值、参数类型,然后小A在调用前先声明。小A在完成自己的工作后就可以休息了,然后小B就按照之前商量好的定义函数,对于函数体中具体怎么写完全由小B自由发挥了:

延续上文的故事,但现在场景变了。为了提高效率需要小A和小B并行操作,但工作类容没有变。显然他们需要在两个文件中完成各自的任务。小A同样需要声明小B的函数。对于现实中的项目开发而言,这种方式最为常见。

看到这里你肯定回想,这也太简单了,完全没有啰嗦的必要,其实我只是想一步步引出为什么要有回调函数的存在。通过上面的例子我们不难发现,小A为小B提供了接口,使得小B定义的函数能够在合适的时机被调用,这里必须有一个前提是小A和小B要提前商量好小B所需实现的函数的名称、返回值、参数列表。 而现在我们延续上面的故事,但场景又变了,小A和小B完全不认识,甚至小A不知道小B要用自己的接口,那么再按照上面的思路无论对小A还是小B来说都不那么友好了。首先对于小A来说,每次调用小B定义的函数前都需要声明,很是麻烦。而对于小B来说,由于小A定义的函数函数体对于小B来说是封装看不见的,小B对于自己定义的函数如何传到小A定义的函数中去很不直观,而且小B定义的函数名称、返回值类型、参数列表必须与小A在之前做出的声明保持完全一致。 因而,回调函数闪亮登场。如下例所示,小A不再需要每次调用小B定义的函数之前都要进行声明,小A只需要提供一个函数指针来接收小B传过来的函数的地址,而不用管函数名是什么。小A用这个指针就可以直接调用小B定义的函数。小B可以自己起函数名,只是返回类型和参数列表必须和小A的声明保持一致。

上面例子可以看出,回调函数必须通过函数指针进行传递和调用,为了简化代码,一般会将函数指针起个别名,格式为:

则上面的例子部分代码可以修改如下。无论是pf(a,b)还是(pf)(a,b)都是可以的,我打印了pf和pf,发现它们都表示的是同一函数的地址。

回调函数规避了必须在调用函数前声明的弊端,而且能够让用户直观地感受到自己定义的函数被调用。小A需要在声明函数指针时规定参数列表,小B在定义回调函数时需要与小A声明的函数指针保持相同的参数列表。当然小B可以自己决定在函数体中是否使用这些参数,前提是小A会提前为回调函数形参设置默认值。

3、有哪些函数可以做回调函数
可以做回调函数的函数在C++中目前我遇到过的有两种,第一种是C语言风格的函数,第二种是静态成员函数。第一种上面已经详细介绍过了,就不再赘述。第二种我在此做详细说明。
3.1静态成员函数做回调函数
众所周知,类的非静态成员函数的参数列表中隐含了一个this指针,当用类的对象访问类的非静态成员函数时,C++编译器会将this指针指向该对象,从而保证了函数体中操纵的成员变量是该对象的成员变量。即使你没有写上this指针,编译器在编译的时候自动加上this指针。 而在调用回调函数的函数中,它会提供接受回调函数地址的函数指针,该函数指针严格规定了参数列表。由于this指针的存在,非静态成员函数的参数列表和函数指针的参数列表参数个数无法匹配。如下例所示,函数指针callbackfun有两个参数,非静态成员函数隐含this指针从而有三个参数,显然不匹配。

当然如果你执着于使用非静态成员函数,就只能通过普通全局函数做中转,如下例所示。虽然能够实现,但看起来总是傻傻的。

类的静态成员函数属于类,为所有对象所共享,它没有this指针,因此这里我们采用静态成员函数作为回调函数。但这里我们又遇到了一个问题,就是静态成员函数是无法访问类的非静态成员的,因此如果用户有访问非静态成员的需求,那么需要在静态成员函数的参数列表中做一点小小的修改。在形参列表中加入万能指针void*作为一个形参,然后在函数体中再进行类型强转。

4、小结
①回调函数本质其实是对函数指针的一种应用,上面的例子都比较简单,还没有完全体现回调函数的威力。

②回调函数是一种设计系统的思想,能够解决系统架构中的部分问题,但是系统中不能过多使用回调函数,因为回调函数会改变整个系统的运行轨迹和执行顺序,耗费资源,而且会使得代码晦涩难懂。

③C++ STL中有大量使用回调函数的例子。如下例所示,遍历函数for_each()中的lamda表达式就是一个回调函数,当然我们也可以像上面那样定义全局函数或静态成员函数然后将地址传进去。

最后,我只是站在应用者的角度提出了我对于回调函数的一点浅显的见解,可能还有许多更高端更系统的观点,如果有能指出我的不足,将不胜感激。

Original: https://www.cnblogs.com/ybqjymy/p/16531689.html
Author: 一杯清酒邀明月
Title: C++ 回调函数详解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535306/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 拓扑排序(二)之 C++详解

    拓扑排序(Topological Order)是指,将一个有向无环图(Directed Acyclic Graph简称DAG)进行排序进而得到一个有序的线性序列。 这样说,可能理解…

    C++ 2023年5月29日
    053
  • 自己总结 C++ 代码规范

    1.编写原则,代码尽量简单,简洁,高效,自己写的代码让自己和别人容易看懂 2.命名: a. 类的成员变量加前缀 m_(表示 member)。 常量全用大写的字母,用下划线分割单词(…

    C++ 2023年5月29日
    066
  • C++11中的右值引用及move语义编程

    C++0x中加入了右值引用,和move函数。右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能…

    C++ 2023年5月29日
    069
  • Modern C++ 模板通用工厂

    Modern C++ 模板通用工厂 1 简单应用示例 1.1 示例代码 1.2 分析说明 2 简单工厂模式 2.1 示例代码 2.2 分析说明 3 工厂 + 静态注册 3.1 示例…

    C++ 2023年5月29日
    077
  • C++常用的设计模式

    单例模式: 单例模式:确保一个类只有一个实例,并且这个实例化向整个系统提供 (例如只有一台打印机,可以有多个打印任务队列,但是只能有一个正在打印)。单例模式又分为(饿汉模式,懒汉模…

    C++ 2023年5月29日
    056
  • C++中的静态绑定和动态绑定(转)

    C++在面向对象编程中,存在着静态绑定和动态绑定的定义,本节即是主要讲述这两点区分。我是在一个类的继承体系中分析的,因此下面所说的对象一般就是指一个类的实例。首先我们需要明确几个名…

    C++ 2023年5月29日
    055
  • 《转载》强大全面的C++框架和库推荐!

    关于 C++ 框架、库和资源的一些汇总列表,内容包括:标准库、Web应用框架、人工智能、数据库、图片处理、机器学习、日志、代码分析等。 标准库 C++标准库,包括了STL容器,算法…

    C++ 2023年5月29日
    075
  • C++ std::ofstream 和 std::ifstream

    简介 C++中对文件进行读写的。 使用Demo #include #include #include <string> #include <string.h&gt…

    C++ 2023年5月29日
    047
  • EclipseC++学习笔记-1 环境搭建

    最近一个项目使用的EclipseC++编写的,所以需要搭建一个EclipseC++平台开发。1、windows下载https://www.eclipse.org/downloads…

    C++ 2023年5月29日
    090
  • C++经典案例

    per-thread的单例模式 //单例子模式,per-thread的单例模式 IPCThreadState* IPCThreadState::self() //IPCThread…

    C++ 2023年5月29日
    066
  • c和c++开发工具之clion和vs

    个人体验结果 如果是CMake或者要跨平台的话,建议使用CLion 像我在看书写练习题的话,Clion使用cmake编译c/c++源码更简单上手使用。 如果项目不大,两者都可以。如…

    C++ 2023年5月29日
    0133
  • (筆記) 常用設定暫存器值的編程技巧 (SOC) (C/C++) (C) (Verilog)

    Abstract設定暫存器值是寫firmware時最常見的控制,本文歸納出C語言在寫firmware時常見的編程技巧,並與Verilog相互對照。 Introduction本文將討…

    C++ 2023年5月29日
    0100
  • vscode配置c++

    在.vscode里创建三个文件 c_cpp_properties.json, launch.json, settings.json, tasks.json c_cpp_proper…

    C++ 2023年5月29日
    081
  • c和c++编译器之gcc和mingw

    三大编译器:gcc,llvm,clang 什么是gcc? gcc 官方网站:https://gcc.gnu.org GCC(GNU Compiler Collection,GNU编…

    C++ 2023年5月29日
    079
  • 使用VS2015进行C++开发的6个主要原因

    使用VS2015进行C++开发的6个主要原因 使用Visual Studio 2015进行C++开发 在今天的 Build 大会上,进行了”将你的 C++ 代码转移至 …

    C++ 2023年5月29日
    072
  • clang-format 对 c++ 进行格式化

    在 VS Code 中安装了 C/C++ 插件后会自动带上格式化工具 clang-format。按 option+shift+f 即可对文件进行 format(格式化)。 在目录下…

    C++ 2023年5月29日
    080
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球