当前位置: 首页 > ds >正文

C++ Lambda 表达式

Lambda 表达式的完整语法如下:

[capture](parameters) mutable -> return_type { body }

[capture](捕获列表):指定外部变量如何被 Lambda 表达式捕获(按值或按引用)。

(parameters)(参数列表):类似普通函数的参数,定义 Lambda 接受的输入。

mutable(可选):允许在按值捕获时修改捕获的变量(默认按值捕获是只读的)。

-> return_type(返回类型,可选):显式指定返回类型,通常由编译器推导。

{ body }(函数体):Lambda 的实现逻辑。

捕获列表决定了 Lambda 如何访问外部作用域的变量。捕获方式有以下几种:

1.按值捕获 [x]

外部变量被复制到 Lambda 内部,Lambda 持有该变量的副本。

int x = 10;
auto func = [x]() { return x * 2; };  // x 是副本
std::cout << func() << "\n";  // 输出 20
x = 20;
std::cout << func() << "\n";  // 依然输出 20,因为 func 内部的 x 是副本

按值捕获默认只读,使用 mutable 允许修改副本,但不会影响外部变量。

int x = 10;
auto func = [x]() mutable { x += 1; return x; };
std::cout << func() << "\n";  // 输出 11
std::cout << x << "\n";       // 输出 10,外部 x 不变

2.按引用捕获 [&x]

Lambda 直接引用外部变量,修改 Lambda 内部的变量会影响外部变量。如果外部变量被销毁(例如离开作用域),Lambda 引用它会导致未定义行为(悬垂引用)。

示例:

int x = 10;
auto refFunc = [&x]() { return x * 2; };  // x 是引用
x = 20;
std::cout << refFunc() << "\n";  // 输出 40,因为 refFunc 引用了修改后的 x

3.全局捕获

[=]:按值捕获所有外部变量的副本。

[&]:按引用捕获所有外部变量。

混合捕获:可以组合,例如 [=, &x] 表示默认按值捕获,但 x 按引用捕获。

示例:

int x = 10, y = 5;
auto mixed = [=, &x]() { return x + y; };  // y 按值,x 按引用
x = 20;
std::cout << mixed() << "\n";  // 输出 25(x=20, y=5 的副本)

4.捕获 this

在类成员函数中,[this] 捕获当前对象的指针,[*this](C++17 起)捕获当前对象的副本。按引用捕获 [&] 隐式包含 this

示例:

struct Example {int x = 10;void func() {auto lambda = [this]() { return x * 2; };std::cout << lambda() << "\n";  // 输出 20}
};

5.空捕获 []

示例:

auto callback = [](int x) { std::cout << "Callback: " << x << "\n"; };

Lambda 表达式的捕获列表是 [],表示空捕获,即不捕获任何外部变量,既不是按值捕获也不是按引用捕获。[] 表示 Lambda 表达式不从外部作用域捕获任何变量。Lambda 的函数体 { std::cout << "Callback: " << x << "\n"; } 只使用了参数 x(通过函数调用传入)和全局对象 std::coutstd::cout 是全局的,不需要捕获,而 x 是 Lambda 的参数,不是外部作用域的变量。

捕获(按值 [=] 或按引用 [&])只有在 Lambda 访问外部作用域的变量时才起作用。例如,如果 Lambda 使用了外部的 int y,才会涉及捕获方式。

Lambda 表达式的实现原理:

Lambda 表达式实际上是编译器生成的匿名类的实例(称为闭包对象)。例如:

auto func = [x]() { return x * 2; };

编译器会生成类似以下的类:

class Lambda {int x;  // 捕获的变量
public:Lambda(int x_) : x(x_) {}int operator()() const { return x * 2; } // 重载函数调用操作符,operator() 是重载的函数调用操作符,() 表示这个操作符不接受参数(空参数列表),int 是返回值类型,表示调用这个操作符会返回一个整数,const 表示这个成员函数不会修改对象的状态(x 不会被改变)。
};

调用 func() 实际上是调用这个类的 operator()。这解释了 Lambda 为什么可以像函数一样使用。

Lambda 的常见用途:

1.标准库算法:与 <algorithm> 配合,例如 std::sortstd::for_each

std::vector<int> vec = {3, 1, 4, 1, 5};
std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });

2.异步编程:与 std::async 或线程配合。

auto task = []() { std::cout << "Running task\n"; };
std::async(std::launch::async, task);

3.回调函数:传递给需要回调的函数。

auto callback = [](int x) { std::cout << "Callback: " << x << "\n"; };
someFunction(callback);

4.立即执行(IIFE,Immediately Invoked Function Expression):

int result = []() { return 42; }();  // 立即调用,result = 42

按引用捕获时,确保捕获的变量在 Lambda 使用时仍然有效。错误用法:

auto createLambda() {int x = 10;return [&x]() { return x; };  // 悬垂引用,x 在函数返回后销毁
}

按值捕获会复制变量,可能会增加内存开销。对于大对象,考虑按引用捕获或使用 std::move(C++11 起支持移动捕获,C++14 增强)。

C++14 支持泛型 Lambda(auto 参数)和初始化捕获。

auto lambda = [y = 10](auto x) { return x + y; };  // 初始化捕获

在类成员函数中,Lambda 表达式可以通过 [this] 捕获当前对象的指针,或者通过 [&] 隐式捕获 this。然而,这会导致 Lambda 持有指向对象的引用,如果对象在 Lambda 调用时被销毁,会导致未定义行为(悬垂指针)。C++17 引入了 [*this],允许 Lambda 按值捕获当前对象的副本,从而避免悬垂引用问题。适合需要延长对象生命周期的场景,例如异步回调或线程中。

示例:

#include <iostream>
#include <functional>struct Example {int x = 10;void createLambda() {// 使用 [*this] 捕获对象副本auto lambda = [*this]() {std::cout << "Lambda: x = " << x << "\n";// 修改 x 不影响原始对象x = 20;std::cout << "Lambda modified: x = " << x << "\n";};// 调用 Lambdalambda();// 原始对象的 x 未改变std::cout << "Original: x = " << x << "\n";}// 模拟异步回调std::function<void()> createAsyncCallback() {// 返回 Lambda,捕获 [*this]return [*this]() {std::cout << "Async callback: x = " << x << "\n";};}
};int main() {// 测试 [*this] 捕获Example obj;obj.createLambda();// 测试异步场景auto callback = obj.createAsyncCallback();// obj 销毁后,callback 仍然有效,因为它持有 obj 的副本callback();return 0;
}

输出:

Lambda: x = 10
Lambda modified: x = 20
Original: x = 10
Async callback: x = 10

在 C++11 到 C++17 中,Lambda 表达式生成的闭包类型没有默认构造函数,且不能直接赋值(即使 Lambda 是无状态的,即不捕获任何变量)。这限制了 Lambda 在某些场景下的使用,例如存储在需要默认构造或赋值的容器中。C++20 放宽了这一限制,允许无状态 Lambda(不捕获任何变量的 Lambda)具有默认构造函数和赋值操作符。便于在容器(如 std::vector)、可选类型(如 std::optional)或需要默认构造的场景中使用。

特点:

1.无状态 Lambda:指 Lambda 表达式不捕获任何变量([] 为空,且函数体不依赖外部变量)。

2.默认构造:C++20 允许无状态 Lambda 的闭包类型有默认构造函数,可以创建未初始化的闭包对象。

3.赋值:无状态 Lambda 可以相互赋值,因为它们的行为完全由代码定义,不依赖捕获的状态。

示例:

#include <iostream>
#include <vector>
#include <optional>int main() {// 定义一个无状态 Lambda,lambda 不捕获任何变量([]),因此是无状态的,行为完全由函数体 { return 42; } 定义。auto lambda = []() { return 42; };// 默认构造无状态 Lambda(C++20),decltype(lambda) defaultLambda; 创建一个默认构造的闭包对象,行为与 lambda 相同。decltype(lambda) defaultLambda; // 默认构造std::cout << "Default constructed Lambda: " << defaultLambda() << "\n";// 赋值(C++20),assignedLambda = lambda; 将 lambda 的行为复制到 assignedLambda,这是 C++20 新增的功能。decltype(lambda) assignedLambda;assignedLambda = lambda; // 赋值操作std::cout << "Assigned Lambda: " << assignedLambda() << "\n";// 存储在容器中,std::vector 和 std::optional 可以存储无状态 Lambda,因为它们支持默认构造和赋值。std::vector<decltype(lambda)> lambdaVector(3); // 默认构造 3 个 Lambdafor (const auto& l : lambdaVector) {std::cout << "Vector Lambda: " << l() << "\n";}// 使用 std::optionalstd::optional<decltype(lambda)> optionalLambda;optionalLambda = lambda; // 赋值if (optionalLambda) {std::cout << "Optional Lambda: " << optionalLambda.value()() << "\n";}return 0;
}

输出:

Default constructed Lambda: 42
Assigned Lambda: 42
Vector Lambda: 42
Vector Lambda: 42
Vector Lambda: 42
Optional Lambda: 42

如果 Lambda 捕获变量(有状态 Lambda),则无法使用默认构造或赋值,因为它们的行为依赖捕获的变量。

int x = 10;
auto statefulLambda = [x]() { return x; };
decltype(statefulLambda) defaultLambda; // 错误:无默认构造函数
statefulLambda = statefulLambda; // 错误:无赋值操作符
http://www.xdnf.cn/news/1865.html

相关文章:

  • 黑马点评商户查询缓存--缓存更新策略
  • shell练习(2)
  • github 简单访问方法(无魔法)
  • 数据库-数据类型、约束 和 DQL语言
  • QComboBox自适应下拉展开区域宽度但控件本身限制宽度
  • leetcode刷题日记——有效的括号
  • IOMUXC_SetPinMux的0,1参数解释
  • Java集合框架解析
  • 【TS入门笔记1---初识TS】
  • A*迷宫寻路
  • 【频谱分析仪与信号分析仪】异同比较
  • 【力扣刷题|第五天作业】二分查找-寻找旋转排序数组中的最小值 II
  • Redis Bitmaps
  • 网络编程!
  • Android 16强制横竖屏设置
  • SQL进阶知识:七、数据库设计
  • 每日英语:每周背10句
  • PyQt6实例_pyqtgraph散点图显示工具_代码分享
  • AI大模型从0到1记录学习 数据结构和算法 day20
  • 分片算法详解:原理、类型与实现方案
  • 链表-两两交换链表中的结点
  • 接地电阻柜的主要材料有哪些?
  • 树莓派的系统烧录
  • 差分探头关键性能参数解析
  • 【盈达科技】GEO(生成式引擎优化)底层逻辑
  • 2025年3月电子学会青少年机器人技术(五级)等级考试试卷-实际操作-测评
  • el-menu箭头改为右下
  • 运算符重载 (Operator Overloading)
  • 雨晨 27842.1000 Windows 11 金丝雀 企业版 IE Edge 适度 2合1
  • 【多篇博客整理】 如何在linux虚拟环境中安装java + Daikon