【1】lambda表達式語法定義php
lambda表達式的語法定義以下:ios
[capture] (parameters) mutable ->return-type {statement};程序員
(1) [capture]: 捕捉列表。捕捉列表老是出如今lambda函數的開始處。實質上,[]是lambda引出符(即獨特的標誌符)express
編譯器根據該引出符判斷接下來的代碼是不是lambda函數編程
捕捉列表可以捕捉上下文中的變量以供lambda函數使用vim
捕捉列表由一個或多個捕捉項組成,並以逗號分隔,捕捉列表通常有如下幾種形式:閉包
<1> [var] 表示值傳遞方式捕捉變量varide
<2> [=] 表示值傳遞方式捕捉全部父做用域的變量(包括this指針)函數
<3> [&var] 表示引用傳遞捕捉變量var學習
<4> [&] 表示引用傳遞捕捉全部父做用域的變量(包括this指針)
<5> [this] 表示值傳遞方式捕捉當前的this指針
<6> [=,&a,&b] 表示以引用傳遞的方式捕捉變量 a 和 b,而以值傳遞方式捕捉其餘全部的變量
<7> [&,a,this] 表示以值傳遞的方式捕捉 a 和 this,而以引用傳遞方式捕捉其餘全部變量
備註:父做用域是指包含lambda函數的語句塊
另外,須要注意的是,捕捉列表不容許變量重複傳遞。下面的例子就是典型的重複,會致使編譯錯誤:
[=, a] 這裏 = 已經以值傳遞方式捕捉了全部的變量,那麼再捕捉 a 屬於重複
[&,&this] 這裏 & 已經以引用傳遞方式捕捉了全部變量,那麼再捕捉 this 屬於重複
(2)(parameters):參數列表。與普通函數的參數列表一致。若是不須要參數傳遞,則能夠連同括號()一塊兒省略
(3)mutable : mutable修飾符。默認狀況下,lambda函數老是一個const函數,mutable能夠取消其常量性(後面有詳解)
在使用該修飾符時,參數列表不可省略(即便參數爲空)
(4)->return-type : 返回類型。用追蹤返回類型形式聲明函數的返回類型。
出於方便,不須要返回值的時候也能夠連同符號->一塊兒省略
此外,在返回類型明確的狀況下,也能夠省略該部分,讓編譯器對返回類型進行推導
(5){statement} : 函數體。內容與普通函數同樣,不過除了可使用參數以外,還可使用全部捕獲的變量
在lambda函數的定義中,參數列表和返回類型都是可選的部分,而捕捉列表和函數體均可能爲空
那麼,在極端狀況下,C++11中最爲簡單的lambda函數只須要聲明爲:
[] {};
就能夠了。不過顯然,這樣的lambda函數不能作任何事情(乍一看好漂亮,其實只是好看)。
【2】lambda函數示例代碼
示例代碼1:
1 #include <iostream>
2 using namespace std; 3
4 void main() 5 { 6 int a = 20, b = 10; 7
8 auto totalAB = [] (int x, int y)->int{ return x + y; }; 9 int aAddb = totalAB(a, b); 10 cout << "aAddb :" << aAddb << endl; 11
12 auto totalAB2 = [a, &b]()->int{ return a + b; }; 13 int aAddb2 = totalAB2(); 14 cout << "aAddb2 :" << aAddb2 << endl; 15
16 auto totalAB3 = [=]()->int{ return a + b; }; 17 int aAddb3 = totalAB3(); 18 cout << "aAddb3 :" << aAddb3 << endl; 19
20 []{}; // 最簡lambda函數
21 [=] { return a + b; }; // 省略了參數列表與返回類型,返回類型由編譯器推斷爲int
22 auto fun1 = [&] (int c) { b = a + c; }; // 省略了返回類型,無返回值
23 auto fun2 = [=, &b](int c)->int { return b += a + c; }; // 各部分都很完整的lambda函數
24 cout << "fun2(100) :" << fun2(100) << endl; 25 } 26 // Result:
27 /*
28 aAddb :30 29 aAddb2 :30 30 aAddb3 :30 31 fun2(100) :130 32 */
以上代碼僅供學習參考
【3】lambda函數的做用
lambda函數的使用示例代碼:
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4 #include "time.h"
5 using namespace std; 6
7 void main() 8 { 9 vector<int> nVec; 10 for (int i = 0; i < 100000; ++i) 11 { 12 nVec.push_back(i); 13 } 14
15 double time_Start = (double)clock(); 16 for (vector<int>::const_iterator it = nVec.begin(); it != nVec.end(); ++it) 17 { 18 cout << *it << endl; 19 } 20 double time_Finish = (double)clock(); 21 double time_Interval_1 = (double)(time_Finish - time_Start) / 1000; 22
23 time_Start = (double)clock(); 24 for_each( nVec.begin(), nVec.end(), [] (int val){ cout << val << endl; } ); 25 time_Finish = (double)clock(); 26 double time_Interval_2 = (double)(time_Finish - time_Start) / 1000; 27
28 cout << "time_Interval_1 :" << time_Interval_1 << endl; 29 cout << "time_Interval_2 :" << time_Interval_2 << endl; 30
31 } 32 // Result:
33 /*
34 time_Interval_1 :17.748 35 time_Interval_2 :17.513 36 */
lambda函數的引入爲STL的使用提供了極大的方便。一樣是遍歷容器,效率反而提升了不少。
【4】lambda函數 與 仿函數
何謂仿函數?我的理解,像函數同樣工做的對象。
根據面向對象的編程思想,那麼問題來了!既然主語是一個對象,建立這個對象的類長什麼樣子呢?
據據說,全部科學中數學學科最重要,語文重要性次之。爲何呢?
數學能夠利用來解決問題,但當問題解決不了的時候,能夠用語文塗畫,塗畫得讓人聽不懂。好像很高大上同樣同樣~
關於仿函數,請看下面示例:
1 #include <iostream>
2 using namespace std; 3
4 class _functor_plus 5 { 6 private: 7 int m_nValue; 8
9 public: 10 _functor_plus(int nValue = 100); 11 _functor_plus operator+ (const _functor_plus & funObj); 12 void printInfo(); 13 }; 14
15 _functor_plus::_functor_plus(int nValue) : m_nValue(nValue) 16 { 17 } 18
19 _functor_plus _functor_plus::operator+ (const _functor_plus & funObj) 20 { 21 m_nValue += funObj.m_nValue; 22 return _functor_plus(m_nValue); 23 } 24
25 void _functor_plus::printInfo() 26 { 27 cout << m_nValue << endl; 28 } 29
30 class _functor_override 31 { 32 public: 33 int operator()(int x, int y); 34 }; 35
36 int _functor_override::operator()(int x, int y) 37 { 38 return x + y; 39 } 40
41 int main() 42 { 43 _functor_plus plusA, plusB, plusC; 44 plusC = plusA + plusB; 45 plusA.printInfo(); 46 plusB.printInfo(); 47 plusC.printInfo(); 48
49 int boys = 4, girls = 3; 50 _functor_override totalChildren; 51 cout << "totalChildren(int, int): " << totalChildren(boys, girls); 52 } 53 // Result:
54 /*
55 200 56 100 57 200 58 totalChildren(int, int): 7 59 */
在這個例子中,_functor_override類的operator()被重載。
所以,在調用該函數的時候,咱們看到與函數調用同樣的形式。
只不過這裏的totalChildren不是函數名稱,而是一個對象名稱。
相比於函數,仿函數能夠擁有初始化狀態:
通常經過class定義私有成員,並在聲明對象的時候對其進行初始化,
那般,私有成員的狀態就成了仿函數的初始狀態。
因爲聲明一個仿函數對象能夠擁有多個不一樣的初始狀態的實例,
所以,能夠藉由仿函數產生多個功能相似實質卻各不一樣的仿函數實例。
請參見下例:
1 #include <iostream>
2 using namespace std; 3
4 class Tax 5 { 6 private: 7 double m_dRate; 8 int m_nBase; 9
10 public: 11 Tax(double dRate, int nBase) : m_dRate(dRate), m_nBase(nBase) 12 { 13 } 14
15 double operator() (double dMoney) 16 { 17 return (dMoney - m_nBase) * m_dRate; 18 } 19 }; 20
21 int main() 22 { 23 Tax high(0.40, 30000); 24 Tax middle(0.25, 20000); 25 cout << "tax over 3w: " << high(37500) << endl; 26 cout << "tax over 2w: " << middle(24000) << endl; 27 } 28 // Result:
29 /*
30 tax over 3w: 3000 31 tax over 2w: 1000 32 */
到這裏,是否發現仿函數和lambda之間存在一種「衍生」的關係?
難道還不明顯?沒看懂?咱再接着剖析,誰讓程序員就這麼理性呢?
請再看下例:
1 #include <iostream>
2 using namespace std; 3
4 class AirportPrice 5 { 6 private: 7 double m_dDutyfreeRate; 8
9 public: 10 AirportPrice(double dDutyfreeRate) : m_dDutyfreeRate(dDutyfreeRate) 11 {} 12
13 double operator() (double dPrice) 14 { 15 return dPrice * (1 - m_dDutyfreeRate/100); 16 } 17 }; 18
19 void main() 20 { 21 double dRate = 5.5; 22 AirportPrice fanFunObj(dRate); 23
24 auto ChangLambda = [dRate](double dPrice)->double
25 { 26 return dPrice * (1 - dRate/100); 27 }; 28 double purchased1 = fanFunObj(3699); 29 double purchased2 = ChangLambda(3699); 30 cout << "purchased1:" << purchased1 << endl; 31 cout << "purchased2:" << purchased2 << endl; 32 } 33 // Result:
34 /*
35 purchased1:3495.55 36 purchased2:3495.55 37 */
分別使用了仿函數和lambda兩種方式來完成扣稅後的產品價格計算。
lamba函數捕捉了dRate變量,而仿函數則以dRate進行初始化類。
其餘的,在參數傳遞上,二者保持一致,結果也一致。
能夠看到,除去在語法層面的差別,lambda函數和仿函數有着相同的內涵:
即均可以捕捉一些變量做爲初始化狀態,並接受參數進行運算。
而事實上,仿函數正是編譯器實現lambda的一種方式。
在現階段,一般編譯器都會把lambda函數轉化爲一個仿函數對象。
所以,C++11中,lambda能夠視爲仿函數的一種等價形式。
備註:有時,編譯時發現lambda函數出現了錯誤,編譯器會提示一些構造函數相關的信息,
顯然是因爲lambda的這種實現方式形成的。理解這種實現也可以正確理解錯誤信息的由來。
【5】lambda函數等同於一個局部函數
局部函數,在函數做用域中定義的函數,也稱爲內嵌函數。
局部函數一般僅屬於其父做用域,可以訪問父做用域的變量。
C/C++語言標準中不容許局部函數存在(FORTRAN語言支持)
C++11標準卻用比較優雅的方式打破了這個規則。
由於事實上,lambda能夠像局部函數同樣使用。請參見下例:
1 #include <iostream>
2 using namespace std; 3
4 extern int z = 100; 5 extern float c = 100.00; 6
7 void Calc(int& rnOne, int nTwo, float& rfThree, float fFour) 8 { 9 rnOne = nTwo; 10 rfThree = fFour; 11 } 12
13 void TestCalc() 14 { 15 int x, y = 3; 16 float a, b = 4.0; 17 int success = 0; 18
19 auto validate = [&]()->bool
20 { 21 if ((x == y + z) && (a == b + c)) 22 return 1; 23 else
24 return 0; 25 }; 26
27 Calc(x, y, a, b); 28 success += validate(); 29
30 y = 1024; 31 b = 100.0; 32 Calc(x, y, a, b); 33 success += validate(); 34 } 35
36 void main() 37 { 38 }
在沒有lambd函數以前,一般須要在TestCalc外聲明一樣一個函數,
而且把TestCalc中的變量看成參數進行傳遞。
出於函數做用域及運行效率的考慮,那樣聲明函數一般要加上關鍵字static 和 inline
相比於一個傳統意義上的函數定義,lambda函數在這裏直觀,使用方即可讀性很好。請參見下例:
1 #include <iostream>
2 using namespace std; 3
4 int Prioritize(int nValue) 5 { 6 return nValue + 10; 7 } 8
9 int AllWorks(int nTimes) 10 { 11 int i = 0, x = 0; 12 try
13 { 14 for (i = 0; i < nTimes; ++i) 15 { 16 x += Prioritize(i); 17 } 18 } 19 catch (...) 20 { 21 x = 0; 22 } 23
24 const int y = [=]()->int
25 { 26 int i = 0, val = 0; 27 try
28 { 29 for (; i < nTimes; ++i) 30 { 31 val += Prioritize(i); 32 } 33 } 34 catch (...) 35 { 36 val = 0; 37 } 38 return val; 39 }(); 40 // lambda表達式
41 { 42 []{}(); 43 [](){}(); 44 []{ cout << "emptyLambdaExec" << endl; }(); 45 [=](){ cout << "const int y :" << y << endl; }(); 46 [&](){ cout << "int x :" << x << endl; }(); 47 } 48
49 return 0; 50 } 51
52 void main() 53 { 54 AllWorks(10); 55 } 56
57 // Result:
58 /*
59 emptyLambdaExec 60 const int y :145 61 int x :145 62 */
備註:注意此例中的lambda表達式做用域中比較特殊的幾個lambda函數。
【6】關於lambda的一些問題及其有趣的測試
(1)使用lambda函數時候,不一樣的捕捉方式會致使不一樣的結果:
請看下例:
1 #include <iostream>
2 using namespace std; 3
4 void main() 5 { 6 int j = 10; 7 auto by_val_lambda = [=] { return j + 1; }; 8 auto by_ref_lambda = [&] { return j + 1; }; 9 cout << "by_val_lambda: " << by_val_lambda() << endl; 10 cout << "by_ref_lambda: " << by_ref_lambda() << endl; 11 ++j; 12 cout << "by_val_lambda: " << by_val_lambda() << endl; 13 cout << "by_ref_lambda: " << by_ref_lambda() << endl; 14 } 15
16 //Result:
17 /*
18 by_val_lambda: 11 19 by_ref_lambda: 11 20 by_val_lambda: 11 21 by_ref_lambda: 12 22 */
充分說明了傳值和引用方式的區別。
(2)使用lambda函數與函數指針
通常狀況下,把匿名的lambda函數賦值給一個auto類型的變量,
這是一種聲明和使用lambda函數的方法。
結合關於auto的知識,有人會猜想totalChild是一種函數指針類型的變量
結合lambda函數和仿函數之間關係,大多人會傾向於認爲lambda是一種自定義類型。
實質上,lambda的類型並不是簡單函數指針類型或自定義類型。
從C++11標準定義發現,lambda類型被定義爲「閉包」的類,而每個lambda表達式則會產生一個閉包類型的臨時對象。
也所以,嚴格地講,lambda函數並不是函數指針。
可是,C++11標準卻容許lambda表達式向函數指針的轉換,
前提是lambda函數沒有捕捉任何變量,且函數指針所示的函數原型,必須跟lambda函數有着相同的調用方式。
1 #include <iostream>
2 using namespace std; 3
4 void main() 5 { 6 int girs = 3, boys = 4; 7 auto totalChild = [](int x, int y)->int{ return x + y; }; 8 typedef int (*pFunAll)(int x, int y); 9 typedef int (*pFunOne)(int x); 10
11 pFunAll funAll; 12 // funAll = totalChild; // 編譯失敗!
13
14 pFunOne funOne; 15 // funOne = totalChild; //編譯失敗!參數必須一致
16
17 decltype(totalChild) allPeople = totalChild; // 需經過decltype得到lambda的類型 18 // decltype(totalChild) totalPeople = funAll; // 編譯失敗,指針沒法轉換lambda
19 }
第 12 行,編譯錯誤信息以下:
error C2440: 「=」: 沒法從「`anonymous-namespace'::<lambda0>」轉換爲「pFunAll」
沒有可用於執行該轉換的用戶定義的轉換運算符,或者沒法調用該運算符
MSVC10環境下,第一步編譯不經過。
關於此問題參見文章《在 MSVC10 下,將 lambda expression 轉換成 C 的 function pointer》
第 15 行 編譯失敗,參數不一致
第 18 行 編譯失敗,函數指針轉換爲lambda也是不成功的。
值得注意的是,能夠經過decltype的方式獲取lambda函數的類型。
(3)lambda函數的常量性以及mutable關鍵字
C++11中,默認狀況下lambda函數是一個const函數。
神馬意思呢?請參見下例:
1 #include <iostream>
2 using namespace std; 3
4 class const_val_lambda 5 { 6 public: 7 const_val_lambda(int v) : m_nVal(v) 8 {} 9 public: 10 void operator() () const
11 { 12 // m_nVal = 3; /*注意:常量成員函數*/
13 } 14
15 void ref_const_Fun(int& nValue) const
16 { 17 nValue = 100; 18 } 19
20 private: 21 int m_nVal; 22 }; 23
24 void main() 25 { 26 int val = 10; 27 // 編譯失敗!在const的lambda中修改常量 28 // auto const_val_lambda = [=]() { val = 3;}; // 不能在非可變 lambda 中修改按值捕獲 29 // 非const的lambda,能夠修改常量數據
30 auto mutable_val_lambda = [=]() mutable{ val = 3; }; 31 // 依然是const的lambda,不過沒有改動引用自己
32 auto const_ref_lambda = [&] { val = 3; }; 33 // 依然是const的lambda,經過參數傳遞val
34 auto const_param_lambda = [&](int varA) { varA = 3;}; 35 const_param_lambda(val); 36 }
備註:使用引用方式傳遞的變量在常量成員函數中修改值並不會致使錯誤。
【7】lambda 與 STL
lambda對C++11最大的貢獻,或者說改變,應該在STL庫中
相關應用,具體請再參見下例:
1 #include <vector>
2 #include <algorithm>
3 #include <iostream>
4 using namespace std; 5
6 const int ubound = 3; 7
8 vector<int> nums; 9 vector<int> largeNums; 10
11 void initNums() 12 { 13 for (int i = 1; i < 5; ++i) 14 { 15 nums.push_back(i); 16 } 17 } 18
19 inline void largeNumsFunc(int i) 20 { 21 if (i > ubound) 22 { 23 largeNums.push_back(i); 24 } 25 } 26
27 void filter() 28 { 29 for (auto it = nums.begin(); it != nums.end(); ++it) 30 { 31 if ((*it) > ubound) 32 { 33 largeNums.push_back(*it); 34 } 35 } 36
37 for_each (nums.begin(), nums.end(), largeNumsFunc); 38
39 for_each (nums.begin(), nums.end(), [=](int i) 40 { 41 if (i > ubound) 42 { 43 largeNums.push_back(i); 44 } 45 }); 46 } 47
48 void printInfo() 49 { 50 for_each (largeNums.begin(), largeNums.end(), [=](int i) 51 { 52 cout << i << " "; 53 }); 54 cout << endl; 55 } 56
57 void main() 58 { 59 initNums(); // 初始化值
60 filter(); // 過濾值
61 printInfo(); //打印信息
62 }
具體遇到其它的問題 ,再具體分析和學習。
【8】lambda函數的總結
C++ 11中的Lambda表達式用於定義並建立匿名的函數對象,以簡化編程工做。
Good Good Study, Day Day Up.
順序 選擇 循環 總結