C++ Lambda 表达式从入门到进阶

一、为什么需要 Lambda 表达式?

在 C++ 编程的早期阶段,我们处理"行为"的主要方式只有几种:

  1. 普通函数
  2. 函数指针
  3. 仿函数(Functor / 函数对象)

这些方式各有优缺点,但它们都有一个共同的问题:

当某段逻辑只在局部使用时,代码会变得臃肿、不直观、难以维护。

举一个非常典型的例子:排序。

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

bool cmp(int a, int b)
{
    return a > b;
}

int main()
{
    std::vector<int> v = {1, 3, 5, 2, 4};
    std::sort(v.begin(), v.end(), cmp);
}

问题来了:

  • cmp 这个函数只为 sort 服务
  • 它却被"提升"为了一个全局或静态函数
  • 代码的局部性极差

为了解决这类"只用一次的行为 "问题,C++11 正式引入了 ------ Lambda 表达式

一句话总结:

Lambda 表达式 = 可以在代码中"就地定义"的匿名函数


二、Lambda 表达式是什么?

2.1 最直观的理解

Lambda 表达式本质上就是一个匿名函数对象

cpp 复制代码
auto f = []() {
    std::cout << "hello lambda" << std::endl;
};

f();

你可以把它理解成:

  • 没有名字的函数
  • 可以像变量一样传递
  • 可以捕获当前作用域中的变量

2.2 Lambda 的基本语法

C++ 中 Lambda 的完整语法形式如下:

cpp 复制代码
[capture](params) mutable -> return_type {
    function_body;
}

我们逐个拆解:

部分 含义
[] 捕获列表(Capture List)
() 参数列表
mutable 是否允许修改捕获变量
-> return_type 返回值类型(可省略)
{} 函数体

最简形式:

cpp 复制代码
[]{};

三、Lambda 的最基本用法

3.1 无参数、无返回值

cpp 复制代码
auto print = []() {
    std::cout << "Hello Lambda" << std::endl;
};

print();

print()的类型是编译器自动生成的、独一无二的匿名闭包类型(closuretype),没有可直接书写的标准名称,无法用普通类型名显式声明。

3.2 带参数

cpp 复制代码
auto add = [](int a, int b) {
    return a + b;
};

std::cout << add(3, 4) << std::endl; // 7

3.3 自动推导返回值

从 C++11 开始,只要 return 唯一,编译器就可以推导返回类型

cpp 复制代码
auto max = [](int a, int b) {
    return a > b ? a : b;
};

四、Lambda 捕获列表(核心重点)

Lambda 的灵魂在于捕获(capture)

4.1 为什么要捕获?

普通函数:

  • 只能访问参数
  • 不能直接访问外部局部变量

Lambda:

  • 可以"抓住"当前作用域中的变量
cpp 复制代码
int x = 10;

auto f = []() {
    // std::cout << x; // ❌ 编译错误
};

必须通过捕获:

cpp 复制代码
int x = 10;

auto f = [x]() {
    std::cout << x << std::endl;
};

4.2 值捕获(Capture by Value)

cpp 复制代码
int x = 10;

auto f = [x]() {
    std::cout << x << std::endl;
};

x = 20;
f(); // 10

特点:

  • 捕获的是 一份拷贝
  • Lambda 内部看不到外部后续修改
  • 默认是 const 的

4.3 引用捕获(Capture by Reference)

cpp 复制代码
int x = 10;

auto f = [&x]() {
    x = 100;
};

f();
std::cout << x << std::endl; // 100

特点:

  • 捕获的是引用
  • 可以修改外部变量
  • 生命周期必须保证安全

4.4 混合捕获

cpp 复制代码
int a = 1, b = 2;

auto f = [a, &b]() {
    // a 是值捕获,b 是引用捕获
};

4.5 默认捕获方式

cpp 复制代码
[a, b]      // 显式值捕获
[&a, &b]    // 显式引用捕获
[=]         // 所有变量按值捕获
[&]         // 所有变量按引用捕获

⚠️ 强烈建议:

能显式写就别用默认捕获(可读性 + 安全性)


五、mutable 关键字

默认情况下:

值捕获的变量在 Lambda 内部是只读的

cpp 复制代码
int x = 10;

auto f = [x]() {
    // x++; // ❌ 错误
};

使用 mutable表示可以修改捕获变量

cpp 复制代码
int x = 10;

auto f = [x]() mutable {
    x++;
    std::cout << x << std::endl;
};

f();      // 11
std::cout << x << std::endl; // 10

本质:

  • 修改的是 Lambda 内部的拷贝
  • 外部变量不受影响

六、返回值类型与尾置返回

6.1 返回类型推导失败的情况

cpp 复制代码
auto f = [](int x) {
    if (x > 0)
        return x;
    else
        return -x;
};

这种可以推导。

但下面这种不行:

cpp 复制代码
auto f = [](bool flag) {
    if (flag)
        return 1;
    else
        return 1.5; // ❌
};

必须显式指定返回类型:

cpp 复制代码
auto f = [](bool flag) -> double {
    return flag ? 1 : 1.5;
};

七、Lambda 的类型本质

7.1 Lambda 是一个"匿名类"

cpp 复制代码
auto f = [](int a, int b) {
    return a + b;
};

编译器大致生成:

cpp 复制代码
class __lambda_1 {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};

这解释了:

  • 为什么 Lambda 可以像函数一样调用
  • 为什么它可以捕获变量(成员变量)

八、Lambda 与 std::function

8.1 std::function 基本使用

cpp 复制代码
#include <functional>

std::function<int(int, int)> f = [](int a, int b) {
    return a + b;
};

8.2 对比模板 vs std::function

方案 优点 缺点
模板 零开销 接口复杂
std::function 统一接口 有性能损耗

九、Lambda 在 STL 中的经典应用

9.1 sort

cpp 复制代码
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;
});

9.2 find_if

cpp 复制代码
auto it = std::find_if(v.begin(), v.end(), [](int x) {
    return x % 2 == 0;
});

9.3 for_each

cpp 复制代码
std::for_each(v.begin(), v.end(), [](int x) {
    std::cout << x << " ";
});

十、Lambda 与多线程

cpp 复制代码
#include <thread>

int main()
{
    int x = 10;
    std::thread t([&x]() {
        x++;
    });
    t.join();
}

⚠️ 注意:

  • 捕获引用时要注意生命周期
  • 多线程下要考虑数据竞争

十一、C++14 / C++17 / C++20 中的 Lambda 演进

11.1 C++14:泛型 Lambda

cpp 复制代码
auto add = [](auto a, auto b) {
    return a + b;
};

11.2 C++17:constexpr Lambda

cpp 复制代码
constexpr auto square = [](int x) {
    return x * x;
};

11.3 C++20:模板 Lambda

cpp 复制代码
auto f = []<typename T>(T a, T b) {
    return a + b;
};

十二、Lambda 使用中的常见陷阱

  1. 悬空引用捕获
  2. 默认捕获导致的误用
  3. 过度使用 std::function
  4. 捕获 this 的生命周期问题

十三、工程实践建议

  • 短小逻辑:Lambda
  • 复杂逻辑:具名函数 / 类
  • 性能敏感:模板 + Lambda
  • 接口统一:std::function

十四、总结

Lambda 表达式的引入,极大地提升了 C++ 的:

  • 表达力
  • 局部性
  • 抽象能力

它不是"语法糖",而是现代 C++ 的核心工具之一。

写好 Lambda,是迈入现代 C++ 的重要一步。


相关推荐
郝学胜-神的一滴2 小时前
Linux网络编程之Socket函数:构建通信的桥梁
linux·服务器·网络·c++·程序人生
weixin_445402302 小时前
模板元编程应用场景
开发语言·c++·算法
xyq20242 小时前
Julia 日期和时间处理指南
开发语言
s1hiyu2 小时前
嵌入式C++低功耗设计
开发语言·c++·算法
阿钱真强道2 小时前
11 JetLinks MQTT 直连设备功能调用完整流程与 Python 实现
服务器·开发语言·网络·python·物联网·网络协议
fpcc2 小时前
跟我学C++中级篇—线程局部存储的底层分析
c++
£漫步 云端彡2 小时前
Golang学习历程【第十二篇 错误处理(error)】
开发语言·学习·golang
Cinema KI2 小时前
C++11(中):可变参数模板将成为重中之重
开发语言·c++
凯子坚持 c2 小时前
C++基于微服务脚手架的视频点播系统---客户端(2)
开发语言·c++·微服务