Lambda 表達式是 C++11 中最重要的新特性之一。類似匿名函數,當需要一個函數但是又不想費力去命名時使用。
這樣的場景其實有很多,所以匿名函數幾乎是現代編程語言的標配。
Lambda 表達式基礎
Lambda 表達式的基本語法如下:
[捕獲列表](參數列表) mutable(可選)異常屬性 -> 返回類型 {
// 函數體
}
捕獲列表,可以理解爲參數的一種類型。
lambda 表達式內部函數體在默認情況下是不能夠使用函數體外部的變量的,這時候捕獲列表可以起到傳遞外部數據的作用。
根據傳遞的行爲,捕獲列表也分爲以下幾種:
- 值捕獲
值捕獲的前提是變量可以拷貝,被捕獲的變量在 lambda 表達式被創建時拷貝,而非調用時才拷貝:
void learn_lambda_func_1() {
int value_1 = 1;
auto copy_value_1 = [value_1] {
return value_1;
};
value_1 = 100;
auto stored_value_1 = copy_value_1();
// 這時, stored_value_1 == 1, 而 value_1 == 100.
// 因爲 copy_value_1 在創建時就保存了一份 value_1 的拷貝
}
- 引用捕獲
引用捕獲保存的是引用,值會發生變化:
void learn_lambda_func_2() {
int value_2 = 1;
auto copy_value_2 = [&value_2] {
return value_2;
};
value_2 = 100;
auto stored_value_2 = copy_value_2();
// 這時, stored_value_2 == 100, value_1 == 100.
// 因爲 copy_value_2 保存的是引用
}
- 隱式捕獲
手動書寫捕獲列表有時候是非常複雜的,這種機械性的工作可以交給編譯器來處理。
可以在捕獲列表中寫一個 & 或 = 向編譯器聲明採用 引用捕獲或者值捕獲。
捕獲列表的最常用的四種形式可以是:
- [] 空捕獲列表
- [name1, name2, …] 捕獲一系列變量
- [&] 引用捕獲, 讓編譯器自行推導捕獲列表
- [=] 值捕獲, 讓編譯器執行推導應用列表
- 表達式捕獲(C++14)
這部分內容需要了解後面馬上要提到的右值引用以及智能指針
上面提到的值捕獲、引用捕獲都是已經在外層作用域聲明的變量,因此這些捕獲方式捕獲的均爲左值,而不能捕獲右值。
C++14允許捕獲的成員用任意的表達式進行初始化。
被聲明的捕獲變量類型會根據表達式進行判斷,判斷方式與使用auto 本質上是相同的:
#include <iostream>
#include <utility>
int main() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y)
-> int {
return x + y + v1 + (*v2);
};
std::cout << add(3, 4) << std::endl;
return 0;
}
在上面的代碼中, important 是一個獨佔指針,是不能夠被捕獲到的,這時候我們需要將其轉移爲右值,在表達式中初始化。
泛型 Lambda(C++14)
Lambda 表達式並不是普通函數,所以 Lambda 表達式並不能夠模板化。
這就爲我們造成了一定程度上的麻煩:參數表不能夠泛化,必須明確參數表類型。
從 C++14 開始,Lambda 函數的形式參數可以使用 auto 關鍵字來產生意義上的泛型:
auto add = [](auto x, auto y) {
return x + y;
};
add(1, 2);
add(1.1, 2.2);