C++ lambda表達式

C++11 引入了lambda表達式,這個特性的最廣泛應用,就是配合泛型算法。算法

泛型算法,採用了迭代器操做,從而使得各類不一樣的容器能使用一套算法。泛型算法容許咱們定製本身的操做,即傳遞一個可調用對象,lambda其實也是一個可調用對象。下面先介紹下lambda表達式的基本結構和特性,最後再深刻探索lambda表達式的本質。函數

1、lambda表達式的基本構成spa

lambda表達式表示了一個可調用的單元代碼,與函數相似,具備一個返回類型,一個參數列表,一個函數體,不一樣之處在於,還有一個捕獲參數列表。翻譯

[](int a, int b)->bool{return a > b; }

須要注意的是,返回類型必須使用尾置返回。參數列表和返回類型都是能夠忽略的,可是捕獲列表和函數體必須保留。我的猜測,省略了捕獲列表和函數體可能會使得編譯器沒法判斷類型。指針

lambda既然是個可調用對象,那麼它的調用方法也與通常的函數對象一致:code

class Increase
{
public:
    void operator()(int& val){++val;}
};
int main()
{
    int i = 1, j = 1;
    auto f = [](int& j){ ++j; };//等價於Increase
    f(j);//1
    Increase Inc;
    Inc(i);//2
}//1與2等價

2、捕獲對象

捕獲能夠說是lambda最難理解的部分。所謂捕獲,指的是容許lambda使用它所在函數中的局部變量。對於函數外的變量以及函數內的static變量,無需進行捕獲便可直接使用。blog

捕獲有兩種方式,一種是值捕獲,另一種是引用捕獲。排序

static i=1;
int j=1;
auto f1 = [&j]()->int{ ++j; return j; };//引用捕獲
auto f2 = [j](){return j; };//值捕獲
auto f3 = [](){return i; }//靜態變量無需捕獲

引用捕獲,實際上就是引用了一個局部變量。既然是引用,就要注意引用存在的問題。必須確保引用的變量在整個lambda執行的週期內都是存在的,而且爲一個正確的值。編譯器

值捕獲,至關於函數參數值傳遞的過程。所以,必須保證捕獲的量是可拷貝的。此外,針對捕獲的值爲指針或者引用類型,也存在確保對象依然存在的問題。

所以,應對儘可能減小捕獲,避免潛在的問題。可能的話,儘可能避免捕獲指針或者引用。

隱式捕獲

隱式捕獲是一種簡略的方法,下面舉個例子來講明一下:

int i = 1, j = 1;
auto f = [=, &j](){return i + (++j); };

前面「=」或者「&」說明默認捕獲的類型,若是有須要顯示捕獲的另一種類型,在後面單獨列出便可。

修改捕獲量的值

auto f2 = [j](){return ++j; };//error,由於這是個右值
auto f4 = [j]()mutable{return ++j; };//正確

值捕獲是沒法修改變量值的,加一個mutable便可。

3、返回類型

前面的例子中咱們能夠看到,咱們沒有顯示說明返回類型,也獲得了正確的結果。不過,默認的規則以下

一、若是僅有return ,那麼根據return 的類型肯定返回類型。

二、若是除了return 還有別的語句,那麼返回void。

因此,不只有return語句時,儘可能要本身顯示說明返回類型。

4、lambda表達式的本質

咱們知道,定義了調用運算符的對象,能夠稱爲函數對象,即這個對象能夠像函數同樣被調用,行爲相似函數。

與lambda同樣,函數對象一樣能夠做爲泛型算法的自定義操做:

class Bigger//1
{
public:
    bool operator()(int a, int b){ return a > b; }
};
bool bigger(int a, int b){ return a > b; }
int main()
{
    vector<int> vec{ 2, 0, 1, 3, 3, 0, 1, 9, 7, 7 };
    sort(vec.begin(), vec.end(), [](int a, int b)->bool{return a > b; });//2
    sort(vec.begin(), vec.end(), bigger);
    sort(vec.begin(), vec.end(), Bigger());
}

上面的例子中,分別用了lambda表達式、函數指針、函數對象來進行了排序,結果是相同的。

事實上,lambda表達式就是一個函數對象。當編寫了一個lambda表達式的時候,編譯器將該表達式翻譯成一個未命名類的未命名對象。

lambda表達式產生的類中會有一個重載的函數調用運算符。若是沒有捕獲任何變量,如上面咱們定義的lambda(1)和函數對象(2),實際上是徹底相同的。

若是lambda採用引用捕獲的方式,那麼該變量其實不是lambda對象的成員,因此無需進行額外操做。

若是採用值捕獲,那麼就至關於在對象內部建立了本身的成員,所以須要增長一部份內容:

class F
{
public:
    F(int n) :num(n){}
    int operator()(){ return num; }
private:
    int num;
};
int num = 100;
auto f = [num](){return num; };//等價於F

即增長了一個用捕獲的值進行構造的構造函數。

lambda表達式產生的類,不含有默認構造函數、賦值運算符和默認析構函數,其餘成員由須要捕獲的類型肯定。

 

本文只是簡單介紹了lambda的基本操做和功能,以及lambda表達式編譯時產生函數對象的行爲,更加深刻的內容,本人暫時還不清楚0 0

相關文章
相關標籤/搜索