1. 程式人生 > >C++11新特性之Lambda函式

C++11新特性之Lambda函式

我是搞C++的

一直都在提醒自己,我是搞C++的;但是當C++11出來這麼長時間了,我卻沒有跟著隊伍走,發現很對不起自己的身份,也還好,發現自己也有段時間沒有寫C++程式碼了。今天看到了C++中的Lambda表示式,雖然用過C#的,但是C++的,一直沒有用,也不知道怎麼用,就可憐的連Lambda語法都看不懂。好了,這裡就對C++中的Lambda進行一個簡單的總結,就算是對自己的一個交代,我是搞C++的,我是一個C++ programmer。

一段簡單的Code

我也不是文藝的人,對於Lambda的歷史,以及Lambda與C++的那段淵源,我也不是很熟悉,技術人,講究拿程式碼說事。

#include
<iostream>
using namespace std; int main() { int a = 1; int b = 2; auto func = [=, &b](int c)->int {return b += a + c;}; return 0; }

當我第一次看到這段程式碼時,我直接凌亂了,直接看不懂啊。上面這段程式碼,如果你看懂了,下面的內容就當時複習了;如果看不懂了,就接著和我一起總結吧。

基本語法

簡單來說,Lambda函式也就是一個函式,它的語法定義如下:

[capture](parameters) mutable
->return-type{statement}
  1. [capture]:捕捉列表。捕捉列表總是出現在Lambda函式的開始處。實際上,[]是Lambda引出符。編譯器根據該引出符判斷接下來的程式碼是否是Lambda函式。捕捉列表能夠捕捉上下文中的變數以供Lambda函式使用;
  2. (parameters):引數列表。與普通函式的引數列表一致。如果不需要引數傳遞,則可以連同括號“()”一起省略;
  3. mutable:mutable修飾符。預設情況下,Lambda函式總是一個const函式,mutable可以取消其常量性。在使用該修飾符時,引數列表不可省略(即使引數為空);
  4. ->return-type:返回型別。用追蹤返回型別形式宣告函式的返回型別。我們可以在不需要返回值的時候也可以連同符號”->”一起省略。此外,在返回型別明確的情況下,也可以省略該部分,讓編譯器對返回型別進行推導;
  5. {statement}:函式體。內容與普通函式一樣,不過除了可以使用引數之外,還可以使用所有捕獲的變數。

與普通函式最大的區別是,除了可以使用引數以外,Lambda函式還可以通過捕獲列表訪問一些上下文中的資料。具體地,捕捉列表描述了上下文中哪些資料可以被Lambda使用,以及使用方式(以值傳遞的方式或引用傳遞的方式)。語法上,在“[]”包括起來的是捕捉列表,捕捉列表由多個捕捉項組成,並以逗號分隔。捕捉列表有以下幾種形式:

  1. [var]表示值傳遞方式捕捉變數var;
  2. [=]表示值傳遞方式捕捉所有父作用域的變數(包括this);
  3. [&var]表示引用傳遞捕捉變數var;
  4. [&]表示引用傳遞方式捕捉所有父作用域的變數(包括this);
  5. [this]表示值傳遞方式捕捉當前的this指標。

上面提到了一個父作用域,也就是包含Lambda函式的語句塊,說通俗點就是包含Lambda的“{}”程式碼塊。上面的捕捉列表還可以進行組合,例如:

  1. [=,&a,&b]表示以引用傳遞的方式捕捉變數a和b,以值傳遞方式捕捉其它所有變數;
  2. [&,a,this]表示以值傳遞的方式捕捉變數a和this,引用傳遞方式捕捉其它所有變數。

不過值得注意的是,捕捉列表不允許變數重複傳遞。下面一些例子就是典型的重複,會導致編譯時期的錯誤。例如:

  1. [=,a]這裡已經以值傳遞方式捕捉了所有變數,但是重複捕捉a了,會報錯的;
  2. [&,&this]這裡&已經以引用傳遞方式捕捉了所有變數,再捕捉this也是一種重複。

Lambda的使用

對於Lambda的使用,說實話,我沒有什麼多說的,個人理解,在沒有Lambda之前的C++ , 我們也是那樣好好的使用,並沒有對缺少Lambda的C++有什麼抱怨,而現在有了Lambda表示式,只是更多的方便了我們去寫程式碼。不知道大家是否記得C++ STL庫中的仿函式物件,仿函式想對於普通函式來說,仿函式可以擁有初始化狀態,而這些初始化狀態是在宣告仿函式物件時,通過引數指定的,一般都是儲存在仿函式物件的私有變數中;在C++中,對於要求具有狀態的函式,我們一般都是使用仿函式來實現,比如以下程式碼:

#include<iostream>
using namespace std;
 
typedef enum
{
    add = 0,
    sub,
    mul,
    divi
}type;

class Calc
{
    public:
        Calc(int x, int y):m_x(x), m_y(y){}
 
        int operator()(type i)
        {
            switch (i)
            {
                case add:
                    return m_x + m_y;
                case sub:
                    return m_x - m_y;
                case mul:
                    return m_x * m_y;
                case divi:
                    return m_x / m_y;
            }
        }
 
    private:
        int m_x;
        int m_y;
};

int main()
{
    Calc addObj(10, 20);
    cout<<addObj(add)<<endl; // 發現C++11中,enum型別的使用也變了,更“強”了                                                                                                                                              
    return 0;
}

現在我們有了Lambda這個利器,那是不是可以重寫上面的實現呢?看程式碼:

#include<iostream>
using namespace std;
      
typedef enum
{     
    add = 0,
    sub,
    mul,
    divi
}type;
      
int main()
{     
    int a = 10;
    int b = 20;
      
    auto func = [=](type i)->int {
        switch (i)
        {
            case add:
                return a + b;
            case sub:
                return a - b;
            case mul:
                return a * b;
            case divi:
                return a / b;
        }
    };
      
    cout<<func(add)<<endl;
}

顯而易見的效果,程式碼簡單了,你也少寫了一些程式碼,也去試一試C++中的Lambda表示式吧。

關於Lambda那些奇葩的東西

看以下一段程式碼:

#include<iostream>         
using namespace std;       
                           
int main()                 
{                          
    int j = 10;            
    auto by_val_lambda = [=]{ return j + 1; };
    auto by_ref_lambda = [&]{ return j + 1; };
    cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
    cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
                           
    ++j;                   
    cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
    cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
                           
    return 0;              
}

程式輸出結果如下:

by_val_lambda: 11
by_ref_lambda: 11
by_val_lambda: 11
by_ref_lambda: 12

你想到了麼???那這又是為什麼呢?為什麼第三個輸出不是12呢?

在by_val_lambda中,j被視為一個常量,一旦初始化後不會再改變(可以認為之後只是一個跟父作用域中j同名的常量),而在by_ref_lambda中,j仍然在使用父作用域中的值。所以,在使用Lambda函式的時候,如果需要捕捉的值成為Lambda函式的常量,我們通常會使用按值傳遞的方式捕捉;相反的,如果需要捕捉的值成成為Lambda函式執行時的變數,則應該採用按引用方式進行捕捉。

再來一段更暈的程式碼:

#include<iostream>                  
using namespace std;                
                                    
int main()                          
{                                   
    int val = 0;                                    
    // auto const_val_lambda = [=](){ val = 3; }; wrong!!!
                                    
    auto mutable_val_lambda = [=]() mutable{ val = 3; };
    mutable_val_lambda();           
    cout<<val<<endl; // 0
                                    
    auto const_ref_lambda = [&]() { val = 4; };
    const_ref_lambda();             
    cout<<val<<endl; // 4
                                    
    auto mutable_ref_lambda = [&]() mutable{ val = 5; };
    mutable_ref_lambda();           
    cout<<val<<endl; // 5
                                    
    return 0;      
}

這段程式碼主要是用來理解Lambda表示式中的mutable關鍵字的。預設情況下,Lambda函式總是一個const函式,mutable可以取消其常量性。按照規定,一個const的成員函式是不能在函式體內修改非靜態成員變數的值。例如上面的Lambda表示式可以看成以下仿函式程式碼:

class const_val_lambda
{
public:
    const_val_lambda(int v) : val(v) {}
    void operator()() const { val = 3; } // 常量成員函式

private:
    int val;
};

對於const的成員函式,修改非靜態的成員變數,所以就出錯了。而對於引用的傳遞方式,並不會改變引用本身,而只會改變引用的值,因此就不會報錯了。都是一些糾結的規則。慢慢理解吧。

總結

對於Lambda這種東西,有的人用的非常爽,而有的人看著都不爽。仁者見仁,智者見智。不管怎麼樣,作為程式設計師的你,都要會的。這篇文章就是用來彌補自己對C++ Lambda表示式的認知不足的過錯,以免以後在別人的程式碼中看到了Lambda,還看不懂這種東西,那就丟大人了。

2014年10月11日 於深圳。

相關推薦

C++11特性Lambda函式

我是搞C++的 一直都在提醒自己,我是搞C++的;但是當C++11出來這麼長時間了,我卻沒有跟著隊伍走,發現很對不起自己的身份,也還好,發現自己也有段時間沒有寫C++程式碼了。今天看到了C++中的Lambda表示式,雖然用過C#的,但是C++的,一直沒有用,也不知道怎麼

C++11特性Lambda函式(匿名函式

基本的Lambda函式 我們可以這樣定義一個Lambda函式: #include <iostream> using namespace std; int main() { auto func = [] () { c

C++11 特性右值引用和轉移建構函式

問題背景 #include <iostream> usingnamespace std;   vector<int> doubleValues (const vector<int>& v)   {  

C++11特性 std::forward(完美轉發)(轉)

tails array sin .com std utili res details calling 我們也要時刻清醒,有時候右值會轉為左值,左值會轉為右值。 (也許“轉換”二字用的不是很準確) 如果我們要避免這種轉換呢? 我們需要一種方法能按照參數原來的類型轉發到另一個函

C++11特性右值引用

什麼是左值,什麼是右值? An l-value expression refers to an object's identity, whereas a r-value expression refers to an object's value. 左值標識物件的身份,右值標識物件的值(我把它理解為被物

C++11特性十:enable_shared_from_this

 enable_shared_from_this是一個模板類,定義於標頭檔案<memory>,其原型為: template< class T > class enable_shared_from_this;        std::enable_s

C++11特性final override識別符號

final:   final修飾符可用於修飾類,放在類名後面,被final修飾符修飾的類不能被繼承。示例程式碼: // 正確的示範 #include <iostream> class A { public: void show_a_info() { st

C++11特性final override標識符

並不會 ptr 輸出 erro 子類 main count color 特性 final:   final修飾符可用於修飾類,放在類名後面,被final修飾符修飾的類不能被繼承。示例代碼: // 正確的示範 #include <iostream> class

C++11 特性operator "" xxx

從C++11開始,我們可以使用以下形式通過常量字串構造自定義型別, 比如: class Person { public: Person(const std::string& name): _name(name){} std::string name() const {

C++ 面試 C++ 11 特性右值引用與移動

右值引用 什麼是左值,什麼是右值,簡單說左值可以賦值,右值不可以賦值。以下面程式碼為例,“ A a = getA();”該語句中a是左值,getA()的返回值是右值。 #include <iostream> class A { public

C++11特性列表初始化

在我們實際程式設計中,我們經常會碰到變數初始化的問題,對於不同的變數初始化的手段多種多樣,比如說對於一個數組我們可以使用 int arr[] = {1,2,3}的方式初始化,又比如對於一個簡單的結構體: struct A { int x; int y; }a={1,2

C++11特性 Move semantics(移動語義)

按值傳遞的意義是什麼? 當一個函式的引數按值傳遞時,這就會進行拷貝。當然,編譯器懂得如何去拷貝。 而對於我們自定義的型別,我們也許需要提供拷貝建構函式。 但是不得不說,拷貝的代價是昂貴的。 所以我們需要尋找一個避免不必要拷貝的方法,即C++11提供的移動

C++11特性智慧指標

  這一節將從用法上、記憶體儲存上以及生存週期上,對unique_ptr, shared_ptr和weak_ptr做一個深入剖析。 unique_ptr   不共享它的指標。它無法複製到其他 unique_ptr,無法通過值傳遞到函式,也無法用於需要副本的

C++11特性 std::array container

陣列每個人都很熟悉,vector更是我們常常用到的。 但是某些場合,使用vector是多餘的,尤其能明確元素個數的情況下,這樣我們就付出了效率稍低的代價! 但是你使用陣列的代價是那麼的不安全,那麼的不方便。 於是,C++11推出了模板類array,位於st

C++11特性 std::forward(完美轉發)

上篇部落格對右值、右值引用都做了簡要介紹。 我們也要時刻清醒,有時候右值會轉為左值,左值會轉為右值。 (也許“轉換”二字用的不是很準確) 如果我們要避免這種轉換呢? 我們需要一種方法能按照引數原來的型別轉發到另一個函式中,這才完美,我們稱之為完美轉發

C++11 特性Lambda 表示式

或許,Lambda 表示式算得上是 C++ 11 新增特性中最激動人心的一個。這個全新的特性聽起來很深奧,但卻是很多其他語言早已提供(比如 C#)或者即將提供(比如 Java)的。簡而言之,Lambda 表示式就是用於建立匿名函式的。GCC 4.5.x 和 Micro

C++11 特性 序列for循環

return 新特性 art tor href mes 定義 數組 pac 版權聲明:本文為博主原創文章。未經博主同意不

C++11特性——lambda表達式

amp 多個 str exp href 似的 exception 定義 參數傳遞 C++11的一大亮點就是引入了Lambda表達式。利用Lambda表達式,可以方便的定義和創建匿名函數。對於C++這門語言來說來說,“Lambda表達式”或“匿名函數”這些概念聽起來好像很深奧

C++11特性(80)-繼承的建構函式與多重繼承

複習 本文算是前面文章的繼續。 對於繼承的建構函式,C++11通過一個簡單的using語句,使得繼承一個類時可以省去一些麻煩。具體可以參照下面的文章。 C++11新特性(59)-繼承的建構函式 https://mp.weixin.qq.com/s/BGUa7-RSCtFRnBYj

C++11特性——default函式和deleted函式

轉自:http://blog.jobbole.com/103669/ default函式 default函式作用於類的特殊成員函式,為其自動生成預設的函式定義體,提高程式碼的執行效率。 類的特殊成員函式: 預設建構函式 解構函式 複