C++ lambda表達式


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

泛型算法,采用了迭代器操作,從而使得各種不同的容器能使用一套算法。泛型算法允許我們定制自己的操作,即傳遞一個可調用對象,lambda其實也是一個可調用對象。下面先介紹下lambda表達式的基本結構和特性,最后再深入探索lambda表達式的本質。

一、lambda表達式的基本構成

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

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

需要注意的是,返回類型必須使用尾置返回。參數列表和返回類型都是可以忽略的,但是捕獲列表和函數體必須保留。個人猜想,省略了捕獲列表和函數體可能會使得編譯器無法判斷類型。

lambda既然是個可調用對象,那么它的調用方法也與一般的函數對象一致:

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等價

二、捕獲

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

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

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即可。

三、返回類型

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

1、如果僅有return ,那么根據return 的類型確定返回類型。

2、如果除了return 還有別的語句,那么返回void。

所以,不僅有return語句時,盡量要自己顯示說明返回類型。

四、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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM