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

c++11 lambda 表示式

轉:https://www.cnblogs.com/ChinaHook/p/7658443.html

1、  定義

  lambda表示式是C++11非常重要也是很常用的特性之一,來源於函數語言程式設計的概念,也是現代程式語言的一個特點。它有如下特點:

  • 宣告式程式設計風格:就地匿名定義目標函式或者函式,不需要額外寫一個命名函式或者函式物件,以更直接的方式寫程式。
  • 簡潔:不需要額外再寫一個函式或者函式物件,避免了程式碼膨脹和功能分散。
  • 在需要的時間和地點實現功能閉包,使程式更加靈活。

  lambda表示式定義一個匿名函式,並且可以捕獲一定範圍內的變數,其定義如下:

  [captrue] (params) opt -> ret {body};

  其中,capture是捕獲列表;params是引數列表;opt是函式選項;ret是返回值型別;body是函式體。

  我們來做一個簡單的例子,定義一個簡單的封包,用來將輸入的結果+1並返回。

auto f = [](int a) -> int {return a + 1; };
std::cout << f(1) << std::endl;

  lambda表示式的返回值通過返回值後置語法來定義,所以很多時候可以省略返回值型別,編譯器根據return語句自動推導返回值型別。

auto f = [] (int a) {return a + 1;};

  但是初始化列表不能作為返回值的自動推導,需要顯示給出具體的返回值型別。

auto f1 = [] (int a) {return a + 1;};  //ok,return type is int
auto f2 = [] () {return {1, 2}};    //error:無法推匯出返回值型別

  lambda表示式在沒有引數列表的時候,引數列表可以省略。

auto f1 = [] () {return 1;};
auto f2 = [] {return 1;} //省略空引數表

2、  捕捉

  lambda表示式可以通過捕獲列表捕獲一定範圍內的變數:

  • []不捕獲任何變數;
  • [&]捕獲外部作用域所有變數,並作為引用在函式體使用(按引用捕獲);
  • [=]捕獲外部作用域作用變數,並作為副本在函式體使用(按值捕獲);
  • [=,&foo]按值捕獲外部作用域所有變數,並按引用捕獲foo變數;
  • [bar]按值捕獲bar變數,同時不捕獲其他變數;
  • [this]捕獲當前類中的this指標,讓lambda擁有和當前類成員函式同樣的訪問許可權,如果已經使用了&或者=,就預設新增此選項。捕獲this的目的是可以在lambda中使用當前類的成員函式和成員變數。

複製程式碼

class A
{
public:
    int mi = 0;

    void func(int x, int y)
    {
        auto x1 = []{return mi;};                      //error,沒有捕獲外部變數
        auto x2 = [=] {return mi + x + y;};            //ok,按值捕獲所有外部變數
        auto x3 = [&] {return mi + x + y;};            //ok,按引用捕獲所有外部變數
        auto x4 = [this] {return mi;};                 //ok,捕獲this指標
        auto x5 = [this] {return mi + x + y;};         //error,沒有捕獲x,y
        auto x6 = [this,x,y] {return mi + x + y;};     //ok,捕獲this,x,y
        auto x7 = [this] {return mi++;};               //ok,捕獲this指標,並修改成員的值
    }
};

int a = 0, b = 2;
auto f1 = [] {return a;} ;                 //error,沒有捕獲外部變數
auto f2 = [&] {return a++;};               //ok,按引用捕獲所有外部變數,並對a執行自加運算
auto f3 = [=] {return a;};                 //ok,按值捕獲所有外部變數,並返回a
auto f4 = [=] {return a++;};               //error,按值引用不能改變值
auto f5 = [a] {return a + b;};             //error,沒有捕獲b
auto f6 = [a, &b] {return a + (b++);};     //ok,捕獲a和b的值,並對b做自加運算
auto f7 = [=, &b] {return a + (b++);};     //ok,捕獲所有外部變數和b的引用,並對b做自加運算

複製程式碼

  從例子中可以看到,lambda表示式的捕獲列表精細的控制表示式能夠訪問的外部變數,以及如何訪問這些變數。需要注意的是,預設狀態下的lambda表示式無法修改通過複製方式捕獲的外部變數,如果希望修改這些變數,需要用引用的方式進行修改。但是按值引用的話,如果延遲呼叫,那麼在呼叫該lambda表示式的時候,其捕獲的變數值還是在lambda定義的時候的值。

int a = 0;
auto f = [=] {return a;};      //按值捕獲外部變數
a += 1;                        //修改值
std::cout << f() << std::endl; //輸出0            

  這個例子中,lambda表示式按值捕獲了所有外部變數,在捕獲的一瞬間a的值就已經被賦值在其中,之後a值修改並不會改變lambda表達中存的a的值,因此最終結果輸出還是0。如果希望lambda表示式在呼叫的時候能即時訪問外部變數,應當使用引用的方式捕獲。

  如果希望去修改按值捕獲的外部變數,需要顯示指明lambda表示式為mutable,但是被mutable修飾的lambda表示式有一個特點,就是無論有沒有引數都要寫明引數列表。

int a = 0;
auto f1 = [=] {return a++;}; //error,不能修改按值捕獲的變數
auto f2 = [=] () mutable {return a++; }; //ok,mutable

  lambda表示式其實是一個帶有operator()的類,即仿函式,因此我們可以使用std::bind和std::function來儲存和操作lambda表示式:

std::function<int(int)> f1 = [] (int a){ return a;};
std::function<int(void)> f2 = std::bind([](int a) {return a;}, 123);

  對於沒有捕獲任何變數的lambda表示式,還可以被轉換成一個普通的函式指標:

using func_t = int(*)(int);
func_t f = [](int a){return a;};
f(123);

  lambda表示式可以說是定義仿函式閉包的語法糖,它捕獲的任何外部變數都會轉換為閉包型別的成員變數。而使用成員變數的類的operator(),如果能直接轉換為普通的函式指標,那lambda表示式本身的this指標會丟失,沒有捕獲任何外部變數的lambda表示式則不存在這個問題,所以按值捕獲的外部變數無法修改。因為lambda表示式中的operator()預設是const的,一個const成員函式無法修改成員變數的值,而mutable則是取消operator()的const。

  所以,沒有捕獲變數的lambda表示式可以直接轉換為函式指標,而捕獲變數的lambda表示式則不能轉換為函式指標。

typedef void(*Ptr)(int *);
Ptr p1 = [](int *p) {delete p;}; //ok,沒有捕獲的lambda表示式可以轉換為函式指標
Ptr p2 = [&](int *p){delete p;}; //error,有捕獲的lambda表示式不能直接轉換為函式指標,不能通過編譯

3、  簡潔程式碼

  就地定義匿名函式,不再需要定義函式物件,大大簡化了標準庫的呼叫。比如我們使用for_each將vector中的偶數打印出來。

複製程式碼

class CountEven
{
private:
    int &micount;

public:
    CountEven(int &count) : micount(count) {}

    void operator()(int val)
    {
        if(!(val & 1))
        {
            ++micount;
        }
    }       
};

std::vector<int> v = {1, 2, 3, 4, 5, 6};
int count = 0;
for_each(v.begin(), v.end(), CountEven(count));
std::cout << "The number:" << count << std::endl;

複製程式碼

  這樣寫比較繁瑣,如果用lambda表示式,使用閉包概念來替換這裡的仿函式。

複製程式碼

std::vector<int> v = {1, 2, 3, 4, 5, 6};
int count = 0;

for_each(v.begin(), v.end(), [&count] (int val)
{
    if(!(val & 1))
    {
        ++count;
    }
});

複製程式碼

  在之前的例子中,使用std::bind組合多個函式,實現了計算集合中大於5小於10的元素個數。

using std::placeholders::_1;
auto f = std::bind(std::logical_and<bool>(),
std::bind(std::greater<int>(), std::placeholders::_1, 5),
std::bind(std::less_equal<int>(), std::placeholders::_1, 10));
int count = std::count_if(coll.begin(), coll.end(), f);

  通過lambda表示式可以輕鬆的實現類似的功能:

  //查詢大於5小於10的元素個數

int count  = std::count_if(coll.begin(), coll.end(), [](int x) {return x > 5 && x < 10;})

  lambda表示式比std::bind更加靈活和簡潔,如果簡單的邏輯處理,用lambda表示式來代替function,提升開發效率,效果會更好。