1. 项目概述:为什么我们需要Lambda表达式?
干了这么多年C++,从C++98/03一路走到C++17/20,我越来越觉得Lambda表达式是每个C++开发者必须熟练掌握的“瑞士军刀”。它不仅仅是一个语法糖,更是一种编程范式的转变。回想早期写STL算法,为了传一个简单的比较逻辑,得专门写个函数对象(Functor),或者定义一个全局函数,代码跳来跳去,阅读和维护都是一种折磨。Lambda的出现,让“就地定义、就地使用”的匿名函数成为可能,代码的意图瞬间变得清晰。
简单来说,Lambda表达式就是一个可以在函数内部定义的匿名函数对象。它的核心价值在于封装局部逻辑和简化回调。当你需要向std::sort、std::for_each或者std::accumulate这样的算法传递一个定制化的操作时,Lambda是最优雅的选择。它把相关的代码紧紧绑在一起,避免了为了一个只用一次的简单逻辑而去污染命名空间或者类作用域。
对于初学者,Lambda可能看起来有点古怪,特别是那个方括号[]。但一旦你理解了它的设计哲学——捕获上下文、定义参数、执行操作——你就会发现它极大地提升了代码的表达力和局部性。无论是事件驱动编程、并发编程(配合std::thread或std::async),还是日常的容器操作,Lambda都是提升代码质量和开发效率的利器。
2. Lambda表达式核心语法全解
理解Lambda,首先要拆解它的完整语法结构。一个标准的Lambda表达式看起来是这样的:[capture] (parameters) mutable -> return-type { body }
虽然各部分大多是可选的,但理解每一部分的含义是灵活运用的前提。
2.1 捕获子句:连接内外世界的桥梁
捕获子句(Capture Clause)是Lambda最独特也最容易出错的部分,它定义了Lambda体如何访问其外部作用域的变量。
空捕获[]:Lambda体不访问任何外部变量。这是最纯粹、最安全的用法,通常用于纯算法操作。
auto printHello = [] { std::cout << "Hello, Lambda!\n"; };值捕获[=]:以值(拷贝)的方式捕获所有外部变量。在Lambda内部,你拥有这些变量的一个副本。修改这些副本(需要mutable关键字)不会影响外部原始变量。
int base = 10; auto addBase = [=](int x) { return x + base; }; // 捕获时拷贝base的值 std::cout << addBase(5); // 输出15 base = 20; // 修改外部base std::cout << addBase(5); // 仍然输出15,因为内部使用的是捕获时的副本引用捕获[&]:以引用的方式捕获所有外部变量。Lambda内部直接操作外部变量,任何修改都会反映到外部。
int counter = 0; auto increment = [&] { ++counter; }; increment(); std::cout << counter; // 输出1,外部counter被修改注意:引用捕获非常强大,但也非常危险。你必须确保被捕获的变量在Lambda被调用时依然有效。如果捕获了一个局部变量的引用,而该局部变量已经离开了作用域(例如,将Lambda存储起来后续调用),就会导致“悬垂引用”(Dangling Reference),引发未定义行为,这是Lambda使用中最常见的坑之一。
混合捕获与显式捕获:你可以混合使用值和引用捕获,也可以显式指定要捕获的变量,这提供了更精细的控制。
int a = 1, b = 2, c = 3; // 显式值捕获a,显式引用捕获b,其他不捕获 auto lambda1 = [a, &b] { return a + b; }; // 默认引用捕获所有,但显式值捕获c(覆盖默认规则) auto lambda2 = [&, c] { /* 可以访问所有外部变量的引用,但c是副本 */ }; // 默认值捕获所有,但显式引用捕获c(覆盖默认规则) auto lambda3 = [=, &c] { /* 可以访问所有外部变量的副本,但c是引用 */ };这里有一个关键细节:当使用默认捕获模式([=]或[&])时,后续显式指定的捕获必须采用相反的模式。例如,[=, &x]是合法的(默认值捕获,但x是引用),而[=, x]则是非法的,因为x已经隐含在值捕获中,重复指定了。
C++14的初始化捕获(广义捕获):这是C++14引入的强大特性,允许你在捕获子句中直接定义并初始化一个新变量。这解决了两个问题:1) 捕获只移动类型(如std::unique_ptr);2) 为捕获的变量起别名或进行预处理。
std::unique_ptr<int> pInt = std::make_unique<int>(42); // 错误!unique_ptr不可拷贝,无法用[=]或[pInt]值捕获。 // auto badLambda = [pInt] { return *pInt; }; // 正确:使用初始化捕获,移动pInt到Lambda内部的ptr变量中。 auto goodLambda = [ptr = std::move(pInt)] { return *ptr; }; // 此时pInt变为nullptr,所有权转移到了Lambda内部。 // 也可以用于计算或重命名 int x = 10; auto lambda = [y = x * 2] { return y; }; // y被初始化为20捕获this指针:在类的成员函数中定义Lambda时,如果需要访问类的成员变量或成员函数,必须捕获this指针。在C++11/14中,捕获[this]或[=](隐式包含this)是以引用方式捕获当前对象。从C++17开始,支持[*this]以值方式捕获当前对象的副本,这在并行或异步编程中非常有用,可以避免生命周期问题。
class MyClass { int value = 100; public: void foo() { // 捕获this,可以访问成员value auto lambda = [this] { std::cout << value; }; lambda(); } };2.2 参数列表与返回类型
参数列表(params):和普通函数一样,定义Lambda接受的参数。从C++14开始,参数类型可以使用auto进行泛型推导,这使得Lambda可以像模板函数一样工作,非常灵活。
// C++11/14: 明确类型 auto addInt = [](int a, int b) { return a + b; }; // C++14及以上: 泛型Lambda auto addGeneric = [](auto a, auto b) { return a + b; }; std::cout << addGeneric(1, 2); // 3 std::cout << addGeneric(1.5, 2.3); // 3.8 std::string s1 = "Hello, ", s2 = "World!"; std::cout << addGeneric(s1, s2); // Hello, World!返回类型-> ret-type:大多数情况下,编译器可以自动推导返回类型(当函数体只是一个return语句时)。但在一些复杂情况下,例如函数体内有多个返回路径且类型不完全一致,或者返回类型需要明确指定时,就必须使用尾置返回类型。
// 自动推导返回类型为int auto simple = [](int x) { return x * 2; }; // 需要显式指定返回类型(例如,条件运算符返回不同类型) auto complex = [](int x) -> double { if (x > 0) return 3.14; else return 2.71; // 如果没有-> double,编译器可能推导为int,导致精度丢失或错误 };mutable说明符:默认情况下,通过值捕获([=]或显式变量名)的变量在Lambda体内是const的,不能被修改。如果你需要修改这些副本,就必须在参数列表后加上mutable关键字。重要:这修改的只是副本,外部原始变量不受影响。
int count = 0; auto tryIncrement = [count]() mutable { ++count; // 没有mutable,这行编译错误 std::cout << "Internal count: " << count << '\n'; }; tryIncrement(); // 输出: Internal count: 1 tryIncrement(); // 输出: Internal count: 2 std::cout << "External count: " << count << '\n'; // 输出: External count: 0注意,mutableLambda的调用运算符是一个const成员函数,但它允许修改通过值捕获的成员。这是一个容易混淆的点。
异常规范与noexcept:和普通函数一样,你可以为Lambda指定异常规范,例如noexcept,表示该Lambda不会抛出异常。这有助于编译器进行优化。
auto safeDivide = [](int a, int b) noexcept -> int { if (b == 0) return 0; // 处理除零,而不是抛出异常 return a / b; };3. Lambda在STL算法与函数式编程中的应用实战
Lambda真正大放异彩的地方是与C++标准模板库(STL)算法的结合。它让“将函数作为参数传递”这一函数式编程思想在C++中变得异常简洁。
3.1 定制排序与查找
std::sort、std::stable_sort等排序算法通常需要一个比较函数。Lambda让自定义排序规则变得直观。
std::vector<std::pair<int, std::string>> items = {{2, "foo"}, {1, "bar"}, {3, "baz"}}; // 按pair的第一个元素(int)升序排序 std::sort(items.begin(), items.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); // 按pair的第二个元素(string)长度降序排序 std::sort(items.begin(), items.end(), [](const auto& a, const auto& b) { return a.second.size() > b.second.size(); }); // 查找第一个满足条件的元素 auto it = std::find_if(items.begin(), items.end(), [](const auto& item) { return item.first == 42; }); if (it != items.end()) { std::cout << "Found: " << it->second << '\n'; }3.2 遍历与变换
std::for_each、std::transform是遍历和修改容器的利器。
std::vector<int> nums = {1, 2, 3, 4, 5}; std::vector<int> squares; // 使用for_each进行遍历操作(通常有副作用) int sum = 0; std::for_each(nums.begin(), nums.end(), [&sum](int n) { sum += n; }); std::cout << "Sum: " << sum << '\n'; // 输出15 // 使用transform进行元素变换(无副作用,生成新序列) std::transform(nums.begin(), nums.end(), std::back_inserter(squares), [](int n) { return n * n; }); // squares 现在为 {1, 4, 9, 16, 25} // C++17引入了std::for_each_n,处理前n个元素 std::for_each_n(nums.begin(), 3, [](int& n) { n *= 10; }); // nums 现在为 {10, 20, 30, 4, 5}3.3 条件移除与分区
std::remove_if和std::partition经常与Lambda配合,用于筛选元素。
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9}; // 移除所有偶数 (remove-erase惯用法) data.erase(std::remove_if(data.begin(), data.end(), [](int n) { return n % 2 == 0; }), data.end()); // data 现在为 {1, 3, 5, 7, 9} // 分区:将大于5的元素放到前面 std::vector<int> vals = {9, 1, 7, 3, 5, 8, 2}; auto bound = std::partition(vals.begin(), vals.end(), [](int n) { return n > 5; }); // vals 可能变为 {9, 8, 7, 3, 5, 1, 2},bound指向3 // 分区后,[begin, bound)是满足条件的元素,[bound, end)是不满足的。3.4 数值计算与折叠
std::accumulate(或C++17的std::reduce)用于折叠(fold)操作,Lambda可以定义任意的二元操作。
std::vector<int> v = {1, 2, 3, 4, 5}; // 求和 int total = std::accumulate(v.begin(), v.end(), 0, [](int acc, int elem) { return acc + elem; }); // 求乘积 int product = std::accumulate(v.begin(), v.end(), 1, [](int acc, int elem) { return acc * elem; }); // 连接字符串 std::vector<std::string> words = {"Hello", " ", "World", "!"}; std::string sentence = std::accumulate(words.begin(), words.end(), std::string(), [](std::string acc, const std::string& elem) { return acc + elem; }); // sentence 为 "Hello World!"4. Lambda在并发与异步编程中的关键作用
现代C++并发编程(std::thread,std::async,std::packaged_task)严重依赖Lambda来封装任务。
4.1 与std::thread配合
创建线程时,Lambda是传递任务最自然的方式。
#include <thread> #include <iostream> int main() { int sharedData = 0; std::mutex mtx; // 启动一个线程,通过引用捕获sharedData和mutex std::thread worker([&sharedData, &mtx]() { for (int i = 0; i < 100000; ++i) { std::lock_guard<std::mutex> lock(mtx); // 加锁保护 ++sharedData; } }); // 主线程也进行操作 for (int i = 0; i < 100000; ++i) { std::lock_guard<std::mutex> lock(mtx); --sharedData; } worker.join(); std::cout << "Final sharedData: " << sharedData << '\n'; // 理论上应为0 return 0; }重要经验:在多线程中使用Lambda时,要极度小心捕获方式。
- 避免悬垂引用:确保Lambda捕获的引用在其执行期间一直有效。如果Lambda可能比捕获的局部变量活得久(例如,将Lambda交给线程池延迟执行),必须使用值捕获或
std::shared_ptr等智能指针来管理生命周期。- 注意数据竞争:如果多个线程通过引用捕获并修改同一变量,必须使用互斥锁(
std::mutex)、原子操作(std::atomic)或其他同步机制来保护。- 值捕获的陷阱:值捕获看起来安全,但如果你捕获了一个指针,你捕获的只是指针这个值(地址),而不是指针指向的数据。多个线程通过指针修改同一块内存,同样需要同步。
4.2 与std::async和std::future配合
std::async用于启动一个异步任务,返回一个std::future。Lambda是定义异步任务的理想选择。
#include <future> #include <iostream> #include <chrono> int expensiveComputation(int x) { std::this_thread::sleep_for(std::chrono::seconds(1)); return x * x; } int main() { // 异步启动一个计算任务,Lambda包装了函数调用和参数 std::future<int> fut = std::async(std::launch::async, [](int arg) { return expensiveComputation(arg); }, 42); // 主线程可以继续做其他事情... std::cout << "Main thread is working...\n"; // 在需要结果时等待并获取 int result = fut.get(); // 这里可能会阻塞,直到异步任务完成 std::cout << "Result: " << result << '\n'; // 输出1764 return 0; }这里有一个关键点:传递给std::async的Lambda及其捕获的参数,会被拷贝或移动到异步任务的上下文中。因此,即使主线程中的局部变量销毁了,异步任务中的副本仍然是安全的。这比直接传递引用给std::thread要安全得多。
4.3 实现简单的回调机制
Lambda非常适合用于实现事件回调或完成回调。
class Downloader { public: using Callback = std::function<void(const std::string& data, bool success)>; void startDownload(const std::string& url, Callback cb) { // 模拟异步下载 std::thread([this, url, cb]() { std::this_thread::sleep_for(std::chrono::milliseconds(500)); bool success = (url.find("error") == std::string::npos); std::string data = success ? "Downloaded data from " + url : ""; // 通常需要通过某种机制(如主线程事件循环)安全地调用回调 cb(data, success); }).detach(); // 分离线程,实际项目中应使用线程池 } }; int main() { Downloader dl; dl.startDownload("http://example.com/file", [](const std::string& data, bool success) { if (success) { std::cout << "Download succeeded: " << data << '\n'; } else { std::cout << "Download failed.\n"; } }); // 主线程等待一下,防止程序过早退出 std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; }在这个例子中,Lambda作为回调函数被传递给startDownload。它捕获了必要的上下文(这里没有),并在下载完成后被调用。使用std::function作为回调类型,可以接受任何可调用对象,包括Lambda、函数指针、函数对象等,非常灵活。
5. 高阶Lambda与函数组合技巧
当Lambda本身作为参数或返回值时,就进入了高阶函数(Higher-Order Function)的领域,这能构建出非常强大和抽象的逻辑。
5.1 Lambda返回Lambda
一个Lambda可以返回另一个Lambda,这可以用来创建配置好的函数工厂。
// 创建一个乘法器工厂函数 auto makeMultiplier = [](int factor) { // 返回一个捕获了factor的Lambda return [factor](int x) { return x * factor; }; }; auto doubleIt = makeMultiplier(2); auto tripleIt = makeMultiplier(3); std::cout << doubleIt(5) << '\n'; // 10 std::cout << tripleIt(5) << '\n'; // 15 // 更复杂的例子:创建谓词生成器 auto isInRange = [](int low, int high) { return [low, high](int value) { return value >= low && value <= high; }; }; auto isAdult = isInRange(18, 120); // 检查是否是成年人年龄 auto isSingleDigit = isInRange(0, 9); // 检查是否是单个数字 std::cout << std::boolalpha; std::cout << isAdult(25) << '\n'; // true std::cout << isSingleDigit(25) << '\n'; // false这种技术常用于创建闭包(Closure),即一个函数与其相关的引用环境组合而成的实体。makeMultiplier返回的Lambda就是一个闭包,它“记住”了创建时的factor值。
5.2 Lambda作为算法参数进行函数组合
你可以编写接受Lambda作为参数的函数,从而实现高度可定制的行为,这是策略模式(Strategy Pattern)的一种轻量级实现。
// 一个通用的处理函数,接受一个数据向量和一个处理策略(Lambda) template<typename T, typename Func> std::vector<T> processVector(const std::vector<T>& input, Func processor) { std::vector<T> output; output.reserve(input.size()); for (const auto& elem : input) { output.push_back(processor(elem)); } return output; } int main() { std::vector<int> numbers = {1, -2, 3, -4, 5}; // 策略1:取绝对值 auto absNumbers = processVector(numbers, [](int x) { return std::abs(x); }); // absNumbers: {1, 2, 3, 4, 5} // 策略2:平方 auto squaredNumbers = processVector(numbers, [](int x) { return x * x; }); // squaredNumbers: {1, 4, 9, 16, 25} // 策略3:转换为字符串 auto stringNumbers = processVector(numbers, [](int x) { return "Num: " + std::to_string(x); }); // stringNumbers: {"Num: 1", "Num: -2", ...} // 甚至可以组合使用 auto complexOp = processVector(numbers, [](int x) { return std::abs(x) * 2 + 1; }); return 0; }processVector函数本身不关心具体的处理逻辑,它只负责遍历和收集结果。具体的变换规则由调用者通过Lambda提供,这使得该函数的复用性极高。
5.3 递归Lambda
Lambda想调用自身(递归)在C++11中有点棘手,因为Lambda在定义时,它的类型尚未完全已知,无法在捕获子句中捕获自己。常见的解决方法是使用std::function进行类型擦除,或者从C++14开始使用泛型Lambda和auto参数配合std::function。
// 方法1:使用std::function(C++11) std::function<int(int)> factorial; factorial = [&factorial](int n) -> int { // 注意这里捕获了factorial自身的引用 if (n <= 1) return 1; return n * factorial(n - 1); }; std::cout << factorial(5) << '\n'; // 120 // 方法2:使用泛型Lambda和Y组合子(更高级,C++14) auto y_combinator = [](auto self) { return [self](int n) { if (n <= 1) return 1; return n * self(self)(n - 1); // 通过self调用自身 }; }; auto factorial2 = y_combinator(y_combinator); std::cout << factorial2(5) << '\n'; // 120 // 方法3:C++14 使用auto递归(最简单) auto factorial3 = [](auto self, int n) -> int { if (n <= 1) return 1; return n * self(self, n - 1); }; std::cout << factorial3(factorial3, 5) << '\n'; // 120对于简单的递归,方法1最直观。但要注意,std::function有一定的性能开销(类型擦除和可能的动态分配)。方法3在语法上更简洁,且没有额外开销,但调用时需要传递自身作为参数。
6. 性能考量、陷阱与最佳实践
Lambda用起来爽,但如果不了解其背后的机制,很容易掉进坑里,或者写出性能不佳的代码。
6.1 Lambda的性能本质
一个Lambda表达式在编译器看来,就是一个匿名类(闭包类型)的实例。捕获的变量成为这个匿名类的成员,Lambda体成为其operator()的函数体。因此,一个简单的Lambda:
int x = 10; auto lambda = [x](int y) { return x + y; };大致等价于编译器生成的如下代码:
class __SomeAnonymousType { int x; // 值捕获的变量成为成员 public: __SomeAnonymousType(int x_) : x(x_) {} int operator()(int y) const { // 默认是const的 return x + y; } }; auto lambda = __SomeAnonymousType(x);这意味着:
- 零成本抽象:Lambda没有运行时开销,所有操作都在编译期确定。
- 内联优化:由于
operator()通常很简单,编译器很容易将其内联,性能与手写循环无异。 - 大小:Lambda对象的大小取决于其捕获的变量。空捕获的Lambda大小通常为1字节(占位),而捕获了大量数据的Lambda可能会很大。
6.2 常见陷阱与避坑指南
陷阱一:悬垂引用(Dangling Reference)这是最危险、最常见的错误。
std::function<void()> createCallback() { int localVar = 42; // 错误!捕获了局部变量localVar的引用。 return [&localVar]() { std::cout << localVar; }; // localVar在函数返回后销毁,回调中的引用无效! } void useCallback() { auto cb = createCallback(); cb(); // 未定义行为!访问已销毁的内存。 }修复:如果Lambda的生命周期可能超过被捕获的变量,务必使用值捕获或使用std::shared_ptr等智能指针来共享所有权。
std::function<void()> createSafeCallback() { int localVar = 42; // 正确:通过值捕获拷贝 return [localVar]() { std::cout << localVar; }; // 或者,如果变量很大或不可拷贝,使用shared_ptr // auto data = std::make_shared<int>(42); // return [data]() { std::cout << *data; }; }陷阱二:默认捕获的过度使用过度使用[=]或[&]可能导致意外的捕获或性能问题。
class BigObject { /* ... 很大的数据 ... */ }; BigObject bigObj; auto lambda = [=]() { /* 做一些事情 */ }; // 使用[=]可能无意中拷贝了整个bigObj(如果bigObj在作用域内),造成巨大的性能开销!最佳实践:始终使用显式捕获列表。明确列出你需要捕获的每一个变量,并仔细选择是值捕获[var]还是引用捕获[&var]。这使代码意图更清晰,也避免了意外的拷贝或引用。
陷阱三:在成员函数中捕获this的隐患
class Widget { std::vector<int> data; std::function<void()> callback; public: void setup() { // 危险!Lambda捕获了this指针 callback = [this]() { process(data); }; } void process(const std::vector<int>&) { /* ... */ } ~Widget() { // 假设Widget销毁后,callback还在别处被调用... // 此时this已失效,访问data是未定义行为! } };修复:如果Lambda的生命周期可能超过当前对象,考虑使用弱引用std::weak_ptr,或者在C++17及以上使用值捕获[*this]来捕获对象的副本。
// C++17 值捕获this callback = [*this]() mutable { process(data); }; // 注意:需要mutable来修改副本成员 // 或者使用智能指针管理Widget的生命周期 auto self = std::shared_from_this(); // Widget需继承std::enable_shared_from_this callback = [self]() { self->process(self->data); };陷阱四:mutable的误用
int x = 0; auto lambda = [x]() mutable { ++x; // 修改的是副本 std::cout << x; }; lambda(); // 输出1 lambda(); // 输出2 std::cout << x; // 输出0,外部x未变记住,mutable允许你修改的是值捕获的副本,而不是原始变量。如果你需要修改外部变量,应该使用引用捕获[&x]。
6.3 最佳实践总结
- 优先使用显式捕获:避免
[=]和[&],明确列出捕获的变量。 - 生命周期管理:分析Lambda和其捕获变量的生命周期。如果Lambda可能比变量活得久,用值捕获或智能指针。
- 多线程安全:在多线程上下文中,对共享数据的访问必须同步。值捕获通常更安全,但捕获指针时仍需小心。
- 保持简洁:Lambda的优势在于局部性和简洁性。如果一个Lambda体超过10行,或者逻辑非常复杂,考虑将其提取成一个命名函数或函数对象。
- 配合
auto:用auto来接收Lambda,避免书写复杂的类型。需要存储或传递时,使用std::function,但要知道其有类型擦除的开销。 - 善用泛型Lambda(C++14+):
[](auto x, auto y) { ... }能让你的Lambda像模板一样工作,提高代码的通用性。 - 理解编译器生成的内容:在调试复杂问题或关心性能时,心里要清楚Lambda被编译成了什么。
7. C++14/17/20中Lambda的增强特性
C++标准在后续版本中不断强化Lambda,使其更强大、更易用。
C++14:泛型Lambda与初始化捕获如前所述,auto参数和初始化捕获是C++14的两大亮点,极大地提升了灵活性和安全性。
C++17:constexprLambda 和*this捕获
constexprLambda:如果Lambda满足constexpr函数的要求(例如,函数体足够简单),它可以被用在常量表达式中。constexpr auto square = [](int n) { return n * n; }; static_assert(square(5) == 25); // 编译期计算 int array[square(3)]; // 数组大小在编译期确定*this捕获:如前所述,允许以值方式捕获当前对象,对于在异步任务中安全地使用对象成员至关重要。
C++20:模板Lambda与可构造可赋值
- 模板Lambda:C++20允许在Lambda的
()前使用模板语法,提供了比C++14的auto参数更强的类型控制。// C++14 泛型Lambda auto generic14 = [](auto x, auto y) { return x + y; }; // C++20 模板Lambda auto generic20 = []<typename T, typename U>(T x, U y) { return x + y; }; // 可以在函数体内使用T和U作为具体的类型名 auto printSize = []<typename T>(const std::vector<T>& vec) { std::cout << "Size: " << vec.size() << '\n'; }; - 无状态Lambda的默认构造和赋值:在C++20之前,即使没有捕获任何变量的Lambda(无状态Lambda),其默认构造函数和赋值运算符也被定义为
delete。C++20开始,无状态Lambda是默认可构造和可赋值的,这提高了其与标准库组件(如默认构造的std::function)的兼容性。// C++20 auto less = [](int a, int b) { return a < b; }; decltype(less) anotherLess; // 正确,可以默认构造 anotherLess = less; // 正确,可以赋值
掌握这些新特性,能让你在合适的场景下写出更现代、更安全、更高效的C++代码。Lambda从C++11的一个便利特性,已经演变为现代C++编程风格的核心组成部分。花时间深入理解它,绝对是一笔高回报的投资。