C++11 新特性:Lambda 表達式

參考文章:https://blogs.oracle.com/pcarlini/entry/c_1x_tidbits_lambda_expressions

或許,Lambda 表達式算得上是 C++ 11 新增特性中最激動人心的一個。這個全新的特性聽起來很深奧,但倒是不少其餘語言早已提供(好比 C#)或者即將提供(好比 Java)的。簡而言之,Lambda 表達式就是用於建立匿名函數的。GCC 4.5.x 和 Microsoft Visual Studio 早已提供了對 lambda 表達式的支持。在 GCC 4.7 中,默認是不開啓 C++ 11 特性的,須要添加  -std=c++11 編譯參數。而 VS2010 則默認開啓。c++

爲何說 lambda 表達式如此激動人心呢?舉一個例子。標準 C++ 庫中有一個經常使用算法的庫,其中提供了不少算法函數,好比 sort() 和 find()。這些函數一般須要提供一個「謂詞函數 predicate function」。所謂謂詞函數,就是進行一個操做用的臨時函數。好比 find() 須要一個謂詞,用於查找元素知足的條件;可以知足謂詞函數的元素纔會被查找出來。這樣的謂詞函數,使用臨時的匿名函數,既能夠減小函數數量,又會讓代碼變得清晰易讀。算法

下面來看一個例子:express

  1:   #include <algorithm>
  2:   #include <cmath>
  3:   
  4:   void abssort(float *x, unsigned N)
  5:   {
  6:       std::sort(x,
  7:                 x + N,
  8:                 [](float a, float b) { return std::abs(a) < std::abs(b); });
  9:   }

從上面的例子來看,儘管支持 lambda 表達式,但 C++ 的語法看起來卻很「神奇」。lambda 表達式使用一對方括號做爲開始的標識,相似於聲明一個函數,只不過這個函數沒有名字,也就是一個匿名函數。這個匿名函數接受兩個參數,ab;其返回值是一個 bool 類型的值,注意,返回值是自動推斷的,不須要顯式聲明,不過這是有條件的!條件就是,lambda 表達式的語句只有一個 return。函數的做用是比較 a、b 的絕對值的大小。而後,在此例中,這個 lambda 表達式做爲一個閉包被傳遞給 std::sort() 函數。閉包

下面,咱們來詳細解釋下這個神奇的語法到底表明着什麼。oracle

咱們從另一個例子開始:函數

  1: std::cout << [](float f) { return std::abs(f); } (-3.5);

輸出值是什麼?3.5!注意,這是一個函數對象(由 lambda 表達式生成),其實參是 -3.5,返回值是參數的絕對值。lambda 表達式的返回值類型是語言自動推斷的,由於std::abs()的返回值就是 float。注意,前面咱們也提到了,只有當 lambda 表達式中的語句「足夠簡單」,才能自動推斷返回值類型。this

C++ 11 的這種語法,其實就是匿名函數聲明以後立刻調用(不然的話,若是這個匿名函數既不調用,又不做爲閉包傳遞給其它函數,那麼這個匿名函數就沒有什麼用處)。若是你以爲奇怪,那麼來看看 JavaScript 的這種寫法:spa

  1: function() {} ();
  2: 
  3: function(a) {} (-3.5);

C++ 11 的寫法徹底相似 JavaScript 的語法。指針

若是我不想讓 lambda 表達式自動推斷類型,或者是 lambda 表達式的內容很複雜,不能自動推斷怎麼辦?好比,std::abs(float)的返回值是 float,我想把它強制轉型爲 int。那麼,此時,咱們就必須顯式指定 lambda 表達式返回值的類型:c++11

  1: std::cout << [](float f) -> int { return std::abs(f); } (-3.5);

這個語句與前面的不一樣之處在於,lambda 表達式的返回時不是 float 而是 int。也就是說,上面語句的輸出值是 3。返回值類型的概念同普通的函數返回值類型是徹底同樣的。

引入 lambda 表達式的前導符是一對方括號,稱爲 lambda 引入符(lambda-introducer)。lambda 引入符是有其本身的做用的,不只僅是代表一個 lambda 表達式的開始那麼簡單。lambda 表達式可使用與其相同範圍 scope 內的變量。這個引入符的做用就是代表,其後的 lambda 表達式以何種方式使用(正式的術語是「捕獲」)這些變量(這些變量可以在 lambda 表達式中被捕獲,其實就是構成了一個閉包)。目前爲止,咱們看到的僅僅是一個空的方括號,其實,這個引入符是至關靈活的。例如:

  1: float f0 = 1.0;
  2: 
  3: std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5);

其輸出值是 4.5。[=] 意味着,lambda 表達式以傳值的形式捕獲同範圍內的變量。另一個例子:

  1: float f0 = 1.0;
  2: 
  3: std::cout << [&](float f) { return f0 += std::abs(f); } (-3.5);
  4: 
  5: std::cout << '\n' << f0 << '\n';

輸出值是 4.5 和 4.5。[&] 代表,lambda 表達式以傳引用的方式捕獲外部變量。那麼,下一個例子:

  1: float f0 = 1.0;
  2: 
  3: std::cout << [=](float f) mutable { return f0 += std::abs(f); } (-3.5);
  4: 
  5: std::cout << '\n' << f0 << '\n';

這個例子頗有趣。首先,[=]意味着,lambda 表達式以傳值的形式捕獲外部變量。C++ 11 標準說,若是以傳值的形式捕獲外部變量,那麼,lambda 體不容許修改外部變量,對 f0 的任何修改都會引起編譯錯誤。可是,注意,咱們在 lambda 表達式前聲明瞭mutable關鍵字,這就容許了 lambda 表達式體修改 f0 的值。所以,咱們的例子本應報錯,可是因爲有 mutable 關鍵字,則不會報錯。那麼,你會以爲輸出值是什麼呢?答案是,4.5 和 1.0。爲何 f0 仍是 1.0?由於咱們是傳值的,雖然在 lambda 表達式中對 f0 有了修改,但因爲是傳值的,外部的 f0 依然不會被修改。

上面的例子是,全部的變量要麼傳值,要麼傳引用。那麼,是否是有混合機制呢?固然也有!好比下面的例子:

  1: float f0 = 1.0f;
  2: 
  3: float f1 = 10.0f;
  4: 
  5: std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5);
  6: 
  7: std::cout << '\n' << f0 << '\n';

這個例子的輸出是 14.5 和 14.5。在這個例子中,f0 經過引用被捕獲,而其它變量,好比 f1 則是經過值被捕獲。

下面咱們來總結下全部出現的 lambda 引入符:

  • []        // 不捕獲任何外部變量
  • [=]      // 以值的形式捕獲全部外部變量
  • [&]      // 以引用形式捕獲全部外部變量
  • [x, &y] // x 以傳值形式捕獲,y 以引用形式捕獲
  • [=, &z]// z 以引用形式捕獲,其他變量以傳值形式捕獲
  • [&, x]  // x 以值的形式捕獲,其他變量以引用形式捕獲

另外有一點須要注意。對於[=][&]的形式,lambda 表達式能夠直接使用 this 指針。可是,對於[]的形式,若是要使用 this 指針,必須顯式傳入:

  1: [this]() { this->someFunc(); }();

至此,咱們已經大體瞭解了 C++ 11 提供的 lambda 表達式的概念。建議經過結合 lambda 表達式與std::sort()std::for_each()這樣的標準函數來嘗試使用一下吧!

相關文章
相關標籤/搜索