1. 什么是lambda表达式lambda表达式是一种匿名函数也就是没有名字的函数。它是一种简洁的函数定义方式不需要显式命名函数它可以方便地定义简短的可调用对象用于各种需要函数对象的场景可定义在函数内部普通函数只能定义在全局、命名空间或类中语法层面无显式类型需通过auto或模板参数接收其匿名类型主要特点匿名性没有函数名简洁性通常用于简单操作可以在一行内完成临时性常用于一次性使用或作为参数传递给高阶函数2. lambda表达式的基本语法形式基本语法形式代码语言javascriptAI代码解释[捕获列表](参数列表) - 返回类型 { 函数体 } [capture-list] (parameters) - return-type { function-body }捕获列表capture-list用于指定在 lambda 表达式中可以访问的外部变量是 lambda 能够访问所在作用域中变量的关键。必须出现在 lambda 最开头编译器通过[]识别 lambda 开始它可以为空表示不捕获任何外部变量但是[]不能省略空捕获列表是语法要求参数列表parameters和普通函数的参数列表类似用于指定 lambda 函数的参数。它可以为空若无需参数传递可连同()一起省略如无参 lambda 可写[] { ... }返回值类型return-type指定 lambda 函数的返回值类型。无返回值时如void可直接省略返回值类型明确时如单一return语句编译器可自动推导也可省略函数体function-body定义 lambda 函数的具体操作和逻辑也就是函数执行时要做的事情。除参数外可直接使用捕获列表中的变量即使函数体为空如[] {}{}也不能省略通过上面的关于lambda的语法的介绍现在我们可以明白空lambda的形式了代码语言javascriptAI代码解释// 空lambda所有可省略部分均省略 auto emptyLambda [] {}; // 合法但无实际逻辑2.1捕获列表lambda表达式默认仅能访问自身函数体、参数列表里的变量。若需使用外层作用域如包含 lambda 的函数作用域的变量需通过捕获列表显式声明。捕获列表决定了 lambda 表达式对外部变量的访问方式常见的捕获方式有以下几种显示捕获在捕获列表中明确写出变量名用逗号分隔多个变量同时通过符号区分捕获方式值捕获直接写变量名如[x, y]引用捕获变量名前加如[z]隐式捕获无需逐个写变量名用符号批量指定捕获规则编译器会自动识别 lambda 内用到的外层变量并捕获按值隐式捕获捕获列表写[]lambda 内用到的外层变量全部传值捕获创建副本按引用隐式捕获捕获列表写[]lambda 内用到的外层变量全部传引用捕获使用引用混合捕获同时用隐式捕获符号或和显式变量。值捕获在 lambda 内部对捕获变量的修改不会影响外部的原始变量。例如[a]表示按值捕获变量a代码语言javascriptAI代码解释#include iostream using namespace std; int main() { //1.定义一个局部变量初始值为10 int num 10; //2.定义一个lambda表达式通过值捕获方式获取外部变量num auto lambda [num]()mutable { //2.1尝试修改捕获的副本注意修改的是副本不是原始变量 num 20; //注意此操作不会影响外部的num变量 //2.2输出捕获副本的值此时副本已被修改为20 cout num endl; // 输出: 20 }; /* 注意事项默认情况下按值捕获的变量在lambda表达式内部是不可修改的const * * 1. 当你使用值捕获 [num] 时lambda 内部得到的是 num 的一个副本 * 2. 但这个副本默认是 const 的不能修改。 * 3. 因此当你尝试在 lambda 内部修改 num 时编译器会报错。 * 4. 要使按值捕获的变量可以在 lambda 内部修改需要使用 mutable 关键字 */ //3.调用lambda表达式执行内部逻辑 lambda(); //4.输出外部num的值未被lambda内部修改 cout num endl; // 输出: 10 return 0; }在这里插入图片描述引用捕获在 lambda 内部对捕获变量的修改会直接影响外部的原始变量。例如[a]表示按引用捕获变量a代码语言javascriptAI代码解释#include iostream int main() { //1.定义一个局部变量 num int num 10; //2.定义一个lambda表达式使用引用捕获的方式捕获外部变量 num auto lambda [num]() { //2.1由于是引用捕获这里对 num 的修改会直接反映到外部的 num 变量上 num 20; //2.2输出修改后的 num 的值此时会输出 20 std::cout num std::endl; }; /* 注意事项 * 1. [num] 表示以引用的方式捕获变量 num * 2. 这意味着 lambda 表达式内部对 num 的操作会直接作用于外部定义的这个 num 变量 * 3. 因为它们共享同一块内存空间 */ //3.调用定义好的lambda表达式执行其内部的代码逻辑 lambda(); //4.再次输出 num 的值 std::cout num std::endl; //因为 lambda 表达式中通过引用捕获修改了 num所以这里输出 20 return 0; }在这里插入图片描述隐式捕获可以让编译器自动推导捕获列表。 分为按值隐式捕获[]按引用隐式捕获[]代码语言javascriptAI代码解释#include iostream int main() { //1.定义两个局部变量 int num1 10; int num2 20; //2.lambda1按值隐式捕获所有用到的外部变量 auto lambda1 []() { //2.1输出捕获的副本值 std::cout num1 num2 std::endl; // 输出: 10 20 }; /* 注意事项 * []表示以值捕获方式捕获所有外部变量 * lambda内部使用的是外部变量的副本无法修改原始变量 */ //3.lambda2按引用隐式捕获所有用到的外部变量 auto lambda2 []() { //3.1直接修改外部的num1变量 num1 30; //3.2输出修改后的外部变量值 std::cout num1 num2 std::endl; // 输出: 30 20 }; /* 注意事项 * []表示以引用捕获方式捕获所有外部变量 * lambda内部使用的是外部变量的引用可以直接修改原始变量 */ //4.调用lambda1输出捕获时的值副本 lambda1(); // 输出: 10 20 //5.调用lambda2修改外部变量并输出 lambda2(); // 输出: 30 20 //6.验证外部变量确实被修改 std::cout 外部num1: num1 std::endl; // 输出: 30 return 0; }在这里插入图片描述混合捕获可以同时使用值捕获和引用捕获。若开头用[]隐式传值后续显式变量必须传引用如[, x]其他变量隐式传值x显式传引用若开头用[]隐式传引用后续显式变量必须传值如[, x]其他变量隐式传引用x显式传值所以[, a]表示按值捕获除a以外的所有用到的外部变量而a按引用捕获。总结捕获列表的常见形式捕获列表写法含义示例外部变量 int a10; double b3.14;[]不捕获任何外部变量lambda 内无法使用 a、b[a]传值捕获 a修改 lambda 内的 a 不影响外部lambda 内 a 是 10 的副本[b]传引用捕获 b修改 lambda 内的 b 会影响外部lambda 内 b 是外部 b 的引用[]传值捕获所有用到的外部变量lambda 内 a 是副本b 是副本修改不影响外部[]传引用捕获所有用到的外部变量lambda 内 a、b 都是引用修改会影响外部[a, b]混合捕获a 传值b 传引用lambda 内 a 是副本b 是引用2.2参数列表与返回值参数列表和普通函数一样可以有不同类型的参数也可以没有参数。[]() { std::cout Hello Lambda! std::endl; }没有参数只是输出一条信息[](int a, int b) { return a b; }接收两个int类型的参数并返回它们的和返回值如果函数体只有一条返回语句返回值类型可以省略编译器会自动推导。代码语言javascriptAI代码解释[](int a, int b) //编译器会自动推导返回值类型为int { return a b; }如果函数体有多条语句且需要明确返回值类型就必须显式指定。代码语言javascriptAI代码解释[](int a, int b) - bool //注意必须显式指定返回类型bool { if (a b) return true; else return false; }3. lambda表达式怎么使用代码案例1lambda表达式的使用代码语言javascriptAI代码解释#include iostream using namespace std; int main() { /*------------------- 完整语法示例显式声明所有部分-------------------*/ auto add1 [](int a, int b) - int { return a b; }; /* 定义一个完整语法的lambda表达式 * 1. [] 空捕获列表不捕获任何外部变量 * 2. (int a, int b) 参数列表接收两个int类型参数 * 3. - int 显式指定返回值类型为int * 4. { return a b; } 函数体实现两数相加 */ cout add1(3,4) add1(3, 4) endl; /*------------------- 省略返回值类型编译器自动推导-------------------*/ auto add2 [](int a, int b) { return a b; // 返回值类型被自动推导为int }; /* 省略返回值类型编译器根据return语句自动推导返回类型为int * 其他部分与add1完全相同 */ cout add2(5,6) add2(5, 6) endl; /*------------------- 省略参数列表无参场景-------------------*/ auto greet [] { std::cout Hello Lambda endl; }; /* 省略参数列表的写法当lambda不需要参数时()可以连同参数一起省略 * 但[]和{}不能省略 */ greet(); /*------------------- 省略捕获列表和参数列表最简形式-------------------*/ auto emptyLambda [] {}; /* 最简lambda形式空捕获列表空参数列表 * 可以理解为一个无输入、无输出的函数 */ emptyLambda(); //调用空lambda无任何效果但语法合法 return 0; }在这里插入图片描述代码案例2lambda表达式的使用代码语言javascriptAI代码解释#include iostream using namespace std; //定义全局变量 x int x 0; int main() { // 1. “全局变量”与“空捕获列表” auto func1 []() //注意全局变量无需捕获即可使用lambda 捕获列表必须为空 { x; }; // 2. 显式捕获传值 传引用 //1.定义局部变量 a,b,c,d int a 0, b 1, c 2, d 3; //2.显式捕获a 传值const 只读b 传引用可修改 auto func2 [a, b]() { // a; // 错误传值捕获的变量默认 const无法修改除非加 mutable b; // 正确传引用捕获可直接修改外部变量 b int ret a b; // a0原值, b2已自增 return ret; }; cout func2() endl; // 3. 隐式传值捕获[] //3.隐式传值捕获所有用到的变量a, b, c auto func3 []() { // a; // 错误传值捕获默认 const无法修改 int ret a b c; // a0, b2, c2 return ret; }; cout func3() endl; // 4. 隐式传引用捕获[] //4.隐式传引用捕获所有用到的变量a, c, d auto func4 []() { a; // 正确传引用修改外部 a → a1 c; // 正确传引用修改外部 c → c3 d; // 正确传引用修改外部 d → d4 }; func4(); cout a b c d endl; // 5. 混合捕获隐式传引用 显式传值 //5. 隐式传引用 显式传值a, b //规则隐式符号在前显式变量必须传值 auto func5 [, a, b]() { // a; // 错误a 是显式传值默认 const // b; // 错误b 是显式传值默认 const c; // 正确隐式传引用修改外部 c → c4 d; // 正确隐式传引用修改外部 d → d5 return a b c d; // 0 1 4 5 10 }; func5(); cout a b c d endl; // 6. 混合捕获隐式传值 显式传引用 //6.隐式传值 显式传引用a, b //规则隐式符号在前显式变量必须传引用 auto func6 [, a, b]() { a; // 正确显式传引用修改外部 a → a2 b; // 正确显式传引用修改外部 b → b3 // c; // 错误c 是隐式传值默认 const // d; // 错误d 是隐式传值默认 const return a b c d; // 2 3 4 5 14 }; func6(); cout a b c d endl; // 7. “静态变量”与“全局变量”的访问 //静态局部变量无需捕获即可使用 static int m 0; auto func7 []() { int ret x m; // 全局变量 x0静态变量 m0 return ret; // 返回 0 0 0 }; cout func7() endl; // 8. mutable 修饰符突破传值捕获的 const 限制 //隐式传值捕获[] mutable允许修改传值副本不影响外部变量 auto func8 []() mutable { a; // 修改传值副本 a → 副本 a3外部 a 仍为 2 b; // 修改传值副本 b → 副本 b4外部 b 仍为 3 c; // 修改传值副本 c → 副本 c5外部 c 仍为 4 d; // 修改传值副本 d → 副本 d6外部 d 仍为 5 return a b c d; // 3 4 5 6 18 }; //输出内部变量的和 cout func8() endl; // 输出: 18 //输出外部变量 cout a b c d endl; //a2, b3, c4, d5未被修改 return 0; }在这里插入图片描述4. 特殊的捕获限制有哪些lambda表达式定义的位置若lambda 若定义在函数局部作用域如main 函数内 仅能捕获lambda 定义位置之前的局部变量静态局部变量static int var; 和全局变量无需捕获可直接在 lambda 内使用因为它们的作用域不受函数栈限制 若 lambda 定义在全局作用域函数外 捕获列表必须为空无外层局部变量可捕获 mutable修饰符突破传值捕获的只读限制默认情况下传值捕获的变量在 lambda 内是const只读的若需修改传值捕获的副本可在参数列表后加mutable此时参数列表不能省略即使无参数也要写()5. lambda表达式的价值是什么lambda表达式通过简洁语法实现了匿名函数对象核心价值在于支持函数内部定义灵活捕获局部变量语法各部分可按需省略适配不同场景如无参、无返回值等配合 auto 使用无需显式定义函数类型让代码更简洁掌握 lambda 的语法结构和捕获规则是高效使用 C 现代特性如算法库、异步编程的基础。6. lambda表达式的应用有哪些在学习 lambda 表达式之前我们所使用的可调用对象只有函数指针和仿函数对象函数指针的类型定义较为繁琐仿函数需要定义一个类操作相对麻烦而使用 lambda 来定义可调用对象简单又便捷。