1. 什么是lambda表达式?

lambda表达式:是一种匿名函数,也就是没有名字的函数。

  • 它是一种简洁的函数定义方式,不需要显式命名函数
  • 它可以方便地定义简短的可调用对象,用于各种需要函数对象的场景
  • 可定义在函数内部(普通函数只能定义在全局命名空间中)
  • 语法层面无显式类型,需通过 auto模板参数接收其匿名类型

主要特点:

  1. 匿名性:没有函数名
  2. 简洁性:通常用于简单操作,可以在一行内完成
  3. 临时性:常用于一次性使用或作为参数传递给高阶函数
2. lambda表达式的基本语法形式?

基本语法形式:

代码语言:javascript

AI代码解释

[捕获列表](参数列表) -> 返回类型 
{ 
    	函数体 
}


[capture-list] (parameters) -> return-type 
{ 
    	function-body 
}
  • 捕获列表capture-list):用于指定在 lambda 表达式中可以访问的外部变量,是 lambda 能够访问所在作用域中变量的关键。
    • 必须出现在 lambda 最开头,编译器通过 [] 识别 lambda 开始
    • 它可以为空,表示不捕获任何外部变量,但是[] 不能省略(空捕获列表是语法要求)
  • 参数列表parameters):和普通函数的参数列表类似,用于指定 lambda 函数的参数。
    • 它可以为空
    • 若无需参数传递,可连同 () 一起省略(如:无参 lambda 可写 [] { ... }
  • 返回值类型return-type):指定 lambda 函数的返回值类型。
    • 无返回值时(如:void),可直接省略
    • 返回值类型明确时(如:单一 return 语句),编译器可自动推导,也可省略
  • 函数体function-body):定义 lambda 函数的具体操作和逻辑,也就是函数执行时要做的事情。
    • 除参数外,可直接使用捕获列表中的变量
    • 即使函数体为空(如:[] {}),{}不能省略

通过上面的关于lambda的语法的介绍,现在我们可以明白空lambda的形式了:

代码语言:javascript

AI代码解释

// 空lambda(所有可省略部分均省略)
auto emptyLambda = [] {};  // 合法(但无实际逻辑)
2.1:捕获列表

lambda表达式默认仅能访问自身函数体、参数列表里的变量。若需使用外层作用域(如:包含 lambda 的函数作用域)的变量,需通过 捕获列表 显式声明。


捕获列表决定了 lambda 表达式对外部变量的访问方式,常见的捕获方式有以下几种: 显示捕获:在捕获列表中明确写出变量名,用逗号分隔多个变量,同时通过符号区分捕获方式:

  • 值捕获:直接写变量名(如:[x, y]
  • 引用捕获:变量名前加 &(如:[&z]

隐式捕获:无需逐个写变量名,用符号批量指定捕获规则,编译器会自动识别 lambda 内用到的外层变量并捕获:

  • 按值隐式捕获:捕获列表写 [=] ,lambda 内用到的外层变量全部传值捕获(创建副本)
  • 按引用隐式捕获:捕获列表写 [&] ,lambda 内用到的外层变量全部传引用捕获(使用引用)

混合捕获:同时用隐式捕获符号=&)和显式变量


值捕获:在 lambda 内部对捕获变量的修改不会影响外部的原始变量。

  • 例如[a]表示按值捕获变量 a

代码语言:javascript

AI代码解释

#include <iostream>
using namespace std;

int main()
{
    //1.定义一个局部变量,初始值为10
    int num = 10;

    //2.定义一个lambda表达式,通过值捕获方式获取外部变量num
    auto lambda = [num]()mutable
        {
            //2.1:尝试修改捕获的副本(注意:修改的是副本,不是原始变量)
            num = 20; //注意:此操作不会影响外部的num变量

            //2.2:输出捕获副本的值(此时副本已被修改为20)
            cout << num << endl;  // 输出: 20
        };

    /* 注意事项:默认情况下,按值捕获的变量在lambda表达式内部是不可修改的(const)
    *    
    *      1. 当你使用值捕获 [num] 时,lambda 内部得到的是 num 的一个副本,
    *      2. 但这个副本默认是 const 的,不能修改。
    *      3. 因此当你尝试在 lambda 内部修改 num 时,编译器会报错。
    *      4. 要使按值捕获的变量可以在 lambda 内部修改,需要使用 mutable 关键字
    */

    //3.调用lambda表达式,执行内部逻辑
    lambda();

    //4.输出外部num的值(未被lambda内部修改)
    cout << num << endl;  // 输出: 10
    return 0;
}

在这里插入图片描述

在这里插入图片描述


引用捕获:在 lambda 内部对捕获变量的修改会直接影响外部的原始变量。

  • 例如[&a],表示按引用捕获变量 a

代码语言:javascript

AI代码解释

#include <iostream>

int main()
{
    //1.定义一个局部变量 num
    int num = 10;

    //2.定义一个lambda表达式,使用引用捕获的方式捕获外部变量 num
    auto lambda = [&num]()
        {
            //2.1:由于是引用捕获,这里对 num 的修改会直接反映到外部的 num 变量上
            num = 20;

            //2.2:输出修改后的 num 的值,此时会输出 20
            std::cout << num << std::endl;
        };

    /* 注意事项:
    *      1. [&num] 表示以引用的方式捕获变量 num,
    *      2. 这意味着 lambda 表达式内部对 num 的操作会直接作用于外部定义的这个 num 变量
    *      3. 因为它们共享同一块内存空间
    */

    //3.调用定义好的lambda表达式,执行其内部的代码逻辑
    lambda();

    //4.再次输出 num 的值
    std::cout << num << std::endl; //因为 lambda 表达式中通过引用捕获修改了 num,所以这里输出 20
    return 0; 
}

在这里插入图片描述

在这里插入图片描述


隐式捕获:可以让编译器自动推导捕获列表。 分为:

  • 按值隐式捕获[=]
  • 按引用隐式捕获[&]

代码语言:javascript

AI代码解释

#include <iostream>
int main()
{
    //1.定义两个局部变量
    int num1 = 10;
    int num2 = 20;

    //2.lambda1:按值隐式捕获所有用到的外部变量
    auto lambda1 = [=]()
        {
            //2.1:输出捕获的副本值
            std::cout << num1 << " " << num2 << std::endl;  // 输出: 10 20
        };

    /* 注意事项:
    *   [=]:表示以值捕获方式捕获所有外部变量
    *   lambda内部使用的是外部变量的副本,无法修改原始变量
    */

    //3.lambda2:按引用隐式捕获所有用到的外部变量
    auto lambda2 = [&]()
        {
            //3.1:直接修改外部的num1变量
            num1 = 30;  

            //3.2:输出修改后的外部变量值
            std::cout << num1 << " " << num2 << std::endl;  // 输出: 30 20
        };

    /* 注意事项:
    *   [&]:表示以引用捕获方式捕获所有外部变量
    *   lambda内部使用的是外部变量的引用,可以直接修改原始变量
    */

    //4.调用lambda1:输出捕获时的值副本
    lambda1();  // 输出: 10 20

    //5.调用lambda2:修改外部变量并输出
    lambda2();  // 输出: 30 20

    //6.验证外部变量确实被修改
    std::cout << "外部num1: " << num1 << std::endl;  // 输出: 30

    return 0;
}

在这里插入图片描述

在这里插入图片描述


混合捕获:可以同时使用值捕获和引用捕获。

  • 若开头用 [=](隐式传值),后续显式变量必须传引用
    • [=, &x](其他变量隐式传值,x 显式传引用)
  • 若开头用 [&](隐式传引用),后续显式变量必须传值
    • [&, x](其他变量隐式传引用,x 显式传值)

所以[=, &a] 表示按值捕获除 a 以外的所有用到的外部变量,而 a 按引用捕获。


总结:捕获列表的常见形式

捕获列表写法

含义

示例(外部变量 int a=10; double b=3.14;)

[]

不捕获任何外部变量

lambda 内无法使用 a、b

[a]

传值捕获 a(修改 lambda 内的 a 不影响外部)

lambda 内 a 是 10 的副本

[&b]

传引用捕获 b(修改 lambda 内的 b 会影响外部)

lambda 内 b 是外部 b 的引用

[=]

传值捕获所有用到的外部变量

lambda 内 a 是副本,b 是副本(修改不影响外部)

[&]

传引用捕获所有用到的外部变量

lambda 内 a、b 都是引用(修改会影响外部)

[a, &b]

混合捕获(a 传值,b 传引用)

lambda 内 a 是副本,b 是引用

2.2:参数列表与返回值

参数列表:和普通函数一样,可以有不同类型的参数,也可以没有参数。

  • []() { std::cout << "Hello Lambda!" << std::endl; } 没有参数,只是输出一条信息
  • [](int a, int b) { return a + b; } 接收两个 int 类型的参数并返回它们的和

返回值

如果函数体只有一条返回语句,返回值类型可以省略,编译器会自动推导。

代码语言:javascript

AI代码解释

[](int a, int b)   //编译器会自动推导返回值类型为:int
{ 
    return a + b; 
}

如果函数体有多条语句需要明确返回值类型,就必须显式指定。

代码语言:javascript

AI代码解释

[](int a, int b) -> bool  //注意:必须显式指定返回类型:bool
{ 
    if (a > b) return true; 
    else return false;
}
3. lambda表达式怎么使用?

代码案例1:lambda表达式的使用

代码语言:javascript

AI代码解释

#include <iostream>
using namespace std;

int main()
{
    /*------------------- 完整语法示例(显式声明所有部分)-------------------*/
    auto add1 = [](int a, int b) -> int
        {
            return a + b;
        };

    /* 定义一个完整语法的lambda表达式:
    *               1. []          空捕获列表(不捕获任何外部变量)
    *               2. (int a, int b) 参数列表,接收两个int类型参数
    *               3. -> int      显式指定返回值类型为int
    *               4. { return a + b; } 函数体,实现两数相加
    */

    cout << "add1(3,4) = " << add1(3, 4) << endl;

    /*------------------- 省略返回值类型(编译器自动推导)-------------------*/
    auto add2 = [](int a, int b)
        {
            return a + b;  // 返回值类型被自动推导为int
        };

    /* 省略返回值类型,编译器根据return语句自动推导返回类型为int
    *  其他部分与add1完全相同
    */

    cout << "add2(5,6) = " << add2(5, 6) << endl;


    /*------------------- 省略参数列表(无参场景)-------------------*/ 
    auto greet = []
        {
            std::cout << "Hello Lambda" << endl;
        };

    /* 省略参数列表的写法:当lambda不需要参数时,()可以连同参数一起省略
    *  但[]和{}不能省略
    */
    greet();


    /*------------------- 省略捕获列表和参数列表(最简形式)-------------------*/
    auto emptyLambda = [] {};

    /* 最简lambda形式:空捕获列表+空参数列表
    *  可以理解为一个无输入、无输出的函数
    */
    emptyLambda();  //调用空lambda(无任何效果,但语法合法)
    return 0;
}

在这里插入图片描述

在这里插入图片描述

代码案例2:lambda表达式的使用

代码语言:javascript

AI代码解释

#include <iostream>
using namespace std;

//定义全局变量 x
int x = 0;

int main()
{
    // ====================== 1. “全局变量”与“空捕获列表” ======================
    auto func1 = []() //注意:全局变量无需捕获即可使用,lambda 捕获列表必须为空
        {
            x++;  
        };


    // ====================== 2. 显式捕获(传值 + 传引用) ======================

    //1.定义局部变量 a,b,c,d
    int a = 0, b = 1, c = 2, d = 3;

    //2.显式捕获:a 传值(const 只读),b 传引用(可修改)
    auto func2 = [a, &b]()
        {
            // a++;  // 错误:传值捕获的变量默认 const,无法修改(除非加 mutable)
            b++;     // 正确:传引用捕获,可直接修改外部变量 b

            int ret = a + b;  // a=0(原值), b=2(已自增)
            return ret;      
        };
    cout << func2() << endl;  


    // ====================== 3. 隐式传值捕获([=]) ======================
    //3.隐式传值捕获所有用到的变量(a, b, c)
    auto func3 = [=]()
        {
            // a++;  // 错误:传值捕获默认 const,无法修改
            int ret = a + b + c;  // a=0, b=2, c=2
            return ret;      
        };
    cout << func3() << endl; 


    // ====================== 4. 隐式传引用捕获([&]) ======================
    //4.隐式传引用捕获所有用到的变量(a, c, d)
    auto func4 = [&]()
        {
            a++;  // 正确:传引用,修改外部 a → a=1
            c++;  // 正确:传引用,修改外部 c → c=3
            d++;  // 正确:传引用,修改外部 d → d=4
        };
    func4();
    cout << a << " " << b << " " << c << " " << d << endl;


    // ====================== 5. 混合捕获(隐式传引用 + 显式传值) ======================
    //5. 隐式传引用(&) + 显式传值(a, b)
    //规则:隐式符号(&)在前,显式变量必须传值
    auto func5 = [&, a, b]()
        {
            // a++;  // 错误:a 是显式传值,默认 const
            // b++;  // 错误:b 是显式传值,默认 const
            c++;  // 正确:隐式传引用,修改外部 c → c=4
            d++;  // 正确:隐式传引用,修改外部 d → d=5

            return a + b + c + d;  // 0 + 1 + 4 + 5 = 10
        };
    func5();

    cout << a << " " << b << " " << c << " " << d << endl;


    // ====================== 6. 混合捕获(隐式传值 + 显式传引用) ======================
    //6.隐式传值(=) + 显式传引用(&a, &b)
    //规则:隐式符号(=)在前,显式变量必须传引用
    auto func6 = [=, &a, &b]()
        {
            a++;  // 正确:显式传引用,修改外部 a → a=2
            b++;  // 正确:显式传引用,修改外部 b → b=3
            // c++;  // 错误:c 是隐式传值,默认 const
            // d++;  // 错误:d 是隐式传值,默认 const

            return a + b + c + d;  // 2 + 3 + 4 + 5 = 14
        };
    func6();

    cout << a << " " << b << " " << c << " " << d << endl;


    // ====================== 7. “静态变量”与“全局变量”的访问 ======================
    //静态局部变量:无需捕获即可使用
    static int m = 0;

    auto func7 = []()
        {
            int ret = x + m;  // 全局变量 x=0,静态变量 m=0
            return ret;       // 返回 0 + 0 = 0
        };
    cout << func7() << endl;


    // ====================== 8. mutable 修饰符(突破传值捕获的 const 限制) ======================
    //隐式传值捕获([=]) + mutable:允许修改传值副本(不影响外部变量)
    auto func8 = [=]() mutable
        {
            a++;  // 修改传值副本 a → 副本 a=3(外部 a 仍为 2)
            b++;  // 修改传值副本 b → 副本 b=4(外部 b 仍为 3)
            c++;  // 修改传值副本 c → 副本 c=5(外部 c 仍为 4)
            d++;  // 修改传值副本 d → 副本 d=6(外部 d 仍为 5)

            return a + b + c + d;  // 3 + 4 + 5 + 6 = 18
        };
    //输出内部变量的和
    cout << func8() << endl;  // 输出: 18

    //输出外部变量
    cout << a << " " << b << " " << c << " " << d << endl; //a=2, b=3, c=4, d=5(未被修改)

    return 0;
}

在这里插入图片描述

在这里插入图片描述

4. 特殊的捕获限制有哪些?

lambda表达式定义的位置: 若lambda 若定义在函数局部作用域(如:main 函数内 ):

  • 仅能捕获 lambda 定义位置之前 的局部变量
  • 静态局部变量(static int var; )和 全局变量 无需捕获,可直接在 lambda 内使用(因为:它们的作用域不受函数栈限制 )

若 lambda 定义在全局作用域(函数外 ):

  • 捕获列表必须为空(无外层局部变量可捕获 )

mutable修饰符:突破传值捕获的只读限制:

  • 默认情况下,传值捕获的变量在 lambda 内是 const 只读的
  • 若需修改传值捕获的副本,可在参数列表后加 mutable,此时参数列表不能省略(即使无参数,也要写 ()
5. lambda表达式的价值是什么?

lambda表达式通过 简洁语法 实现了匿名函数对象,核心价值在于:

  1. 支持函数内部定义,灵活捕获局部变量
  2. 语法各部分可按需省略,适配不同场景(如:无参、无返回值等)
  3. 配合 auto 使用,无需显式定义函数类型,让代码更简洁

掌握 lambda 的语法结构和捕获规则,是高效使用 C++ 现代特性(如:算法库、异步编程)的基础。

6. lambda表达式的应用有哪些?

在学习 lambda 表达式之前,我们所使用的可调用对象只有 函数指针仿函数对象

  • 函数指针的类型定义较为繁琐
  • 仿函数需要定义一个类,操作相对麻烦

而使用 lambda 来定义可调用对象,简单又便捷。

更多推荐