1. 程式人生 > >C++中的Lambda表示式詳解

C++中的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,引用傳遞方式捕捉其它所有變數。

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

3.[=,a]這裡已經以值傳遞方式捕捉了所有變數,但是重複捕捉a了,會報錯的;
4.[&,&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,還看不懂這種東西,那就丟大人了。

相關推薦

C++lambda表示式與原理分析

lambda表示式的本質就是過載了()運算子的類,這種類通常被稱為functor,即行為像函式的類。因此lambda表示式物件其實就是一個匿名的functor。 C++中lambda表示式的構成 一個標準的lambda表示式包括:捕獲列表、引數列表、mu

Javalambda表示式

原文地址:http://blog.laofu.online/2018/04/20/java-lambda/ 為什麼使用lambda 在java中我們很容易將一個變數賦值,比如int a =0;int b=a; 但是我們如何將一段程式碼和一個函式賦值給一個變數?這個變數應該是什麼的型別? 在java

C++Lambda表示式

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

C/C++extern關鍵字

編譯器 fin 生成 接口 bcd 只需要 c++環境 結束 編程 轉自:http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html 1 基本解釋:extern可以置於變量或者函數前,以標示變量或者

C/C++作用域(轉)

防止 局部作用域 gist 文件中 方式 為什麽不使用 形式參數 lan archive 作用域規則告訴我們一個變量的有效範圍,它在哪兒創建,在哪兒銷毀(也就是說超出了作用域)。變量的有效作用域從它的定義點開始,到和定義變量之前最鄰近的開括號配對的第一個閉括號。也就是說,作

C#const用法

htm 鏈接 服務器 span img body 用法詳解 -s 設計 本文實例講述了C#中const用法。分享給大家供大家參考。具體用法分析如下: const是一個c語言的關鍵字,它限定一個變量不允許被改變。使用const在一定程度上可以提高程序的安全性和可靠性,另外,

C#protected用法

base 而是 報錯 public 我們 此刻 訪問 .html 定義 轉自(https://www.cnblogs.com/wangyt223/archive/2012/08/08/2627801.html) 在c#的可訪問性級別中,public和private算是最

c/c++static的

extern info system pan 特點 靜態成員 額外 定義 全局 C 語言的 static 關鍵字有三種(具體來說是兩種)用途: 1. 靜態局部變量:用於函數體內部修飾變量,這種變量的生存期長於該函數。 int foo(){ st

C#ToString()格式

padding design otn href 有效 詳解 pattern console AS 以下內容均摘自博客園,僅供資料查詢。原文連接http://www.cnblogs.com/xdotnet/archive/2009/01/17/tostring_format.

C++的繼承

C++ 繼承 [TOC] 繼承基本知識 定義:  繼承是面向對復用的重要手段。通過繼承定義一個類,繼承是類型之間的關系建模,共享公有的東西,實現各自本質不同的東西。 繼承關系:  三種繼承關系下基類成員的在派生類的訪問關系變化(圖) 舉個栗子(公有繼承) ```c+

C++列舉enum

轉載部落格地址:https://blog.csdn.net/bruce_0712/article/details/54984371     眾所周知,C/C++語言可以使用#define和const建立符號常量,而使用enum工具不僅能夠建立符號常量,還能定義新

C/C++volatile關鍵字

asm 運行 多線程並發 這樣的 修改 由於 設定 其他 硬件 1. 為什麽用volatile? C/C++ 中的 volatile 關鍵字和 const 對應,用來修飾變量,通常用於建立語言級別的 memory barrier。這是 BS 在 "The C++ P

《隨筆十七》—— C++的 “ 指標

目錄 指標的概念 指標所指向的型別 指標的值 指標本身所佔據的記憶體區 指標的算術運算          運算子&和* 指標表示式 陣列和指標的關係 指標和結構型別的關係 指標和函式的關係

C++的string

標準庫型別string表示可變長的字元序列,為了在程式中使用string型別,我們必須包含標頭檔案: #include <string>  宣告一個字串 宣告一個字串有很多種方式,具體如下: 1 string s;//呼叫預設建構函式,s為一個空

C++string類(轉載)(最下面有程式碼實現)

作者:yzl_rex 來源:CSDN 原文:https://blog.csdn.net/yzl_rex/article/details/7839379 要想使用標準C++中string類,必須要包含 #include < string>// 注意是< string>

C++const關鍵字

const關鍵字作用    1. 修飾變數        用法:const 型別說明符 變數名。        含義:說明該變數不可以被改變。        用途:常量命名等    2. 修飾

quartz定時表示式

(一)格式講解 Cron表示式的格式:秒 分 時 日 月 周 年。其欄位取值如下圖所示: “?”字元:表示不確定的值 “,”字元 :指定數個值 “-”字元:指定一個值的範圍 “/”字元:指定一個值的增加幅度。n/m表示從n開始,每次增加m “L”

C++char型別

# 1char與字元的關係 ##1.1char型別到底代表什麼 提到char型別,我相信學過C/C++的並不會陌生,char型別代表一個位元組,在記憶體中有8位,所以signed char的範圍為-128~127,unsigned char的

Lambda表示式

前言         1、天真熱,程式設計師活著不易,星期天,也要頂著火辣辣的太陽,總結這些東西。         2、誇誇lambda吧:簡化了匿名委託的使用,讓你讓程式碼更加簡潔,優雅。據說它是微軟自c#1.0後新增的最重要的功能之一。 lambda簡介  

C++set用法

1.關於set C++ STL 之所以得到廣泛的讚譽,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封裝了許多複雜的資料結構演算法和大量常用資料結構操作。vector封裝陣列,list封裝了連結串列,map和se