什么是 Lambda?为什么要用它
Lambda(闭包)是 C++11 引入的语言特性,本质上是匿名函数对象(函数对象 = 重载 operator() 的类) 。它能方便地把一个小函数内联到代码里,常见用途包括:STL 算法回调(sort/transform/copy_if)、线程任务、事件回调、比较器、简短的自定义逻辑等。
语法骨架:
[捕捉列表](参数列表) -> 返回类型 { 函数体 }
捕捉列表决定闭包如何获得外部数据(按值、按引用或捕捉 this)。
捕捉列表详解
-
[=]:按值捕捉作用域内所有需要的外部变量(拷贝进闭包)。闭包内部的副本与外部变量独立,外部修改不影响闭包内部。 -
[&]:按引用捕捉作用域内所有外部变量。闭包内访问的是原变量,外部修改会反映进去。注意生命周期问题:如果闭包存活超过外部变量,引用会悬空(UB)。 -
[x]/[&x]:只捕捉指定变量(值或引用)。 -
[this]:捕捉当前对象指针(拷贝this指针)。闭包内部通过this->member访问成员,所以成员改变会反映。注意:如果闭包在对象销毁后仍被调用会 UB。若 lambda 在异步场景中延长生命周期,请确保对象生命周期足够或使用shared_ptr/weak_ptr等保护。
以下是代码示例:
cpp
//(1)[=]
int x = 100, y = 200;
//以值传递的方式捕捉作用域内的所有变量
auto lam = [=]() {return x + y; };
cout << lam() << endl;//300
x++, y++;
cout << lam() << endl;//???
//结果说300,但x,y已经加+1了,为啥呢
//值传递,外面变了,里面不变,只传递了一次
//(2)[变量1,变量2]
//以值传递的方式捕捉特定变量
int x = 100;
auto lam = [x]()->int {return x * x; };
cout << lam() << endl;
x++;
cout << lam() << endl;
//(3)[&]
//以引用的方式,捕捉作用域内的所有变量
int x = 100;
auto lam = [&]()->int {return x * x; };
cout << lam() << endl;
x += 1;
cout << lam() << endl;
//4[&变量1,&变量2]
//以引用的方式捕捉作用域的特定方式
int x = 100, y = 200;
auto lam = [&x, &y]()->int {return x + y; };
cout << lam() << endl;
x++, y++;
cout << lam() << endl;
5[]不捕捉
auto add = [](int a, int b)->int
{
return a + b;
};
int x = add(3,5);
cout << x << endl;
//6[this]
class A
{
public:
void test()
{
//[this]到底是值传递,还是引用传递
//貌似:值传递
//实际上,也是值传递
//但由于是传进来的是指针,所有变了
auto func = [this]()->int {return a + b; };
cout << func() << endl;//300
a++;
cout << func() << endl;//301
}
private:
int a = 100;
int b = 200;
};
A a;
a.test();
//7[=,&]混合捕捉
int x = 100, y = 200;
auto lam=[=, &x]()->int { return x + y; };///先是全部值传递,然后覆盖原来的
cout << lam() << endl;
x++;
cout << lam() << endl;
auto lam = [=, x]()->int {return x + y; };//不可以,不能重复,可以覆盖
实践建议:
-
捕捉小对象或不可复制的资源时优先按值(安全);若按引用,一定保证被捕变量比闭包寿命更长或使用同步/智能指针。
-
在类成员函数里用
[this]很常见,但在异步/线程场景需小心对象生命周期。
Lambda 的底层:闭包类型与 decltype
每个 lambda 编译器生成一个匿名类(闭包类型),捕获的数据变成类成员,operator() 被重载用于调用。所以闭包是 "有类型 的对象",只是这个类型没有名字。
cpp
lambda表达式的底层实现是怎样的?
底层实现:就是c++的"函数对象"
函数对象是什么?
class A
{
public:
int operator ()(int a,int b}
{
return a+b;
}
}
A add;
cout<<add(3,5)<<endl;
这就是一个函数对象
让一个对象通过重载的方式实现函数
当你想把 lambda 类型作为模板参数(比如 std::set / std::map 的比较器类型),可以用 decltype(lambdaVar) 获取该闭包类型。例如:
cpp
auto cmp = [](const auto& a, const auto& b){ return a.first > b.first; }; std::set<std::pair<int,int>, decltype(cmp)> st(cmp);
decltype(cmp) 是 lambda 的闭包类型;构造 st(cmp) 把 comparator 对象的实例传入容器(很多捕获了东西的闭包没有默认构造,所以必须在构造时提供实例)。
下面是lambda在STL与算法中的常见应用
cpp
1.STL容器与算法中的应用
//transform
int data[5] = { 1,2,3,4,5 };
transform(data, data + 5, data, [](int x)->int {return x * x; });
for (auto i : data) { cout << i << endl; }
vector<int>num = { 1,2,3,4,5 };
transform(num.begin(), num.end(), num.begin(), [](int x) {return x * 2; });
for (auto i : num) { cout << i << endl; }
//排序
int data[5] = { 1,2,3,4,5 };
sort(data, data + 1, [](int a, int b) {return a > b; });
for (auto i : data)cout << i << " ";
//STL中的算法回调
vector<int>data1 = { 1,2,3,4,5 };
vector<int>data2;
copy_if(data1.begin(), data1.end(),back_inserter(data2), [](int x) {return x & 1; });
for (auto i : data2)cout << i << endl;
//并行编程(多线程编程)
thread t1([]()
{
for (int i = 0; i < 10; i++)
{
cout << "thread1: " << i << endl;
Sleep(1000);
}
});
thread t2([]()
{
for (int i = 0; i < 10; i++)
{
cout << "thread2: " << i << endl;
Sleep(1000);
}
});
t1.join();
t2.join();
递归 lambda:为什么不能直接在 lambda 内调用自身?怎么解决?
问题根源:lambda 的类型是匿名的,无法像普通函数那样在自身内部直接用名字调用(编译期没名字)。解决办法常见两种:
方法 A --- 传自身法(auto self)
cpp
auto fab = [](auto self, int n)->int
{
if (n <= 2)return 1;
return self(self, n - 2) + self(self, n - 1);
};
cout << fab(fab, 10) << endl;
优点:无运行时开销,编译器可做内联优化。
缺点:调用上略不直观(要传自己)。
方法 B --- std::function 捕捉法(先声明再赋)
cpp
function<int(int)>fab = [&](int n)->int
{
if (n <= 2)return 1;
else return fab(n - 2) + fab(n - 1);
};
cout << fab(5) << endl;
优点:写法直观,可直接 fib(n) 调用;
缺点:std::function 有类型擦除和可能的堆分配开销,频繁调用时显著慢。
深入:为什么传自身法快,而 std::function 慢?
传自身法的优点:
-
都是编译期确定的类型(闭包类型 / 模板类型),调用可被编译器直接内联或做静态优化。
-
无类型擦除、无间接函数指针跳转、无堆分配。
-
调用开销接近普通函数调用(甚至更好,取决于内联)。
std::function 慢的原因:
-
std::function是类型擦除(type-erasure):它通过内部统一的调用接口(函数指针 + void*)来调用任意可调用对象。每次调用都要做一次间接调用(类似虚函数跳转),阻止内联优化。 -
如果被包装对象不适合 Small-Buffer-Optimization(SBO),会发生堆分配,增加 malloc/free 开销。
-
因为运行期抽象,编译器无法做很多静态优化(比如内联、常量传播等)。
结论 :性能敏感或高频递归场景用传自身或 ;若是一次性简便实现、性能不是关键点,std::function 可以接受。
下面介绍lambda在set和map的简单写法
cpp
lambda在set与map中的用法
auto cmp = [](const pair<int,int>& a, const pair<int,int>& b)
{
return a.first > b.first;
};
set<pair<int, int>,decltype(cmp)>st(cmp);
st.insert({ 10,1 });
st.insert({ 11,12 });
st.insert({ 13,0 });
for (const auto &item:st)cout <<item.first<< endl;
auto cmp = [](const pair<int, int>& a, const pair<int, int>& b)
{
return a.first < b.first;
};
map<pair<int, int>, int, decltype(cmp)>mp(cmp);
mp[{1, 4}]++;
mp[{5, 1}]++;
mp[{-1, 2}]++;
for (auto item : mp)
{
cout << item.first.first << endl;
}