云代码-回调函数



  • 什么是回调函数

    编程

    • 系统编程->库->留下API application programming interface
    • 应用编程--利用库-->写应用

    正常情况: 应用编程直接调用 API 调用库里的函数

    非正常情况(用到回调函数): 库函数(library function)要求应用传给库函数一个函数A, 库函数会在合适的时候调用函数 A, 这个函数 A 就是回调函数.

    例子: 旅馆叫醒客人

    • 叫醒服务由旅馆提供->api 由库函数提供
    • 客人需要叫醒服务->应用需要调用api
    • 客人决定叫醒的方式(电话, 冰桶挑战, 美少女服务)->应用传入(登记 register)回调函数

    总结: 回调就成了一个高层调用底层,底层再 过头来 用高层的过程。

    回调函数的分类

    在讨论回调函数的时候, 涉及到三个部分: 主函数, 回调函数, 库函数.

    A() {
      // output P
    }
    
    B(fn) {
      fn();  // B knows only fn, not A
             // B treats fn as a variable
    }
    
    B(A);  // B called at T
           // B calling fn() (i.e. calling A())
    

    阻塞式回调

    阻塞式回调里,回调函数的调用一定发生在主函数返回之前

    下面的代码展示了如何给一个数组从小到大排序的代码。代码调用了函数 qsort 进行排序,并通过指定 compare 为参数,实现元素大小的比较。

    int compare (const void * a, const void * b) {
        return (*(int*) a - *(int*) b);
    }
    
    ...
    
    int values[] = { 20, 10, 50, 30, 60, 40 };
    qsort (values, sizeof (values) / sizeof (int), sizeof(int), compare);
    

    代码对应了描述的五个要素:

    • compare 相当于是 函数 A
    • qsort 相当于是 函数 B
    • 对于 qsort 来说,qsort 的第四个参数 相当于是 参数 fn
    • 对于 qsort 来说,排序过程中,比较两个元素大小的时刻 相当于是 调用时刻 T
    • compare 返回两个元素比较大小的结果 相当于是 回调结果 P

    由于调用 compare 的时刻 T 均是在 调用 qsort 结束之前(qsort 未返回),所以这样的回调被称为 同步回调

    延迟式回调

    延迟式回调里,回调函数的调用有可能是在主函数返回之后

    下面的代码展示了如何在 Linux 下,阻止用户使用 Ctrl C 退出程序,并打印 Press ^C 提示。代码调用了函数 signal 进行回调函数的 注册(和同步回调不同,这里仅是注册)。

    void block_interrupt (int code) { printf("\rPress ^C\n"); }
    
    ...
    
    signal (SIGINT, block_interrupt);
    

    代码对应了描述的五个要素:

    • block_interrupt 相当于是 函数 A
    • Linux 终端(Ctrl C 信号的发送者) 相当于是 函数 B
    • 调用 signal 函数注册 SIGINT 事件时的参数 相当于是 参数 fn
    • 对于 Linux 终端来说,用户按下 Ctrl C 的时刻 相当于是 调用时刻 T
    • block_interrupt 打印 Press ^C 提示 相当于是 回调结果 P

    由于调用 block_interrupt 的时刻 T用户按下 Ctrl C 的时刻,均是在 调用 signal 结束之后(signal 已返回),所以这样的回调被称为 异步回调

    区分

    • 同步方式 通过 参数传递 的方法(例如 qsort)传递回调函数;调用者 直接使用回调函数,从而完成回调(调用时刻在函数返回前)
    • 异步方式 通过 注册 的方式(例如 signal)告知未来的调用者,并 存储回调函数;调用者在未来某个调用时刻 T,取出并调用回调函数,从而完成回调

    回调函数在 c++ 中的使用

    不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。????

    c++ 中的回调函数

    https://blog.csdn.net/clirus/article/details/50350519

    https://zhuanlan.zhihu.com/p/88434924 里面的链接也有参考

    存在问题: 如果要以类里的函数作为回调函数, 需要指定一个实例( fooClass::fooFun() ), (但是如果调用类的静态函数就不需要类实例)

    虚拟方法(虚函数)

    http://blog.csdn.net/hackbuteer1/article/details/7558868

    核心理念就是通过基类访问派生类定义的函数。

    class A
    {
    public:
        virtual void foo()
        {
            cout<<"A::foo() is called"<<endl;
        }
    };
    
    class B:public A
    {
    public:
        void foo()
        {
            cout<<"B::foo() is called"<<endl;
        }
    };
    
    int main(void)
    {
        A *a = new B();
        a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
        return 0;
    }
    

    一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。

    纯虚函数

    纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都 必须 要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”

    virtual void funtion1()=0
    
    区分虚函数, 纯虚函数, 接口(cpp抽象类)

    https://blog.csdn.net/huangyimo/article/details/50480313

    通过虚函数,在调用不同的衍生类的时候,可以拥有不同的功能。同时,我们可以通过将每个继承类都重写命名一个函数来替代也可以,这么做完全可以,只要你自己能熟记或者找到这个重命名函数是干嘛用的;但是在大一点的项目中,由于类中的函数成百上千,恐怕你就会为此疯狂。

    函数符

    https://blog.csdn.net/u012507022/article/details/51942423

    https://www.cnblogs.com/Braveliu/p/12637130.html

    函数对象也叫函数符,函数符是可以以函数方式与()结合使用的任意对象。这包括函数名、指向函数的指针和重载了()运算符的类对象。

    //函数名
    int add(int a, int b)
    {
        return (a + b);
    }
    
    //函数指针
    typedef int(*funcPtr)(int, int);
    funcPtr fpAdd = add;
    int a = 100, b = 200;
    int c = fpAdd(a, b); 
    
    //重载()运算符, 简单来讲就是一个类重载了运算符(), 就可以当函数名用
    class Linear
    {
      private:
        double slope;
        double y0;
      public:
        //构造函数
        Linear(double sl_ = 1, double y_ = 0) :slope(sl_), y0(y_) {}
        //重载()运算符!!!!!
        double operator()(double x)
        {
        	return (y0 + slope * x);
        }
    };
    int main()
    {
        Linear f1;
        Linear f2(2.5, 10.0);
        //在此处Linear类的对象f1和f2利用重载的()运算符以函数的方式实现了 y0 + slope * x 功能
        //因此f1和f2可以成为函数符(或函数对象)的另一种范畴
        double y1 = f1(12.5);
        double y2 = f2(0.4);
    
        std::cout << "y1: " << y1 << std::endl; // 0 + 12.5 * 1 = 12.5
        std::cout << "y2: " << y2 << std::endl; // 10.0 + 2.5 * 0.4 = 11
        return 0;
    }
    

    其他备注

    • std::function

    • using相当于 typedef eg: using ref = _Ty&

    • 闭包

      • 重载 operator()

      • lambda

        完整格式: [capture list] (params list) mutable exception-> return type { function body }

        每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)

        前面的方括号[]就是用来定义捕捉模式以及变量

        int main() {
            int round = 2;
            auto f = [=](int f) -> int { return f + round; } ;
            cout << "result = " << f(1) << endl;
            return 0;
        }  
        
      • boost::bind std::bind

    • event loop https://github.com/guodongxiaren/Blog/issues/26


登录后回复
 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

与 Dian 的连接断开,我们正在尝试重连,请耐心等待