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」是什麼感覺。