總述
C++11提供了對匿名函數的支持,稱為Lambda函數(也叫Lambda表達式). 它是定義和使用匿名函數對象的一種簡便的方式。匿名函數是我們需要用到的一個函數,但是又不想去費力命名一個函數的場景。我們無需為每個值或者每種類型)單獨編寫函數,更不必把值保存在讓人厭倦的全局變量中 。 利用lambda表達式可以編寫內嵌的匿名函數,用以替換獨立函數或者函數對象,並且使代碼更可讀。
工作的時候這個是比較常用的,通過匿名函數進行函數內部變量的捕獲,繼而進行操作變量等。那么現在就由我來給大家分享一下,我對Lambda表達式的認知,僅作為一個基礎的介紹哈,畢竟C++博大精深,吾輩還需要深究。
作者:良知猶存
轉載授權以及圍觀:歡迎添加微信公眾號:Conscience_Remains
1 它的結構
一條lambda表達式一般會有以下部分:
1.一個可能為空的捕獲列表,指明定義環境中的那些名字能被用在lambda表達式內,以及這些名字的訪問形式拷貝還是引用,捕獲列表位於 [] 內。
2.一個可選的參數列表,指明lambda表達式所需的參數,參數列表位於 () 內。
3.一個可以選的mutable修飾符,指明該lambda表達式可能會修改它自身的狀態(即,改變通過值捕獲的變量的副本)
4.一個可選的 -> 形式的返回類型聲明
5.一個表達式體,指明要執行的代碼,表達式位於 {} 內。
上面的語法規則除了 [捕獲列表]
內的東西外,其他部分都很好理解,只是一般函數的函數名被略去, 返回值使用了一個 ->
的形式進行。
所謂捕獲列表,其實可以理解為參數的一種類型,lambda 表達式內部函數體在默認情況下是不能夠使用函數體外部的變量的, 這時候捕獲列表可以起到傳遞外部數據的作用。
在lambda中,傳參、返回結果以及定義表達式體和普通的函數都是一致的,區別就在於普通函數沒有提供局部變量“捕獲”功能,而局部捕獲的功能,就意味着lambda可以做局部函數使用,而普通函數不能。
展示一個小例子證明lambda表達式的簡潔性:
Greater than 是一個函數對象,保存了要比較的值:
struct Greater_than {
int val;
Greater_than(lnt v) : val{v} { } ;
bool operatorO(const pair<string.int>& r) { return r.second>val};
};
我們也可以使用 lambda 表達式 :
auto p =find_if(m.beginO, m.endO,
[](const pair<string, int>& r) { return r.second>42; });
每當你定義一個lambda
表達式后,編譯器會自動生成一個匿名類(這個類當然重載了()
運算符),我們稱為閉包類型(closure type)。
2 基本的參數分析
C++11中的Lambda表達式捕獲外部變量主要有以下形式:
-
[]:默認不捕獲任何變量;
-
[=]:默認以值捕獲所有變量;
-
[&]:默認以引用捕獲所有變量;
-
[x]:僅以值捕獲x,其它變量不捕獲;
-
[&x]:僅以引用捕獲x,其它變量不捕獲;
-
[=, &x]:默認以值捕獲所有變量,但是x是例外,通過引用捕獲;
-
[&, x]:默認以引用捕獲所有變量,但是x是例外,通過值捕獲;
-
[this]:通過引用捕獲當前對象(其實是復制指針);
-
[*this]:通過傳值方式捕獲當前對象;
在上面的捕獲方式中,注意最好不要使用[=]和[&]默認捕獲所有變量。首先說默認引用捕獲所有變量,你有很大可能會出現懸掛引用(Dangling references),因為引用捕獲不會延長引用的變量的聲明周期,例如一個形參傳進來我們進行捕獲並作為一個返回值執行。因為函數傳參進來之后,本函數不會保存該變量,函數執行完就會自動釋放,那么這個時候返回值就可能產生一個沒有意義的結果。
auto evt_set_status_x = [&](EventType x){ status[x] = true;/*通過引用捕獲的變量 我們可以進行修改變量的數據*/};
[&]是一個捕獲列表( capture l ist ), 它指明所用的局部名字(如 x) 將通過引用訪問 。如果我們希望只"捕獲 "x ,則可以寫成 [&x] ;如果希望給生成的函數對象傳遞一個 的拷貝, 則寫成[ x] 。什么也不捕獲是[],捕獲所有通過引用訪問的局部名字是[&],捕獲所有以值訪問的局部名字是[=] 。
並且lambda表達式也可以賦值給相對應的函數指針,這也使得你完全可以把lambda表達式看成對應函數類型的指針。
當我們需要訪問它的局部變量的時候,我們需要特別定義捕獲列表中的類型
下面是一個沒有使用局部變量的lambda表達式,所以它的[]里面為空
void part(vector<int>& v){
sort(v.begin,v.end);//排列值 sort(v.begin,v.end,
[](int x,int y){return abs(x) < abs(y);});//排列絕對值 }
下面是一個使用局部變量的lambda表達式,所以它的[]里面為空就會出錯
void part(vector<int>& v){
bool value = true; sort(v.begin,v.end, [](int x,int y){return value ? x<y:abs(x) < abs(y);}); }
這時候就錯誤了,因為我們用到了value這個局部變量,而沒有進行捕獲列表的設置。
3 捕獲使用分析
使用 lambda 雖然簡單便捷,但也有可能顯得晦澀難懂 。
值捕獲
與參數傳值類似,值捕獲的前提是變量可以拷貝,不同之處則在於,被捕獲的變量在 lambda 表達式被創建時拷貝, 而非調用時才拷貝:
#include <iostream> void value_capture() { int value = 1; auto copy_value = [value] { return value; }; value = 100; auto stored_value = copy_value(); std::cout << "stored_value = " << stored_value << std::endl; } int main(int argc,char ** argv) { value_capture(); } // 這時, stored_value == 1, 而 value == 100. // 因為 copy_value 在創建時就保存了一份 value 的拷貝
記得編譯的時候加 -std=c++11
引用捕獲
與引用傳參類似,引用捕獲保存的是引用,值會發生變化:
泛型lambda表達式
從C++14
開始,lambda
表達式支持泛型:其參數可以使用自動推斷類型的功能,而不需要顯示地聲明具體類型。這就如同函數模板一樣,參數要使用類型自動推斷功能,只需要將其類型指定為auto
,類型推斷規則與函數模板一樣。就用我最早給出的那個例子好了。
auto evt_set_status_x = [&](EventType x){
status[x] = true;
};
這就是我分享的c++中的lambda表達式,以后有機會再往深入去分析一哈,其次如果大家有什么更好的思路,歡迎分享交流哈。
更多分享,掃碼關注我