C++11 lambda表达式使用讲解

文章目录

  • [C++11 lambda表达式使用讲解](#C++11 lambda表达式使用讲解)
    • [一、lambda 表达式的基本语法](#一、lambda 表达式的基本语法)
    • [二、lambda 表达式的使用示例](#二、lambda 表达式的使用示例)
      • [2.1 基础用法(无捕获、无参数)](#2.1 基础用法(无捕获、无参数))
      • [2.2 捕获外部变量](#2.2 捕获外部变量)
        • [2.2.1 捕获列表](#2.2.1 捕获列表)
        • [2.2.2 捕获的三种主要方式](#2.2.2 捕获的三种主要方式)
        • [2.2.3 特殊变量的捕获规则](#2.2.3 特殊变量的捕获规则)
        • [2.2.4 `mutable` 修饰符与捕获的可修改性](#2.2.4 mutable 修饰符与捕获的可修改性)
      • [2.3 带参数和返回值](#2.3 带参数和返回值)
      • [2.4 作为参数传递(回调函数)](#2.4 作为参数传递(回调函数))
      • [2.5 作为智能指针的删除器](#2.5 作为智能指针的删除器)
    • [三、lambda 表达式的核心特性总结](#三、lambda 表达式的核心特性总结)

C++11 lambda表达式使用讲解

在 C++11 中,lambda 表达式是一种匿名函数(没有名称的函数),可以在需要函数的地方直接定义和使用,主要用于简化代码,尤其是在需要传递简短函数作为参数的场景(如算法回调、智能指针删除器等)。

一、lambda 表达式的基本语法

lambda 表达式的完整语法格式如下:

cpp 复制代码
[capture-list](parameters) mutable noexcept -> return-type {
    // 函数体
}

lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。 lambda 表达式语法使用层而言没有类型,所以我们一般是用auto 或者模板参数定义的对象去接收 lambda 对象。

各部分含义

  1. [capture-list](捕获列表)

    用于捕获 lambda 外部的变量,使其能在函数体内使用。这是 lambda 与普通函数的核心区别(普通函数无法直接访问外部局部变量)。

    • 常见用法:
      • []:不捕获任何外部变量。
      • [=]:按值捕获所有外部变量(拷贝一份,函数体内不能修改)。
      • [&]:按引用捕获所有外部变量(可修改,需注意变量生命周期)。
      • [x, &y]:按值捕获 x,按引用捕获 y
  2. (parameters)(参数列表)

    与普通函数的参数列表一致,用于接收传入的参数。如果没有参数,可省略括号(但建议保留以明确意图)。

  3. mutable(可选)

    默认情况下,按值捕获的变量在 lambda 内是只读的。加上 mutable 后,允许修改按值捕获的变量(但修改不会影响外部原变量)。

  4. noexcept(可选)

    声明 lambda 不会抛出异常,用于优化和明确接口。

  5. -> return-type(返回值类型)

    返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此 部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

  6. { ... }(函数体)

    包含具体的执行逻辑,与普通函数体相同。

二、lambda 表达式的使用示例

2.1 基础用法(无捕获、无参数)

cpp 复制代码
#include <iostream>

int main() {
    // 定义一个简单的lambda表达式(打印信息)
    auto printHello = []() {
        std::cout << "Hello, lambda!" << std::endl;
    };

    // 调用lambda表达式(像调用函数一样)
    printHello();  // 输出:Hello, lambda!
    return 0;
}
  • auto 用于自动推导 lambda 的类型(lambda 类型是编译器生成的匿名类型,无法显式写出)。
  • 调用方式与普通函数相同:printHello()

2.2 捕获外部变量

2.2.1 捕获列表

lambda 表达式的捕获列表是 lambda 能够访问外部作用域变量的关键机制,lambda 表达式默认只能直接使用自身函数体和参数中的变量。如果要使用外层作用域(比如定义 lambda 的函数中的局部变量)的变量,就必须通过"捕获列表"明确声明要捕获哪些外部变量。

2.2.2 捕获的三种主要方式
(1)显式捕获(按值/按引用)
  • 格式:在捕获列表中显式列出变量名 ,用逗号分隔。

    • 按值捕获:直接写变量名(如 [x, y]),会拷贝一份外部变量的值到 lambda 内部,lambda 内修改该拷贝不影响外部原变量。
    • **按引用捕获:**变量名前加 &(如 [&z]),lambda 内部通过引用访问外部变量,修改会直接影响外部原变量。
  • 示例:

    cpp 复制代码
    int a = 0, b = 1;
    // 显式按值捕获a,按引用捕获b
    auto func = [a, &b]() {
        // a++;  // 错误:按值捕获的变量默认是const,不能修改
        b++;    // 正确:按引用捕获,可修改外部b
        return a + b;
    };
(2)隐式捕获

无需显式列出变量名,编译器会自动捕获 lambda 内部使用的外部变量。

  • **隐式按值捕获:**捕获列表写 [=],lambda 内使用的所有外部变量都按值捕获(拷贝)。

  • **隐式按引用捕获:**捕获列表写 [&],lambda 内使用的所有外部变量都按引用捕获。

  • 示例(对应图中代码片段思路):

    cpp 复制代码
    int a = 0, b = 1, c = 2;
    // 隐式按值捕获所有用到的外部变量(a、b、c)
    auto func = [=]() {
        return a + b + c;  // 使用a、b、c的拷贝值
    };
(3)混合捕获(显式 + 隐式)

同时使用显式和隐式捕获,需遵循规则:

  • 若第一个元素是 &(如 [&, x, y]):表示除显式列出的变量外 ,其他用到的外部变量隐式按引用 捕获;显式列出的 xy 需按值捕获。

  • 若第一个元素是 =(如 [=, &x, &y]):表示除显式列出的变量外 ,其他用到的外部变量隐式按值 捕获;显式列出的 xy 需按引用捕获。

  • 示例(对应图中代码片段思路):

    cpp 复制代码
    int a = 0, b = 1, c = 2, d = 3;
    // 混合捕获:隐式按引用捕获其他变量,显式按值捕获a、b
    auto func = [&, a, b]() {
        // a++;  // 错误:a是按值捕获的拷贝,且默认const
        // b++;  // 错误:同理
        c++;    // 正确:c是隐式按引用捕获
        d++;    // 正确:d是隐式按引用捕获
        return a + b + c + d;
    };
2.2.3 特殊变量的捕获规则
  • 全局变量、静态局部变量:无需捕获,lambda 可直接使用(因为它们的作用域是全局或整个函数,生命周期足够长)。如果 lambda 定义在全局作用域,捕获列表必须为空(没有局部变量可捕获)。
  • 局部变量 :只能捕获 lambda 定义位置之前的局部变量,之后定义的局部变量无法捕获。
2.2.4 mutable 修饰符与捕获的可修改性

默认情况下,按值捕获 的变量在 lambda 内部是 const 修饰的(即只读)。如果需要修改按值捕获的变量,需在参数列表后加 mutable

  • 作用:移除按值捕获变量的 const 属性,允许在 lambda 内部修改。

  • 注意:修改的是"拷贝的副本",不会影响外部原变量。且加 mutable 后,即使参数列表为空,也不能省略参数列表的括号 (如 []() mutable { ... })。

  • 示例(对应图中代码片段思路):

    cpp 复制代码
    int a = 0, b = 1, c = 2, d = 3;
    auto func = [=]() mutable {
        a++;  // 允许修改(修改的是拷贝)
        b++;
        c++;
        d++;
        return a + b + c + d;
    };
    func();
    // 外部a、b、c、d的值不变,因为修改的是lambda内的拷贝
    cout << a << " " << b << " " << c << " " << d << endl;

2.3 带参数和返回值

cpp 复制代码
#include <iostream>

int main() {
    // 带参数的lambda(计算两数之和)
    auto add = [](int x, int y) -> int {
        return x + y;
    };

    // 编译器可自动推导返回类型,简化写法
    auto multiply = [](int x, int y) {
        return x * y;  // 自动推导返回类型为int
    };

    std::cout << add(2, 3) << std::endl;      // 输出:5
    std::cout << multiply(2, 3) << std::endl; // 输出:6
    return 0;
}

2.4 作为参数传递(回调函数)

lambda 最常用的场景是作为算法或函数的参数(如标准库算法 std::for_each):

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

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

    // 用lambda作为回调,打印所有元素
    std::for_each(nums.begin(), nums.end(), [](int x) {
        std::cout << x << " ";
    });  // 输出:1 2 3 4 5

    return 0;
}

2.5 作为智能指针的删除器

在之前的智能指针代码中,lambda 被用作删除器,简化自定义释放逻辑:

cpp 复制代码
// 用lambda作为删除器,释放数组
auto delArrLambda = [](Date* ptr) {
    std::cout << "lambda: 释放数组" << std::endl;
    delete[] ptr;
};
std::unique_ptr<Date, decltype(delArrLambda)> up4(new Date[3], delArrLambda);

三、lambda 表达式的核心特性总结

  1. 匿名性 :lambda 没有名称,通常通过 auto 变量接收后使用,或直接作为参数传递。
  2. 捕获机制 :通过 [capture-list] 灵活捕获外部变量,解决了普通函数无法直接访问局部变量的问题。
  3. 简洁性:对于简短的函数逻辑,无需单独定义函数,直接在使用处编写,减少代码跳转。
  4. 类型唯一性 :每个 lambda 表达式的类型都是编译器生成的唯一匿名类型,无法显式声明,只能用 autostd::function 存储。
与普通函数的区别
特性 lambda 表达式 普通函数
名称 匿名(无名称) 有明确名称
外部变量访问 通过捕获列表灵活访问 只能访问全局变量或静态变量
类型 编译器生成的匿名类型 函数类型(可通过函数指针引用)
用途 短期使用(如回调、局部逻辑) 长期复用、跨范围调用
复制代码
        | 有明确名称                     |

| 外部变量访问 | 通过捕获列表灵活访问 | 只能访问全局变量或静态变量 |

| 类型 | 编译器生成的匿名类型 | 函数类型(可通过函数指针引用) |

| 用途 | 短期使用(如回调、局部逻辑) | 长期复用、跨范围调用 |