lambda 表达式
第 12 课降序排序写的是 std::greater<int>(),第 14 课按成员排序要在上面另写一个比较函数。其实可以在调用 sort 的地方,就地写一个小函数,这叫 lambda 表达式:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> scores{72, 95, 88, 60};7 std::sort(scores.begin(), scores.end(), [](int a, int b) { return a > b; });8 for (int s : scores) {9 std::cout << s << " ";10 }11 std::cout << '\n';12 return 0;13}
95 88 72 60
[](int a, int b) { return a > b; } 就是一个没有名字的函数:两个参数,a 比 b 大就返回 true,意思是「大的排前面」。sort 每比较两个元素,就调用它一次。
lambda 的样子
一个 lambda 有三部分:方括号 [](捕获,下面讲)、圆括号里的参数、花括号里的函数体。返回类型不用写,编译器从 return 推出来。
lambda 也可以存进变量,像函数一样调用。它的类型只有编译器知道,所以用 auto:
1#include <iostream>2 3int main() {4 auto with_tax = [](double price) { return price * 1.1; };5 auto is_even = [](int n) { return n % 2 == 0; };6 std::cout << with_tax(50) << '\n';7 std::cout << with_tax(200) << '\n';8 std::cout << is_even(7) << '\n';9 return 0;10}
55 220 0
点单步:每次调用 with_tax,画面上和普通函数一样多出一层,price 是这一次的值。
给 sort 用
按成员排序,以前要另写一个函数,现在比较的规矩就写在 sort 旁边,读的人不用往上翻:
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Student {7 std::string name;8 int score;9};10 11int main() {12 std::vector<Student> list{{"小林", 82}, {"小周", 95}, {"小陈", 76}};13 std::sort(list.begin(), list.end(), [](const Student& a, const Student& b) {14 return a.score > b.score;15 });16 for (const auto& s : list) {17 std::cout << s.name << " " << s.score << '\n';18 }19 return 0;20}
小周 95 小林 82 小陈 76
参数写 const Student&,不复制、也不改(第 10 课)。lambda 的函数体可以有好几行,换行写,和普通函数一样。
count_if 和 find_if
<algorithm> 里很多函数名字带 _if,要的都是一个「问一个元素,回答 true 或 false」的函数。std::count_if 数有几个回答 true;std::find_if 找第一个回答 true 的,返回指着它的迭代器,找不到就是 end():
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> scores{72, 58, 91, 45, 66};7 auto failed = std::count_if(scores.begin(), scores.end(), [](int s) { return s < 60; });8 std::cout << "不及格:" << failed << " 人\n";9 auto it = std::find_if(scores.begin(), scores.end(), [](int s) { return s > 90; });10 if (it != scores.end()) {11 std::cout << "第一个超过 90 的:" << *it << '\n';12 }13 return 0;14}
不及格:2 人 第一个超过 90 的:91
和第 12 课的 std::count、std::find 比:那两个只能数、找「等于某个值」的;带 _if 的,条件随你写。(迭代器第 27 课再细讲,现在知道 *it 取值就够了。)
for_each
std::for_each 对每个元素调用一次你给的函数。参数写成引用,就能改元素:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<double> prices{20, 35, 12.5};7 std::for_each(prices.begin(), prices.end(), [](double& p) { p = p * 0.8; });8 std::for_each(prices.begin(), prices.end(), [](double p) {9 std::cout << p << " 元\n";10 });11 return 0;12}
16 元 28 元 10 元
说实话,这样的事用范围 for 一样清楚。for_each 的好处是:手里已经有一个存在变量里的 lambda 时,直接交给它就行。
捕获:用外面的变量
lambda 里想用外面的变量,比如一个及格线,直接写会编译错误:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> scores{72, 58, 91, 45, 66};7 int pass_line = 70;8 auto passed = std::count_if(scores.begin(), scores.end(), [](int s) { return s >= pass_line; });9 std::cout << passed << '\n';10 return 0;11}
main.cpp: In lambda function:
main.cpp:8:87: error: 'pass_line' is not captured
8 | auto passed = std::count_if(scores.begin(), scores.end(), [](int s) { return s >= pass_line; });
| ^~~~~~~~~'pass_line' is not captured:pass_line 没有被捕获。lambda 默认看不到外面的局部变量,要在方括号里列出来,叫捕获。写成 [pass_line] 就好了,结果是 2。
[pass_line] 是按值捕获:建 lambda 的那一刻,把 pass_line 的值复制一份带进去。之后外面再改,lambda 里的不变:
这段程序显示什么?
1#include <iostream>2 3int main() {4 int bonus = 5;5 auto add_bonus = [bonus](int s) { return s + bonus; };6 bonus = 100;7 std::cout << add_bonus(80) << '\n';8 return 0;9}
第 5 行建 lambda 时,bonus 是 5,复制了一份进去。第 6 行改的是外面的 bonus,lambda 里那份还是 5,所以是 85。
按引用捕获
要让 lambda 改外面的变量,或者看到它最新的值,就按引用捕获:[&total]。lambda 里的 total 是外面那个的引用:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> steps{4200, 8100, 6500, 9900};7 int total = 0;8 int good_days = 0;9 int goal = 6000;10 std::for_each(steps.begin(), steps.end(), [&total, &good_days, goal](int s) {11 total += s;12 if (s >= goal) {13 good_days++;14 }15 });16 std::cout << "一共 " << total << " 步," << good_days << " 天达标\n";17 return 0;18}
一共 28700 步,3 天达标
点单步:lambda 每运行一次,main 里的 total 和 good_days 就变一次。goal 只看不改,按值捕获就行。
| 写法 | 意思 |
|---|---|
[] | 什么也不捕获 |
[x] | 复制 x |
[&x] | 引用 x |
[=] | 用到的外面变量都复制 |
[&] | 用到的外面变量都引用 |
注意: 按值捕获的变量在 lambda 里是只读的,[total](int s) { total += s; } 是编译错误。按引用捕获时要小心:lambda 用的时候,被引用的变量必须还活着。
std::function
lambda 的类型写不出来,存进变量靠 auto。可要是想让一个变量先存这个 lambda、以后换成那个,或者写一个「参数是一个函数」的函数,就用 <functional> 里的 std::function。尖括号里写「返回类型(参数类型)」:
1#include <functional>2#include <iostream>3#include <vector>4 5void show_prices(const std::vector<double>& prices, std::function<double(double)> rule) {6 for (double p : prices) {7 std::cout << rule(p) << " ";8 }9 std::cout << '\n';10}11 12int main() {13 std::vector<double> prices{20, 50, 120};14 std::function<double(double)> rule = [](double p) { return p; };15 show_prices(prices, rule);16 rule = [](double p) { return p * 0.9; };17 show_prices(prices, rule);18 double cut = 15;19 show_prices(prices, [cut](double p) { return p >= 100 ? p - cut : p; });20 return 0;21}
20 50 120 18 45 108 20 50 105
std::function<double(double)> 能装下任何「给一个 double、还一个 double」的东西:不同的 lambda,带捕获的也行,普通函数也行。show_prices 不管规矩是什么,只管调用它。(最后一个 lambda 里的 条件 ? 甲 : 乙:条件成立取甲,不然取乙。)
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
便宜的排前面
✓用 std::sort 和一个 lambda,按价格从低到高排好商品,再按顺序显示名字。程序要显示三行:鸡蛋、牛奶、咖啡豆。
鸡蛋 牛奶 咖啡豆
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Product {7 std::string name;8 double price;9};10 11int main() {12 std::vector<Product> cart{{"咖啡豆", 68}, {"鸡蛋", 15.5}, {"牛奶", 22}};13 // 在这里排序14 for (const auto& p : cart) {15 std::cout << p.name << '\n';16 }17 return 0;18}
热天有几天
✓用 std::count_if 数一数 temps 里有几天高于 hot(捕获它)。程序要显示 高温:3 天。
高温:3 天
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> temps{29, 33, 35, 31, 36, 28};7 int hot = 32;8 // 在这里数9 return 0;10}
修好它:改不了的总数
✓这段代码想用 for_each 把账单加起来,却编译不过。只加一个字符,让程序显示 合计:86。
合计:86
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> bills{30, 12, 44};7 int total = 0;8 std::for_each(bills.begin(), bills.end(), [total](int b) { total += b; });9 std::cout << "合计:" << total << '\n';10 return 0;11}
这一课学会了
- lambda 是就地写的小函数:
[捕获](参数) { 函数体 },返回类型自己推;存进变量用auto。 - 给
std::sort一个 lambda 定排序规矩,比如[](int a, int b) { return a > b; }。 std::count_if、std::find_if要一个回答true/false的 lambda;std::for_each对每个元素调用一次。- lambda 要用外面的局部变量,得捕获:
[x]复制一份(建的时候的值,只读),[&x]是引用(能改,看到最新的值)。 [=]全部复制,[&]全部引用。std::function<返回类型(参数类型)>能装下各种函数和 lambda,用来存起来、换掉,或者当参数传。