語法書 / AA 競程語法書 下冊 / 第十二單元 / lambda 運算式(延伸知識)

12.8 lambda 運算式(延伸知識)

11.6 寫比較函式時有個小彆扭:函式得定義在 main 外面,但用它的地方在 main 裡面——規則明明只有一行,卻要跑到程式頂端去讀。lambda 運算式(lambda expression)讓你把函式寫在使用的地方

[](參數列表) -> 回傳型態 { 函式本體 }

[] 是 lambda 的招牌(角色下一段講),後面跟一般函式一樣:參數、回傳型態、本體。回傳型態通常連箭頭一起省略——編譯器從 return 推得出來(12.2 的精神)。整個 lambda 是一個「值」,可以存進變數(型態很怪,交給 auto)再呼叫:

#include <bits/stdc++.h>
using namespace std;

int main() {
    auto square = [](int x) { return x * x; };   // 定義一個小函式,存進 square

    cout << square(5) << '\n';     // 呼叫方式跟一般函式一樣
    cout << square(12) << '\n';
    return 0;
}

執行結果:

25
144

最實用的場景:sort 的比較函式

lambda 既然是值,就能直接塞進 sort 的第三個參數——比較函式不用再定義在老遠的全域。11.6 的「由大到小」改寫成 lambda 版:

11.6 的寫法(函式在天邊):

bool cmp(int x, int y) {
    return x > y;
}
// ……隔了很遠之後……
sort(a, a + 5, cmp);

✔︎ lambda 版(規則就在眼前):

sort(a, a + 5, [](int x, int y) { return x > y; });

排序規則跟 sort 這行黏在一起,讀的人視線不用跳。當然,11.7 的鐵則原封不動適用——lambda 只是換個地方寫,「相等必須回傳 false」一個字都不能鬆。

capture list:[] 與 [&]

那對中括號叫 capture list,管的是「lambda 本體能不能用到外面的變數」:

  • []:什麼都不抓——本體只能用自己的參數與內部變數。
  • [&]:外面的變數全部可用(以參考的方式,12.3 的別名)。

差別看 11.6 的「排編號」例子最清楚。當時比較的依據 height 被迫開成全域,因為全域函式 cmp 搆不到 main 裡的變數;lambda 用 [&] 就地取材,height 終於可以待在 main 裡:

#include <bits/stdc++.h>
using namespace std;

int main() {
    int height[5] = {0, 160, 155, 170, 158};   // 不用開全域了
    int id[4] = {1, 2, 3, 4};
    sort(id, id + 4, [&](int x, int y) { return height[x] < height[y]; });
    for (int x : id) cout << x << ' ';
    cout << '\n';
    return 0;
}

執行結果:

2 4 1 3

[] 的話,本體裡的 height 會編譯錯誤('height' is not captured)——看到這個錯誤訊息,就是提醒你把 [] 改成 [&]

整理 lambda 的兩個賣點:定義在使用處附近(規則與用途零距離)、少開全域變數[&] 就地取材)。capture list 還有 [=][x] 等抓法,競程用 [][&] 兩種就夠,其他的等你好奇時再查 cppreference: Lambda expressions

動手試試看:把 11.6 動手試試看解過的特殊數字排序翻出來,比較函式改寫成 lambda 版重新提交。再試試 12.4 的限制能不能用 lambda 繞開——寫一個 [&](int i) 印出 v[i] 的 lambda,體會「抓得到外面的 v」是什麼感覺。