c++11 lambda 表示式
轉:https://www.cnblogs.com/ChinaHook/p/7658443.html
1、 定義
lambda表示式是C++11非常重要也是很常用的特性之一,來源於函數語言程式設計的概念,也是現代程式語言的一個特點。它有如下特點:
- 宣告式程式設計風格:就地匿名定義目標函式或者函式,不需要額外寫一個命名函式或者函式物件,以更直接的方式寫程式。
- 簡潔:不需要額外再寫一個函式或者函式物件,避免了程式碼膨脹和功能分散。
- 在需要的時間和地點實現功能閉包,使程式更加靈活。
lambda表示式定義一個匿名函式,並且可以捕獲一定範圍內的變數,其定義如下:
[captrue] (params) opt -> ret {body};
其中,capture是捕獲列表;params是引數列表;opt是函式選項;ret是返回值型別;body是函式體。
我們來做一個簡單的例子,定義一個簡單的封包,用來將輸入的結果+1並返回。
auto f = [](int a) -> int {return a + 1; };
std::cout << f(1) << std::endl;
lambda表示式的返回值通過返回值後置語法來定義,所以很多時候可以省略返回值型別,編譯器根據return語句自動推導返回值型別。
auto f = [] (int a) {return a + 1;};
但是初始化列表不能作為返回值的自動推導,需要顯示給出具體的返回值型別。
auto f1 = [] (int a) {return a + 1;}; //ok,return type is int
auto f2 = [] () {return {1, 2}}; //error:無法推匯出返回值型別
lambda表示式在沒有引數列表的時候,引數列表可以省略。
auto f1 = [] () {return 1;};
auto f2 = [] {return 1;} //省略空引數表
2、 捕捉
lambda表示式可以通過捕獲列表捕獲一定範圍內的變數:
- []不捕獲任何變數;
- [&]捕獲外部作用域所有變數,並作為引用在函式體使用(按引用捕獲);
- [=]捕獲外部作用域作用變數,並作為副本在函式體使用(按值捕獲);
- [=,&foo]按值捕獲外部作用域所有變數,並按引用捕獲foo變數;
- [bar]按值捕獲bar變數,同時不捕獲其他變數;
- [this]捕獲當前類中的this指標,讓lambda擁有和當前類成員函式同樣的訪問許可權,如果已經使用了&或者=,就預設新增此選項。捕獲this的目的是可以在lambda中使用當前類的成員函式和成員變數。
class A
{
public:
int mi = 0;
void func(int x, int y)
{
auto x1 = []{return mi;}; //error,沒有捕獲外部變數
auto x2 = [=] {return mi + x + y;}; //ok,按值捕獲所有外部變數
auto x3 = [&] {return mi + x + y;}; //ok,按引用捕獲所有外部變數
auto x4 = [this] {return mi;}; //ok,捕獲this指標
auto x5 = [this] {return mi + x + y;}; //error,沒有捕獲x,y
auto x6 = [this,x,y] {return mi + x + y;}; //ok,捕獲this,x,y
auto x7 = [this] {return mi++;}; //ok,捕獲this指標,並修改成員的值
}
};
int a = 0, b = 2;
auto f1 = [] {return a;} ; //error,沒有捕獲外部變數
auto f2 = [&] {return a++;}; //ok,按引用捕獲所有外部變數,並對a執行自加運算
auto f3 = [=] {return a;}; //ok,按值捕獲所有外部變數,並返回a
auto f4 = [=] {return a++;}; //error,按值引用不能改變值
auto f5 = [a] {return a + b;}; //error,沒有捕獲b
auto f6 = [a, &b] {return a + (b++);}; //ok,捕獲a和b的值,並對b做自加運算
auto f7 = [=, &b] {return a + (b++);}; //ok,捕獲所有外部變數和b的引用,並對b做自加運算
從例子中可以看到,lambda表示式的捕獲列表精細的控制表示式能夠訪問的外部變數,以及如何訪問這些變數。需要注意的是,預設狀態下的lambda表示式無法修改通過複製方式捕獲的外部變數,如果希望修改這些變數,需要用引用的方式進行修改。但是按值引用的話,如果延遲呼叫,那麼在呼叫該lambda表示式的時候,其捕獲的變數值還是在lambda定義的時候的值。
int a = 0;
auto f = [=] {return a;}; //按值捕獲外部變數
a += 1; //修改值
std::cout << f() << std::endl; //輸出0
這個例子中,lambda表示式按值捕獲了所有外部變數,在捕獲的一瞬間a的值就已經被賦值在其中,之後a值修改並不會改變lambda表達中存的a的值,因此最終結果輸出還是0。如果希望lambda表示式在呼叫的時候能即時訪問外部變數,應當使用引用的方式捕獲。
如果希望去修改按值捕獲的外部變數,需要顯示指明lambda表示式為mutable,但是被mutable修飾的lambda表示式有一個特點,就是無論有沒有引數都要寫明引數列表。
int a = 0;
auto f1 = [=] {return a++;}; //error,不能修改按值捕獲的變數
auto f2 = [=] () mutable {return a++; }; //ok,mutable
lambda表示式其實是一個帶有operator()的類,即仿函式,因此我們可以使用std::bind和std::function來儲存和操作lambda表示式:
std::function<int(int)> f1 = [] (int a){ return a;};
std::function<int(void)> f2 = std::bind([](int a) {return a;}, 123);
對於沒有捕獲任何變數的lambda表示式,還可以被轉換成一個普通的函式指標:
using func_t = int(*)(int);
func_t f = [](int a){return a;};
f(123);
lambda表示式可以說是定義仿函式閉包的語法糖,它捕獲的任何外部變數都會轉換為閉包型別的成員變數。而使用成員變數的類的operator(),如果能直接轉換為普通的函式指標,那lambda表示式本身的this指標會丟失,沒有捕獲任何外部變數的lambda表示式則不存在這個問題,所以按值捕獲的外部變數無法修改。因為lambda表示式中的operator()預設是const的,一個const成員函式無法修改成員變數的值,而mutable則是取消operator()的const。
所以,沒有捕獲變數的lambda表示式可以直接轉換為函式指標,而捕獲變數的lambda表示式則不能轉換為函式指標。
typedef void(*Ptr)(int *);
Ptr p1 = [](int *p) {delete p;}; //ok,沒有捕獲的lambda表示式可以轉換為函式指標
Ptr p2 = [&](int *p){delete p;}; //error,有捕獲的lambda表示式不能直接轉換為函式指標,不能通過編譯
3、 簡潔程式碼
就地定義匿名函式,不再需要定義函式物件,大大簡化了標準庫的呼叫。比如我們使用for_each將vector中的偶數打印出來。
class CountEven
{
private:
int &micount;
public:
CountEven(int &count) : micount(count) {}
void operator()(int val)
{
if(!(val & 1))
{
++micount;
}
}
};
std::vector<int> v = {1, 2, 3, 4, 5, 6};
int count = 0;
for_each(v.begin(), v.end(), CountEven(count));
std::cout << "The number:" << count << std::endl;
這樣寫比較繁瑣,如果用lambda表示式,使用閉包概念來替換這裡的仿函式。
std::vector<int> v = {1, 2, 3, 4, 5, 6};
int count = 0;
for_each(v.begin(), v.end(), [&count] (int val)
{
if(!(val & 1))
{
++count;
}
});
在之前的例子中,使用std::bind組合多個函式,實現了計算集合中大於5小於10的元素個數。
using std::placeholders::_1;
auto f = std::bind(std::logical_and<bool>(),
std::bind(std::greater<int>(), std::placeholders::_1, 5),
std::bind(std::less_equal<int>(), std::placeholders::_1, 10));
int count = std::count_if(coll.begin(), coll.end(), f);
通過lambda表示式可以輕鬆的實現類似的功能:
//查詢大於5小於10的元素個數
int count = std::count_if(coll.begin(), coll.end(), [](int x) {return x > 5 && x < 10;})
lambda表示式比std::bind更加靈活和簡潔,如果簡單的邏輯處理,用lambda表示式來代替function,提升開發效率,效果會更好。