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

 更新时间:2025年09月19日 08:58:59   作者:xclic  
C++11引入的lambda匿名函数(Lambda Expression)是一种轻量级的函数对象,可在需要函数的地方直接定义,无需单独声明,极大简化了代码编写,本文给大家介绍C++ lambda 匿名函数的相关知识,感兴趣的朋友一起看看吧

1、基本介绍

C++11 引入的 lambda 匿名函数(Lambda Expression)是一种轻量级的函数对象,可在需要函数的地方直接定义,无需单独声明,极大简化了代码编写(尤其是回调函数、算法谓词等场景)。

基本语法:

[capture-list] (parameter-list) mutable noexcept(optional) -> return-type { function-body }
组成部分说明
capture-list捕获列表:指定如何捕获 lambda 所在作用域的局部变量(值捕获、引用捕获等),不可省略。
parameter-list参数列表:与普通函数的参数列表一致(可省略,若无形参)。
mutable可选关键字:允许在 lambda 内部修改值捕获的变量(默认值捕获变量为 const)。
noexcept可选:指定 lambda 是否可能抛出异常(C++11 起)。
-> return-type返回类型可选,若函数体仅有一条 return 语句,编译器可自动推导返回类型。
function-body函数体:lambda 的执行逻辑。

简单的例子

#include <iostream>
#include <vector>
#include <algorithm>
int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
    // 使用 Lambda 表达式作为 std::sort 的比较准则
    // 按降序排序
    std::sort(v.begin(), v.end(), 
              [](int a, int b) { return a > b; } // Lambda 表达式
             );
    for (int i : v) {
        std::cout << i << " ";
    }
    // 输出: 9 6 5 4 3 2 1 1
    return 0;
}

2、捕获列表

捕获列表定义了 Lambda 表达式如何从其所在的作用域中访问外部变量。

2.1 值捕获

将外部变量的值拷贝到 Lambda 中。Lambda 内部修改不会影响外部变量。

int main() {
    int x = 10;
    int y = 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, 20
    return 0;
}

2.2 引用捕获

捕获外部变量的引用。Lambda 内部修改会直接影响外部变量。

int main() {
    int x = 10;
    int y = 20;
    // 引用捕获:捕获 x 和 y 的引用
    auto lambda_ref = [&x, &y]() { 
        std::cout << "Inside lambda (by ref): " << x << ", " << y << std::endl;
        x++; y++; // 修改会影响外部变量
    };
    lambda_ref(); // 调用 Lambda
    std::cout << "After lambda: " << x << ", " << y << std::endl;
    // 输出: 
    // Inside lambda (by ref): 10, 20
    // After lambda: 11, 21
    return 0;
}

2.3 隐式捕获

让编译器根据 Lambda 体内的代码自动推断需要捕获哪些变量。

  • [=]:以值捕获的方式捕获所有使用到的外部变量。
  • [&]:以引用捕获的方式捕获所有使用到的外部变量。
int a = 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 混合捕获

int a = 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 指针,从而访问类的成员变量和函数。

class MyClass {
public:
    void doSomething() {
        // 捕获 this,从而可以访问成员变量 value
        auto lambda = [this]() { 
            std::cout << "Value: " << value << std::endl; 
            memberFunction(); 
        };
        lambda();
    }
private:
    int value = 42;
    void memberFunction() { std::cout << "Member func called\n"; }
};

2.6 初始化捕获(C++14)

允许在捕获时初始化变量(类似变量声明),解决 “移动捕获” 等场景

#include <vector>
#include <utility>  // for std::move
int main() {
    vector<int> 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已被移动,处于无效状态
    return 0;
}

3、mutable 关键字

默认情况下,对于值捕获的变量,Lambda 的 operator() 是 const 的,这意味着你不能在 Lambda 体内修改这些拷贝。
使用 mutable 关键字可以移除这个 const 限制。

int main() {
    int count = 0;
    // 没有 mutable: 错误!不能修改值捕获的变量。
    // auto lambda = [count]() { count++; }; 
    // 使用 mutable
    auto lambda = [count]() mutable { 
        count++; // 现在可以修改了
        std::cout << "Count inside lambda: " << count << std::endl;
    };
    lambda(); // 输出: Count inside lambda: 1
    lambda(); // 输出: Count inside lambda: 2
    std::cout << "Count outside: " << count << std::endl; // 输出: Count outside: 0
    // 注意:修改的是 Lambda 内部的副本,不影响外部变量。
    return 0;
}

重要mutable 允许你修改的是 Lambda 内部副本的值,对外部变量毫无影响。引用捕获不需要 mutable

4、返回类型

编译器通常可以自动推导 Lambda 的返回类型。但如果函数体中有多个返回语句且类型不同,或者你想要更明确的代码,可以显式指定。

std::vector<int> numbers = {1, 2, 3, 4, 5};
// 编译器自动推导返回类型为 bool
auto is_even = [](int n) { return n % 2 == 0; };
// 显式指定返回类型为 double (使用尾置返回类型语法)
auto divide = [](int a, int b) -> double {
    if (b == 0) {
        return 0.0; // 返回 double
    }
    return static_cast<double>(a) / b; // 返回 double
};

5、常见用法与示例

5.1 与 STL 算法结合

std::vector<int> vec = {5, 3, 8, 1, 9};
// 计算大于 5 的元素数量
int count = std::count_if(vec.begin(), vec.end(), 
                         [](int n) { return n > 5; });
// 将所有元素翻倍
std::for_each(vec.begin(), vec.end(), 
             [](int& n) { n *= 2; }); // 注意:需要引用才能修改原值
vector<int> nums = {3, 1, 4, 1, 5, 9};
// 用lambda作为sort的比较函数(降序排序)
sort(nums.begin(), nums.end(), [](int a, int b) { 
    return a > b; 
});  // nums变为:9,5,4,3,1,1
// 用lambda作为find_if的条件(查找偶数)
auto it = find_if(nums.begin(), nums.end(), [](int x) { 
    return x % 2 == 0; 
});
if (it != nums.end()) {
    cout << "找到偶数:" << *it << endl;  // 输出:4
}

5.2 并发编程中的任务

线程或异步任务(std::threadstd::async)需要执行函数,lambda 可直接定义任务逻辑

#include <thread>
#include <future>
int main() {
    // 线程任务用lambda定义
    thread t([] { 
        cout << "线程执行中..." << endl; 
    });
    t.join();
    // 异步任务用lambda定义
    future<int> fut = async([] { 
        return 1 + 2; 
    });
    cout << "异步结果:" << fut.get() << endl;  // 输出:3
    return 0;
}

5.3 自定义比较器

std::map<std::string, int> name_age;
// 按值(年龄)排序,而不是键(名字)
std::vector<std::pair<std::string, int>> vec(name_age.begin(), name_age.end());
std::sort(vec.begin(), vec.end(),
         [](const auto& a, const auto& b) { return a.second < b.second; });

6、常见问题

1. Lambda 表达式中的捕获列表 [=] 和 [&] 有什么区别?[=] 表示隐式值捕获,Lambda 体内使用的所有外部变量都会将其当前值拷贝一份到 Lambda 对象中。[&] 表示隐式引用捕获,Lambda 体内使用的所有外部变量都会以其引用被捕获,在 Lambda 内部修改它们会影响外部变量。应谨慎使用 [&],以免造成意外的副作用或悬空引用。

2. 什么是“初始化捕获”(Init Capture)?它解决什么问题?初始化捕获(C++14)允许在捕获列表中直接初始化一个新的成员变量。它主要解决了移动捕获的问题。例如,你不能用普通捕获移动一个 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::function<void()>),这会带来一些类型擦除的开销,但非常灵活。
  • 在模板中使用(template<typename F> void foo(F func)),这是零开销的方式。

5. 在类的成员函数中,Lambda 如何访问类的成员变量?需要通过捕获 this 指针。使用 [this] 或 [&](隐式捕获)可以捕获当前对象的 this 指针,从而在 Lambda 内部访问类的成员变量和成员函数。需要注意的是,如果 Lambda 的生命周期可能比对象更长(例如,被放入一个全局队列),这会导致悬空 this 指针。C++17 的 [\*this] 可以按值捕获整个对象的副本,避免这个问题。

到此这篇关于C++ lambda 匿名函数深入解析的文章就介绍到这了,更多相关C++ lambda 匿名函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文了解什么是CDN及实现原理

    一文了解什么是CDN及实现原理

    这篇文章主要为大家介绍了什么是CDN及实现原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • TortoiseSVN忽略(Global ignore)提交文件设定方式

    TortoiseSVN忽略(Global ignore)提交文件设定方式

    文章介绍两种SVN忽略杂碎文件的方法:本地配置忽略规则(修改config文件中的global-ignores参数)和服务器端设置(通过svn:ignore属性),方法一适合个人,方法二需管理员权限,均需注意空格格式避免报错
    2025-08-08
  • Memcached简介_动力节点Java学院整理

    Memcached简介_动力节点Java学院整理

    这篇文章主要介绍了Memcached简介,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • 在mac上安装虚拟机搭载Windows服务的方法

    在mac上安装虚拟机搭载Windows服务的方法

    这篇文章主要介绍了在mac上安装虚拟机搭载Windows服务的方法,本文通过图文并茂的形式给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-12-12
  • 网站数据自动备份方法

    网站数据自动备份方法

    本文是根据作者自己多年的维护经验,来和大家分享下网站数据自动备份的一些经验。
    2010-04-04
  • curl.exe安装使用的最全参数详解以及常用命令汇总

    curl.exe安装使用的最全参数详解以及常用命令汇总

    Curl是一个功能强大的命令行工具,可以看做是命令行浏览器,用于与服务器进行数据交互,支持多种数据传输协议,如HTTP、HTTPS、FTP等,它支持文件的上传和下载,它是一款开源软件,在多个操作系统上均可运行,包括Windows、Linux、macOS等
    2024-04-04
  • Memcache缓存系统知识点梳理

    Memcache缓存系统知识点梳理

    Memcached是一个免费开源的,高性能的,具有分布式对象的缓存系统,它可以用来保存一些经常存取的对象或数据,保存的数据像一张巨大的HASH表,该表以Key-value对的方式存在内存中
    2012-09-09
  • 华为服务器RAID阵列卡配置教程 SR430 LSISAS3108(EFI/UEFI模式)

    华为服务器RAID阵列卡配置教程 SR430 LSISAS3108(EFI/UEFI模式)

    最近采购了华为服务器的服务器第一次做阵列,没想到使用了以后发现与dell的阵列卡配置差不多,这里就为大家分享一下SR430 LSISAS3108EFI/UEFI模式下阵列卡的配置方法
    2025-02-02
  • 手把手教你搭建腾讯云服务器入门(图文教程)

    手把手教你搭建腾讯云服务器入门(图文教程)

    这篇文章主要介绍了手把手教你搭建腾讯云服务器入门,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • windows和Linux使用命令行计算文件的MD5值

    windows和Linux使用命令行计算文件的MD5值

    在Windows和Linux系统中,您可以使用命令行(终端或命令提示符)来计算文件的MD5值,文章介绍了在Windows和Linux/macOS系统上计算文件MD5值的方法
    2025-05-05

最新评论