Lambda 表達式
注意:考慮到算法競賽的實際情況,本文將不會全面研究語法,只會講述在算法競賽中可能會應用到的部分。
本文語法參照 C++11 標準。語義不同的將以 C++11 作為標準,C++14、C++17 的語法視情況提及並會特別標註。
Lambda 表達式
Lambda 表達式因數學中的 \(\lambda\) 演算得名,直接對應於其中的 lambda 抽象。Lambda 表達式能夠捕獲作用域中的變量的無名函數對象。我們可以將其理解為一個匿名的內聯函數,可以用來替換獨立函數或者函數對象,從而使代碼更可讀。但是從本質上來講,Lambda 表達式只是一種語法糖,因為它能完成的工作也可以用其他複雜的 C++ 語法來實現。
下面是 Lambda 表達式的語法:
1 | |
下面我們分別對其中的 capture, parameters, mutable, return-type, statement 進行介紹。
capture 捕獲子句
Lambda 表達式以 capture 子句開頭,它指定哪些變量被捕獲,以及捕獲是通過值還是引用:有 & 符號前綴的變量通過引用訪問,沒有該前綴的變量通過值訪問。空的 capture 子句 [] 指示 Lambda 表達式的主體不訪問封閉範圍中的變量。
我們也可以使用默認捕獲模式:& 表示捕獲到的所有變量都通過引用訪問,= 表示捕獲到的所有變量都通過值訪問。之後我們可以為特定的變量 顯式 指定相反的模式。
例如 Lambda 體要通過引用訪問外部變量 a 並通過值訪問外部變量 b,則以下子句等效:
[&a, b][b, &a][&, b][b, &][=, &a]
默認捕獲時,會捕獲 Lambda 中提及的變量。獲的變量成為 Lambda 的一部分;與函數參數相比,調用 Lambda 時不必傳遞它們。
以下是一些常見的例子:
1 2 3 4 5 6 | |
parameters 參數列表
大多數情況下類似於函數的參數列表,例如:
1 2 | |
C++14 中,若參數類型是泛型,則可以使用 auto 聲明類型:
1 | |
一個例子:
1 2 3 | |
這將打印出 x 數組從大到小排序後的結果。
由於 parameters 參數列表 是可選的,如果不將參數傳遞給 Lambda 表達式,並且其 Lambda 聲明器不包含 mutable,且沒有後置返回值類型,則可以省略空括號。
Lambda 表達式也可以將另一個 Lambda 表達式作為其參數。
一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
mutable 可變規範
利用可變規範,Lambda 表達式的主體可以修改通過值捕獲的變量。若使用此關鍵字,則 parameters 不可省略(即使為空)。
一個例子,使用 capture 捕獲字句 中的例子,來觀察 a 的值的變化:
1 2 | |
此時 lambda 中的 a 的值改變為 1,lambda 外的 a 保持不變。
return-type 返回類型
用於指定 Lambda 表達式的返回類型。若沒有指定返回類型,則返回類型將被自動推斷(行為與用 auto 聲明返回值的普通函數一致)。具體的,如果函數體中沒有 return 語句,返回類型將被推導為 void,否則根據返回值推導。若有多個 return 語句且返回值類型不同,將產生編譯錯誤。
例如,上文的 lam 也可以寫作:
1 | |
再舉兩個例子:
1 2 | |
statement Lambda 主體
Lambda 主體可包含任何函數可包含的部分。普通函數和 Lambda 表達式主體均可訪問以下變量類型:
- 從封閉範圍捕獲變量
- 參數
- 本地聲明的變量
- 在一個
class中聲明時,若捕獲this,則可以訪問該對象的成員 - 具有靜態存儲時間的任何變量,如全局變量
下面是一個例子
1 2 3 4 5 6 7 8 | |
最後我們得到輸出 5 0。這是由於 n 是通過值捕獲的,在調用 Lambda 表達式後仍保持原來的值 0 不變。mutable 規範允許 n 在 Lambda 主體中被修改,將 mutable 刪去則編譯不通過。
使用類完成更復雜的操作
在 C++11 前沒有 Lambda 表達式,但可以使用稍複雜的方法替代,儘管看上去更復雜卻更易理解及擴展。
首先我們已經知道 Lambda 本質是一個可調用的對象,那麼直接定義一個類並構造一個對象,重載其 operator() 運算符就可以完成和 Lambda 一樣的操作,下面看一個簡單的例子,我們將使用 C++17 的語法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
在寫 Lambda 表達式時,我們幾乎都可以將其等價的映射為上面這種形式。
| Lambda 表達式相關語法 | 類的語法 |
|---|---|
| capture 捕獲子句 | 構造函數 |
| - | 析構函數 |
使用 std::function 包裝傳遞 |
基類指針/引用傳遞 |
| 拷貝多個 Lambda 的函數對象 | 自定義的拷貝函數 |
mutable |
operator() 函數是否為 const |
在 Lambda 的捕獲子句中分為引用捕獲和按值捕獲(暫不考慮比較特殊的捕獲 this 等),而在類的構造函數中我們可以更精細的控制這一點,另外自定義的析構函數的存在也方便我們更好的擴展,缺點是不夠「匿名」,因為仍需要類名。
假設我們有一個函數
1 | |
那麼上述用例中就可以改為
1 | |
並且既然 Callable 是一個可調用對象,我們也可以通過 std::bind(&AbstractCallable::operator(), t, std::placeholders::_1) 來將其轉換再轉換為 std::function<int(int)>。
如果不需要實現類似 std::function 的包裝,那麼也無需使用抽象基類,這樣便和一般的 Lambda 表達式一樣不會產生額外的虛擬函數表的開銷。
參考文獻
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用