c++ lambda表達式詳解
轉載請注明:http://krystism.is-programmer.com/若有錯誤,請多多指正,謝謝!
lambda表達式是c++11標準新加特性,學過python的一定不會陌生了,或者類似javascript的閉包。cppreference中的定義是:--*Constructs a closure: an unnamed function object capable of capturing variables in scope. *—-簡單地說就是定義一個臨時局部匿名函數。語法為:
[ capture ] ( params ) mutable exception attribute -> ret { body }
其中capture為定義外部變量是否可見(捕獲),(這里的外部變量是指與定義這個lambda處于同一個作用域的變量)。
若為空,則表示不捕獲所有外部變量,即所有外部變量均不可訪問,= 表示所有外部變量均以值的形式捕獲,在body中訪問外部變量時,訪問的是外部變量的一個副本,類似函數的值傳遞,因此在body中對外部變量的修改均不影響外部變量原來的值。& 表示以引用的形式捕獲,后面加上需要捕獲的變量名,沒有變量名,則表示以引用形式捕獲所有變量,類似函數的引用傳遞,body操作的是外部變量的引用,因此body中修改外部變量的值會影響原來的值。例如:[ ]表示不捕獲任何外部變量, [a, b]以值的形式捕獲a,b, [=]以值的形式捕獲所有外部變量,[&a, b]a以引用的形式捕獲,而b以值的形式捕獲,[&, a],除了a以值的形似捕獲,其他均以引用的形式捕獲,[this]以值的形式捕獲this指針!
params就是函數的形參,和普通函數類似,不過若沒有形參,這個部分可以省略。
mutalbe表示運行body修改通過拷貝捕獲的參數,exception聲明可能拋出的異常,attribute 修飾符,參考:http://en.cppreference.com/w/cpp/language/attributes 。->ret ret表示返回類型,如果能夠根據返回語句自動推導,則可以省略,body即函數體。
注意:除了capture和body是必需的,其他均可以省略,即
int int main(int argc, char const *argv[])
{
[]() {};
return 0;
}
定義了一個空lambda表達式,并執行(實際上它什么都沒有做)。
int main(int argc, char **argv)
{
int i = 0;
[]{cout << i << endl;}(); /* 'i' is not captured */
}
聲明這段代碼不能編譯通過,因為[ ] 沒有捕獲任何外部變量,因此i是不可見的,lambda不能訪問i。
int main(int argc, char **argv)
{
int i = 0;
cout << i << endl;
[=]()mutable{cout << ++i << endl;}();
cout << i << endl;
上面的代碼輸出為0, 1, 0,注意mutable是必需的,因為body中修改了捕獲的i,由于i是以值傳遞的,因此并沒有修改i原來的值,而是i的一個副本。
int main(int argc, char **argv)
{
int i = 0;
cout << i << endl;
[&](){cout << ++i << endl;}();
cout << i << endl;
上面代碼輸出0, 1, 1,因為i是以引用傳遞的,而body中修改了i的值。
lambda表達式有什么用呢? lambda的作用就是創建一個臨時匿名函數,想想有STL算法中很多需要傳遞謂詞函數,比如count_if。假如我有一個字符串容器,我需要統計長度大于3的個數,則可以這樣:
int main(int argc, char **argv)
{
vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"};
cout << count_if(begin(s), end(s), [](string s){return s.size() > 3;});
}
這樣就不必要再聲明一個函數了,代碼比較簡潔。
還有一個問題就是,我可能需要統計字符串長度大于4,或者5,或者6,顯然不能通過傳遞一個形參來實現,因為謂詞函數限定只能傳遞一個形參,這里我們傳遞的是string,而不能再傳遞一個表示長度的數。 如果定義全局函數實現,則我們有兩種方式:
一是聲明一個全局變量,通過和這個全局變量作比較。二是分別聲明大于4或者5的函數。顯然兩個辦法都不太好。全局變量污染一直是很危險的,而聲明多個函數顯然不科學,如果有更多的需求,則需聲明gt2, gt3, gt4 ...。 這個用lambda表達式似乎更好,如:
int main(int argc, char **argv)
{
vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"};
string::size_type n = 3;
cout << count_if(begin(s), end(s), [n](string s){return s.size() > n;});
}
注意這里的n并不是全局變量,所以不存在全局變量污染的問題。當然還有更好的辦法,那就是利用函數對象,如:
using namespace std;
class Gt
{
public:
typedef string::size_type size_type;
Gt(size_type i):n(i){};
Gt():n(0){};
void setN(const size_type& i) {
n = i;
}
size_type getN() const{return n;}
bool operator () (const string &s) const {return s.size() > n;}
operator int() const { return n;}
private:
string::size_type n = 0;
};
inline Gt operator + (const Gt &s1, const Gt &s2)
{
return Gt(s1.getN() + s2.getN());
}
int main(int argc, char **argv)
{
vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"};
cout << count_if(begin(s), end(s), Gt(1)) << endl; /* > 1 */
cout << count_if(begin(s), end(s), Gt(2)) << endl; /* > 2 */
cout << count_if(begin(s), end(s), Gt(2) + Gt(3)) << endl; /* > 5 */
return 0;
}
上面分別統計長度大于1,大于2,大于5的字符串數量(重載+運算符,純屬娛樂?。?。
lambda表達式另外一個功能是聲明局部函數。我們知道c++中是不允許在函數中嵌套定義函數的,如果要聲明一個局部函數,即只能在函數內部調用,則可以用lambda實現,如聲明一個min函數,返回a,b中較小值。則
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
auto min = [](int a, int b) -> int{
return a < b ? a : b;
};
cout << min(1, 2) << endl;
cout << min(8, 2) << endl;
return 0;
}```