深入了解 Lambda 表达式
本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 85 课,共 104 课。
Lambda 表达式在 C++11 中引入,是你可以在行内定义的匿名函数。你已经见过与 STL 算法搭配使用的基础 Lambda 表达式,理解它们的完整语法还能解锁强大的功能,让你捕获变量并控制访问方式。
完整的 lambda 语法是:[capture](parameters) mutable -> return_type { body }。捕获子句决定 lambda 可以访问哪些外部变量,以及如何访问:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // 复制 x
auto byRef = [&y]() { y += 5; }; // 引用 y
auto allByValue = [=]() { return x + y; }; // 全部复制
auto allByRef = [&]() { x++; y++; }; // 全部引用
auto mixed = [x, &y]() { y += x; }; // 混合使用两种方式
byRef();
std::cout << y << "\n"; // 25
}默认情况下,按值捕获的变量在 lambda 内部是 const。mutable 关键字允许修改这些副本:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // 修改 lambda 中的副本
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0,原始值不变C++14 添加了初始化捕获,让你可以创建新变量,或将对象移动到 lambda 中:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // 所有权已转移到 lambda 中在 OOP 中,Lambda 特别有用。当你需要将行为作为参数传递时,例如用于回调、自定义比较器或事件处理程序,就不必单独定义函数对象。
挑战
简单我们来构建一个事件处理系统,展示不同捕获模式下 lambda 表达式的强大之处。你将创建一个简单的事件分发器,用于存储和调用回调,演示 lambda 如何以各种方式捕获外部状态。
你需要把代码分成 3 个文件:
EventDispatcher.h:定义一个管理事件回调的EventDispatcher类。分发器应使用
std::vector和std::function<void()>来存储回调。添加以下方法:addCallback(std::function<void()> callback):向列表中添加一个回调fireAll():按顺序调用所有已存储的回调clear():移除所有回调
你需要包含
<functional>和<vector>。EventDispatcher.cpp:实现分发器的方法。fireAll()方法只需遍历所有回调并逐个调用。main.cpp:读取两个输入:- 一个基数(整数)
- 一个乘数(整数)
创建一个
EventDispatcher,并通过添加 3 个回调来演示不同的 lambda 捕获方式:- 一个按值捕获基数并打印
Base value: [base]的 lambda - 一个按引用捕获乘数、将其加 1,然后打印
Multiplier after increment: [multiplier]的 lambda - 一个按值捕获计数器变量(初始值为 0)、每次调用时都将其加 1,并打印
Call count: [counter]的 mutable lambda
添加所有回调后,调用两次
fireAll(),看看不同的捕获模式在多次调用时会如何表现。在两次调用fireAll()之间,打印---作为分隔符。最后,在两轮调用结束后,从 main 打印乘数变量的最终值,以展示按引用捕获对它的影响:
Final multiplier: [multiplier]
例如,输入 10 和 5 时:
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7输入 42 和 0 时:
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2注意以下关键行为:按值捕获会保留原始基数,不会改变它;按引用捕获会修改 main 中实际的乘数变量,并在多次调用间累积;mutable lambda 中的计数器每轮都会读取到 1。这是因为 fireAll() 会按值遍历回调:每一轮都会调用存储的 std::function 的一个新副本。这个副本捕获的计数器初始值为 0,并递增到 1,因此存储的回调自身状态始终不会递增。
亲手试试
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO:添加一个按值捕获 base 的 lambda
// 它应输出:"Base value: [base]"
// TODO:添加一个按引用捕获 multiplier 的 lambda
// 它应将 multiplier 加 1,然后输出:"Multiplier after increment: [multiplier]"
// TODO:添加一个可修改的 lambda,按值捕获一个初始化为 0 的计数器
// 它应将计数器加 1,然后输出:"Call count: [counter]"
// TODO:调用 fireAll() 来执行所有回调
// TODO:输出 "---" 作为分隔符
// TODO:再次调用 fireAll()
// TODO:输出 multiplier 的最终值:"Final multiplier: [multiplier]"
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
12现代 C++ 特性
移动语义与右值完美转发深入了解 Lambda 表达式std::function 与 std::bindconstexpr 与 consteval结构化绑定optional、variant 和 any自己练习: C++ 在线编译器