lambda表達式是C++11很是重要也是很經常使用的特性之一,來源於函數式編程的概念,也是現代編程語言的一個特色。它有以下特色:編程
lambda表達式定義一個匿名函數,而且能夠捕獲必定範圍內的變量,其定義以下:閉包
[captrue] (params) opt -> ret {body};
其中,capture是捕獲列表;params是參數列表;opt是函數選項;ret是返回值類型;body是函數體。less
咱們來作一個簡單的例子,定義一個簡單的封包,用來將輸入的結果+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表達式在沒有參數列表的時候,參數列表能夠省略。this
auto f1 = [] () {return 1;}; auto f2 = [] {return 1;} //省略空參數表
lambda表達式能夠經過捕獲列表捕獲必定範圍內的變量:spa
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表達式在調用的時候能即時訪問外部變量,應當使用引用的方式捕獲。code
若是但願去修改按值捕獲的外部變量,須要顯示指明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表達式不能直接轉換爲函數指針,不能經過編譯
就地定義匿名函數,再也不須要定義函數對象,大大簡化了標準庫的調用。好比咱們使用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,提高開發效率,效果會更好。