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

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; // 错误:无赋值操作符

相关文章:

  • 24FIC 决赛 计算机部分
  • SAP SuccessFactors Recruiting and Onboarding The Comprehensive Guide
  • [250423] Caddy 2.10 正式发布:引入 ECH、后量子加密等重要更新
  • 基于javaweb的SpringBoot校园服务平台系统设计与实现(源码+文档+部署讲解)
  • 差分探头关键性能参数解析
  • 【Python语言基础】24、并发编程
  • 单片机 + 图像处理芯片 + TFT彩屏 触摸滑动条控件
  • github 简单访问方法(无魔法)
  • YOLOv8 涨点新方案:SlideLoss FocalLoss 优化,小目标检测效果炸裂!
  • LeetCode算法题(Go语言实现)_60
  • 【python】一文掌握 markitdown 库的操作(用于将文件和办公文档转换为Markdown的Python工具)
  • 第1讲:Transformers 的崛起:从RNN到Self-Attention
  • 【AI提示词】艺人顾问
  • 实验三 进程间通信实验
  • Flink介绍——实时计算核心论文之Flink论文
  • 入门-C编程基础部分:19、输入 输出
  • nuxt3持久化存储全局变量
  • 深入浅出:Pinctrl与GPIO子系统详解
  • 模板偏特化 (Partial Specialization)
  • 开源漏洞扫描器:OpenVAS
  • 健康社区“免疫行动”促进计划启动,发布成人预防“保典”
  • 美银证券前董事总经理胡霁光履新,任摩根士丹利中国区副主席
  • 期待会师!神二十与空间站完成对接
  • 体坛联播|AC米兰挺进意大利杯决赛,弗雷戴特宣布退役
  • 建行原副行长章更生被开除党籍:靠贷吃贷,大搞权钱交易
  • 上海市闵行区原二级巡视员琚汉铮接受纪律审查和监察调查