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++中cout输出的字体颜色

    一种方法是通过右键控制台进行颜色设置,但是这种方法的问题在于它是全局的,没有具体文字的区分。另外一种方法就是使用代码来修改,本文主要介绍的就是这种方法。 最重要的函数是SetCon…

    C++ 2023年5月29日
    050
  • C++基础 (杂七杂八的汇总 )

    各数据类型在32位系统和64位系统占的字节数: C类型 32 64 char 1 1 short int 2 2 int 4 4 long int 4 8 long long in…

    C++ 2023年5月29日
    065
  • c/c++本地时间获取

    在记录程序日志时,需要记录时间。如下: 即Y为年、m为月、d为日、X为具体时分秒、A为星期、j为天数、z为其他,结果如下: 如果通过函数返回,需要这样: 其中,char tmp[6…

    C++ 2023年5月29日
    061
  • C++11 并发指南三(Lock 详解)

    C++11 标准为我们提供了两种基本的锁类型,分别如下: std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。 std::unique_lock,…

    C++ 2023年5月29日
    072
  • C++ 借助指针突破访问权限的限制,访问private、protected属性的成员变量(花拳绣腿)

    #include <iostream> using namespace std; class A { public: A(int a, int b, int c); p…

    C++ 2023年5月29日
    058
  • C#调用C++的dll两种方法(托管与非托管)

    C#与C++交互,总体来说可以有两种方法: 利用PInvoke实现直接调用 非托管C+ 利用C++/CLI作为代理中间层 一、非托管C++ 由于C#编写的是托管代码,编译生成微软中…

    C++ 2023年5月29日
    051
  • C++类中静态数据成员MAP如何初始化

    cpp;gutter:true; conv_xxx.hpp</p> <p>class convolution { …</p> <pre…

    C++ 2023年5月29日
    057
  • C++ 虚继承

    转自:http://www.cppblog.com/chemz/archive/2007/06/12/26135.html 虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断…

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

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

    C++ 2023年5月29日
    075
  • Cannot find a C++ compiler that supports both C++11 and the specified C++ flags.

    Linux 安装 cmake 时候出现的问题,解决方法: yum install gcc-c++ Original: https://www.cnblogs.com/hunttow…

    C++ 2023年5月29日
    056
  • C++ #ifndef、#define、#endif作用

    在C++项目中,#ifndef、#define、#endif非常常见,接下来就来简单说一下它们的作用。 作用:防止头文件被重复引用,防止被重复编译。 简介: ifndef 它是if…

    C++ 2023年5月29日
    077
  • (筆記) 如何讀取binary file某個byte連續n byte的值? (C/C++) (C)

    Abstract通常公司為了保護其智慧財產權,會自己定義檔案格式,其header區會定義每個byte各代表某項資訊,所以常常需要直接對binary檔的某byte直接進行讀取,且連續…

    C++ 2023年5月29日
    051
  • C++map值排序

    class Solution { public: static bool cmp(pair a, pair b){ return a.second>b.second; } s…

    C++ 2023年5月29日
    042
  • C++设计模式(转载)

    C++设计模式之Adapter一、功能 将一个类的接口转换成客户希望的另外一个接口,解决两个已有接口之间不匹配的问题。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类…

    C++ 2023年5月29日
    086
  • 【C++服务端技术】对象池

    代码没贴全,就少一个锁头文件,可以做设计参考 设计思想就是维护一个空闲链表,没有用的就重新申请,有的话就拿链表的头,使用完又还给空闲链表。 /* 一个分配固定大小内存的内存池,使用…

    C++ 2023年5月29日
    068
  • Android jni c/c++线程通过CallVoidMethod调用java函数出现奔溃问题

    最近在移植网络摄像机里的p2p库到android平台,需要用到jni,最近在c线程了调用java函数的时候出现一个问题,假如在同一个线程调用java函数是没问题的,但在一个c线程了…

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