目录 标签 归档 RSS
- 目录 -
C++11新特性--Lambda表达式
C++11新特性--Lambda表达式

基本介绍

C++11 引入了 Lambda 表达式(Lambda Expression),它是一种用于定义 匿名函数对象 的语法。Lambda 表达式让我们可以更方便地编写内联函数,尤其是在需要临时函数行为的场景,比如 std::sort()std::for_each()、回调函数等。

一、基本语法

1[capture](parameters) -> return_type {
2    function_body
3};

各部分含义如下:

部分含义
[capture]捕获外部变量的方式(值捕获、引用捕获等)
(params)参数列表(类似普通函数)
-> type返回类型(可省略,编译器会推导)
{ body }函数体

二、基本示例

1#include 
2int main() {
3    auto add = [](int a, int b) {
4        return a + b;
5    };
6    std::cout << add(2, 3) << std::endl;  // 输出 5
7}

三、捕获变量(capture)

捕获列表 [ ] 指定了 Lambda 可以使用的外部变量,有以下方式:

1. 值捕获(by value)

1int x = 10;
2auto f = [x]() {
3    std::cout << x << std::endl;  // 拷贝 x
4};

2. 引用捕获(by reference)

1int x = 10;
2auto f = [&x]() {
3    x += 1;
4};

3. 混合捕获

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

4. 隐式捕获

  • [=]:所有使用的外部变量按 捕获
  • [&]:所有使用的外部变量按 引用 捕获

四、返回类型

如果返回值复杂或不易推导,可以使用 -> return_type 指定:

1auto f = [](int a, int b) -> double {
2    return a / (double)b;
3};

五、可变 Lambda(mutable)

默认情况下,值捕获的变量在 Lambda 内部是 const 的。如果想修改这些值,可以使用 mutable

1int x = 10;
2auto f = [x]() mutable {
3    x += 5;
4    std::cout << x << std::endl;  // 输出 15
5};
6f();
7std::cout << x << std::endl;  // 输出 10,外部不变

六、Lambda 与标准算法结合

1#include 
2#include 
3
4std::vector v = {1, 2, 3, 4, 5};
5std::for_each(v.begin(), v.end(), [](int x) {
6    std::cout << x << " ";
7});

七、Lambda 的类型和 std::function

Lambda 是编译器生成的匿名类,可以赋值给 std::function

1#include 
2
3std::function f = [](int a, int b) {
4    return a + b;
5};

总结:Lambda 的强大之处

  • 内联定义函数逻辑,避免冗余函数定义
  • 方便与 STL 配合
  • 支持 值/引用捕获,灵活性强
  • 实现简单的 回调函数 或事件处理逻辑

如果你有具体场景或者想结合 STL 算法深入了解,我可以给你举几个实用的例子。是否需要?

实际例子

一、结合标准算法的实用示例

示例1:std::sort 自定义排序

 1#include 
 2#include 
 3#include 
 4
 5int main() {
 6    std::vector v = {5, 1, 3, 4, 2};
 7
 8    // 按降序排序
 9    std::sort(v.begin(), v.end(), [](int a, int b) {
10        return a &gt; b;
11    });
12
13    for (int x : v) std::cout << x << " ";  // 输出:5 4 3 2 1
14}

示例2:std::find_if 查找第一个满足条件的元素

 1#include 
 2#include 
 3#include 
 4
 5int main() {
 6    std::vector v = {1, 3, 5, 8, 10};
 7
 8    auto it = std::find_if(v.begin(), v.end(), [](int x) {
 9        return x % 2 == 0;
10    });
11
12    if (it != v.end()) std::cout << "找到偶数:" << *it << std::endl;  // 输出 8
13}

示例3:std::count_if 统计满足条件的个数

 1#include 
 2#include 
 3#include 
 4
 5int main() {
 6    std::vector v = {1, 4, 6, 9, 10};
 7
 8    int count = std::count_if(v.begin(), v.end(), [](int x) {
 9        return x &gt; 5;
10    });
11
12    std::cout << "大于5的元素个数:" << count << std::endl;  // 输出 3
13}

示例4:传递状态的 Lambda(通过捕获变量)

 1#include 
 2#include 
 3#include 
 4
 5int main() {
 6    std::vector v = {1, 2, 3, 4, 5};
 7    int threshold = 3;
 8
 9    std::vector filtered;
10    std::copy_if(v.begin(), v.end(), std::back_inserter(filtered), [threshold](int x) {
11        return x &gt; threshold;
12    });
13
14    for (int x : filtered) std::cout << x << " ";  // 输出 4 5
15}

二、Lambda 表达式的底层本质(编译器实现方式)

Lambda 是一种 匿名函数对象。本质上,编译器会为每个 Lambda 生成一个匿名的类并重载 operator(),例如:

1auto f = [](int x) { return x + 1; };

等价于:

1struct Lambda {
2    int operator()(int x) const {
3        return x + 1;
4    }
5};
6
7Lambda f;

如果 Lambda 捕获了变量,编译器会将这些变量存储为类的成员变量。

三、lambda 与 std::function 的比较

  • auto f = [](int x) { return x + 1; }; —— 这时候 f 的类型是一个编译器生成的类,不能直接写出类型名。
  • 如果你需要把 lambda 存入容器或传递给函数,你通常用 std::function
1#include 
2
3std::function func = [](int x) { return x + 1; };

注意:std::function 是有开销的(类型擦除、堆分配等),如果能使用 auto,通常性能更好。

四、可递归 Lambda

C++11 不支持 lambda 自引用自己的名字(因为没有名字),不过我们可以使用 std::function 来递归:

 1#include 
 2#include 
 3
 4int main() {
 5    std::function fib = [&](int n) -> int {
 6        if (n <= 1) return n;
 7        return fib(n - 1) + fib(n - 2);
 8    };
 9
10    std::cout << fib(10) << std::endl;  // 输出 55
11}

五、总结要点

特性描述
[] 捕获列表捕获外部变量(值/引用)
mutable修改捕获的值变量副本
-> return_type明确返回类型
可与 STL 配合sort、find_if、count_if 等更清晰
类型为匿名类实现 operator() 的闭包
与 std::function 配合支持递归、类型擦除(但略有性能开销)

C++14/C++17 中 Lambda 的扩展

C++14 和 C++17 对 Lambda 表达式进行了非常实用的增强,尤其是 泛型 Lambda更灵活的捕获方式

✅ 一、C++14:Lambda 表达式的增强

1. 泛型 Lambda(Generic Lambda)

C++14 允许你在 Lambda 的参数列表中使用 auto,表示参数类型自动推导 —— 这就使得 Lambda 本身变成模板函数的感觉。

✅ 示例:

1auto add = [](auto a, auto b) {
2    return a + b;
3};
4
5std::cout << add(1, 2) << std::endl;         // 输出 3(int)
6std::cout << add(1.5, 2.5) << std::endl;     // 输出 4.0(double)
7std::cout << add(std::string("a"), "b") << std::endl; // 输出 ab

✅ 背后原理:

泛型 Lambda 编译器实现其实就是自动为每个调用生成一个模板的 operator()

2. 初始化捕获(Init-capture)

C++14 引入了一种 按值初始化局部变量 并捕获的方式,语法是:

1[variable_name = expression]

✅ 示例:

1int x = 10;
2
3auto f = [y = x + 5]() {
4    std::cout << y << std::endl;  // 输出 15
5};
6f();

这种捕获方式非常适合将复杂表达式的结果捕获进 Lambda,而不是捕获变量本身再在 Lambda 中求值

✅ 二、C++17:Lambda 表达式的增强

1. 默认捕获与显式混合捕获

在 C++11 中,不能同时使用 [=]&x;而在 C++17 中,允许组合默认捕获和显式捕获

✅ 示例:

1int a = 1, b = 2;
2
3auto f = [=, &b]() {
4    // a 被值捕获,b 被引用捕获
5    std::cout << a << " " << ++b << std::endl;
6};
7f();  // 输出:1 3(b 修改了)

同样也可以反过来 [&, x] 表示默认引用捕获,但 x 值捕获。

2. 捕获 this 指针的成员变量(C++17)

C++11 只能捕获 this 指针,然后在 lambda 中访问 this->member。 C++17 支持 值捕获成员变量 —— 如果用 [=] 捕获,就相当于按值拷贝 this 所指对象的成员:

 1struct MyClass {
 2    int x = 42;
 3
 4    void print() {
 5        auto f = [=]() {
 6            std::cout << x << std::endl;  // 相当于 this->x
 7        };
 8        f();
 9    }
10};

注意:实际上 [=] 捕获的是 this,只是 C++17 在语义上简化了访问方式。

3. Lambda 可以是 constexpr

C++17 开始允许 Lambda 成为 constexpr,即可以在编译期调用。

✅ 示例:

1constexpr auto square = [](int x) {
2    return x * x;
3};
4
5constexpr int y = square(5);  // 编译期计算 y = 25

✅ 总结

特性C++14 / C++17 状态示例
泛型 Lambda✅ C++14[](auto a, auto b){…}
初始化捕获✅ C++14[val = expr]
混合捕获(默认+显式)✅ C++17[=, &x] 或 [&, x]
简化 this 捕获✅ C++17[=] 可直接访问成员变量
constexpr Lambda✅ C++17constexpr auto f = …;

底层编译器如何生成 Lambda 的类结构

✅ Lambda 的本质

Lambda 表达式在编译器眼中是一个 匿名类(closure class) 的实例,它 重载了 operator()。如果你写了一个 Lambda,编译器会自动帮你生成一个类似下面结构的类。

🔧 示例 1:无捕获 Lambda

1auto f = [](int x) { return x + 1; };

等价于(伪代码):

1struct __Lambda_1 {
2    int operator()(int x) const {
3        return x + 1;
4    }
5};
6
7__Lambda_1 f;

无捕获的 Lambda 可以 隐式转换为函数指针

1int (*fp)(int) = [](int x) { return x + 1; };

🔧 示例 2:有捕获变量的 Lambda

1int a = 10;
2auto f = [a](int x) { return x + a; };

编译器等价于:

 1struct __Lambda_2 {
 2    int a;  // 捕获的变量成为成员
 3
 4    __Lambda_2(int _a) : a(_a) {}
 5
 6    int operator()(int x) const {
 7        return x + a;
 8    }
 9};
10
11__Lambda_2 f(a);

✅ 注意:

  • 捕获的变量作为类成员存储;
  • operator() 默认为 const 成员函数;
  • mutable 会移除这个 const 修饰。

🔧 示例 3:引用捕获

1int a = 10;
2auto f = [&a](int x) { a += x; };

编译器等价于:

 1struct __Lambda_3 {
 2    int& a;
 3
 4    __Lambda_3(int& _a) : a(_a) {}
 5
 6    void operator()(int x) const {
 7        a += x;
 8    }
 9};
10
11__Lambda_3 f(a);

🔧 示例 4:mutable Lambda(值捕获但允许修改副本)

1int a = 10;
2auto f = [a]() mutable { a += 1; std::cout << a; };

编译器生成的伪代码:

 1struct __Lambda_4 {
 2    int a;
 3
 4    __Lambda_4(int _a) : a(_a) {}
 5
 6    void operator()() {
 7        a += 1;
 8        std::cout << a;
 9    }
10};

注意 operator() 不再是 const,因为需要修改成员 a

✅ 实际观察 Lambda 类:使用 clang++ -Xclang -ast-dump 或 MSVC /d1reportAllClassLayout

在实际项目中你可以用 clang 或 MSVC 工具查看 Lambda 对象的实际类结构。比如:

1clang++ -std=c++14 -Xclang -ast-dump -fsyntax-only lambda.cpp

可以看到 Lambda 转换成了具体类结构,编译器自动生成了:

  • 数据成员(捕获变量)
  • operator()
  • 构造函数(初始化捕获成员)
  • operator==(C++20 起支持比较)
  • 可能的 constexprnoexcept 属性

✅ 特殊情况:泛型 Lambda(C++14+)

1auto f = [](auto x) { return x * x; };

等价于一个 类模板的 operator()

1struct __Lambda {
2    template
3    auto operator()(T x) const {
4        return x * x;
5    }
6};

这是 C++14 的一大增强:Lambda 自带模板机制。

✅ 总结

特性底层实现
无捕获 Lambda类中无成员,支持函数指针转换
值捕获捕获变量是类成员
引用捕获成员是引用类型
mutableoperator() 去掉 const
泛型 Lambdaoperator() 是模板函数
Lambda 本质就是带状态的函数对象

手动模拟一个 Lambda 的底层结构

亲手实现一个类,功能和下面的 Lambda 完全等价:

1int a = 10;
2int b = 20;
3auto lambda = [a, &b](int x) {
4    return a + b + x;
5};

🎯 目标:自己实现等价的类结构

Lambda 等价于这样的调用方式:

1int result = lambda(5);  // 返回 a + b + x = 10 + 20 + 5 = 35

🛠 手动实现

我们来一步步写出它对应的类结构:

 1#include 
 2
 3struct MyLambda {
 4    int a;    // 值捕获的 a
 5    int& b;   // 引用捕获的 b
 6
 7    // 构造函数:模拟捕获
 8    MyLambda(int a_, int& b_) : a(a_), b(b_) {}
 9
10    // operator() 模拟 lambda 的调用
11    int operator()(int x) const {
12        return a + b + x;
13    }
14};
15
16int main() {
17    int a = 10;
18    int b = 20;
19
20    MyLambda lambda(a, b);  // 模拟捕获
21    std::cout << lambda(5) << std::endl;  // 输出 35
22}

✅ 分析

部分实现逻辑
int a模拟值捕获
int& b模拟引用捕获
构造函数初始化捕获变量
operator()模拟函数调用语法 lambda(x)
const 修饰表示函数不会修改 a 和 b 的值

🔄 加上 mutable 的变体

Lambda:

1auto lambda = [a]() mutable {
2    a += 1;
3    std::cout << a;
4};

等价类:

 1struct MutableLambda {
 2    int a;
 3
 4    MutableLambda(int a_) : a(a_) {}
 5
 6    void operator()() {
 7        a += 1;
 8        std::cout << a << std::endl;
 9    }
10};

注意:没有 const,因为 mutable 表示我们允许修改副本。

🧠 思考练习(你可以尝试实现):

  1. 模拟捕获 [=][&] 混合的更复杂结构;
  2. 模拟一个泛型 Lambda,比如 [](auto x, auto y) { return x + y; }
  3. 给你实现的类加上 constexprnoexcept 支持;
  4. MyLambda 添加 clone()equals() 等高级能力,像标准函数对象那样可复制可比较。

✅ 总结

手动实现 Lambda 类的过程,你其实在做的是:

  • 模拟 闭包(Closure)结构
  • 理解 值捕获 vs 引用捕获的语义区别
  • 实践 函数对象(functor)模式
  • 学会在模板、函数指针、std::function 等之间切换。

C++ Lambda 面试题集

下面是一个系统化的 C++ Lambda 面试题集(含解析 & 难度分级),分为 4 个阶段:基础语法STL 应用底层原理高级陷阱 & 模拟实现

🧩 第一部分:基础语法与行为(难度:★☆☆)

题 1:值捕获 vs 引用捕获

1int x = 10;
2auto f = [x]() mutable { x += 5; std::cout << x; };
3f();
4std::cout << x;
  • 输出是什么?
  • 为什么 x 没有被修改?

✔️ 答案:输出 15 10[x] 是值捕获,mutable 允许修改的是副本。

题 2:Lambda 返回类型推导

1auto f = [](bool b) {
2    if (b) return 1;
3    else return 3.14;
4};
  • 这段代码是否合法?
  • 如果不合法,怎么修复?

❌ 非法:两个 return 的类型不同。 ✔️ 修复方法一:手动指定返回类型:

1auto f = [](bool b) -> double {
2    if (b) return 1;
3    else return 3.14;
4};

题 3:初始化捕获(C++14)

1int x = 10;
2auto f = [y = x + 5]() { std::cout << y; };
3f();
  • 输出什么?
  • 初始化捕获在 C++11 中合法吗?

✔️ 输出 15;初始化捕获是 C++14 引入的新特性

🧩 第二部分:Lambda 与 STL 应用(难度:★★☆)

题 4:std::sort + Lambda 排序

按字符串长度从短到长排序:

1std::vector vec = {"apple", "kiwi", "banana"};

请写出排序语句。

1std::sort(vec.begin(), vec.end(), [](const std::string& a, const std::string& b) {
2    return a.size() < b.size();
3});

题 5:std::copy_if + 外部变量捕获

1std::vector v = {3, 12, 5, 8, 20};
2int threshold = 10;
3std::vector filtered;
4
5std::copy_if(v.begin(), v.end(), std::back_inserter(filtered), [threshold](int x) {
6    return x &gt; threshold;
7});
  • 捕获 threshold 是否可以用 [=]
  • 如果使用 [&] 有何风险?

✔️ [=] 是值捕获,可安全使用; ⚠️ [&] 如果 threshold 是局部变量,会导致悬空引用风险。

🧩 第三部分:底层原理理解(难度:★★★)

题 6:解释这个 Lambda 是如何被编译器转换的?

1int a = 10, b = 20;
2auto f = [=, &b](int x) { return a + b + x; };

要求写出其等价的结构体类。

 1struct Lambda {
 2    int a;
 3    int& b;
 4
 5    Lambda(int a_, int& b_) : a(a_), b(b_) {}
 6
 7    int operator()(int x) const {
 8        return a + b + x;
 9    }
10};
11
12Lambda f(a, b);

题 7:为什么无捕获 Lambda 能转换为函数指针?

1auto f = [](int x) { return x + 1; };
2int (*fp)(int) = f;
  • 如果改成 [x], 能转换吗?
  • 为什么?

✔️ 原因:无捕获 Lambda 编译为 static 函数,无状态,所以可以退化为函数指针。 ❌ 有捕获 Lambda 是一个 有状态类对象,不能退化。

题 8:泛型 Lambda 的 operator() 是如何工作的?

1auto f = [](auto x) { return x * x; };
  • 编译器为此生成了什么?
  • 这可以理解为什么结构?

✔️ 类似:

1struct Lambda {
2    template
3    auto operator()(T x) const { return x * x; }
4};

🧩 第四部分:陷阱题 & 实现题(难度:★★★★)

题 9:悬空引用捕获

 1std::function f;
 2
 3{
 4    int x = 42;
 5    f = [&x]() {
 6        std::cout << x << std::endl;
 7    };
 8}
 9
10f();  // 安全吗?

❌ 危险!x 生命周期已经结束,引用捕获导致 悬空引用

题 10:手写模拟 Lambda 的类

模拟这个 Lambda:

1int a = 1, b = 2;
2auto f = [a, &b](int x) { return a + b + x; };

模拟实现如下:

 1struct MyLambda {
 2    int a;
 3    int& b;
 4
 5    MyLambda(int a_, int& b_) : a(a_), b(b_) {}
 6
 7    int operator()(int x) const {
 8        return a + b + x;
 9    }
10};

最后修改于 2025-05-14 11:35

- 目录 -