為什麼需要lambda函數
匿名函數是許多程式設計語言都支援的概念,有函數體,沒有函數名。1958年,lisp首先采用匿名函數,匿名函數最常用的是作為回調函數的值。正因為有這樣的需求,c++引入了lambda 函數,你可以在你的源碼中内聯一個lambda函數,這就使得建立快速的,一次性的函數變得簡單了。例如,你可以把lambda函數可在參數中傳遞給std::sort函數
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned N) {
std::sort(x, x + N,
// Lambda expression begins
[](float a, float b) {
return std::abs(a) < std::abs(b);
});
}
你可能會問,使用函數對象不是也可以嗎?是的,函數對象當然沒問題,自己寫的回調函數,你可以傳個函數指針也沒有問題。他們有優點也有缺點。函數對象能維護狀态,但文法開銷大,而函數指針文法開銷小,卻沒法儲存範圍内的狀态。如果你覺得魚和熊掌不可兼得,那你可錯了。lambda函數結合了兩者的優點,讓你寫出優雅簡潔的代碼。
基本lambda文法
基本形式如下:
[capture](parameters)->return-type {body}
[]叫做捕獲說明符,表示一個lambda表達式的開始。接下來是參數清單,即這個匿名的lambda函數的參數,->return-type表示傳回類型,如果沒有傳回類型,則可以省略這部分。想知道為什麼傳回類型可以這麼表示,這涉及到c++11的另一特性,參見自動類型推導,最後就是函數體部分了。
我們可以這樣輸出"hello,world"
auto func = [] () { cout << "hello,world"; };
func(); // now call the function
變量捕獲與lambda閉包實作
string name;
cin >> name;
[&](){cout << name;}();
lambda函數能夠捕獲lambda函數外的具有自動存儲時期的變量。函數體與這些變量的集合合起來叫閉包。
- [] 不截取任何變量
- [&} 截取外部作用域中所有變量,并作為引用在函數體中使用
- [=] 截取外部作用域中所有變量,并拷貝一份在函數體中使用
- [=, &foo] 截取外部作用域中所有變量,并拷貝一份在函數體中使用,但是對foo變量使用引用
- [bar] 截取bar變量并且拷貝一份在函數體重使用,同時不截取其他變量
- [x, &y] x按值傳遞,y按引用傳遞
- [this] 截取目前類中的this指針。如果已經使用了&或者=就預設添加此選項。
看到這,不禁要問,這魔法般的變量捕獲是怎麼實作的呢?原來,lambda是通過建立個小類來實作的。這個類重載了操作符(),一個lambda函數是該類的一個執行個體。當該類被構造時,周圍的變量就傳遞給構造函數并以成員變量儲存起來。看起來跟函數對象很相似。
最後,lambda函數的類型是什麼呢,答案是std:function。
C++11 的 lambda 表達式規範如下:
capture params mutable exception attribute ret body | (1) |
| (2) |
| (3) |
| (4) |
其中
- (1) 是完整的 lambda 表達式形式,
- (2) const 類型的 lambda 表達式,該類型的表達式不能改捕獲("capture")清單中的值。
- (3)省略了傳回值類型的 lambda 表達式,但是該 lambda 表達式的傳回類型可以按照下列規則推演出來:
- 如果 lambda 代碼塊中包含了 return 語句,則該 lambda 表達式的傳回類型由 return 語句的傳回類型确定。
- 如果沒有 return 語句,則類似 void f(...) 函數。
- 省略了參數清單,類似于無參函數 f()。
mutable 修飾符說明 lambda 表達式體内的代碼可以修改被捕獲的變量,并且可以通路被捕獲對象的 non-const 方法。
exception 說明 lambda 表達式是否抛出異常(
noexcept
),以及抛出何種異常,類似于void f() throw(X, Y)。
-
a變量以值的方式呗捕獲,b以引用的方式被捕獲。[a,&b]
-
以值的方式捕獲 this 指針。[this]
-
以引用的方式捕獲所有的外部自動變量。[&]
-
以值的方式捕獲所有的外部自動變量。[=]
-
不捕獲外部的任何變量。[]