C++11—lambda函數

【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.

順序  選擇  循環  總結

相關文章
相關標籤/搜索