C++中的函數指針

  python中裝飾器和偏函數兩個概念。裝飾器是其本質原理也是將一個函數當作參數傳入另一個函數中,起到一層封裝的作用,這樣可以不改變原有函數的基礎上而添加新的功能,比如經典的測函數運行時間,在我之前web性能測試一章用過裝飾器(http://blog.csdn.net/cracker_zhou/article/details/50408514),再結合python的@語法糖就完全不用考慮實現的語法問題。python中的偏函數是將一個函數中某些位置參數固定而生成了另一個函數。
  在C++中有函數指針和指針函數兩個名字相似但其實東西完全不同的概念。函數指針是一個指向函數的指針(pointer),指針函數是一個返回指針類型的函數(function)。之前我一直感嘆於python中裝飾器和偏函數的神奇,沒想到今天竟然在C++中發現了類似的實現方式,但確實比python要多考慮語法問題。

1.最基礎的傳入全局函數

#include <iostream>
#include <functional>
typedef std::function<int(int)> func;
int main()
{
    //等效於 int f1(int x){return x;}
    func f1 = [](int x){return x; }; //lambda表達式
    //等效於 void f2(func f, int y){ std::cout << f(y)<<std::endl; }
    auto f2 = [](func f,int y){std::cout << f(y)<<std::endl; };//lambda表達式
    f2(f1,10);  // 控制檯輸出10
    return 0;
}

  從上面代碼看出,函數也是有類型的!比如這裏f1的數據類型爲std::function<int(int)>當我們知道函數有準確的數據類型時,我們就可以像其他int,char之類的基本數據類型傳參一樣理解。
  
2.傳入類成員函數
  類成員函數要區分爲兩種:靜態函數和非靜態函數。
  對於類的靜態函數來說其實跟上面全局函數的參數傳入沒什麼區別,只是對於非靜態函數語法上要麻煩一點,需要用bind函數綁定將函數與實例綁定。

#include <iostream>
#include <functional>
typedef std::function<int(int)> func;
typedef std::function<int()> vfunc;
class A
{
public:
    static int add(){return 1;}
    int add2(int x){ return x + 2; }
};
void test_add(vfunc f){ std::cout << f()<<std::endl; }
void test_add2(func f, int y){ std::cout << f(y)<<std::endl; }
int main()
{
    test_add(A::add); // 輸出1
    auto f2 = std::bind(&A::add2, A(), std::placeholders::_1);
    test_add2(f2, 1); // 輸出3
    return 0;
}

  從上面代碼可以看出,其實對類靜態函數的使用與之前的全局函數沒有區別,但是當想傳入非靜態函數時則需要多一行bind操作,將類實例與函數綁定,額外的還需要一個參數佔位符std::placeholders::_1,其實這個參數佔位符也是一個很神奇的東西,下面會講到對這個參數佔位符的使用。

3.偏函數
  其實準確的說應該叫部分應用函數,大家通用的叫法是偏函數。主要通過設定參數的默認值,可以降低函數調用的難度。

#include <iostream>
class A
{
public:
    int add(int x,int y=10){ return x + y; }
};
int main()
{
    std::cout << A().add(1);   //輸出11,採用了默認的y參數
    return 0;
}

  當使用偏函數時,可以鎖定任意位置參數,其實本質上是在std::bind時提供好位置參數生成一個新的函數。

#include <iostream>
#include <functional>
typedef std::function<int(int)> func;
class A
{
public:
    int add(int x,int y){ return x + y*2; }
};
void test_add(func f, int y){ std::cout << f(y)<<std::endl; }
int main()
{
    auto f1 = std::bind(&A::add, A(), 1,std::placeholders::_1);//鎖定第一個位置參數的值爲1
    test_add(f1, 1); // 輸出3
    test_add(f1, 2); // 輸出5
    return 0;
}

  test_add需要的第一個參數類型是std::function<int(int)>,但是在類A中add的函數是帶有兩個int參數的函數,其實本身這是不允許傳入的。但是我們通過bind函數時鎖定x位置參數使add變爲帶有一個int的一個新函數,名字叫f1。

4.函數指針
  如果你一定要糾結於這篇文章的標題,我就再來一個example。(其實之前一直都是值傳參,只不過現在變成址傳參了而已)

#include <iostream>
#include <functional>
typedef std::function<int(int)> func;
class A
{
public:
    int add(int x){ return x; }
};
void test_add(func* f,int x){ std::cout << (*f)(x)<<std::endl; }
int main()
{
    func f1 = std::bind(&A::add, A(), std::placeholders::_1);
    test_add(&f1, 1); // 輸出3
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章