Lambda表达式基础与语法
极难5什么是 Lambda 表达式?
想象你放学回家,妈妈让你把一袋苹果按大小从小到大排列。如果你需要写一份“排序说明书”,你会专门写一篇长长的论文介绍“如何比较两个苹果”,还是直接写个便条:“比较两个苹果:先看第一个是不是比第二个小,是的话就放前面”?
编程中也常常遇到类似情况:你只需要一个临时用一下的比较方法,比如排序时指定按什么规则排。以前你必须定义一个单独的函数(或者一个仿函数类),用完就丢,有点麻烦。C++11 引入了一个很棒的特性——Lambda 表达式,让你可以像写便条一样,直接在需要的地方写出一个小函数,用完就消失,非常方便。
Lambda 表达式本质上是一个匿名的函数对象(编译器会悄悄帮你生成一个匿名的仿函数类)。它可以被当作一个变量来保存,也可以直接传递给算法,就像传递一个数字或字符串一样简单。
最简单的 Lambda 长这样:
auto double_it = [](int x) { return x * 2; }; // 定义一个“乘以2”的小函数
int result = double_it(5); // result = 10
这个 [](int x) { return x * 2; } 就是 Lambda 表达式,它像一个“随身携带的计算器”。
Lambda 表达式语法详解
Lambda 表达式的基本结构是:
[capture] (parameters) -> return_type { body }
[capture]:捕获列表,用来“借”周围作用域中的变量,让 Lambda 内部可以使用它们。这个符号是必须的,哪怕什么都不写([])。(parameters):参数列表,和普通函数一样,可以省略(如果无参数)。-> return_type:返回类型(可选),大多数时候编译器能自动推导,但如果想强制返回类型,可以写在这里。{ body }:函数体,包含你要执行的代码。
1. 最简单的 Lambda
auto say_hello = [] { // 无参数,捕获列表为空,返回类型自动推导
cout << "你好!" << endl;
};
say_hello(); // 调用它
2. 带参数的 Lambda
auto add = [](int a, int b) { return a + b; };
cout << add(3, 4) << endl; // 7
3. 显式指定返回类型
如果 Lambda 体内有多个 return 语句类型不同,或者你希望强制转换类型,可以加上 -> type:
auto divide = [](double a, double b) -> double {
if (b == 0) return 0.0;
return a / b;
};
4. 捕获列表:让 Lambda 使用外部的变量
这是 Lambda 最神奇的地方——它可以“借用”它所在函数或代码块里的变量。捕获列表就是用来声明你需要借哪些变量,以及怎么借(按值还是按引用)。
[x]:按值捕获变量x(复制一份给 Lambda,Lambda 内改不了外部的 x)。[&x]:按引用捕获x(Lambda 内可以直接修改外部的 x)。[=]:按值捕获所有用到的外部变量。[&]:按引用捕获所有用到的外部变量。- 可以混合,比如
[=, &y]:y 按引用,其他按值。
生活例子:你帮妈妈比较苹果大小,但你手里有一个“标准苹果”作为参考。你就可以把这个标准苹果“捕获”到比较规则里。
int base = 10; // 基准值
auto compare = [base](int x) { return x > base; }; // 按值捕获 base
cout << compare(15) << endl; // 1 (true)
cout << compare(5) << endl; // 0 (false)
注意:如果按值捕获,Lambda 内部得到的 base 是捕获那一刻的副本,后面外部 base 变了也不会影响 Lambda 内部。
如果希望 Lambda 内部能修改捕获的变量,必须按引用捕获:
int count = 0;
auto increment = [&count]() { count++; }; // 引用捕获
increment();
cout << count << endl; // 1
新手易犯错误:忘记写捕获列表,却想在 Lambda 内部使用外部变量,编译器会报错。
在 STL 算法中使用 Lambda
很多 STL 算法都接受可调用对象作为参数,比如 sort、for_each、find_if、transform、count_if。Lambda 最常和这些算法配合使用。
例子 1:排序时指定降序
vector<int> v = {3, 1, 4, 1, 5};
sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // 降序
这里我们没有写 greater<int>(),而是用 Lambda 直接表达了“a > b”的比较逻辑,就像在排序的说明书上写了一句注释。
例子 2:for_each 打印每个元素
vector<int> v = {10, 20, 30};
for_each(v.begin(), v.end(), [](int x) {
cout << "值: " << x << endl;
});
例子 3:按字符串长度排序(生活例子)
假设你有一堆同学的名字,你想按名字长度从短到长排列,比较规则用 Lambda 写出来:
vector<string> names = {"Alice", "Bob", "Charlie", "David"};
sort(names.begin(), names.end(), [](const string& a, const string& b) {
return a.size() < b.size(); // 长度小的放前面
});
// 结果: Bob, Alice, David, Charlie
例子 4:find_if 查找第一个偶数
vector<int> v = {1, 3, 5, 6, 7};
auto it = find_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
if (it != v.end()) cout << "找到偶数: " << *it << endl; // 6
例子 5:count_if 统计成绩及格人数
vector<int> scores = {85, 42, 73, 60, 91, 55};
int passed = count_if(scores.begin(), scores.end(), [](int s) { return s >= 60; });
cout << "及格人数: " << passed << endl; // 4
例子 6:transform 把数组每个元素加 10
vector<int> v = {1, 2, 3};
vector<int> result(v.size());
transform(v.begin(), v.end(), result.begin(), [](int x) { return x + 10; });
// result = {11, 12, 13}
新手容易犯的常见错误
❌ 错误 1:忘记写捕获列表
int threshold = 5;
vector<int> v = {10, 2, 30};
auto it = find_if(v.begin(), v.end(), [](int x) { return x > threshold; }); // 错误!没有捕获 threshold
编译器会报错:threshold 未捕获。应该写成 [threshold] 或 [&]。
❌ 错误 2:按值捕获后想修改外部变量,但没成功
int count = 0;
auto increment = [count]() { count++; }; // 按值捕获,count 是副本
increment();
cout << count << endl; // 依然是 0,因为修改的是副本
如果想修改外部的 count,必须按引用捕获:[&count]。
❌ 错误 3:Lambda 体内有多个 return 语句,返回类型不同
auto check = [](int x) {
if (x > 0) return x; // int
else return 0.5; // double,类型冲突
};
编译器无法自动推导返回类型。解决方法:显式指定返回类型 -> double,或者让两个 return 类型一致。
❌ 错误 4:Lambda 体内写了复杂语句(超过两行)却不注意可读性
Lambda 适合临时短小的逻辑。如果逻辑很长(比如超过 5 行),建议单独写成一个普通函数或仿函数类,代码更清晰。
完整可运行的 C++ 示例(含捕获列表)
下面的代码综合展示了 Lambda 的多种用法,包括捕获列表的使用。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
int main() {
// 1. 基础:定义乘法器
auto multiply = [](int x, int y) { return x * y; };
cout << "3 * 4 = " << multiply(3, 4) << endl; // 12
// 2. 捕获外部变量:统计大于基准值的元素个数
int base = 10;
vector<int> numbers = {5, 12, 18, 3, 20};
// 按值捕获 base
int big_count = count_if(numbers.begin(), numbers.end(), [base](int n) { return n > base; });
cout << "大于 " << base << " 的个数: " << big_count << endl; // 3
// 3. 引用捕获:在 Lambda 内累加计数
int sum = 0;
for_each(numbers.begin(), numbers.end(), [&sum](int n) { sum += n; });
cout << "总和: " << sum << endl; // 58
// 4. 混合捕获:按值捕获变量 min,按引用捕获 max
int min_val = 10, max_val = 0;
auto find_min_max = [min_val, &max_val](int x) {
if (x < min_val) min_val = x; // 错误:min_val 是按值捕获,不能修改副本
// 应改为按引用捕获两者,或者用另一种方式。这里演示错误,实际不会编译
};
// 正确做法:全部按引用
// auto find_min_max = [&min_val, &max_val](int x) { ... };
// 5. 立即执行的 Lambda(常用于初始化)
int random_seed = []() {
// 这里可以放一些复杂的初始化代码
return 42;
}(); // 调用这个 Lambda,结果赋给 random_seed
cout << "随机种子: " << random_seed << endl;
// 6. Lambda 作为函数参数(自定义排序:按字符串长度排序)
vector<string> fruits = {"banana", "apple", "cherry", "date"};
sort(fruits.begin(), fruits.end(), [](const string& a, const string& b) {
return a.size() < b.size(); // 按长度升序
});
cout << "按长度排序后的水果: ";
for (const string& f : fruits) cout << f << " "; // date apple banana cherry
cout << endl;
return 0;
}
输出说明:
3 * 4 = 12大于10的个数: 3总和: 58随机种子: 42按长度排序后的水果: date apple banana cherry
Python 中的 Lambda(对比学习)
Python 的 lambda 更简洁,但只能写一个表达式(不能包含语句),适合简单逻辑。
from typing import List
def main():
# 1. 基础
multiply = lambda x, y: x * y
print("3 * 4 =", multiply(3, 4)) # 12
# 2. 按值捕获——Python 的 lambda 会自动捕获外部变量(闭包)
base = 10
numbers = [5, 12, 18, 3, 20]
big_count = sum(1 for n in numbers if n > base)
print("大于", base, "的个数:", big_count) # 3
# 3. 累加(使用 for 循环或 reduce)
total = 0
for n in numbers:
total += n
print("总和:", total) # 58
# 4. 立即执行的 lambda
random_seed = (lambda: 42)()
print("随机种子:", random_seed)
# 5. 排序:按字符串长度
fruits = ["banana", "apple", "cherry", "date"]
fruits.sort(key=lambda s: len(s))
print("按长度排序后的水果:", fruits) # ['date', 'apple', 'banana', 'cherry']
if __name__ == "__main__":
main()
总结与相关指引
要点回顾:
- Lambda 表达式是创建匿名函数对象的语法糖,核心结构
[] (params) { body }。 - 捕获列表
[]不可或缺,用于借入外部变量。 - 常与 STL 算法(
sort、for_each、find_if等)配合,使代码紧凑、可读性强。 - 注意按值捕获和按引用捕获的区别,以及返回类型推导的规则。
- 如果 Lambda 体超过 5 行,建议提取为单独的函数。
接下来可以学习:
- 捕获列表的深入用法(
mutable、初始化捕获、泛型 Lambda)。 - Lambda 与仿函数的对比。
- 在自定义容器或算法中如何利用 Lambda 提高灵活性。
Lambda 是现代 C++ 必备的工具,掌握它后你会发现写排序、过滤、变换等操作变得非常自然,就像在一张便条上写下规则一样简单。快去试试吧!
例题精讲
以下关于C++ Lambda表达式的说法中,哪一项是正确的?
对于无捕获的Lambda表达式(即捕获列表为空),它可以被隐式转换为函数指针。
补全以下代码,使得Lambda能够修改外部变量x的值,并输出修改后的结果。
#include <iostream>
int main() {
int x = 10;
auto lambda = [__](int y) { x = y; };
lambda(20);
std::cout << x << std::endl;
return 0;
}对于以下Lambda表达式: auto f = [](auto a, auto b) { return a + b; }; 下列哪一项描述是正确的?
以下代码尝试将Lambda表达式赋值给一个函数指针,请补全Lambda定义,使其能够成功编译。
#include <iostream>
int (*funcPtr)(int, int) = [](int a, int b) -> int { return a * b; };
// 或者
int main() {
int (*fp)(int, int) = [](int a, int b) __ { return a + b; };
std::cout << fp(3,4) << std::endl;
return 0;
}
请填空。