c++类成员函数作为回调函数

        一直以来都被回调函数的定义给整蒙了。最近又仔细学了会,感觉回调函数,我认为就是将一个函数指针A作为参数传入另外一个函数B,然后在函数B中调用函数A。

      普通回调

         具体先看一个简单的例子:

        

#include<stdio.h>
 
void printWelcome(int len)
{
       printf("welcome -- %d\n", len);
}
 
void printGoodbye(int len)
{
       printf("byebye-- %d\n", len);
}
 
void callback(int times, void (* print)(int))
{
       int i;
       for (i = 0; i < times; ++i)
       {
              print(i);
       }
       printf("\n welcome or byebye !\n");
}
void main(void)
{
       callback(10, printWelcome);
       callback(10, printGoodbye);
}
        我认为这就算一个简单的回调。在callback中使用print函数指针作为参数,然后在函数中调用print相关的函数,这就属于回调。

        当然,这只是一个很简单的例子。在各种c++中,回调可是牛逼闪闪。

        在c++中,一般需要使用类进行函数的封装。但是在调用一般的类的成员函数时,使用类的成员调用时,需要传递this指针

        《深入探索C++对象模型》中提到成员函数时,当成员函数不是静态的,虚函数,那么我们有以下结论: 
        (1) &类名::函数名 获取的是成员函数的实际地址;

        (2) 对于函数x来讲obj.x()编译器转化后表现为x(&obj),&obj作为this指针传入; 
         (3) 无法通过强制类型转换在类成员函数指针与其外形几乎一样的普通函数指针之间进行有效的转换。

         所以,要在回调函数中传入一个类的普通成员函数时,this指针无处安放,使得回调函数比较复杂。

      类成员函数回调 

          在c++中,常用的回调函数场景是,在一个类A中,有一个普通成员函数a,在类B中,有一个普通成员函数b,在b中,想要回调函数a,这才是c++回调函数的正确打开方式。
         先上一段代码:
         
#include <iostream>

#include <functional>

using namespace std;
using namespace std::placeholders;

typedef std::function<void(int,int)> Fun;

class B{
	public:
        void call(int a,Fun f)
        {
            f(a,2);
        }
};

class Test{
public:
	void callback(int a,int b)
	{
        cout<<a<<"+"<<b<<"="<<a+b<<endl;
	}

	void bind()
	{
        Fun fun=std::bind(&Test::callback,this,_1,_2);
		B b;
		b.call(1,fun);
	}

};
int main()
{
    Test test;
	test.bind();
	return 0;
}
        上面的程序中,Test类中的bind函数调用B类中的call函数,b中的call函数又反过来回调Test类中的callback函数。记住function和bind都是c++11标准函数,编译的时候要加-std=c++11。
         在分析上面程序之前,先介绍一下两个辅助函数,分别是bind函数和function函数,这两个函数之前是boost函数成员,现在加入到c++11标准中,使用更加方便。

       bind函数

          定义在头文件functional中。可以看成是对一个函数的改造器,可以借助于集合的观点来说(尽管可能没这回事),可以将bind函数看作是返回一个子函数。这个子函数可以是bind绑定的函数的子集,也可以是本身。
          一般常用语法是: newFunName=bind(oldFunName,arg_list);
          bind函数返回一个新的函数对象。其中bind第一个参数是oldFunName,它是待绑定的函数名,arg_list是oldFunName的参数列表。注意,这个参数列表是旧函数的参数列表,前面提到,返回的是子函数。我们可以随便给子函数定几个参数,但是肯定不能多于bind所绑定的原函数的参数个数。举个例子:
          
//g是一个有两个参数的可调用对象
auto g=bind(f,a,b,_2,c,_1);
//其中f是具有5个参数的函数
//当我们调用g(x,y)时,实际调用的是f(a,b,y,c,x)
        在这个示例中,我们可能要调用f函数,并传入5个参数,但是我们现在调用g(x,y),只要传入两个参数,同样能达到这个效果。当然,我费这么多事其实肯定不是为了省几个参数,主要还是为了将一个函数转化成一个可以作为回调的函数指针,可以看成是原函数指针的别名。 
        上面出现的_1,_2是它的占位符,bind最多可以使用9个占位符。这个占位符命名在std的placeholders中,使用时,要使用using std::placeholders.

       function函数

         function是一个函数对象的“容器”。
         如function<int(int,int)> fun;  fun是一个函数模板,可以接受两个int型参数,并返回一个int型参数。平时可以将它赋值给一个函数指针。
         例如上面的回调函数:
         
 Fun fun=std::bind(&Test::callback,this,_1,_2);
          其中bind用于绑定一个Test类的callback函数,它有两个参数,在这里,因为它是一个类成员函数,中间传入一个this指针,另外两个_1和_2则是它的两个参数。bind返回一个函数指针,将它赋给fun,fun作为一个函数容器,容纳bind函数返回的临时函数指针。 这样就成功的将fun作为一个函数参数的别名,可以用于传给回调函数了。
         关于bind和function函数,再举一个例子,这个例子摘自这里
         
#include <iostream>  
#include <functional>  
using namespace std;  
  
typedef std::function<void ()> fp;  
void g_fun()  
{  
    cout<<"g_fun()"<<endl;  
}  
class A  
{  
public:  
    static void A_fun_static()  
    {  
        cout<<"A_fun_static()"<<endl;  
    }  
    void A_fun()  
    {  
        cout<<"A_fun()"<<endl;  
    }  
    void A_fun_int(int i)  
    {  
        cout<<"A_fun_int() "<<i<<endl;  
    }  
  
    //非静态类成员,因为含有this指针,所以需要使用bind  
    void init()  
    {  
        fp fp1=std::bind(&A::A_fun,this);  
        fp1();  
    }  
  
    void init2()  
    {  
        typedef std::function<void (int)> fpi;  
        //对于参数要使用占位符 std::placeholders::_1  
        fpi f=std::bind(&A::A_fun_int,this,std::placeholders::_1);  
        f(5);  
    }  
};  
int main()  
{  
    //绑定到全局函数  
    fp f2=fp(&g_fun);  
    f2();  
  
    //绑定到类静态成员函数  
    fp f1=fp(&A::A_fun_static);  
    f1();  
  
    A().init();  
    A().init2();  
    return 0;  
}

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值