\[\] 什么都不捕捉
x x 按值捕捉
\&x x 按引用捕捉
= 默认全部按值捕捉
\& 默认全部按引用捕捉
=, \&x 默认值捕捉,但 x 引用捕捉
\&, x 默认引用捕捉,但 x 值捕捉
OLambda 最大的意义:减少"样板代码"
这个词你以后会经常遇到:
样板代码(boilerplate code)
比如为了实现一个简单比较:
cpp
class GreaterThan
{
public:
GreaterThan(int x)
: _x(x)
{}
bool operator()(int val) const
{
return val > _x;
}
private:
int _x;
};
实际上你真正想表达的逻辑只有:
return val > x;
Lambda 让你直接写这个逻辑:
[x](int val)
{
return val > x;
}
所以 Lambda 的核心价值不是:
"我比仿函数更强。"
而是:
"我不想为了一个临时的小逻辑,专门创建一个类。"
4. 你可以把它理解成"匿名仿函数"
这个关系非常重要:
仿函数
↓
需要自己写一个类
↓
需要自己写 operator()
↓
创建对象
↓
使用
而 Lambda:
Lambda
↓
编译器帮你生成那个匿名类
↓
编译器帮你生成 operator()
↓
编译器帮你创建对象
↓
你直接使用
find_if 怎么找到大于 3 的数?"
因为它内部:
while (first != last)
{
if (pred(*first))
return first;
++first;
}
↓
"Lambda 在里面干什么?"
它只负责:
return x > 3;
对,你现在问到的是 Lambda 最核心的一个问题:
auto f = [x](int y){ return x + y; };
这里 Lambda 到底是什么东西 ?为什么可以直接赋值给 auto f,而不是非得传给 sort、find_if 这种函数?
答案是:
Lambda 表达式本身会产生一个"函数对象",这个对象可以先被保存到变量里。
auto只是帮你推导这个匿名对象的类型。
我们一步一步来。
1. 先看最普通的变量赋值
你熟悉:
int x = 10;
这里发生了什么?
10
↓
一个 int 类型的值
↓
保存到 x
所以:
cout << x;
就是使用这个变量。
现在看:
auto f = [x](int y)
{
return x + y;
};
这里的思想其实非常类似:
Lambda表达式
↓
产生一个函数对象
↓
这个对象保存到 f
所以 f 不是一个普通函数。
f 是一个对象。
2. 这就是为什么 auto 非常重要
你自己根本没办法轻松写出这个 Lambda 的类型。
比如:
auto f = [x](int y)
{
return x + y;
};
这个 Lambda 的类型是编译器生成的一个唯一的、匿名的闭包类型(closure type)。
你不知道它叫什么。
所以你没法写:
??? f = [x](int y)
{
return x + y;
};
这个 ??? 是什么?
你不知道。
所以 C++ 给了你:
auto
让编译器自己推导。
也就是:
auto f = Lambda对象;
编译器自己知道:
f 的类型 = 这个 Lambda 对应的匿名类型
3. 你可以把它粗略翻译成一个类
比如:
int x = 10;
auto f = [x](int y)
{
return x + y;
};
编译器可以近似理解为:
class Lambda
{
public:
Lambda(int x)
: _x(x)
{}
int operator()(int y) const
{
return _x + y;
}
private:
int _x;
};
然后:
Lambda f(x);
注意:
这里的 Lambda 只是为了帮助你理解。
真实情况下编译器生成的类没有这个名字给你使用。
所以我们才写:
auto f = ...
4. 那 f 到底是什么?
现在非常明确:
int x = 10;
auto f = [x](int y)
{
return x + y;
};
可以理解成:
f
│
│ 是一个对象
↓
┌─────────────────┐
│ Lambda对象 │
│ │
│ _x = 10 │
│ │
│ operator()(y) │
└─────────────────┘
所以:
f(20);
为什么可以调用?
因为:
f(20);
本质:
f.operator()(20);
最后:
return 10 + 20;
得到:
30
5. 那为什么 Lambda 不一定非得传给 sort?
这个问题非常好。
因为 Lambda 本身不是"只能传给 STL 函数的东西"。
它就是一个对象。
既然是对象,那么当然可以:
直接保存
auto f = [](int x)
{
return x * 2;
};
直接调用
cout << f(10);
传给其他函数
someFunction(f);
直接作为参数
sort(v.begin(), v.end(),
[](int a, int b)
{
return a > b;
});
这些都是可以的。
6. 你可以把 Lambda 想成"临时制造对象"
比如:
auto f = [](int x)
{
return x * 2;
};
就像你写:
class A
{
public:
int operator()(int x)
{
return x * 2;
}
};
A f;
然后:
f(10);
所以 Lambda 只是帮你省掉了:
class A
{
public:
...
};
这些麻烦的东西。
7. 为什么传给 sort 的时候看起来不一样?
你之前看到:
sort(v.begin(), v.end(),
[](int a, int b)
{
return a > b;
});
这里 Lambda 没有写:
auto f =
是不是感觉不一样?
其实完全一样。
你完全可以写成:
auto cmp = [](int a, int b)
{
return a > b;
};
sort(v.begin(), v.end(), cmp);
这两个是一个意思。
写法一:直接传
sort(v.begin(), v.end(),
[](int a, int b)
{
return a > b;
});
相当于:
创建一个 Lambda 对象
↓
直接把对象传给 sort
写法二:先保存
auto cmp = [](int a, int b)
{
return a > b;
};
sort(v.begin(), v.end(), cmp);
相当于:
创建 Lambda 对象
↓
保存到 cmp
↓
把 cmp 传给 sort
所以你现在应该明白:
Lambda 可以直接传,也可以先保存。
8. 这和普通函数也很像
比如:
int Add(int a, int b)
{
return a + b;
}
你可以直接:
cout << Add(1, 2);
也可以把函数的地址传给别的函数。
Lambda 则更像是:
"我现场造一个函数对象。"
然后你可以:
现场使用
↓
或者
↓
先保存下来
↓
以后再使用
9. 最关键的区别:Lambda 是"表达式"
你现在看到:
[x](int y)
{
return x + y;
}
不要把它单纯看成:
"这是一个函数。"
更准确地说:
这是一个 Lambda 表达式,它产生一个闭包对象。
所以:
auto f = [x](int y)
{
return x + y;
};
可以理解成:
Lambda表达式
↓
创建闭包对象
↓
赋值给 f
而这个闭包对象内部:
保存 x
+
拥有 operator()
所以它既:
有状态(捕捉的变量)
又:
可以像函数一样调用
这就是 Lambda 的厉害之处。
10. 你现在把这两个例子对照看
直接给 sort
sort(v.begin(), v.end(),
[](int a, int b)
{
return a > b;
});
相当于:
创建 Lambda 对象
↓
直接交给 sort
先保存
auto cmp = [](int a, int b)
{
return a > b;
};
sort(v.begin(), v.end(), cmp);
相当于:
创建 Lambda 对象
↓
保存到 cmp
↓
cmp 本身就是一个函数对象
↓
交给 sort
你现在最应该记住这一句话 🧠
Lambda 不是"只能传给函数的东西",Lambda 表达式本身会创建一个匿名的函数对象。这个对象可以被
auto保存,也可以直接作为参数传给sort、find_if等函数。
所以:
auto f = [x](int y)
{
return x + y;
};
实际上就是:
Lambda表达式
↓
创建一个匿名对象
↓
┌─────────────┐
│ f │
│ │
│ 保存 x │
│ operator() │
└─────────────┘
↓
f(20)
↓
f.operator()(20)
这一下你就把 "Lambda → 闭包对象 → auto → 仿函数 → operator()" 全串起来了。这个链条理解了,C++11 Lambda 基本就真正入门了。