C++ lambda表達式

lambda表達式又稱匿名函數Anonymous function),其構造了一個能夠在其做用範圍內捕獲變量的函數對象。html

lambda表達式實際爲一個仿函數functor,編譯器後會生成一個匿名類(注:這個類重載了()運算符)c++

與普通函數指針相比,Lambda表達式能夠包含數據成員,也就是說它是能夠有狀態的。下面舉一個簡單的例子說明一下:閉包

int sum = 0;
std::vector<int> arry = { 1,2,3,4,5 };
std::for_each(begin(arry), end(arry), [&sum](int x){sum += x;});

上述代碼被編譯器展開後,會變成:函數

struct lambda_b53d8cae67476f0e5f04d9defa3a2e2b
{
private:
    int* m_pSum;
public:
    lambda_b53d8cae67476f0e5f04d9defa3a2e2b(int* pSum)
    {
        m_pSum = pSum;
    }

    void operator()(int x) const 
    {
        *m_pSum += x;
    }
};


int sum = 0;
std::vector<int> arry = { 1,2,3,4,5 };
lambda_b53d8cae67476f0e5f04d9defa3a2e2b obj(&sum);
std::for_each(begin(arry), end(arry), obj);

 

lambda表示式的結構this

從C++11起,開始提供了匿名函數的支持,一個lambda表達式形如:spa

[capture] (parameters) specifiers -> return_type { body }3d

capture指針

捕獲的外部變量列表,經過逗號分隔,可進行傳值捕獲或者引用捕獲,lambda表達式與這些捕獲的外部變量會構成一個閉包Closure),外部變量爲閉包的成員變量code

int g_Value = 0;
class CLambda
{
protected:
    int m_Value;
public:
    void Test1(int InValue)
    {
        int Value = 0;
        auto a1 = [](int x) {/*僅能訪問全局外部變量*/};
        auto a2 = [Value](int x) {/*值傳遞局部變量Value*/};
        auto a3 = [this](int x) {/*值傳遞this指針*/};
        auto a4 = [&Value](int x) {/*引用傳遞局部變量Value*/};
        auto a5 = [=](int x) {/*值傳遞全部可訪問的外部變量*/};
        auto a6 = [&](int x) {/*引用傳遞全部可訪問的外部變量*/};
        auto a7 = [=, &Value](int x) {/*引用傳遞局部變量Value,值傳遞全部其餘可訪問的外部變量*/};
        auto a8 = [&, Value](int x) {/*值傳遞局部變量Value,引用傳遞全部其餘可訪問的外部變量*/};
    }
};

① 全局變量、靜態變量不用顯示寫在外部變量列表中,可直接在lambda表達式中讀寫htm

② 傳值捕獲的外部變量是一個副本,默認狀況下在lambda表達式中是隻讀的,若想修改該副本,須要在lambda表達式上添加mutable關鍵字  如:auto a2 = [Value](int x) mutable {Value++;};

---------------------------------------------------------------------------

在C++中,mutable是爲了突破const的限制而設置的,用來修飾類的非靜態、很是量的數據成員,讓被修飾的變量永遠處於可變的狀態

mutable的做用有兩點:
a. 保持常量對象中大部分數據成員仍然是「只讀」的狀況下,實現對個別數據成員的修改;
b. 使類的const函數能夠修改其mutable數據成員。

---------------------------------------------------------------------------

③ 在類的非靜態成員函數中定義的lambda表達式能夠經過捕捉this指針,來訪問對象的成員變量和成員函數

④ c++14中增長廣義捕獲(Generalized capture):即在捕獲子句中增長並初始化新的變量,該變量不須要在lambda表達式所處的閉包域中存在;

即便在閉包域中存在也會被新變量覆蓋。新變量類型由它的初始化表達式推導獲得。一個用途是能夠從閉包域中捕獲只供移動的變量並使用它。

int Value = 0;
auto a1 = [Value = 100] { return Value; };// 捕獲子句中定義的Value變量會覆蓋同名的外部局部變量
int result = a1(); // result=100

std::vector<int> Vout = { 10, 20, 30, 40, 50 };
auto a2 = [Vin = std::move(Vout)]{ // 將外部局部變量Vout移動到Vin變量,避免發生拷貝耗時操做
    int sum = 0;
    for (auto n : Vin)
    {
        sum += n;
    }

    return sum;
};

int result2 = a2(); // result2=150

 

parameters

lambda表達式本身的參數列表

1. 若lambda函數沒有形參且沒有被mutable等修飾,則參數的空圓括號能夠省略

如:auto a = []{ ++g_Value; };

2. c++14支持auto類型的形參

auto a = [](auto x, auto y) {return x + y;};

//至關於定義了模板類型()運算符的函數對象
struct unnamed_lambda                            
{
  template<typename T, typename U> 
auto operator()(T x, U y) const {return x + y;} } a;

3. c++14支持可變參數模板

int foo(int n1, int n2, bool flag)
{
    return flag ? n1 : n2;
}

auto a = [](auto&&... params) //可變參數的模板
{
    return (foo(std::forward<decltype(params)>(params)...));
};
int result = a(1, 2, true); //result=1

4. 與普通函數相比,lambda表達式的參數有以下限制

① 參數不能有缺省值   如:int Add1(int a, int b=10) 

② 不能有可變長參數列表  如:int  Add2(int count, ...)

③ 不能有無名參數  如:int Add3(int a, int b, int// 第三個參數爲無名參數,用於之後擴展

 

specifiers

說明符,可選。如上文中的mutable

 

return_type

返回類型

在如下狀況下,可省略return_type

① 返回值爲void類型  如:auto a = [](int x) {x = 100;};  //返回值類型爲void

② 全部返回語句用decltype都能檢測到同一類型  如:auto a = [](int x, float y) { if (x > 0) return x + y;  return y; };  //返回值類型推導爲float

 

使用lambda表達式

lambda表達式實際爲一個函數對象,在使用時要注意lambda表達式以及被其捕獲的外部變量的生命週期

把匿名函數存儲在變量,當作有名函數來使用

lambda表達式的數據類型是函數對象,可用auto類型、std::function模板類型(需#include <functional>)進行保存和調用

當捕獲外部變量列表爲空時,也可用普通函數指針進行保存和調用

int Value = 0;

auto a1 = [&Value](int x) {Value = x;};
std::function<float(int,float)> a2 = [Value](int x, float y) { return x + y; }; //須要#include <functional>
a1(100);
a2(100, 200.0f);

// lambda函數的捕獲外部變量列表爲空時,可以使用普通函數指針來保存
int(*func1_ptr)(int) = [](int x) {return x; };
bool(*func2_ptr)(int, int) = [](int x, int y) { return x > y; };
int n = func1_ptr(1);
bool b = func2_ptr(1, 2);

 

懸掛引用問題

lambda表達式的閉包含有局部變量的引用(懸掛引用  Dangling references),在超出建立它的做用域以外的地方被使用的話,將引起內存越界訪問

std::function<int()> a;
{
    int Value = 0;
    a = [&Value] {return Value; };
}
        
a(); // 局部變量Value超過做用域,已被回收,調用lambda表達式將產生內存越界訪問

 

參考

C++ 中的 LAMBDA 表達式

Lambda 表達式 (C++11 起)

相關文章
相關標籤/搜索