1. 程式人生 > >c++11 lambda表達式

c++11 lambda表達式

算法 返回值 臨時對象 war graph oid 編程 fun es2017

c++11 lambda表達式

lambda 表達式(lambda expression)是一個匿名函數,lambda表達式基於數學中的 λ 演算得名。

C++11中的lambda表達式用於定義並創建匿名的函數對象,以簡化編程工作。

lambda表達式的基本構成:

技術分享

函數對象參數

[],標識一個lambda的開始,這部分必須存在,不能省略。函數對象參數是傳遞給編譯器自動生成的函數對象類的構造函數的。函數對象參數只能使用那些到定義lambda為止時lambda所在作用範圍內可見的局部變量(包括lambda所在類的this)。函數對象參數有以下形式:

n 空。沒有使用任何函數對象參數。

n =。函數體內可以使用lambda所在作用範圍內所有可見的局部變量(包括lambda所在類的this),並且是值傳遞方式(相當於編譯器自動為我們按值傳遞了所有局部變量)。

n &。函數體內可以使用lambda所在作用範圍內所有可見的局部變量(包括lambda所在類的this),並且是引用傳遞方式(相當於編譯器自動為我們按引用傳遞了所有局部變量)。

n this。函數體內可以使用lambda所在類中的成員變量。

n a。將a按值進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的a的拷貝,因為默認情況下函數是const的。要修改傳遞進來的a的拷貝,可以添加mutable修飾符。

n &a。將a按引用進行傳遞。

n a, &b。將a按值進行傳遞,b按引用進行傳遞。

n =,&a, &b。除a和b按引用進行傳遞外,其他參數都按值進行傳遞。

n &, a, b。除a和b按值進行傳遞外,其他參數都按引用進行傳遞。

操作符重載函數參數

標識重載的()操作符的參數,沒有參數時,這部分可以省略。參數可以通過按值(如:(a,b))和按引用(如:(&a,&b))兩種方式進行傳遞。

可修改標示符

mutable聲明,這部分可以省略。按值傳遞函數對象參數時,加上mutable修飾符後,可以修改按值傳遞進來的拷貝(註意是能修改拷貝,而不是值本身)。

錯誤拋出標示符

exception聲明,這部分也可以省略。exception聲明用於指定函數拋出的異常,如拋出整數類型的異常,可以使用throw(int)

函數返回值

->返回值類型,標識函數返回值的類型,當返回值為void,或者函數體中只有一處return的地方(此時編譯器可以自動推斷出返回值類型)時,這部分可以省略。

是函數體

{},標識函數的實現,這部分不能省略,但函數體可以為空。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <vector>
#include <map>


class Test
{
public:
    int i;

    void func(int x, int y)
    {
        auto x1 = []{ return i; };          //err, 沒有捕獲外部變量
        auto x2 = [=]{ return i+x+y; };     //ok, 值傳遞方式捕獲所有外部變量
        auto x3 = [=]{ return i+x+y; };     //ok, 引用傳遞方式捕獲所有外部變量
        auto x4 = [this]{ return i; };      //ok, 捕獲this指針
        auto x5 = [this]{ return i+x+y; };  //err, 沒有捕獲x, y
        auto x6 = [this, x, y]{ return i+x+y; };//ok, 捕獲this指針, x, y
        auto x9 = [this]{ return i++; };        //ok, 捕獲this指針, 並修改成員的值
    }
};


void mytest()
{
    int a = 0, b = 1;
    auto f1 = []{ return a; };      //err, 沒有捕獲外部變量
    auto f2 = [=]{ return a; };     //ok, 值傳遞方式捕獲所有外部變量
    auto f3 = [=]{ return a++; };   //err, a是以賦值方式捕獲的,無法修改
    auto f4 = [=]() mutable { return a++; };   //ok, 加上mutable修飾符後,可以修改按值傳遞進來的拷貝
    auto f5 = [&]{ return a++; };               //ok, 引用傳遞方式捕獲所有外部變量, 並對a執行自加運算
    auto f6 = [a]{ return a+b; };               //err, 沒有捕獲變量b
    auto f9 = [a,&b]{ return a+(b++); };        //ok, 捕獲a, &b
    auto f8 = [=,&b]{ return a+(b++); };        //ok, 捕獲所有外部變量,&b

    // 值傳遞和引用傳遞區別
    int j = 12;
    auto by_val_lambda = [=] { return j + 1;};
    auto by_ref_lambda = [&] { return j + 1;};
    std::cout << "by_val_lambda: " << by_val_lambda() << std::endl;
    std::cout << "by_ref_lambda: " << by_ref_lambda() << std::endl;
    std::cout << "by_val_lambda: " << by_val_lambda() << std::endl;
    std::cout << "by_ref_lambda: " << by_ref_lambda() << std::endl;

    j++;
    std::cout << "by_val_lambda: " << by_val_lambda() << std::endl;
    std::cout << "by_ref_lambda: " << by_ref_lambda() << std::endl;

    /*
    運行結果:
        by_val_lambda: 13
        by_ref_lambda: 13
        by_val_lambda: 13 // 第3次調用結果還是13,原因是由於by_val_lambda中,j被視為了一個常量,一旦初始化後不會再改變。
        by_ref_lambda: 14
    */


    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}

lambda與仿函數

通過例子,我們看到,仿函數以round初始化類,而lambda函數也捕獲了round變量,其它的,如果在參數傳遞上,兩者保持一致。

除去在語法層面上的不同,lambda和仿函數有著相同的內涵——都可以捕獲一些變量作為初始化狀態,並接受參數進行運行。

而事實上,仿函數是編譯器實現lambda的一種方式,通過編譯器都是把lambda表達式轉化為一個仿函數對象。因此,在C++11中,lambda可以視為仿函數的一種等價形式。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <vector>
#include <map>

class MyFunctor
{
public:
    MyFunctor(int tmp) : round(tmp) {}
    int operator()(int tmp) { return tmp + round; }

private:
    int round;
};


void mytest()
{

    //仿函數
    int round = 2;
    MyFunctor f1(round);//調用構造函數
    std::cout << "result1 = " << f1(1) << std::endl; //operator()(int tmp)

    //lambda表達式
    auto f2 = [=](int tmp) -> int { return tmp + round; };
    std::cout << "result2 = " << f2(1) << std::endl;

    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}

lambda類型

lambda表達式的類型在C++11中被稱為“閉包類型”,每一個lambda表達式則會產生一個臨時對象(右值)。因此,嚴格地將,lambda函數並非函數指針。

不過C++11標準卻允許lambda表達式向函數指針的轉換,但提前是lambda函數沒有捕獲任何變量,且函數指針所示的函數原型,必須跟lambda函數函數有著相同的調用方式。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <vector>
#include <map>


void mytest()
{
    //使用std::function和std::bind來存儲和操作lambda表達式
    std::function<int(int)> f1 = [](int a) { return a; };
    std::function<int()> f2 = std::bind([](int a){ return a; }, 123);
    std::cout << "f1 = " << f1(123) << std::endl;
    std::cout << "f2 = " << f2() << std::endl;

    auto f3 = [](int x, int y)->int{ return x + y; }; //lambda表達式,沒有捕獲任何外部變量
    typedef int (*PF1)(int x, int y);   //函數指針類型
    typedef int (*PF2)(int x);

    PF1 p1;     //函數指針變量
    p1 = f3;    //ok, lambda表達式向函數指針的轉換
    std::cout << "p1 = " << p1(3, 4) << std::endl;

    PF2 p2;
    //p2 = f3;     //err, 編譯失敗,參數必須一致

    decltype(f3) p3 = f3;   // 需通過decltype獲得lambda的類型
    // decltype(f3) p4 = p1;   // err 編譯失敗,函數指針無法轉換為lambda

    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}

lambda優勢

lambda表達式的價值在於,就地封裝短小的功能閉包,可以及其方便地表達出我們希望執行的具體操作,並讓上下文結合更加緊密。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <vector>
#include <map>
#include <algorithm> // std::for_each

std::vector<int> nums;
std::vector<int> largeNums;

class LNums
{
public:
    LNums(int u): ubound(u) {}
    void operator ()(int i) const
    {
        if (i > ubound)
        {
            largeNums.push_back(i);
        }
    }

private:
    int ubound;
};

void mytest()
{
    //初始化數據
    for(auto i = 0; i < 10; ++i)
    {
        nums.push_back(i);
    }
    int ubound = 5;

    //1、傳統的for循環
    for (auto itr = nums.begin(); itr != nums.end(); ++itr)
    {
        if (*itr > ubound)
        {
            largeNums.push_back(*itr);
        }
    }

    //2、使用仿函數
    std::for_each(nums.begin(), nums.end(), LNums(ubound));

    //3、使用lambda函數和算法for_each
    std::for_each(nums.begin(), nums.end(), [=](int i)
    {
        if (i > ubound)
        {
            largeNums.push_back(i);
        }
    }
    );

    //4、遍歷元素
    for_each(largeNums.begin(), largeNums.end(), [=](int i)
    {
        std::cout << i << ", ";
    }
    );
    std::cout << std::endl;


    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}

c++11 lambda表達式