「學習筆記」Lambda 表示式

2023-07-10 12:00:23

Lambda 表示式因數學中的 \(\lambda\) 演算得名, 直接對應於其中的 lambda 抽象. Lambda 表示式能夠捕獲作用域中的變數的無名函數物件, 我們可以將其理解為一個匿名的行內函式, 可以用來替換獨立函數或者函數物件, 從而使程式碼更可讀. 但是從本質上來講, Lambda 表示式只是一種語法糖, 因為它能完成的工作也可以用其他複雜的 C++ 語法來實現.


下面是 Lambda 表示式的語法結構.

[capture] (parameters) mutable -> return-type {statement}

capture: 捕獲;

parameters: 引數;

mutable: 可變規範;

return-type: 返回型別;

statement: Lambda 表示式的主體;


下面是更高版本的 C++ 的有關 Lambda 表示式的語法. [1]

[capture] (parameters) lambda說明符 約束(可選) {statement}
[capture] {statement}    // (C++23 前)
[capture] lambda說明符 {statement}    // (C++23 起)
[capture] < 模板形參 > 約束(可選) // (C++20 起)
(parameters) lambda說明符 約束(可選) {statement}    // (C++20 起)
[capture] < 模板形參 > 約束(可選) {statement}    // (C++20 起 C++23 前)
[capture] < 模板形參 > 約束(可選) lambda說明符 {statement}    //(C++23 起)

capture 捕獲

捕獲 是一個含有零或更多個捕獲符的逗號分隔列表, 可以 預設捕獲符 開始. 預設捕獲符只有

  • &(以參照隱式捕獲被使用的自動變數)和
  • =(以複製隱式捕獲被使用的自動變數).

當出現任一預設捕獲符時, 都能隱式捕獲當前物件(*this). 如果隱式捕獲它, 那麼會始終以參照捕獲,即使預設捕獲符是 =. 當預設捕獲符為 = 時, *this 的隱式捕獲被棄用. (C++20 起)

當預設捕獲符是 & 時, 後繼的簡單捕獲符不能以 & 開始.

struct S2 { void f(int i); };
void S2::f(int i)
{
    [&]{}; // OK:預設以參照捕獲
    [&, i]{}; // OK:以參照捕獲,但 i 以值捕獲
    [&, &i] {}; // 錯誤:以參照捕獲為預設時的以參照捕獲
    [&, this] {}; // OK:等價於 [&]
    [&, this, i]{}; // OK:等價於 [&, i]
}

當預設捕獲符是 = 時, 後繼的簡單捕獲符必須以 & 開始, 或者為 *this (C++17 起) 或 this (C++20 起).

struct S2 { void f(int i); };
void S2::f(int i)
{
    [=]{}; // OK:預設以複製捕獲
    [=, &i]{}; // OK:以複製捕獲,但 i 以參照捕獲
    [=, *this]{}; // C++17 前:錯誤:無效語法
    // C++17 起:OK:以複製捕獲外圍的 S2
    [=, this] {}; // C++20 前:錯誤:= 為預設時的 this
    // C++20 起:OK:同 [=]
}

任何捕獲符只可以出現一次, 並且名字不能與形參相同:

struct S2 { void f(int i); };
void S2::f(int i)
{
    [i, i] {}; // 錯誤:i 重複
    [this, *this] {}; // 錯誤:"this" 重複(C++17)
    [i] (int i) {}; // 錯誤:形參和捕獲的名字相同
}

parameters 引數

大多數情況下類似於函數的參數列.

C++14 中, 若引數型別是泛型, 則可以使用 auto 宣告型別:

int x[] = {5, 1, 7, 6, 1, 4, 2};
std::sort(x, x + 7, [](int a, int b) { return (a > b); });
for (auto i : x) std::cout << i << " ";

這份程式碼將列印出 x 陣列從大到小排序後的結果.

mutable 可變規範

利用可變規範, Lambda 表示式的主體可以修改通過值捕獲的變數. 若使用此關鍵字, 則 parameters 不可省略 (即使為空).

一個例子, 使用 capture 捕獲字句 中的例子, 來觀察 \(a\) 的值的變化:

int a = 0; 
auto func = [a]() mutable { ++a; };

此時 lambda 中的 \(a\) 的值改變為 \(1\), lambda 外的 \(a\) 保持不變.

return-type 返回型別

用於指定 Lambda 表示式的返回型別. 若沒有指定返回型別, 則返回型別將被自動推斷 (行為與用 auto 宣告返回值的普通函數一致). 具體的, 如果函數體中沒有 return 語句, 返回型別將被推導為 void, 否則根據返回值推導. 若有多個 return 語句且返回值型別不同, 將產生編譯錯誤.

auto lam = [](int a, int b) -> int

statement Lambda 主體

Lambda 主體可包含任何函數可包含的部分。普通函數和 Lambda 表示式主體均可存取以下變數型別:

  • 從封閉範圍捕獲變數
  • 引數
  • 本地宣告的變數
  • 在一個 class 中宣告時,若捕獲 this,則可以存取該物件的成員
  • 具有靜態儲存時間的任何變數,如全域性變數

大部分資料來自 \(\texttt{OI-Wiki}\).


  1. 該資料來自Lambda表示式(C++11起)-cppreference.com ↩︎