C++11 Lambda函數


Lambda函數

C++11新增了lambda函數,其基本格式如下
1 [捕捉列表] (參數) mutable -> 返回值類型 {函數體}
說明
  • []是lambda的引出符,捕捉列表能夠捕捉上下文中的變量,來供lambda函數使用:
    [var] 表示以值傳遞方式捕捉變量var
    [=] 表示值傳遞捕捉所有父作用域變量
    [&var] 表示以引用傳遞方式捕捉變量var
    [&] 表示引用傳遞捕捉所有父作用域變量
    [this] 表示值傳遞方式捕捉當前的this指針
       還有一些組合:
    [=,&a] 表示以引用傳遞方式捕捉a,值傳遞方式捕捉其他變量
       注意:
       捕捉列表不允許變量重復傳遞,如:[=,a]、[&,&this],會引起編譯時期的錯誤
  • 參數列表與普通函數的參數列表一致。如果不需要傳遞參數,可以聯連同()一同【省略】。
  • mutable 可以取消Lambda的常量屬性,因為Lambda默認是const屬性;multable僅僅是讓Lamdba函數體修改值傳遞的變量,但是修改后並不會影響外部的變量。
  • ->返回類型如果是void時,可以連->一起【省略】,如果返回類型很明確,可以省略,讓編譯器自動推倒類型。
  • 函數體和普通函數一樣,除了可以使用參數之外,還可以使用捕獲的變量。
最簡單的Lambda函數:
1 []{}
實例:
 1 int main(int argc, char* argv[])
 2 {
 3     int a = 5, b = 7;
 4     auto total = [](int x, int y)->int {return x + y; };    //接受兩個參數
 5     cout << total(a, b)<<endl;  //12
 6     auto fun1 = [=] {return a + b; };   //值傳遞捕捉父作用域變量
 7     cout << fun1() << endl; //12
 8     auto fun2 = [&](int c) {b = a + c; a = 1; };    //省略了返回值類型,引用捕獲所有
 9     fun2(3);    //1 8
10     cout << a <<" "<< b << endl;
11     a = 5; b = 7;   //被修改后,重新賦值
12     auto fun3 = [=, &b](int c) mutable {b = a + c; a = 1; };    //以值傳遞捕捉的變量,在函數體里如果要修改,要加mutaple,因為默認const修飾
13     fun3(3);
14     cout << a << " " <<b<< endl;    //5,8
15     a = 5; b = 7;   //被修改后,重新賦值
16     auto fun4 = [=](int x, int y) mutable->int {a += x; b += y; return a + b; };
17     int t = fun4(10, 20);
18     cout << t << endl;  //42
19     cout << a <<" "<< b << endl;    //5 7
20     return 0;
21 }

  塊作用域以外的Lambda函數捕捉列表必須為空,因此這樣的函數除了語法上的不同,和普通函數區別不大。

  塊作用域以內的Lambda函數僅能捕捉塊作用域以內的自動變量,捕捉任何非此作用域或非自動變量(靜態變量),都會引起編譯器報錯。
  改為引用依舊會報錯。

Lambda函數與仿函數的關系

  在C++11之前,STL中的一些算法需要使用一種函數對象---仿函數(functor);其本質是重新定義和成員函數operator(),使其使用上很像普通函數,其實,細心的我們已經發現,Lambda函數與仿函數似乎有一些默契。
如下例子:折扣
 1 class Price
 2 {
 3 private:
 4     float _rate;
 5 public:
 6     Price(float rate):_rate(rate){}
 7     float operator()(float price)
 8     {
 9         return price*(1 - _rate / 100);
10     }
11 };
12  
13 int main(int argc, char* argv[])
14 {
15     float rate=5.5f;
16  
17     Price c1(rate);
18     auto c2 = [rate](float price)->float {return price*(1 - rate / 100); };
19  
20     float p1 = c1(3699);    //仿函數
21     float p2 = c2(3699);    //Lambda函數
22  
23     return 0;
24 }
  仿函數以rate初始化,Lambda捕捉rate變量,參數傳遞上,兩者一致。
事實上,仿函數就是實現Lambda函數一種方式,編譯器通常會把Lambda函數轉換為一個放函數對象,但是仿函數的語法卻給我們帶來了很大的便捷。
在C++11中,Lambda函數被廣泛使用,很多仿函數被取代。

Lambda與static inline函數

  Lambda函數可以省略外部聲明的static inline函數,其相當於一個局部函數。局部函數僅屬於父作用域,
比起外部的static inline函數,或者是自定義的宏,Lambda函數並沒有實際運行時的性能優勢(但也不會差),但是Lambda函數可讀性更好。
父函數結束后,該Lambda函數就不再可用了,不會污染任何名字空間。

關於值傳遞捕捉和mutable

  上面提到過mutable 可以取消Lambda的常量屬性,如果值傳遞想要在函數域內修改就要加mutable
先看一個例子:
 1 int main(int argc, char* argv[])
 2 {
 3     int j = 12;
 4     auto by_val = [=] {return j + 1; };
 5     auto by_ref = [&] {return j + 1; };
 6     cout << by_val() << endl;   //13
 7     cout << by_ref() << endl;   //13
 8     j++;
 9     cout << by_val() << endl;   //13
10     cout << by_ref() << endl;   //14
11     return 0;
12 }
  上面的例子,j++了之后調用值傳遞結果依舊是12,原因是,值傳遞j被視為一個常量,一旦初始化,就不會再修改(可以認為是一個和父作用域中j同名的常量),而再引用捕捉中,j仍然是父作用域中的值。
  其實一個值傳遞的的Lambda轉換為放函數后,會成為一個class的常量成員函數,
代碼基本如下:
1 class const_val_lambda
2 {
3 public:
4     const_val_lambda(int v):val(v){}
5 public:
6     void operator()()const { val = 3; } //報錯
7 private:
8     int val;
9 };
  但是使用引用的方式不會報錯,因為不會改變引用本身,只會改變引用的值
  准確地講,現有C++11標准中的lambda等價的是有常量operatorO的仿函數。因此在使用捕捉列表的時候必須注意,按值傳遞方式捕捉的變量是lambda函數中不可更改的常量。標准這么設計可能是源自早期STL算法一些設計上的缺陷(對仿函數沒有做限制,從而導致一些設計不算特別良好的算法出錯)。而更一般地講,這樣的設計有其合理性,改變從上下文中拷貝而來的臨時變量通常不具有任何意義。絕大多數時候,臨時變量只是用於lambda函數的輸入,如果需要輸出結果到上下文,我們可以使用引用,或者通過讓lambda函數返回值來實現。此外,lambda函數的mutable修飾符可以消除其常量性,不過這實際上只是提供了一種語法上的可能性,現實中應該沒有多少需要使用mutable的lambda函數的地方。大多數時候,我們使用默認版本的(非mutable)的lambda函數也就足夠了。

Lambda函數與函數指針

  Lambda函數並不是簡單的函數指針類型,或者自定義類型;每個Lambda函數會產生一個閉包類型的臨時對象(右值)。但是C++11允許Lambda函數向函數指針的轉換,前提是:
    Lambda沒有捕捉任何變量
    函數指針所示的函數原型,必須和Lambda有相同的調用方式
 1 int main(int argc, char* argv[])
 2 {
 3     int a = 3, b = 4;
 4  
 5     auto total = [](int x, int y)->int {return x + y; };
 6     typedef int(*all)(int x, int y);
 7     typedef int(*one)(int x);
 8  
 9     all p;
10     p = total;
11     one q;
12     q = total;  //報錯,參數不一致
13  
14     decltype(total) all_1 = total;
15     decltype(total) all_2 = p;  //報錯,指針無法轉換為Lambda
16  
17     return 0;
18 }

Lambda與STL

  從C++11開始,Lambda被廣泛用在STL中,比如foreach。與函數指針比起來,函數指針有巨大的缺陷:1.函數定義在別處,閱讀起來很困難;2.使用函數指針,很可能導致編譯器不對其進行inline優化,循環次數太多時,函數指針和Lambda比起來性能差距太大。函數2指針不能應用在一些運行時才能決定的狀態,在沒有C++11時,只能用仿函數。使得學習STL算法的代價大大降低。
  但是Lambda並不是仿函數的完全代替者。由Lambda的捕捉列表的限制造成的,僅能捕捉副作用域的變量。放函數具有天生跨作用域共享的特征。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM