C++11——Lambda表示式
轉載來自:https://subingwen.cn/cpp/lambda/
1. 基本用法
lambda 表示式是 C++11 最重要也是最常用的特性之一,這是現代程式語言的一個特點,lambda 表示式有如下的一些優點:
宣告式的程式設計風格:就地匿名定義目標函式或函式物件,不需要額外寫一個命名函式或函式物件。
簡潔:避免了程式碼膨脹和功能分散,讓開發更加高效。
在需要的時間和地點實現功能閉包,使程式更加靈活。
lambda 表示式定義了一個匿名函式,並且可以捕獲一定範圍內的變數。lambda 表示式的語法形式簡單歸納如下:
C++
1
[capture](params) opt -> ret {body;};
其中 capture 是捕獲列表,params 是引數列表,opt 是函式選項,ret 是返回值型別,body 是函式體。
捕獲列表 []: 捕獲一定範圍內的變數
引數列表 (): 和普通函式的引數列表一樣,如果沒有引數引數列表可以省略不寫。
auto f = [](){return 1;} // 沒有引數, 引數列表為空 auto f = []{return 1;} // 沒有引數, 引數列表省略不寫
opt 選項, 不需要可以省略
mutable: 可以修改按值傳遞進來的拷貝(注意是能修改拷貝,而不是值本身)
exception: 指定函式丟擲的異常,如丟擲整數型別的異常,可以使用 throw ();
返回值型別:在 C++11 中,lambda 表示式的返回值是通過返回值後置語法來定義的。
函式體:函式的實現,這部分不能省略,但函式體可以為空。
2. 捕獲列表
lambda 表示式的捕獲列表可以捕獲一定範圍內的變數,具體使用方式如下:
[] - 不捕捉任何變數
[&] - 捕獲外部作用域中所有變數,並作為引用在函式體內使用 (按引用捕獲)
[=] - 捕獲外部作用域中所有變數,並作為副本在函式體內使用 (按值捕獲)
拷貝的副本在匿名函式體內部是隻讀的
[=, &foo] - 按值捕獲外部作用域中所有變數,並按照引用捕獲外部變數 foo
[bar] - 按值捕獲 bar 變數,同時不捕獲其他變數
[&bar] - 按引用捕獲 bar 變數,同時不捕獲其他變數
[this] - 捕獲當前類中的 this 指標
讓 lambda 表示式擁有和當前類成員函式同樣的訪問許可權
如果已經使用了 & 或者 =, 預設新增此選項
下面通過一個例子,看一下初始化列表的具體用法:
// ConsoleApplication1.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。 // #include <iostream> #include <functional> using namespace std; class Test { public: void output(int x, int y) { auto x1 = [] {return m_number; }; // error auto x2 = [=] {return m_number + x + y; }; // ok auto x3 = [&] {return m_number + x + y; }; // ok auto x4 = [this] {return m_number; }; // ok auto x5 = [this] {return m_number + x + y; }; // error auto x6 = [this, x, y] {return m_number + x + y; }; // ok auto x7 = [this] {return m_number++; }; // ok } int m_number = 100; }; int main() { return 0; }
x1:錯誤,沒有捕獲外部變數,不能使用類成員 m_number
x2:正確,以值拷貝的方式捕獲所有外部變數
x3:正確,以引用的方式捕獲所有外部變數
x4:正確,捕獲 this 指標,可訪問物件內部成員
x5:錯誤,捕獲 this 指標,可訪問類內部成員,沒有捕獲到變數 x,y,因此不能訪問。
x6:正確,捕獲 this 指標,x,y
x7:正確,捕獲 this 指標,並且可以修改物件內部變數的值
int main(void) { int a = 10, b = 20; auto f1 = [] {return a; }; // error auto f2 = [&] {return a++; }; // ok auto f3 = [=] {return a; }; // ok auto f4 = [=] {return a++; }; // error auto f5 = [a] {return a + b; }; // error auto f6 = [a, &b] {return a + (b++); }; // ok auto f7 = [=, &b] {return a + (b++); }; // ok return 0; }
f1:錯誤,沒有捕獲外部變數,因此無法訪問變數 a
f2:正確,使用引用的方式捕獲外部變數,可讀寫
f3:正確,使用值拷貝的方式捕獲外部變數,可讀
f4:錯誤,使用值拷貝的方式捕獲外部變數,可讀不能寫
f5:錯誤,使用拷貝的方式捕獲了外部變數 a,沒有捕獲外部變數 b,因此無法訪問變數 b
f6:正確,使用拷貝的方式捕獲了外部變數 a,只讀,使用引用的方式捕獲外部變數 b,可讀寫
f7:正確,使用值拷貝的方式捕獲所有外部變數以及 b 的引用,b 可讀寫,其他只讀
在匿名函式內部,需要通過 lambda 表示式的捕獲列表控制如何捕獲外部變數,以及訪問哪些變數。預設狀態下 lambda 表示式無法修改通過複製方式捕獲外部變數,如果希望修改這些外部變數,需要通過引用的方式進行捕獲。
3. 返回值
很多時候,lambda 表示式的返回值是非常明顯的,因此在 C++11 中允許省略 lambda 表示式的返回值。
// 完整的lambda表示式定義 auto f = [](int a) -> int { return a + 10; }; // 忽略返回值的lambda表示式定義 auto f = [](int a) { return a + 10; };
一般情況下,不指定 lambda 表示式的返回值,編譯器會根據 return 語句自動推導返回值的型別,但需要注意的是 labmda表示式不能通過列表初始化自動推匯出返回值型別。
// ok,可以自動推匯出返回值型別 auto f = [](int i) { return i; } // error,不能推匯出返回值型別 auto f1 = []() { return { 1, 2 }; // 基於列表初始化推導返回值,錯誤 }
4. 函式本質
使用 lambda 表示式捕獲列表捕獲外部變數,如果希望去修改按值捕獲的外部變數,那麼應該如何處理呢?這就需要使用 mutable 選項,被mutable修改是lambda表示式就算沒有引數也要寫明引數列表,並且可以去掉按值捕獲的外部變數的只讀(const)屬性。
int a = 0; auto f1 = [=] {return a++; }; // error, 按值捕獲外部變數, a是隻讀的 auto f2 = [=]()mutable {return a++; }; // ok
最後再剖析一下為什麼通過值拷貝的方式捕獲的外部變數是隻讀的:
lambda表示式的型別在C++11中會被看做是一個帶operator()的類,即仿函式。
按照C++標準,lambda表示式的operator()預設是const的,一個const成員函式是無法修改成員變數值的。
mutable 選項的作用就在於取消 operator () 的 const 屬性。
因為 lambda 表示式在 C++ 中會被看做是一個仿函式,因此可以使用std::function和std::bind來儲存和操作lambda表示式:
#include <iostream> #include <functional> using namespace std; int main(void) { // 包裝可呼叫函式 std::function<int(int)> f1 = [](int a) {return a; }; // 繫結可呼叫函式 std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1); // 函式呼叫 cout << f1(100) << endl; cout << f2(200) << endl; return 0; }
對於沒有捕獲任何變數的 lambda 表示式,還可以轉換成一個普通的函式指標:
using func_ptr = int(*)(int); // 沒有捕獲任何外部變數的匿名函式 func_ptr f = [](int a) { return a; }; // 函式呼叫 f(1314);