C++ Lambda 表达式
Lambda 表达式是 C++11 引入的一种创建匿名函数对象的方式。它让我们可以在需要函数的地方直接定义函数,大大简化了代码,特别是在使用 STL 算法和回调函数时。
Lambda 基础语法
基本形式
Lambda 表达式的完整语法:
[capture](parameters) mutable -> return_type { body }
各部分说明:
| 部分 | 说明 | 是否必需 |
|---|---|---|
[capture] | 捕获列表,指定如何捕获外部变量 | 必需 |
(parameters) | 参数列表 | 可选(无参数时可省略) |
mutable | 允许修改捕获的变量(值捕获时) | 可选 |
-> return_type | 返回类型 | 可选(可自动推导) |
{ body } | 函数体 | 必需 |
最简单的 Lambda
#include <iostream>
int main() {
// 最简单的 lambda:无捕获、无参数
auto greet = []() {
std::cout << "Hello, Lambda!" << std::endl;
};
greet(); // 调用 lambda
// 更简洁:直接调用
[]() { std::cout << "直接调用" << std::endl; }();
// 带参数的 lambda
auto add = [](int a, int b) {
return a + b;
};
std::cout << add(3, 5) << std::endl; // 8
// 自动推导返回类型
auto multiply = [](int a, int b) {
return a * b; // 返回类型推导为 int
};
// 显式指定返回类型
auto divide = [](int a, int b) -> double {
return static_cast<double>(a) / b;
};
std::cout << divide(10, 3) << std::endl; // 3.33333
return 0;
}
捕获外部变量
捕获列表是 Lambda 最核心的特性,它决定了 Lambda 如何访问外部作用域的变量。
捕获方式
#include <iostream>
int main() {
int x = 10;
int y = 20;
// 1. 不捕获任何变量
auto f1 = []() {
// 不能使用 x 或 y
return 42;
};
// 2. 值捕获指定变量
auto f2 = [x]() {
return x * 2; // 使用 x 的副本
};
// 3. 引用捕获指定变量
auto f3 = [&x]() {
x = 100; // 修改外部变量 x
};
f3();
std::cout << x << std::endl; // 100
// 4. 值捕获所有变量
auto f4 = [=]() {
return x + y; // 使用 x 和 y 的副本
};
// 5. 引用捕获所有变量
auto f5 = [&]() {
x = 1; // 修改外部变量
y = 2;
};
// 6. 混合捕获
auto f6 = [=, &x]() { // 默认值捕获,x 引用捕获
x = y + 1; // x 是引用,y 是值
};
auto f7 = [&, x]() { // 默认引用捕获,x 值捕获
y = x + 1; // y 是引用,x 是值
};
// 7. 初始化捕获(C++14)
int z = 30;
auto f8 = [z = z + 10]() { // 捕获表达式的结果
return z; // 40
};
// 8. 移动捕获(C++14)
auto ptr = std::make_unique<int>(42);
auto f9 = [p = std::move(ptr)]() {
return *p;
};
return 0;
}
捕获方式对比
#include <iostream>
#include <vector>
#include <algorithm>
void captureDemo() {
int value = 100;
// 值捕获:捕获变量的副本
auto byValue = [value]() {
// value 是副本,无法修改外部变量
return value;
};
// 引用捕获:捕获变量的引用
auto byReference = [&value]() {
value = 200; // 修改外部变量
};
// 选择原则:
// - 只读使用:值捕获更安全
// - 需要修改:引用捕获
// - 生命周期问题:值捕获或智能指针
}
捕获陷阱
#include <iostream>
#include <functional>
// 错误示例:返回引用捕获的 lambda
std::function<int()> badFactory() {
int x = 42;
return [&x]() { return x; }; // 危险!x 已销毁
}
// 正确示例:返回值捕获的 lambda
std::function<int()> goodFactory() {
int x = 42;
return [x]() { return x; }; // 安全:捕获副本
}
// 悬空引用示例
void danglingDemo() {
std::function<void()> func;
{
int local = 10;
func = [&local]() { // 危险!
std::cout << local << std::endl;
};
} // local 销毁
func(); // 未定义行为!访问已销毁的变量
}
// 正确做法:值捕获
void safeDemo() {
std::function<void()> func;
{
int local = 10;
func = [local]() { // 安全
std::cout << local << std::endl;
};
}
func(); // 正常工作
}
mutable 关键字
默认情况下,值捕获的变量在 Lambda 内是只读的。使用 mutable 可以修改这些副本:
#include <iostream>
int main() {
int counter = 0;
// 没有 mutable:不能修改捕获的值
// auto f1 = [counter]() { counter++; }; // 错误!
// 有 mutable:可以修改捕获的值的副本
auto f2 = [counter]() mutable {
counter++; // 修改的是副本
return counter;
};
std::cout << f2() << std::endl; // 1
std::cout << f2() << std::endl; // 2
std::cout << f2() << std::endl; // 3
std::cout << counter << std::endl; // 0(外部变量不变)
// 引用捕获不需要 mutable
auto f3 = [&counter]() {
counter++; // 直接修改外部变量
};
f3();
std::cout << counter << std::endl; // 1
return 0;
}
Lambda 与 STL 算法
Lambda 在 STL 算法中应用广泛,可以简化大量代码。
排序
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
void sortDemo() {
std::vector<int> nums = {5, 2, 8, 1, 9};
// 升序排序
std::sort(nums.begin(), nums.end());
// 降序排序
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return a > b; });
// 自定义排序
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
std::sort(names.begin(), names.end(),
[](const std::string& a, const std::string& b) {
return a.length() < b.length(); // 按长度排序
});
// 结构体排序
struct Person {
std::string name;
int age;
};
std::vector<Person> people = {
{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}
};
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age; // 按年龄排序
});
}
查找和遍历
#include <algorithm>
#include <vector>
#include <iostream>
void findDemo() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 查找第一个偶数
auto it = std::find_if(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; });
if (it != nums.end()) {
std::cout << "第一个偶数: " << *it << std::endl; // 2
}
// 查找所有偶数
std::vector<int> evens;
std::copy_if(nums.begin(), nums.end(), std::back_inserter(evens),
[](int n) { return n % 2 == 0; });
// 遍历打印
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << " "; });
std::cout << std::endl;
// 带状态的遍历
int sum = 0;
std::for_each(nums.begin(), nums.end(),
[&sum](int n) { sum += n; });
std::cout << "总和: " << sum << std::endl;
// 检查是否所有元素都满足条件
bool allPositive = std::all_of(nums.begin(), nums.end(),
[](int n) { return n > 0; });
// 检查是否存在满足条件的元素
bool hasEven = std::any_of(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; });
}
变换和过滤
#include <algorithm>
#include <vector>
#include <iostream>
void transformDemo() {
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int> result(nums.size());
// 变换:每个元素乘以 2
std::transform(nums.begin(), nums.end(), result.begin(),
[](int n) { return n * 2; });
// result = {2, 4, 6, 8, 10}
// 过滤:移除偶数
nums.erase(
std::remove_if(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; }),
nums.end()
);
// nums = {1, 3, 5}
// 替换:将所有 3 替换为 30
std::replace_if(nums.begin(), nums.end(),
[](int n) { return n == 3; }, 30);
// 分区:将偶数放前面
std::vector<int> nums2 = {1, 2, 3, 4, 5, 6, 7, 8};
std::partition(nums2.begin(), nums2.end(),
[](int n) { return n % 2 == 0; });
}
Lambda 作为回调函数
Lambda 作为回调函数使用非常方便:
#include <functional>
#include <vector>
#include <iostream>
// 使用 std::function 存储回调
class Button {
std::function<void()> onClick;
public:
void setOnClick(std::function<void()> callback) {
onClick = callback;
}
void click() {
if (onClick) onClick();
}
};
// 使用示例
void callbackDemo() {
Button btn;
int clickCount = 0;
btn.setOnClick([&clickCount]() {
clickCount++;
std::cout << "按钮点击次数: " << clickCount << std::endl;
});
btn.click(); // 输出:按钮点击次数: 1
btn.click(); // 输出:按钮点击次数: 2
}
// 事件处理
class EventEmitter {
std::vector<std::function<void(int)>> handlers;
public:
void on(std::function<void(int)> handler) {
handlers.push_back(handler);
}
void emit(int value) {
for (auto& handler : handlers) {
handler(value);
}
}
};
void eventDemo() {
EventEmitter emitter;
// 注册多个处理器
emitter.on([](int x) { std::cout << "处理器1: " << x << std::endl; });
emitter.on([](int x) { std::cout << "处理器2: " << x * 2 << std::endl; });
emitter.on([](int x) { std::cout << "处理器3: " << x * x << std::endl; });
emitter.emit(5);
// 输出:
// 处理器1: 5
// 处理器2: 10
// 处理器3: 25
}
高级用法
立即调用的 Lambda
#include <iostream>
int main() {
// 立即调用
int result = [](int a, int b) {
return a + b;
}(3, 5); // 直接传参并调用
std::cout << result << std::endl; // 8
// 用于初始化 const 变量
const int value = [&]() {
int x = computeSomething();
int y = computeOther();
return x + y;
}();
// 替代复杂的初始化逻辑
const std::string config = []() {
std::ifstream file("config.txt");
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
return content;
}();
return 0;
}
递归 Lambda
#include <functional>
#include <iostream>
int main() {
// 方式1:使用 std::function
std::function<int(int)> factorial = [&factorial](int n) -> int {
return n <= 1 ? 1 : n * factorial(n - 1);
};
std::cout << factorial(5) << std::endl; // 120
// 方式2:Y 组合子(高级技巧)
auto Y = [](auto f) {
return [f](auto&&... args) {
return f(f, std::forward<decltype(args)>(args)...);
};
};
auto fib = Y([](auto self, int n) -> int {
return n <= 1 ? n : self(self, n - 1) + self(self, n - 2);
});
std::cout << fib(10) << std::endl; // 55
return 0;
}
泛型 Lambda(C++14)
#include <iostream>
int main() {
// 泛型 lambda:参数使用 auto
auto add = [](auto a, auto b) {
return a + b;
};
std::cout << add(1, 2) << std::endl; // 3 (int)
std::cout << add(1.5, 2.5) << std::endl; // 4.0 (double)
std::cout << add(std::string("hello"), std::string(" world")) << std::endl;
// 泛型 lambda 的多种用法
auto print = [](const auto& value) {
std::cout << value << std::endl;
};
print(42);
print(3.14);
print("hello");
// 复杂的泛型 lambda
auto process = []<typename T>(T value) { // C++20 模板语法
if constexpr (std::is_integral_v<T>) {
return value * 2;
} else {
return value;
}
};
return 0;
}
constexpr Lambda(C++17)
// C++17: lambda 可以是 constexpr
constexpr auto square = [](int n) { return n * n; };
static_assert(square(5) == 25, "5 squared should be 25");
// 在编译时使用
constexpr int arr[square(3)] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
Lambda 的类型
Lambda 的类型是什么?
每个 Lambda 表达式都有唯一的类型,这是一个编译器生成的匿名类:
#include <type_traits>
int main() {
auto f1 = []() {};
auto f2 = []() {};
// f1 和 f2 的类型不同
static_assert(!std::is_same_v<decltype(f1), decltype(f2)>);
// 不捕获的 lambda 可以转换为函数指针
auto f3 = [](int x) { return x * 2; };
int (*funcPtr)(int) = f3; // 正确
// 捕获的 lambda 不能转换为函数指针
int y = 10;
auto f4 = [y](int x) { return x + y; };
// int (*funcPtr2)(int) = f4; // 错误!
return 0;
}
存储 Lambda
#include <functional>
#include <vector>
#include <iostream>
int main() {
// 使用 std::function 存储
std::function<int(int, int)> op;
op = [](int a, int b) { return a + b; };
std::cout << op(3, 5) << std::endl; // 8
op = [](int a, int b) { return a * b; };
std::cout << op(3, 5) << std::endl; // 15
// 存储 lambda 的 vector
std::vector<std::function<int(int)>> functions = {
[](int x) { return x + 1; },
[](int x) { return x * 2; },
[](int x) { return x * x; }
};
for (auto& f : functions) {
std::cout << f(5) << std::endl; // 6, 10, 25
}
return 0;
}
性能考虑
Lambda 的性能特点
#include <algorithm>
#include <vector>
void performanceDemo() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 1. 无捕获 lambda:通常内联,零开销
std::sort(vec.begin(), vec.end(),
[](int a, int b) { return a < b; });
// 2. 值捕获:小对象通常内联,大对象有拷贝开销
int smallValue = 42;
std::sort(vec.begin(), vec.end(),
[smallValue](int a, int b) { return a < b + smallValue; });
// 3. 引用捕获:零开销,但要注意生命周期
int& ref = smallValue;
std::sort(vec.begin(), vec.end(),
[&ref](int a, int b) { return a < b + ref; });
// 4. std::function 有类型擦除开销
std::function<bool(int, int)> cmp = [](int a, int b) { return a < b; };
std::sort(vec.begin(), vec.end(), cmp); // 可能无法内联
}
避免不必要的捕获
// 不好的做法:捕获不必要的变量
void badExample() {
int a = 1, b = 2, c = 3;
auto f = [=]() { // 捕获了 a, b, c
return a + b; // 但只用到了 a 和 b
};
}
// 好的做法:只捕获需要的变量
void goodExample() {
int a = 1, b = 2, c = 3;
auto f = [a, b]() { // 只捕获 a 和 b
return a + b;
};
}
实用示例
自定义循环
#include <iostream>
void repeat(int n, std::function<void(int)> action) {
for (int i = 0; i < n; i++) {
action(i);
}
}
int main() {
repeat(5, [](int i) {
std::cout << "第 " << i << " 次" << std::endl;
});
return 0;
}
延迟执行
#include <functional>
#include <iostream>
class Defer {
std::function<void()> action;
public:
Defer(std::function<void()> f) : action(f) {}
~Defer() { action(); }
};
int main() {
std::cout << "开始" << std::endl;
{
Defer cleanup([]() {
std::cout << "清理资源" << std::endl;
});
std::cout << "处理中" << std::endl;
} // cleanup 析构,执行清理
std::cout << "结束" << std::endl;
return 0;
}
链式调用
#include <algorithm>
#include <vector>
#include <iostream>
template<typename T>
class Pipeline {
std::vector<T> data;
public:
Pipeline(std::vector<T> v) : data(std::move(v)) {}
Pipeline& filter(std::function<bool(const T&)> pred) {
data.erase(
std::remove_if(data.begin(), data.end(),
[&pred](const T& x) { return !pred(x); }),
data.end()
);
return *this;
}
Pipeline& map(std::function<T(const T&)> func) {
std::transform(data.begin(), data.end(), data.begin(), func);
return *this;
}
void forEach(std::function<void(const T&)> action) {
std::for_each(data.begin(), data.end(), action);
}
};
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Pipeline<int>(nums)
.filter([](int x) { return x % 2 == 0; }) // 过滤偶数
.map([](int x) { return x * x; }) // 平方
.forEach([](int x) { std::cout << x << " "; }); // 输出: 4 16 36 64 100
return 0;
}
小结
本章学习了:
- Lambda 语法:完整的 Lambda 表达式形式
- 捕获机制:值捕获、引用捕获和混合捕获
- mutable 关键字:修改值捕获的变量
- STL 应用:在算法中使用 Lambda
- 回调函数:Lambda 作为回调
- 高级用法:递归、泛型、constexpr Lambda
- 类型和存储:Lambda 的类型和 std::function
- 性能考虑:正确使用 Lambda 以保证性能
下一步
接下来让我们学习 C++ 现代 C++ 特性,全面了解 C++11/14/17/20 的新特性。