《C++——Lambda表达式》

#王者杯·14天创作挑战营·第5期#

在 C++ 编程中,我们常常需要编写一些 “短小精悍” 的函数 —— 它们可能只在某个场景下被调用一次,单独定义成普通函数又显得冗余。这时,C++11 引入的 Lambda 表达式 就成了 “救星”。它允许我们在代码中直接定义 “匿名函数”,既能简化代码,又能让逻辑更紧凑。

一、Lambda 是什么?

Lambda 表达式,本质是 “可以在代码中就地定义的匿名函数”。所谓 “匿名”,是指它不需要像普通函数那样起一个固定的名字;“就地定义” 则意味着我们可以在需要调用它的地方直接写函数逻辑,无需单独声明。

举个最直观的例子:如果我们想打印一句 “Hello Lambda”,用普通函数需要写两行(声明 + 调用),而用 Lambda 只需一行:

// 普通函数
void printHello() {
    std::cout << "Hello Lambda" << std::endl;
}
printHello(); // 调用

// Lambda 表达式(定义后直接调用)
[]() { std::cout << "Hello Lambda" << std::endl; }();

是不是瞬间简洁了?这就是 Lambda 最核心的价值:为 “一次性小逻辑” 提供极简的实现方式。

二、Lambda 基础语法

Lambda 的语法看起来有点 “特殊”,但只要记住固定结构,很快就能上手。完整语法如下:

[capture] (parameters) mutable -> return_type {
    // 函数体(function body)
}

这 5 个部分中,只有 [capture](捕获列表)和 {函数体} 是必须的,其他部分可根据需求省略。我们逐个拆解:

部分作用是否省略
[capture]捕获外部变量(让 Lambda 能使用函数外部的变量)
(parameters)参数列表(和普通函数的参数列表完全一致)
mutable允许修改按值捕获的变量(默认按值捕获的变量是 “只读” 的)
-> return_type返回值类型(编译器可自动推导,除非函数体有多个 return 分支且类型不同)
{}函数体(存放具体的逻辑代码)

1. 最简化 Lambda:无参数、无返回值

当我们不需要参数、不需要返回值,也不需要使用外部变量时,Lambda 可以简化到极致:

// 无捕获、无参数、无返回值的 Lambda
[]() {
    std::cout << "这是一个极简 Lambda" << std::endl;
}(); // 末尾加 () 表示“定义后立即调用”

甚至连 ()(参数列表)都能省略(因为没有参数):

[] { std::cout << "参数列表为空时,() 可以省" << std::endl; }();

2. 带参数的 Lambda:像普通函数一样传参

如果需要给 Lambda 传参,只需在 () 中定义参数列表,用法和普通函数完全一致。比如写一个 “两数相加” 的 Lambda:

int main() {
    // 定义带参数的 Lambda,并赋值给变量 add(auto 自动推导类型)
    auto add = [](int a, int b) {
        return a + b; // 编译器自动推导返回值为 int
        };

    // 调用 Lambda(像调用普通函数一样)
    int result = add(3, 5);
    std::cout << "3 + 5 = " << result << std::endl; // 输出:3 + 5 = 8
}

这里用 auto 接收 Lambda 是因为它的类型是编译器内部生成的 “匿名类型”,我们无需关心具体名字。

3. 返回值类型:什么时候需要显式声明?

大多数情况下,编译器会根据 return 语句自动推导 Lambda 的返回值类型,因此 -> return_type 可以省略。但如果函数体中有 多个 return 分支,且返回值类型不同,就必须显式声明返回值类型。

比如下面的 Lambda,根据条件返回 int 或 double,必须用 -> double 指定返回值:

int main() {
    // 错误写法:返回值类型不统一,编译器无法推导
// auto calculate = [](int a, bool isDouble) {
//     if (isDouble) return a * 2.0; // 返回 double
//     else return a * 2;            // 返回 int
// };

// 正确写法:显式声明返回值为 double
    auto calculate = [](int a, bool isDouble) -> double {
        if (isDouble) return a * 2.0;
        else return a * 2; // 自动转为 double
        };

    std::cout << calculate(5, true) << std::endl;  // 输出:10.0
    std::cout << calculate(5, false) << std::endl; // 输出:10.0
}

4. 微练手

有没有感觉很像三目运算符?好像它们具有同样的逻辑,先判断,然后返回值,那我们就用lambda实现一个:

#include <iostream>

int main() {
    // 接收条件和两个Lambda,执行符合条件的逻辑
    auto conditional_exec = [](bool condition, auto true_func, auto false_func) {
        if (condition) {
            true_func(); // 执行条件为true时的逻辑
        } else {
            false_func(); // 执行条件为false时的逻辑
        }
    };

    int x = 10;
    conditional_exec(
        x > 5, 
        []() { std::cout << "x大于5,执行逻辑A" << std::endl; },
        []() { std::cout << "x小于等于5,执行逻辑B" << std::endl; }
    ); // 输出:x大于5,执行逻辑A

    return 0;
}

在这里插入图片描述

三、核心难点:

Lambda 之所以强大,关键在于它能通过 捕获列表 访问函数外部的变量。捕获列表的写法灵活,核心是控制 “如何捕获外部变量”(按值还是按引用)。

1. 捕获的 4 种常见场景

我们用一个具体例子理解:假设函数中有两个外部变量 x = 10 和 y = 20,Lambda 如何使用它们?

捕获方式语法作用说明示例代码
不捕获任何变量[] Lambda内部无法使用外部变量[] { std::cout << x; }(); // 错误:x 未被捕获
按值捕获[x, y] 或 [=]拷贝外部变量到 Lambda 内部,内部修改不会影响外部变量(默认只读)[x, y] { std::cout << x + y; }(); // 输出 30
按引用捕获[&x, &y] 或 [&]引用外部变量,内部修改会直接影响外部变量[&x] { x++; }(); // 外部 x 变为 11
混合捕获[x, &y]部分变量按值捕获,部分按引用捕获[x, &y] { y = x + 5; }(); // 外部 y 变为 15,x 不变

2. 易错点:按值捕获的 “只读” 特性

按值捕获的变量,默认在 Lambda 内部是 只读 的,不能修改。如果需要修改,必须加上 mutable 关键字

int main() {
    int x = 10;

    // 错误:按值捕获的 x 是只读的,不能修改
    // [x] { x++; }();

    // 正确:加上 mutable 允许修改按值捕获的变量(但外部 x 仍不变)
    [x]() mutable {
        x++;
        std::cout << "Lambda 内部 x = " << x << std::endl; // 输出:11
        }();

    std::cout << "外部 x = " << x << std::endl; // 输出:10(外部变量未被修改)
    return 0;
}

在这里插入图片描述

注意:mutable 只允许修改 Lambda 内部的 “拷贝版” 变量,不会影响外部原变量。如果想修改外部变量,还是得用 按引用捕获。

四、实战场景

学了基础语法,我们更关心:Lambda 在实际开发中能用到哪里?最常见的场景是 配合 C++ 标准库算法(如排序、遍历),替代繁琐的函数对象或全局函数。

场景 1:简化排序逻辑(std::sort)

std::sort 是排序函数,默认升序排序。如果想按降序、或者自定义规则排序,用 Lambda 无需单独定义比较函数:

#include <vector>
#include <algorithm>
#include <iostream>

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

    // 1. 降序排序(替代单独定义比较函数)
    std::sort(nums.begin(), nums.end(), [](int a, int b) {
        return a > b; // 降序规则:a 大于 b 时,a 排在前面
    });

    // 2. 遍历输出(用 std::for_each 配合 Lambda)
    std::cout << "降序排序后:";
    std::for_each(nums.begin(), nums.end(), [](int num) {
        std::cout << num << " "; // 输出每个元素
    });
    // 输出:9 5 4 3 1 1

    return 0;
}

在这里插入图片描述

如果不用 Lambda,我们需要单独写一个比较函数,代码会多出几行:

// 不用 Lambda 的写法:单独定义比较函数
bool compareDesc(int a, int b) {
    return a > b;
}
std::sort(nums.begin(), nums.end(), compareDesc);

场景 2:自定义数据结构的排序

如果要排序的是自定义结构体(如 Person),Lambda 可以轻松实现按 “年龄”“姓名长度” 等规则排序:

#include <vector>
#include <algorithm>
#include <string>
#include <iostream>

struct Person {
    std::string name;
    int age;
};

int main() {
    std::vector<Person> people = {
        {"Alice", 25},
        {"Bob", 20},
        {"Charlie", 30}
    };

    // 按年龄升序排序(年轻的在前)
    std::sort(people.begin(), people.end(), [](const Person& p1, const Person& p2) {
        return p1.age < p2.age;
    });

    // 输出排序结果
    for (const auto& p : people) {
        std::cout << p.name << " (" << p.age << "岁) ";
    }
    // 输出:Bob (20岁) Alice (25岁) Charlie (30岁)

    return 0;
}

在这里插入图片描述

场景 3:捕获外部变量做过滤

假设我们有一个数字列表,想筛选出 “大于某个外部阈值” 的元素,用 Lambda 捕获阈值变量即可:
cpp

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
    int threshold = 5; // 外部阈值:筛选大于 5 的数

    // 用 Lambda 捕获 threshold,筛选符合条件的元素
    std::vector<int> result;
    std::for_each(nums.begin(), nums.end(), [&result, threshold](int num) {
        if (num > threshold) {
            result.push_back(num); // 按引用捕获 result,修改外部容器
        }
    });

    // 输出结果
    std::cout << "大于 " << threshold << " 的数:";
    for (int num : result) {
        std::cout << num << " "; // 输出:6 7 8
    }

    return 0;
}

在这里插入图片描述

五、避坑指南

问题具体原因解决办法
忘记捕获外部变量在 Lambda 中使用了外部变量,但没写捕获列表(如 [] { std::cout << x; }),编译直接报错。根据需求添加捕获方式(如 [x] 或 [&x])。
按值捕获后想修改变量按值捕获的变量默认只读,直接修改会编译错误。如果只需修改内部拷贝,加 mutable;如果要修改外部变量,改用按引用捕获。
捕获列表过度使用 [=] 或 [&][=] 会捕获所有外部变量(按值),[&] 会捕获所有外部变量(按引用),可能导致不必要的性能开销或意外修改。按需捕获,只捕获需要用到的变量(如 [x, &result])。

Lambda 不是 “花里胡哨” 的语法糖,而是能真正提升开发效率的工具。

### C++11 Lambda表达式使用教程 #### 定义与基本语法 C++11引入了lambda表达式这一强大特性,使得开发者能够更加便捷地定义和创建匿名函数。这种新的功能不仅简化了代码编写过程,还提升了程序的功能性和执行效率[^1]。 Lambda表达式的通用形式如下: ```cpp [capture](parameters)->return_type { body } ``` 其中`capture`表示捕获列表,用于指定如何访问外部作用域中的变量;`parameters`代表参数列表;`->return_type`可选部分用来显式声明返回类型;最后是函数体`body`[^2]。 #### 捕获列表详解 捕获列表允许lambda表达式获取其所在环境内的局部变量或全局变量。常见的几种方式包括但不限于按值传递(`[var]`)、按引用传递(` [&var] `),以及默认全部按值/引用捕获(` [= ] / [&] `)[^3]。 ##### 示例:按值捕获 当采用按值的方式捕获时,意味着会复制一份原始数据供内部逻辑操作而不影响原值。 ```cpp #include <iostream> using namespace std; int main(){ int value = 42; auto func_by_value = [value]() { cout << "Value captured by copy: " << value << endl; }; ++value; // 修改外界的value func_by_value(); // 输出的是未修改前的数值 } ``` ##### 示例:按引用捕获 相反地,如果选择了按引用的形式,则任何对于被捕获变量的操作都会直接影响到实际存在的那个实例。 ```cpp #include <iostream> using namespace std; int main(){ int ref_val = 88; auto modify_ref = [&ref_val]() mutable{ ++ref_val; cout << "Modified reference-captured variable to : " << ref_val << endl; }; modify_ref(); } ``` #### 实际应用场景举例 考虑这样一个场景——我们需要交换两个整型数的位置而无需借助额外的空间开销。此时可以巧妙运用带有适当捕获机制的lambda表达式来完成任务[^5]。 ```cpp void swapTwoNumbers(int &num1, int &num2){ auto swapper = [&](int &a, int &b){ int temp=a;a=b;b=temp; }; swapper(num1,num2); } // 或者更为简洁的做法直接在lambda体内处理 auto direct_swap=[&](int &x,int &y){swap(x,y);} direct_swap(a,b); ``` #### 关于赋值行为的重要注意事项 值得注意的一点在于,尽管可以通过拷贝构造的方式来生成另一个具有相同行为的新对象,但是不同lambda表达式之间不允许互相赋值,即便它们看起来拥有完全一致的结构和语义[^4]。 ```cpp auto hello_world_1=[](){std::cout<<"Hello World"<<std::endl;}; auto hello_world_2=[](){std::cout<<"Hello World"<<std::endl;}; // 下面这行会导致编译错误 //hello_world_1=hello_world_2; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值