C++ bind與回調函數

1 回調函數

  1. 註冊回調函數裏可以使用functional來統一接口,可以傳入函數指針,lambda,bind等!!!
  2. 函數1,2 爲一個模塊,爲回調函數,函數3爲一個模塊,爲註冊回調函數,形參爲函數指針
  3. 註冊回調函數的入參爲函數指針,指向回調函數,回調函數實際就是指針類型的實例化

與類無關的回調函數

void basketball()//函數1
{
    printf("選擇籃球");
}
void football()//函數2
{
    printf("選擇足球");
}
void selectball(void (* ball)())//函數3
{
    printf("選擇什麼球?");
    ball();
}
int main(void)
{
    selectball(basketball);
    selectball(football);
    return 0;
}

打個比方,一個芯片廠家爲了方便用戶開發,爲芯片寫了一個函數庫,這個函數庫就是一個註冊回調函數(如函數3),爲什麼廠家不直接把函數一套流程全給寫出來呢?用戶那麼多,廠家不可能爲每個用戶編寫出適合其的一整套函數,故就寫一個註冊回調函數,用戶只需要把自己的處理函數(回調函數)名,傳給函數指針就行。

#include <iostream>

using namespace std;

typedef void (*Func)(int);
Func p = NULL;

void caller(Func pCallback){
    p = pCallback;
    int result = 1;
    (*p)(result);
}

//void caller(void (*pCallback)(int)){
//    void (*p)(int);
//    p = pCallback;
//    int result = 1;
//    (*p)(result);
//}

void callback(int a){
    cout<< "callback result = " << a << endl;
}

int main(){
    caller(callback);
    getchar();
    return 0;
}

與類有關的回調函數

與類相關的回調函數要注意,類成員函數,如果是非靜態,是帶有this指針的,會與函數指針的類型不一致(無this),所以需要使用static函數,或者使用bind(&class::handler,this,_1)

#include <iostream>

using namespace std;

typedef void (*FUNP)();//注意參數是沒有this的

class Test1{
public:
    Test1(){}
    void func1(FUNP p){
        (*p)();
    }
};

class Test2{
public:
    Test2(){
        Test1 tet1;
        tet1.func1(func2);
    }
    static void func2(){ //作用爲fun2爲void(*)()類型,一般類函數爲void(*)(this)
        cout << "I am Test2" << endl;
    }
};

int main(){
    Test2 tet2;
    return 0;
}

在這裏插入圖片描述
上述分析,注意,bind和lambda表達式的類型均爲functional的,例如上例均爲function< void()>,如果用作回調函數,那麼回調註冊函數的形參類型要寫成functional類型,而不是函數指針類型。如果要統一函數指針類型,那麼只能使用static靜態函數!!!

2 bind函數

C++11 bind和function用法
bind詳解

bind原理

  1. auto f = bind(my_handler, 123),表示f() = my_handler(123)
  2. bind函數生成的auto f裏,f的參數個數,由bind裏的(_1,_2)等的個數決定!!!
  3. bind函數裏其他this,123這些表示原函數參數的給定值!!!
    在這裏插入圖片描述
    ==當需要bind的函數爲類成員函數時,由於serrion::handler(this),帶有this隱形參數,所以bind綁定一個this指針給f,
    同理,需要注意如果用在回調函數裏,可以通過bind,組合成一個新的函數指針,滿足原來的註冊回調函數使用需求,
    或者使用lambda,[ this] (){this->fun( )}。
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
#include <iostream>
#include <functional>
using namespace std;
class A
{
public:
    void fun_3(int k,int m)
    {
        cout<<"print: k="<<k<<",m="<<m<<endl;
    }
};

void fun_1(int x,int y,int z)
{
    cout<<"print: x=" <<x<<",y="<< y << ",z=" <<z<<endl;
}

void fun_2(int &a,int &b)
{
    a++;
    b++;
    cout<<"print: a=" <<a<<",b="<<b<<endl;
}

int main(int argc, char * argv[])
{
    //f1的類型爲 function<void(int, int, int)>
    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, placeholders::_1,placeholders::_2,3);
    //表示綁定函數 fun 的第三個參數爲 3,而fun 的第一,二個參數分別由調用 f2 的第一,二個參數指定
    f2(1,2);//print: x=1,y=2,z=3
 
    auto f3 = std::bind(fun_1,placeholders::_2,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, placeholders::_1, n); //表示綁定fun_2的第一個參數爲n, fun_2的第二個參數由調用f4的第一個參數(_1)指定。
    f4(m); //print: m=3,n=4
    cout<<"m="<<m<<endl;//m=3  說明:bind對於不事先綁定的參數,通過std::placeholders傳遞的參數是通過引用傳遞的,如m
    cout<<"n="<<n<<endl;//n=3  說明:bind對於預先綁定的函數參數是通過值傳遞的,如n
    
    A a;
    //f5的類型爲 function<void(int, int)>
    auto f5 = std::bind(&A::fun_3, a,placeholders::_1,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; 
}

上述例子中的auto 爲 std::function<void(int,int)>

3 functional

  1. function是一個template,定義於頭文件functional中。通過function<int(int, int)> 聲明一個function類型,它是“接受兩個int參數、返回一個int類型”的可調用對象,這裏可調用對象可以理解爲函數指針(指針指向一個函數,該函數有兩個int類型參數,返回int類型,即:int (*p)(int, int) )。

  2. 可調用對象:對於一個對象或表達式,如果可以對其使用調用運算符,則稱該對象或表達式爲可調用對象。

  3. C++語言中有幾種可調用對象:函數、函數指針、lambda表達式、bind創建的對象以及重載了函數調用運算符的類。

  4. 和其他對象一樣,可調用對象也有類型。例如,每個lambda有它自己唯一的(未命名)類類型;函數及函數指針的類型則由其返回值類型和實參類型決定。

function的用法

void printA(int a)
{
     cout << a << endl;          
}

std::function<void(int a)> func;  // 這裏定義後,func爲函數指針
func = printA;  //這裏調用了functional的隱形轉換函數
func(2);   //2

lambda

std::function<void()> func_1 = [](){cout << "hello world" << endl;};    
func_1();  //hello world
class Foo{
    Foo(int num) : num_(num){}
    void print_add(int i) const {cout << num_ + i << endl;}
    int num_;  
};

//保存成員函數
std::function<void(const Foo&,int)> f_add_display = &Foo::print_add;
Foo foo(2);
f_add_display(foo,1);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章