• C++中的回调函数再次总结(std::bind和function)



    在这里插入图片描述

    0 引入

    最近看到一些文章说回调函数,再加上看到一些比较好的文章和代码,对于回调函数又有了重新的认识,在这里做了一点总结
    之前转载过一篇文章:MFC中(TCP/IP)回调函数简析


    1、回调函数

    1.定义

    1、回调函数来源于callback,意思就是说回电,试想是说我打电话给某人,某人没有接到但是留下未接电话,看到未接电话之后回过来电话,其实这个业务场景正是回调函数的精髓,下面会说到。
    2、回调函数:字面意思是一个函数,更具体来说是一个函数指针,我们可以说使用函数指针的地方就是运用回调函数

    2.基本格式

    上代码:

    #include 
    typedef int(*callback)(int,int);        //定义一个函数指针
    
    int add1(int a,int b)                   //定义一个函数
    {
        return a+b;
     }
    int add2(int a,int b,callback p)        //使用函数指针作为形参
    {
        return (*p)(a,b);
    }
    int main(int argc,char *args[]){
        int res = add2(4,2,add1);          //使用函数
        printf("%d\n",res);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里 int res = add2(4,2,add1)使用就是运用了回调函数,总结:
    1、定义函数指针的形式,如函数的形参和返回值

    typedef int(*callback)(int,int)
    
    • 1

    2、在被调用者中实现该函数的定义

    int add1(int a,int b) {return a+b;}
    
    • 1

    3、调用者传入函数指针

    int add2(int a,int b,callback p)        
    {
        return (*p)(a,b);
    }
    
    • 1
    • 2
    • 3
    • 4

    调用者一般用一个函数去接收指针,然后再调用者就可以使用该函数。上面例子在一个cpp中实现不是太好说,请看下面应用场景


    2、应用场景

    1.一件事需要多个独立步骤完成

    举例在嵌入式中
    代码如下(示例):

    int callback1(int a) { return a;}                  //定义一个函数表示步骤1
    int callback2(int a) { return a;}                  //定义一个函数表示步骤2
    int callback3(int a) { return a;}                  //定义一个函数表示步骤3
    int callback4(int a) { return a;}                  //定义一个函数表示步骤4
    int callback5(int a) { return a;}                  //定义一个函数表示步骤5
    int handle(int a, int(CallBack*)(int))
    {
    	CallBack(a);
    }
    int main(){
        handle(1,callback1);  
        handle(2,callback2); 
        handle(3,callback3); 
        handle(4,callback4); 
        handle(5,callback5);      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    进一步的也可以用结构去封装一个步骤,步骤里面用函数指针去完成某件事情,然后在定义一个结构体数组,根据当前某个标识去查询数组,然后去执行某件事,这样整个框架就形成。

    上述这些功能不这么用可以吗?当然可以,不这么用仅从功能来说完全写成函数就可以实现,在这里只是说有这么形式可以让工程更好理解。

    2.回调

    这个场景我觉得是用回调函数最为正确的时候:首先有两方,比如A与B,A去调用B某个函数B1,然后B又去使用A中函数A1,比如A调用B中处理数据的函数,然后B处理完成去通知。一般都是异步回调。
    在实际工程应用中,比较典型的例子就是相机获取图像,一般买大华或者海康相机都会有SDK支持二次开发(借鉴于此,实际上我们自己开发sdk在穿大量数据的时候其实也是可以这么做),如下图(例子是从下文章来的):
    c++11 回调函数(以相机SDK采集图像的方式进行讲解)
    在这里插入图片描述

    代码如下(示例):

    #include 
    #include 
    using namespace std;
    
    /*回调函数原型声明*/
    typedef function<void(int)> CALLBACK;
    
    /*相机SDK底层A类*/
    class A_Camera
    {
    public:
    	void regeditCallBack(CALLBACK fun)/*注册回调函数*/
    	{
    		_fun = fun;
    	}
    
    	void getFrame()/*内部获取图像函数(B类调用者不需要关心它什么时候会执行)*/
    	{
    		/*采集到一帧数据_frame*/
    		/****内部操作***/
    		/***内部操作***/
    
    		_frame = rand() % 10;
    		_fun(_frame);/*回传给B_My类*/
    	}
    
    private:
    	int _frame;
    	CALLBACK _fun;
    };
    
    /*应用层B类*/
    class B_My
    {
    public:
    	void callBackFun(int frame)/*获取到A类的图像,此时frame就是一帧数据*/
    	{
    		cout << "B类获取到一帧数据:" << frame << endl;
    	}
    };
    
    int main(int argc, char **argv)
    {
    	/*声明应用层B类对象*/
    	B_My B; 
    
    	auto Fun = bind(&B_My::callBackFun, B, placeholders::_1);/*中转一下,利用C++11特性*/
    
    	/*声明底层相机A类*/
    	A_Camera camera;
    	camera.regeditCallBack(Fun);/*把B类的方法注册给A类*/
    
    	/*以下只是模拟A类内部触发获取到图片,一共模拟触发10次*/
    	for (int i = 0; i < 10; ++i)
    	{
    		camera.getFrame();
    	}
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    3、C++11中的std::function和bind

    在早期C样式编程当中,回调函数必须依赖函数指针来实现。
    现在的C++11语言当中,又引入了 std::function 与 std::bind 来配合进行回调函数实现
    回调的对象类型更是丰富,C++中有如下几种可调用对象:函数、函数指针、lambda表达式、bind对象、函数对象。
    标准库中有大量函数应用到了回调函数,其中 std::sort 就是一个经典例子。
    std::function是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。

    # include 
    # include 
    typedef std::function<int(int, int)> comfun;
    // 普通函数
    int add(int a, int b) { return a + b; }
    // lambda表达式
    auto mod = [](int a, int b){ return a % b; };
    // 函数对象类
    struct divide{
        int operator()(int denominator, int divisor){
            return denominator/divisor;
        }
    };
    
    int main(){
    	comfun a = add;
    	comfun b = mod;
    	comfun c = divide();
        std::cout << a(5, 3) << std::endl;
        std::cout << b(5, 3) << std::endl;
        std::cout << c(5, 3) << std::endl;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    std::bind可以看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表,理解下面的例子即可。

    #include 
    #include 
    
    class A {
    public:
        void fun_3(int k,int m) {
            std::cout << "print: k = "<< k << ", m = " << m << std::endl;
        }
    };
    
    void fun_1(int x,int y,int z) {
        std::cout << "print: x = " << x << ", y = " << y << ", z = " << z << std::endl;
    }
    
    void fun_2(int &a,int &b) {
        ++a;
        ++b;
        std::cout << "print: a = " << a << ", b = " << b << std::endl;
    }
    
    int main(int argc, char * argv[]) {
        //f1的类型为 function
        auto f1 = std::bind(fun_1, 1, 2, 3); 					//表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3
        f1(); 													//print: x=1,y=2,z=3
    
        auto f2 = std::bind(fun_1, std::placeholders::_1, std::placeholders::_2, 3);
        //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f2 的第一,二个参数指定
        f2(1, 2);												//print: x=1,y=2,z=3
     
        auto f3 = std::bind(fun_1, std::placeholders::_2, std::placeholders::_1, 3);
        //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f3 的第二,一个参数指定
        //注意: f2  和  f3 的区别。
        f3(1, 2);												//print: x=2,y=1,z=3
    
        int m = 2;
        int n = 3;
        auto f4 = std::bind(fun_2, std::placeholders::_1, n); //表示绑定fun_2的第一个参数为n, fun_2的第二个参数由调用f4的第一个参数(_1)指定。
        f4(m); 													//print: a=3,b=4
        std::cout << "m = " << m << std::endl;					//m=3  说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的,如m
        std::cout << "n = " << n << std::endl;					//n=3  说明:bind对于预先绑定的函数参数是通过值传递的,如n
        
        A a;
        //f5的类型为 function
        auto f5 = std::bind(&A::fun_3, &a, std::placeholders::_1, std::placeholders::_2); //使用auto关键字
        f5(10, 20);												//调用a.fun_3(10,20),print: k=10,m=20
    
        std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2);
        fc(10, 20);   											//调用a.fun_3(10,20) print: k=10,m=20 
    
        return 0; 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:
    1、将可调用对象和其参数绑定成一个仿函数;
    2、只绑定部分参数,减少可调用对象传入的参数。


    4、引用

    1、【C++】C++11的std::function和std::bind用法详解
    2、回调函数的使用


  • 相关阅读:
    C++智能指针
    python 数字类型
    软件测试曾拿过十几份offer的简历是什么样子的?
    【思想篇-能力】新人成长
    【计算机网络仿真实验-实验2.6】带交换机的RIP路由协议
    MySQL的索引优化
    基于BP神经网络的轨迹跟踪研究(Matlab代码实现)
    Python简直是万能的,这5大主要用途你一定要知道!
    驱动LCD12864显示器
    研究 | CT图像迭代重建算法研究进展
  • 原文地址:https://blog.csdn.net/ljsant/article/details/128144274