[C++高频精进] 现代C++特性:Lambda表达式

核心要点速览

  • 语法:[捕获列表](参数) mutable -> 返回类型 { 函数体 },必填:捕获列表 + 函数体
  • 捕获方式:[=](值)、[&](引用)、[=, &x](混合)、[this](类内)、[x=std::move(y)](C++14 移动捕获)
  • 陷阱:悬垂引用 /thismutable误用、多 return 未显式指定返回类型、STL 排序谓词非严格弱序
  • 特性:无捕获转函数指针、有捕获需std::function包装、C++14 泛型、C++17constexpr/[*this]
  • 用途:简化 STL 算法参数(sort/find_if),替代短小的临时函数和仿函数

一、捕获列表

1. 捕获方式

捕获方式 说明
[=] 捕获时机为 Lambda 创建时,仅拷贝实际使用的外部变量,外部变量后续修改不会影响副本
[&] 默认引用捕获所有用到的外部变量,优点是无拷贝开销,但严禁返回带此捕获的 Lambda(极易产生悬垂引用)
[=, &x] 默认值捕获,仅 x 显式按引用捕获,显式捕获必须与默认方式相反,这是语法硬性规则
[this] 类成员函数中默认捕获,可访问类的成员变量和成员函数,Lambda 生命周期绝对不能超过当前对象
[x=std::move(y)] C++14 新增的移动捕获,专门解决std::unique_ptr等不可拷贝对象的捕获问题,转移 y 的所有权到 x

2. 陷阱

1:悬垂引用(返回局部变量引用捕获)
  • 错误:auto bad() { int x; return [&x]() { return x; }; }(函数返回后 x 销毁,Lambda 引用悬垂,调用时是未定义行为)
  • 正确:值捕获 return [x]() { return x; }(拷贝 x 形成副本,副本生命周期与 Lambda 一致,无风险)
2:悬垂this(类内返回[this] Lambda)
  • 错误:class A { int val; auto get() { return [this]() { cout << val; }; } }(若调用对象是临时对象,对象销毁后this悬垂)
  • 正确(C++17+):return [*this]() { ... }(值捕获整个对象副本,彻底规避悬垂风险)

3. 捕获禁忌

  • 非法:[=, x](重复值捕获)、[&, &y](重复引用捕获)、直接捕获函数参数(需通过 Lambda 参数列表传递)
  • 合法:全局变量、静态变量无需捕获,可在 Lambda 内直接访问(存储在静态区,不属于局部作用域)

二、类型与存储

  1. 无捕获 Lambda:可隐式转换为函数指针,这是因为无状态的 Lambda 本质和普通函数一致

    示例:void(*func)() = []() {};

  2. 有捕获 Lambda:因持有外部变量状态,无法转换为函数指针,必须用std::function包装,适用于函数参数、返回值等场景

    示例:std::function<int(int)> f = [x=2](int a) { return a*x; };

  3. 大小规律:空捕获 Lambda 大小为 1(符合 C++ 空类的默认大小规则),值捕获 Lambda 大小等于捕获变量总大小,引用捕获 Lambda 大小等于指针大小(64 位系统下为 8 字节)

三、版本特性

版本 特性 示例
C++14 泛型 Lambda + 移动捕获 [](auto x){}; / [p=std::move(u)](){}
C++17 constexpr Lambda constexpr auto sq = [](int x){ return x*x; };(编译期执行,无运行时开销)
C++17 [*this]值捕获对象 return [*this]() { cout << val; };(解决悬垂 this 问题)

四、STL 算法结合

1. std::sort

  • 易错点:排序谓词必须满足 "严格弱序",必须用a<ba>b,禁用a<=ba>=b,否则可能导致算法崩溃

    示例:sort(vec.begin(), vec.end(), [desc](int a, int b) { return desc ? a>b : a<b; })

2. std::find_if

  • 核心:谓词返回bool类型,可通过捕获外部变量灵活控制筛选条件,是 STL 查找场景的常用写法

    示例:find_if(strs.begin(), strs.end(), [minLen](const auto& s) { return s.size()>=minLen; })

五、易错

1. mutable

  • 作用:仅解除值捕获 变量的const限制(不影响外部变量,只是允许修改 Lambda 内部的副本)

  • 易错:无参数时必须带(),这是编译器的硬性要求,缺()会直接编译失败

    正确:[a]() mutable { a++; },错误:[a] mutable { a++; }

2. 返回类型推导陷阱

  • return语句时,编译器可自动推导返回类型;多个return语句类型不同时,必须显式指定返回类型,否则编译器无法判定统一类型

    正确:[](bool f) -> double { return f ? 1 : 2.0; },错误:省略-> double(int 与 double 类型冲突)

3. C++14 泛型 Lambda

  • 参数支持auto,无需手动定义模板,就能实现通用逻辑,大幅简化代码

    示例:[](auto x, auto y) { return x + y; }(支持 int、double 等任意可相加的类型)

六、问答

1. 有捕获的 Lambda 为什么不能转函数指针?

  • 函数指针仅存储函数代码地址,是无状态的;而有捕获的 Lambda 持有外部变量状态,这些状态需要额外的存储空间,函数指针无法承载,因此不能转换。

2. [=][&]的风险?

  • [=]:对大对象捕获时会产生拷贝开销,影响性能;[&]:最大风险是悬垂引用,尤其是返回带此捕获的 Lambda 时,几乎必然触发未定义行为。
相关推荐
喜欢吃燃面1 小时前
算法竞赛中的堆
c++·学习·算法
松涛和鸣2 小时前
DAY20 Optimizing VS Code for C/C++ Development on Ubuntu
linux·c语言·开发语言·c++·嵌入式硬件·ubuntu
灯厂码农2 小时前
C++文件操作
开发语言·c++
️停云️2 小时前
C++异常与智能指针
开发语言·c++
chenyuhao20243 小时前
MySQL事务
开发语言·数据库·c++·后端·mysql
爪哇部落算法小助手3 小时前
每日两题day59
数据结构·c++·算法
D_evil__3 小时前
[C++高频精进] 现代C++特性:右值引用和移动语义
c++
Mr_WangAndy3 小时前
C++14新特性_第一章C++语言特性_Lambda初始化捕获,decltype(auto)
c++·c++40周年·lambda初始化捕获·decltype auto
不会c嘎嘎4 小时前
【C++】深入理解多态:从用法到原理
开发语言·c++