C++面向对象程序设计——23.Lambda表达式

一、 什么是 Lambda 表达式?为什么要用它?

你可以把 Lambda 表达式理解成一个**“临时的、匿名的、随手写的”小函数**。

  • 匿名:它没有名字,不像普通函数需要 void myFunction() 这样命名。
  • 临时/内联:它通常直接在需要它的地方定义,比如作为另一个函数的参数,代码更紧凑。

核心优点

  1. 代码简洁:尤其是在调用STL算法(如排序 sort、查找 find_if)时,可以直接将操作逻辑写在算法旁边,而不是另外定义一个完整的函数,代码可读性更高。
  2. 逻辑集中:将小段的功能代码直接内联,让代码逻辑更加集中,易于维护。
  3. 状态捕获:它可以方便地“捕获”上下文中的变量,在函数内部使用,这是普通函数难以做到的。

二、 Lambda 的核心语法(从简到繁)

Lambda 的完整语法看起来有点复杂,但我们把它拆开来看,其实非常符合直觉。

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

我们从最简单的形式开始,一步步加上这些组件。

1. 最基础的形式:[]{}

这是最简单的 Lambda,一个什么都不做,也不接收参数的匿名函数。

// 定义并立即调用一个简单的 Lambda
[] { 
    std::cout << "Hello, Lambda!" << std::endl; 
}(); // 末尾的 () 表示立即执行它

2. 带有参数:[] (参数列表) {}

和普通函数一样,它可以接收参数。

// 定义一个名为 'add' 的 Lambda,它接收两个整数并返回它们的和
auto add = [](int x, int y) {
    return x + y;
};

// 调用它
int result = add(5, 3); // result 的值是 8
std::cout << "5 + 3 = " << result << std::endl;
  • auto add = ...; 这里 auto 关键字会自动推导出 Lambda 的复杂类型,是定义 Lambda 的常用方法。

3. 指定返回值类型:[] () -> 返回值类型 {}

大多数情况下,编译器可以根据 return 语句自动推断出返回类型,所以 -> 返回值类型 这部分通常可以省略

只有在少数复杂情况下(例如函数体中有多个返回不同类型的语句),才需要明确指定。

// 编译器可以自动推断返回值为 double,所以 -> double 可以省略
auto divide = [](double a, double b) {
    return a / b;
};

// 必须显式指定的例子 (虽然不常见)
auto complex_logic = [](int x) -> double {
    if (x > 10) {
        return x; // 返回 int
    }
    return 20.5; // 返回 double
    // 编译器不知道该推断成 int 还是 double,需要你来指定
};

4. 捕获外部变量:[捕获列表] () {} (这是考试的绝对重点)

这是 Lambda 最强大、也最重要的功能。Lambda 默认无法访问其定义范围之外的变量,必须通过捕获列表 [] 来指定它想访问哪些变量,以及如何访问。

捕获方式分为两种:值捕获和引用捕获。

捕获方式语法解释特点与风险
不捕获[]不捕获任何外部变量。安全,但功能受限。
值捕获[var]var 复制一份到 Lambda 内部。安全:Lambda 内修改 var 不影响外部。默认只读:不能修改这份副本 (除非用mutable)。
引用捕获[&var]var引用(可以理解为别名)传给 Lambda。高效/危险:可直接修改外部 var。节省大对象的拷贝开销。风险:注意悬垂引用!如果外部 var 销毁了,Lambda 还在,调用它就会出大问题。
隐式值捕获[=]值捕获方式捕获所有在 Lambda 中用到的外部变量。写法简单省事。但可读性稍差,不易看出捕获了哪些变量。同样默认只读。
隐式引用捕获[&]引用捕获方式捕获所有在 Lambda 中用到的外部变量。写法最省事。但风险最高,容易不经意间修改外部变量,且同样有悬垂引用风险。
混合捕获[=, &var]var 按引用捕获,其它用到的变量按值捕获。灵活,按需控制。
[&, var]var 按值捕获,其它用到的变量按引用捕获。灵活,按需控制。
this 指针[this]在类的成员函数中,捕获当前对象的 this 指针。可以在 Lambda 中调用类的成员函数和访问成员变量。

【机试代码示例】

int main() {
    int a = 10;
    int b = 20;
    int c = 30;

    // 1. 值捕获: 复制 a, b 到 Lambda 内部
    // 默认是 const 的,你不能在内部修改 a 或 b
    // auto func1 = [=] { a = 100; }; // 这行会报错!
    auto func1 = [=] { return a + b + c; }; // c 是参数,a,b是捕获的
    std::cout << "func1: " << func1(50) << std::endl; // 输出 10 + 20 + 50 = 80

    // 2. 引用捕获: 可以修改外部变量
    auto func2 = [&](int x) {
        a = 100; // a 被修改
        b += x;  // b 被修改
    };
    func2(5);
    std::cout << "a=" << a << ", b=" << b << std::endl; // 输出 a=100, b=25

    // 3. 混合捕获: a 按值,b 按引用
    int a = 10;
    int b = 20;
    auto func3 = [a, &b](int x) {
        // a++; // 错误! a 是值捕获,是只读的
        b += a + x; // 正确, b 是引用,可以修改;a 是值,可以读取
    };
    func3(5);
    std::cout << "a=" << a << ", b=" << b << std::endl; // 输出 a=10, b=35 (b = 20 + 10 + 5)
    
    return 0;
}

5. mutable 关键字

mutable 的作用是允许你修改按值捕获的变量。记住,这个修改只影响 Lambda 内部的副本,不会影响外部的原始变量。

int x = 10;

// 使用 mutable 来修改值捕获的变量 x 的副本
auto func = [x]() mutable {
    x = 99; // 修改的是 Lambda 内部的 x 副本
    std::cout << "Inside Lambda, x = " << x << std::endl;
};

func();
std::cout << "Outside Lambda, x = " << x << std::endl;

// --- 输出 ---
// Inside Lambda, x = 99
// Outside Lambda, x = 10 

三、 Lambda 的实际应用(机试高频考点)

Lambda 最强大的地方在于和 STL 算法的无缝结合。

1. 配合 std::for_each 遍历容器

std::vector<int> v = {1, 2, 3, 4, 5};
int sum = 0;

// 使用 Lambda 遍历向量并计算总和
// [&sum] 捕获外部 sum 的引用,以便在 Lambda 内部修改它
std::for_each(v.begin(), v.end(), [&sum](int n) {
    sum += n;
});

std::cout << "Sum: " << sum << std::endl; // 输出 Sum: 15

2. 配合 std::sort 自定义排序

struct Student {
    std::string name;
    int score;
};

std::vector<Student> students = {{"Li Ming", 92}, {"Zhang San", 85}, {"Wang Wu", 95}};

// 使用 Lambda 定义排序规则:按分数从高到低排序
std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
    return a.score > b.score;
});

// 打印排序结果
std::cout << "Sorted by score (desc):" << std::endl;
for (const auto& s : students) {
    std::cout << s.name << ": " << s.score << std::endl;
}

3. 配合 std::find_if 自定义查找

std::vector<int> numbers = {15, 22, 38, 41, 53};

// 查找第一个大于 30 的数字
auto it = std::find_if(numbers.begin(), numbers.end(), [](int n) {
    return n > 30;
});

if (it != numbers.end()) {
    std::cout << "First number greater than 30 is: " << *it << std::endl; // 输出 38
}

四、 备考要点与记忆技巧

  1. 分清“捕获”与“传参”

    • [x]捕获,在 Lambda 定义时就从外部把 x "拿"进来了。
    • (int x)参数,在 Lambda 调用时才从外部把值传进来。
  2. 机试第一要点:捕获列表 []

    • = 是赋值,联想为复制一份,所以是值捕获
    • & 是C++里的引用符号,联想为引用
    • 最关键的区别:引用捕获 [&] 可以修改外部变量,值捕获 [=] 默认不行
  3. 机试第二要点:与 STL 结合

    • 务必熟练 sort, find_if, for_each, count_if 等常用算法如何配合 Lambda 使用。这是最常见的考法。
  4. 注意悬垂引用 (Dangling Reference)

    • 这是一个笔试和面试中可能问到的高级陷阱。如果一个 Lambda 引用捕获了局部变量,而这个 Lambda 的生命周期比局部变量更长(比如被返回或存储在别处),那么当局部变量销毁后,调用这个 Lambda 就会导致程序崩溃。
    • 简单原则:如果 Lambda 会在当前作用域之外被调用,对外部变量最好使用值捕获
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱看烟花的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值