一、基础语法与概念
1.什么是 Lambda 表达式?
一种匿名函数对象,可以在代码中就地定义,无需专门的函数或函数对象类。
核心优势:简洁、闭包(能够捕获上下文变量)。
2.完整语法(C++11及以后)
bash
[ capture-list ] ( parameters ) -> return-type { body }
[ capture-list ](捕获列表)这是lambda表达式最核心,最独特的部分,定义了哪些外部变量能被lambda内部使用,以及捕获方式(值捕获vs引用捕获),捕获列表可以为空,表示不访问任何外部变量,也可以使用默认捕获方式来表示按引用或者按值捕获所有外部变量,还可以混合使用具体的变量名和默认捕获方式混合使用。
( parameters )(参数列表)和普通函数的参数列表一样,可以为空,表示没有参数,C++14中使用auto来实现泛型参数。
-> return-type(返回值类型):可选的,如果省略,编译器会根据函数体中的return推导,C++14中使用auto来实现泛型返回值。
{ body }(函数体):Lambda要执行的代码, c++14 中使用 constexpr 来实现编译期计算。
cpp
最简单的Lambda:不捕获参数,也不接受参数
void test()
{
auto hello = []{std::cout<<"Hello,Lambda!"<<std::endl;}
hello();//调用
}
二、捕获列表
1.捕获的方式
值捕获[=]:捕获所有外部变量的副本。lambda内部修改不影响外部变量
cpp
int x = 10,y = 20;
auto valueCapture = [x,y]{
std::cout <<"x= "<<x<<",y= "<<y<<std::endl;
};
valueCapture();
x = 200;
y = 300;
valueCapture();//外部值改变不影响lambda里面捕获的值
引用捕获[&]:捕获所有外部变量的引用。lambda内部修改会影响外部变量
cpp
引用捕获:内部变量 外部变量会相互影响
int x = 10,y = 20;
auto valueCapture = [&x,&y]{
x++;
y++;
std::cout <<"lamda x= "<<x<<",y= "<<y<<std::endl;
};
valueCapture();//x= 11,y= 21
std::cout <<"x= "<<x<<",y= "<<y<<std::endl;//x= 11,y= 21
x = 200;
y = 300;
valueCapture();//x= 201,y= 301
std::cout <<"222 x= "<<x<<",y= "<<y<<std::endl;//x= 201,y= 301
混合捕获与显式捕获
[x,&y] :值捕获x,引用捕获y。
[=,&y] :默认值捕获所有变量,但y显示指定为引用捕获。
[&,x]:默认引用捕获所有值,但x显式指定为值捕获
cpp
int a=1, b=2, c=3;
//默认值捕获,但显式指定某些变量引用捕获
auto mixed = [=,&a,&b]()mutable{
//a,b是引用捕获,可以修改
a = 10;
b = 20;
//c是值捕获,不能修改,除非()后加上mutable关键字
c = 30;
std::cout <<"lambda a="<<a<<",b= "<<b<<",c="<<c<<std::endl;
};
//默认引用捕获,但显式指定某些变量值捕获
auto mixed2 = [&,a]()mutable{
//a是值捕获不能修改,除非()后加上mutable关键字
a = 100;
//b,c是引用捕获,可以修改
b = 200;
c = 300;
std::cout <<"lambda2 a="<<a<<",b= "<<b<<",c="<<c<<std::endl;
};
mixed();//lambda a=10,b= 20,c=30
//a=10,b= 20,c=3
std::cout <<"a="<<a<<",b= "<<b<<",c="<<c<<std::endl;
mixed2();//lambda2 a=100,b= 200,c=300
//222 a=10,b= 200,c=300
std::cout <<"222 a="<<a<<",b= "<<b<<",c="<<c<<std::endl;
2.mutable 关键字
默认情况下,值捕获的变量在lambda体内是const的,不能被修改。
使用mutable 可以移除这个const限制,允许修改值捕获的副本。
3.捕获 this 指针
在类的成员函数中,Lambda 可以通过 [this] 或 [=] 捕获当前对象的 this 指针,从而访问类的成员变量和函数。
三、返回值类型和模板
1.返回值类型推导
绝大多数情况下,可以省略 -> return-type,编译器会自动推导,当函数体中有多个 return 语句且类型不完全相同,或者逻辑复杂编译器无法推导时,需要显式指定。
2.lambda与泛型(C++14)
可以使用auto作为参数类型,创造泛型Lambda
cpp
auto add = [](auto a,auto b){return a+b;};
std::cout<<add(1,2)<<std::endl; //int
std::cout<<add(1.1,2.2)<<std::endl;//double
四、高级特性与应用
1.lambda的本质
lambda是一个函数对象。编译器会为每个lambda表达式生成一个唯一的,匿名的类类型
这个类重载了operator(),使得对象可以像函数一样被调用。
2.将 Lambda 作为参数传递
lambda最常用的场景是作为算法的谓词,传递给STL算法。
cpp
#include <algorithm>
#include <vector>
std::vector<int> vec={5,2,8,1,9};
std::sort(vec.begin(),vec.end(),[](int a,int b)){return a>b;});//降序排序
3.std::function 包装器
lambda的类型是唯一的、匿名的、无法直接声明。
可以使用std::function来存储和传递任何可调用对象(包括lambda)
cpp
#include <functional>
std::function<int(int,int)>addr = [](int a,int b){return a+b;};
4.初始化捕获(C++14 广义捕获)
cpp
auto ptr = std::make_unique<int>(42);
//将ptr 移动(而并非复制)到Lambda中
auto lambda = [p=std::move(ptr)]{return *p;};
5.constexpr Lambda(C++17)
cpp
可以在编译期求值的Lambda,用于元编程和性能优化。
constexpr auto square = [](int n) { return n * n; };
static_assert(square(5) == 25); // 编译期计算
constexpr:表明这个lambda可以在编译期间被求值
编译期执行:square(5)不是在运行时计算的,而是在编译器处理代码时就已经计算出结果25
类型安全:如果计算结果不匹配,编译直接失败
零运行时开销:最终代码中不会包含这个计算过程
6.模板语法(C++20)
cpp
Lambda 支持显式的模板参数列表。
[捕获列表] <模板参数列表> (函数参数列表) -> 返回类型 { 函数体 }
auto foo = []<typename T>(T t) { /* ... */ };
为什么需要这个特性?
1. 类型约束和保证,如果需要参数的变量都是一样的话,
使用auto genericLambda = [](auto a, auto b) {return a + b;};就没办法保证
auto explicitLambda = []<typename T>(T a, T b) {return a + b;};这两个参数就要一样
2. 访问容器元素类型
//处理容器,需要知道元素类型
auto processContainer = []<typename T>(const std::vector<T>& vec) {
//可以在函数体内使用 T
T sum{};
for (const auto& element : vec) {
sum += element;
}
return sum;
};
1.掌握基础语法和捕获列表。这是最核心的部分,务必理解值捕获、引用捕获和 mutable 的用法。
2.与 STL 算法结合。用 std::sort, std::for_each, std::find_if 等算法来熟悉 Lambda 作为谓词的用法。
3.:理解其本质。明白 Lambda 是一个编译器生成的函数对象,这有助于理解它的行为和限制。
4.:在实战中应用高级特性。当你在项目中遇到需要封装局部状态或传递复杂回调时,自然会用到 std::function、初始化捕获等高级特性。
捕获列表 [ ] 是 C++ Lambda 区别于其他语言 Lambda 的关键。