C++ lambda 匿名函数深入解析

发布时间:2026/10/6 3:18:18
C++ lambda 匿名函数深入解析
1、基本介绍C11 引入的lambda 匿名函数Lambda Expression是一种轻量级的函数对象可在需要函数的地方直接定义无需单独声明极大简化了代码编写尤其是回调函数、算法谓词等场景。基本语法1[capture-list] (parameter-list)mutablenoexcept(optional) -return-type { function-body }组成部分说明capture-list捕获列表指定如何捕获 lambda 所在作用域的局部变量值捕获、引用捕获等不可省略。parameter-list参数列表与普通函数的参数列表一致可省略若无形参。mutable可选关键字允许在 lambda 内部修改值捕获的变量默认值捕获变量为 const。noexcept可选指定 lambda 是否可能抛出异常C11 起。- return-type返回类型可选若函数体仅有一条 return 语句编译器可自动推导返回类型。function-body函数体lambda 的执行逻辑。简单的例子12345678910111213141516#include iostream#include vector#include algorithmintmain() {std::vectorint v {3, 1, 4, 1, 5, 9, 2, 6};// 使用 Lambda 表达式作为 std::sort 的比较准则// 按降序排序std::sort(v.begin(), v.end(),[](inta,intb) {returna b; }// Lambda 表达式);for(inti : v) {std::cout i ;}// 输出: 9 6 5 4 3 2 1 1return0;}2、捕获列表捕获列表定义了 Lambda 表达式如何从其所在的作用域中访问外部变量。2.1 值捕获将外部变量的值拷贝到 Lambda 中。Lambda 内部修改不会影响外部变量。12345678910111213intmain() {intx 10;inty 20;// 值捕获将 x 和 y 的当前值拷贝到 Lambda 中auto lambda_val [x, y]() {std::cout Inside lambda (by value): x , y std::endl;// x; // 错误默认情况下值捕获的变量是 const 的。};x y 100;// 修改外部变量lambda_val();// 调用 Lambda// 输出: Inside lambda (by value): 10, 20return0;}2.2 引用捕获捕获外部变量的引用。Lambda 内部修改会直接影响外部变量。123456789101112131415intmain() {intx 10;inty 20;// 引用捕获捕获 x 和 y 的引用auto lambda_ref [x, y]() {std::cout Inside lambda (by ref): x , y std::endl;x; y;// 修改会影响外部变量};lambda_ref();// 调用 Lambdastd::cout After lambda: x , y std::endl;// 输出:// Inside lambda (by ref): 10, 20// After lambda: 11, 21return0;}2.3 隐式捕获让编译器根据 Lambda 体内的代码自动推断需要捕获哪些变量。[]以值捕获的方式捕获所有使用到的外部变量。[]以引用捕获的方式捕获所有使用到的外部变量。123456inta 1, b 2, c 3;// 隐式值捕获自动捕获所有使用到的外部变量 (a, b)auto lambda1 []() { std::cout a b std::endl; };// 注意c 没有被使用所以不会被捕获// 隐式引用捕获自动捕获所有使用到的外部变量 (a, c)auto lambda2 []() { std::cout a c std::endl; c 100; };注意应谨慎使用隐式捕获尤其是[]因为它可能让你无意中修改外部变量或引入悬空引用。2.4 混合捕获123456inta 1, b 2, c 3, d 4;// a 显式值捕获b 显式引用捕获其他使用到的变量按值捕获但这里没有其他变量了auto lambda1 [, b]() {/* a by value, b by ref */};// a 显式引用捕获b 显式值捕获其他使用到的变量按引用捕获但这里没有其他变量了auto lambda2 [, b]() {/* a by ref, b by value */};// 错误不能混合相同的捕获模式 [, a] 或 [, b]2.5 捕获 this 指针在类的成员函数中Lambda 可以通过值[this]或引用[]捕获this指针从而访问类的成员变量和函数。1234567891011121314classMyClass {public:voiddoSomething() {// 捕获 this从而可以访问成员变量 valueauto lambda [this]() {std::cout Value: value std::endl;memberFunction();};lambda();}private:intvalue 42;voidmemberFunction() { std::cout Member func called\n; }};2.6 初始化捕获C14允许在捕获时初始化变量类似变量声明解决 “移动捕获” 等场景123456789101112#include vector#include utility // for std::moveintmain() {vectorint v {1, 2, 3};// 初始化捕获将v移动到lambda内部的vec避免拷贝大容器auto func [vec move(v)] {cout vec size: vec.size() endl;};func();// 输出vec size: 3// cout v.size() endl; // 错误v已被移动处于无效状态return0;}3、mutable 关键字默认情况下对于值捕获的变量Lambda 的operator()是const的这意味着你不能在 Lambda 体内修改这些拷贝。使用mutable关键字可以移除这个const限制。123456789101112131415intmain() {intcount 0;// 没有 mutable: 错误不能修改值捕获的变量。// auto lambda [count]() { count; };// 使用 mutableauto lambda [count]()mutable{count;// 现在可以修改了std::cout Count inside lambda: count std::endl;};lambda();// 输出: Count inside lambda: 1lambda();// 输出: Count inside lambda: 2std::cout Count outside: count std::endl;// 输出: Count outside: 0// 注意修改的是 Lambda 内部的副本不影响外部变量。return0;}重要mutable允许你修改的是 Lambda内部副本的值对外部变量毫无影响。引用捕获不需要mutable。4、返回类型编译器通常可以自动推导 Lambda 的返回类型。但如果函数体中有多个返回语句且类型不同或者你想要更明确的代码可以显式指定。12345678910std::vectorint numbers {1, 2, 3, 4, 5};// 编译器自动推导返回类型为 boolauto is_even [](intn) {returnn % 2 0; };// 显式指定返回类型为 double (使用尾置返回类型语法)auto divide [](inta,intb) -double{if(b 0) {return0.0;// 返回 double}returnstatic_castdouble(a) / b;// 返回 double};5、常见用法与示例5.1 与 STL 算法结合12345678910111213141516171819std::vectorint vec {5, 3, 8, 1, 9};// 计算大于 5 的元素数量intcount std::count_if(vec.begin(), vec.end(),[](intn) {returnn 5; });// 将所有元素翻倍std::for_each(vec.begin(), vec.end(),[](int n) { n * 2; });// 注意需要引用才能修改原值vectorint nums {3, 1, 4, 1, 5, 9};// 用lambda作为sort的比较函数降序排序sort(nums.begin(), nums.end(), [](inta,intb) {returna b;});// nums变为9,5,4,3,1,1// 用lambda作为find_if的条件查找偶数auto it find_if(nums.begin(), nums.end(), [](intx) {returnx % 2 0;});if(it ! nums.end()) {cout 找到偶数 *it endl;// 输出4}5.2 并发编程中的任务线程或异步任务std::thread、std::async需要执行函数lambda 可直接定义任务逻辑123456789101112131415#include thread#include futureintmain() {// 线程任务用lambda定义threadt([] {cout 线程执行中... endl;});t.join();// 异步任务用lambda定义futureint fut async([] {return1 2;});cout 异步结果 fut.get() endl;// 输出3return0;}5.3 自定义比较器12345std::mapstd::string,int name_age;// 按值年龄排序而不是键名字std::vectorstd::pairstd::string,int vec(name_age.begin(), name_age.end());std::sort(vec.begin(), vec.end(),[](constauto a,constauto b) {returna.second b.second; });6、常见问题1. Lambda 表达式中的捕获列表[]和[]有什么区别[]表示隐式值捕获Lambda 体内使用的所有外部变量都会将其当前值拷贝一份到 Lambda 对象中。[]表示隐式引用捕获Lambda 体内使用的所有外部变量都会以其引用被捕获在 Lambda 内部修改它们会影响外部变量。应谨慎使用[]以免造成意外的副作用或悬空引用。2. 什么是“初始化捕获”Init Capture它解决什么问题初始化捕获C14允许在捕获列表中直接初始化一个新的成员变量。它主要解决了移动捕获的问题。例如你不能用普通捕获移动一个std::unique_ptr因为无法拷贝但可以用[p std::move(unique_ptr)]将其所有权移动到 Lambda 内部。它也允许你以任意表达式初始化捕获的变量。3.mutable关键字在 Lambda 中起什么作用默认情况下对于值捕获的变量Lambda 的函数调用运算符 (operator()) 是const的这意味着你不能修改这些捕获的副本。mutable关键字移除了这个const限制允许你修改 Lambda 内部的值捕获变量。需要注意的是这修改的只是副本不影响外部原始变量。4. Lambda 表达式的类型是什么如何存储或传递一个 Lambda每个 Lambda 表达式都会生成一个唯一的、编译器生成的、未命名的类型闭包类型。存储和传递它的最佳方式是使用auto进行初始化auto lambda [...](){...};。使用std::function如std::functionvoid()这会带来一些类型擦除的开销但非常灵活。在模板中使用templatetypename F void foo(F func)这是零开销的方式。5. 在类的成员函数中Lambda 如何访问类的成员变量需要通过捕获this指针。使用[this]或[]隐式捕获可以捕获当前对象的this指针从而在 Lambda 内部访问类的成员变量和成员函数。需要注意的是如果 Lambda 的生命周期可能比对象更长例如被放入一个全局队列这会导致悬空this指针。C17 的[\*this]可以按值捕获整个对象的副本避免这个问题。