C++ lambda表達式詳解
C++11 lambda表達式精講
[ capture ] ( params ) opt -> ret { body; };
capture 是捕獲列表,params 是參數表,opt 是函數選項,ret 是返回值類型,body是函數體
一個完整的 lambda 表達式看起來像這樣:
auto f = [](int a) -> int { return a + 1; };
std::cout << f(1) << std::endl; // 輸出: 2
lambda 表達式還可以通過捕獲列表捕獲一定范圍內的變量:
- [] 不捕獲任何變量
- [&] 捕獲外部作用域中所有變量,并作為引用在函數體中使用(按引用捕獲)
- [=] 捕獲外部作用域中所有變量,并作為副本在函數體中使用(按值捕獲)
- [=,&foo] 按值捕獲外部作用域中所有變量,并按引用捕獲 foo 變量
- [bar] 按值捕獲 bar 變量,同時不捕獲其他變量
- [this] 捕獲當前類中的 this 指針,讓 lambda 表達式擁有和當前類成員函數同樣的訪問權限。如果已經使用了 & 或者 =,就默認添加此選項。捕獲 this 的目的是可以在 lamda 中使用當前類的成員函數和成員變量
class A
{public:int i_ = 0;void func(int x, int y){auto x1 = []{ return i_; }; // error,沒有捕獲外部變量auto x2 = [=]{ return i_ + x + y; }; // OK,捕獲所有外部變量auto x3 = [&]{ return i_ + x + y; }; // OK,捕獲所有外部變量auto x4 = [this]{ return i_; }; // OK,捕獲this指針auto x5 = [this]{ return i_ + x + y; }; // error,沒有捕獲x、yauto x6 = [this, x, y]{ return i_ + x + y; }; // OK,捕獲this指針、x、yauto x7 = [this]{ return i_++; }; // OK,捕獲this指針,并修改成員的值}
};
int a = 0, b = 1;
auto f1 = []{ return a; }; // error,沒有捕獲外部變量
auto f2 = [&]{ return a++; }; // OK,捕獲所有外部變量,并對a執行自加運算
auto f3 = [=]{ return a; }; // OK,捕獲所有外部變量,并返回a
auto f4 = [=]{ return a++; }; // error,a是以復制方式捕獲的,無法修改
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; // a被修改了
std::cout << f() << std::endl; // 輸出?
在這個例子中,lambda 表達式按值捕獲了所有外部變量。在捕獲的一瞬間,a 的值就已經被復制到f中了。之后 a 被修改,但此時 f 中存儲的 a 仍然還是捕獲時的值,因此,最終輸出結果是 0
如果希望 lambda 表達式在調用時能夠即時訪問外部變量,我們應當使用引用方式捕獲
從上面的例子中我們知道,按值捕獲得到的外部變量值是在 lambda 表達式定義時的值。此時所有外部變量均被復制了一份存儲在 lambda 表達式變量中。此時雖然修改 lambda 表達式中的這些外部變量并不會真正影響到外部,我們卻仍然無法修改它們
那么如果希望去修改按值捕獲的外部變量應當怎么辦呢?這時,需要顯式指明 lambda 表達式為 mutable:
int a = 0;
auto f1 = [=]{ return a++; }; // error,修改按值捕獲的外部變量
auto f2 = [=]() mutable { return a++; }; // OK,mutable
需要注意的一點是,被 mutable 修飾的 lambda 表達式就算沒有參數也要寫明參數列表
聲明式的編程風格,簡潔的代碼
就地定義匿名函數,不再需要定義函數對象,大大簡化了標準庫算法的調用。比如,在 C++11 之前,我們要調用 for_each 函數將 vector 中的偶數打印出來,如下所示
class CountEven
{int& count_;
public:CountEven(int& count) : count_(count) {}void operator()(int val){if (!(val & 1)) // val % 2 == 0{++ count_;}}
};
std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
int even_count = 0;
for_each(v.begin(), v.end(), CountEven(even_count));
std::cout << "The number of even is " << even_count << std::endl;
這樣寫既煩瑣又容易出錯。有了 lambda 表達式以后,我們可以使用真正的閉包概念來替換掉這里的仿函數,代碼如下:
std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
int even_count = 0;
for_each( v.begin(), v.end(), [&even_count](int val){if (!(val & 1)) // val % 2 == 0{++ even_count;}});
std::cout << "The number of even is " << even_count << std::endl;
lambda 表達式的價值在于,就地封裝短小的功能閉包
,可以極其方便地表達出我們希望執行的具體操作,并讓上下文結合得更加緊密