头文件:无需额外头文件,C++11 及以上标准原生支持
Lambda 是 C++11 引入的匿名函数,也是现代 C++ 函数式编程的核心语法,写法简洁、使用灵活,常搭配 STL 算法、线程、回调场景


一、前置认知:什么是函数式编程

1. 基本概念

传统 C++ 以面向对象、命令式编程为主,侧重「怎么做(步骤流程)」;
函数式编程是一种编程思想,核心是把函数当作一等公民

  • 函数可以作为参数传递、作为返回值返回
  • 支持匿名函数、表达式式调用,弱化状态、强调逻辑运算

C++ 并非纯函数式语言,但 C++11 借助 Lambda、std::function、std::bind 等特性,完美支持函数式风格写法,Lambda 就是最常用的载体。

2. Lambda 的定位

Lambda = 匿名临时函数,不用写独立函数名、不用单独定义函数,随用随写,完美适配回调、算法遍历、简单逻辑片段。


二、Lambda 完整语法格式

标准语法框架

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

拆解五大组成部分(按顺序,部分可省略):

  1. [] 捕获列表:捕获当前作用域的外部变量(核心)
  2. () 参数列表:和普通函数参数用法一致,无参数可省略
  3. mutable 关键字:可选,允许修改值捕获的变量
  4. -> 类型 尾置返回值:指定函数返回类型,可自动推导时省略
  5. {} 函数体:具体执行逻辑

三、分步讲解 + 基础示例

3.1 最简 Lambda(无参数、无返回值、不捕获变量)

写法

无参数、无返回值,()-> 均可省略:

#include <iostream>
using namespace std;

int main()
{
    // 定义并直接调用 Lambda
    [](){
        cout << "Hello Lambda" << endl;
    }();

    return 0;
}
  • 末尾 ():表示立即执行这个匿名函数
  • 等价于一个临时的无参无返回函数
把 Lambda 赋值给变量(重复调用)

Lambda 可赋值给变量,像普通函数一样多次调用:

int main()
{
    auto func = [](){
        cout << "重复执行 Lambda" << endl;
    };

    func(); // 第一次调用
    func(); // 第二次调用
    return 0;
}

3.2 带参数的 Lambda

用法和普通函数参数完全一致,() 不能省略:

int main()
{
    // 接收两个 int 参数
    auto add = [](int a, int b){
        cout << a + b << endl;
    };

    add(10, 20);  // 输出 30
    add(50, 60);  // 输出 110
    return 0;
}

3.3 带返回值的 Lambda

分两种场景:显式指定返回值自动推导返回值

3.3.1显式尾置返回值(-> 类型)

语法固定写在参数列表之后,适合多分支返回、类型复杂场景:

int main()
{
    // 明确指定返回 int
    auto add = [](int a, int b) -> int {
        return a + b;
    };

    int res = add(3, 4);
    cout << res << endl; // 输出 7
    return 0;
}
3.3.2自动推导返回值(省略 -> 类型)

满足以下条件可省略返回值声明,编译器自动推导:

  • 函数体只有一条 return 语句
  • 所有 return 返回同一种类型
// 省略返回值,编译器自动推导为 int
auto sub = [](int a, int b){
    return a - b;
};
cout << sub(10, 3); // 输出 7

注意:函数体内有多条 return 且返回类型不同,必须显式写尾置返回值


四、核心重点:捕获列表 []

Lambda 无法直接使用函数体外部的局部变量,必须通过捕获列表把外部变量“抓”进来使用。
捕获列表有 6 种常用规则,逐个讲解。

约定

假设外部变量:

int a = 10;
int b = 20;

4.1 1. 值捕获 [变量名]

  • 作用:拷贝一份外部变量到 Lambda 内部,内部是副本
  • 特点:默认只读,无法修改原变量,也无法修改副本
int main()
{
    int a = 10;
    // 值捕获 a
    auto func = [a](){
        cout << a << endl; 
        // a = 100;  // 编译报错:值捕获默认只读
    };

    func();    // 输出 10
    cout << a; // 外部 a 不变,仍为 10
    return 0;
}

4.2 2. 引用捕获 [&变量名]

  • 作用:捕获变量引用,内部直接操作原变量
  • 特点:读写都生效,修改会影响外部原始变量
int main()
{
    int a = 10;
    // 引用捕获 a
    auto func = [&a](){
        a = 100; // 修改原变量
    };

    func();
    cout << a; // 输出 100,外部变量被修改
    return 0;
}

4.3 3. 全部值捕获 [=]

  • 作用:当前作用域所有局部变量,统一使用值捕获
  • 简写:不用逐个写变量名
int main()
{
    int a = 10;
    int b = 20;
    // [=] 全部值捕获
    auto func = [=](){
        cout << a << " " << b << endl; // 正常使用 a、b
    };
    func();
    return 0;
}

4.4 4. 全部引用捕获 [&]

  • 作用:当前作用域所有局部变量,统一使用引用捕获
int main()
{
    int a = 10;
    int b = 20;
    auto func = [&](){
        a += 5;
        b += 5;
    };
    func();
    cout << a << " " << b; // 输出 15 25
    return 0;
}

4.5 5. 混合捕获(常用组合)

同时使用「全部捕获 + 单独指定捕获方式」,优先级:单独捕获 > 全部捕获

示例1:整体值捕获,单独变量改为引用捕获

int a = 10, b = 20;
// [=, &b]:默认所有变量值捕获,唯独 b 用引用捕获
auto func = [=, &b](){
    // a 只读,b 可修改
    b = 99;
};

示例2:整体引用捕获,单独变量改为值捕获

int a = 10, b = 20;
// [&, a]:默认所有变量引用捕获,唯独 a 用值捕获
auto func = [&, a](){
    // b 可修改,a 只读副本
};

4.6 6. this 捕获 [this](类内专用)

如果 Lambda 写在类的成员函数中,想要访问类的成员变量/成员函数,必须捕获 this 指针:

class Test
{
public:
    int num = 100;
    void run()
    {
        // 捕获 this,访问类成员
        auto func = [this](){
            cout << num << endl; // 访问成员变量
        };
        func();
    }
};

int main()
{
    Test t;
    t.run(); // 输出 100
    return 0;
}

五、mutable 关键字(值捕获专属)

前面提到:值捕获的变量默认只读,如果想在 Lambda 内部修改副本,需要加 mutable

语法规则

(参数列表) mutable { ... }

  • 只改变「副本」,不会影响外部原始变量
  • mutable 必须写在参数列表之后
int main()
{
    int a = 10;
    // 值捕获 + mutable 允许修改副本
    auto func = [a]() mutable {
        a = 100; // 合法,修改内部副本
        cout << a << endl;
    };

    func();     // 输出 100
    cout << a;  // 外部 a 仍然是 10,不受影响
    return 0;
}

总结:mutable 仅用于值捕获,引用捕获不需要。


六、Lambda 与函数式编程实战场景

Lambda 是 C++ 函数式编程的核心,下面是开发中最常用的场景。

6.1 搭配 STL 算法(遍历、排序、筛选)

std::sortstd::for_each 等算法都接收函数作为参数,Lambda 是首选。

示例1:数组/容器排序
#include <iostream>
#include <vector>
#include <algorithm> // sort 算法
using namespace std;

int main()
{
    vector<int> vec = {3, 1, 4, 2, 5};

    // 降序排序:Lambda 作为比较函数
    sort(vec.begin(), vec.end(), [](int x, int y){
        return x > y;
    });

    // 遍历打印
    for (auto v : vec)
    {
        cout << v << " ";
    }
    // 输出:5 4 3 2 1
    return 0;
}
示例2:遍历容器 for_each
vector<int> vec = {1,2,3};
// Lambda 作为遍历回调
for_each(vec.begin(), vec.end(), [](int v){
    cout << v * 2 << " ";
});

6.2 搭配 std::thread 线程(回调函数)

之前讲解线程库时用到过,Lambda 作为线程入口函数,捕获变量极其方便:

#include <iostream>
#include <thread>
using namespace std;

int main()
{
    int num = 100;
    // 引用捕获变量,创建线程
    thread t([&num](){
        cout << "子线程:" << num << endl;
    });

    t.join();
    return 0;
}

6.3 存储 Lambda:std::function(函数对象)

Lambda 本质是临时函数,想要保存起来、多次传递,需要搭配 <functional> 头文件的 std::function,这是函数式编程「函数作为变量」的典型用法。

#include <iostream>
#include <functional>
using namespace std;

int main()
{
    // 声明:接收两个int、返回int的函数类型
    function<int(int, int)> calc;

    // 赋值为 Lambda
    calc = [](int a, int b){
        return a + b;
    };

    cout << calc(2, 3); // 输出 5
    return 0;
}

七、常见误区 & 新手避坑

  1. 引用捕获悬空风险
    如果 Lambda 被延迟执行(线程、异步回调),不要引用局部变量。局部变量销毁后,引用变成野指针,程序崩溃。

  2. 捕获列表不能混用语法
    不允许 [=, =][&, &] 这种重复全部捕获。

  3. mutable 不修改外部变量
    它只修改值捕获的副本,外部变量始终不变。

  4. 全局变量无需捕获
    Lambda 可以直接使用全局变量,不用写进捕获列表。

  5. Lambda 没有函数名
    无法递归调用自身(C++11 原生不支持递归 Lambda,高阶用法除外)。


八、知识点总结

1. Lambda 完整结构回顾

[捕获](参数) mutable -> 返回值 { 函数体 }
  • [] 捕获外部变量(值/引用/全部/this)
  • () 函数参数,无参可省略
  • mutable 允许修改值捕获的副本(可选)
  • -> 尾置返回值,单return可自动推导(可选)

2. 捕获规则速查表

捕获写法 含义 可修改外部变量
[a] 单独值捕获a
[&a] 单独引用捕获a
[=] 所有变量值捕获
[&] 所有变量引用捕获
[this] 捕获类this指针(类内使用) -

3. 函数式编程核心体现

  1. 函数可赋值给变量、可作为参数传递
  2. 匿名函数简化回调逻辑,代码更紧凑
  3. 结合 STL 算法、线程、异步回调,是现代 C++ 主流写法

4. 对比与普通函数和仿函数的核心优势

对比传统普通函数、仿函数:

  • 无需单独定义函数,代码内聚性强
  • 灵活捕获外部变量,使用场景更广
  • 写法简洁,STL 算法、回调场景首选

更多推荐