c++11 lambda表達式
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表達式