前言
很多语言都有lambda, c++自然不能缺, 在c++11里面加入了, 是程序猿喜欢的语法糖, 便于阅读, 也便于理解. 当然, 它有很多相关概念, 这里尽可能展开说.
lambda表达式
[capture list] (params list) mutable exception-> return type { function body }
- 捕获子句(在c++规范中也称为lambda引导)
- 参数列表可有可无. (也称为lambda 声明符)
- 可变规范可有可无.
- 异常规范可有可无.
- 尾随-返回类型可有可无.
- lambda体, 也就是函数体.
你会觉得和函数声明很像, 但是注意, 不可以有默认值, 也不支持可变参数, 类似printf, 参数必须要有参数名. 甚至你会觉得和python3很像, 尤其是这个->return type, 所以你看, c++也越来越友好了.
先来几个小栗子:
auto f = [](int a, int b) { return a + b; };
cout << f(1, 5) << endl;
可以看到a, b是形参, 1, 5是实参, f似乎是函数, 这个放到后面说.
int x = 3, y = 5;
auto f2 = [x, y] { return x + y; };
cout << f2() << endl;
然后你发现, lambda更神奇的地方在于, 可以捕获外界参数, 是不是很酷, 其实这里本质还是值传递, 当然后面有引用, 但是默认是值传递.
参数捕获
捕获形式 | 说明 |
---|---|
[] | 不捕获任何外部变量 |
[var1,var2, …] | 默认以值得形式捕获指定的多个外部变量(用逗号分隔) |
[this] | 以值的形式捕获this指针 |
[=] | 以值的形式捕获所有外部变量 |
[&] | 以引用形式捕获所有外部变量 |
[=, &var1] | 变量var1以引用形式捕获,其余变量以传值形式捕获 |
[&, var1] | 变量var1以值的形式捕获,其余变量以引用形式捕获 |
很多都是一眼懂, 举个栗子吧:
int x = 1, y = 2, z = 3;
cout << "before: " << x << ' ' << y << ' ' << z << endl;
auto f = [&, x]()mutable {
x = 5;
y = 6;
z = 7;
cout << "in: " << x << ' ' << y << ' ' << z << endl;
};
f();
cout << "after: " << x << ' ' << y << ' ' << z << endl;
before: 1 2 3
in: 5 6 7
after: 1 6 7
x是值传递, y, z是引用传递, 值传递实际上是不能修改的, 但是这里加了mutable, 所以可以在函数体内进行改动. 然后引用传入, 会对外部产生影响, 值传入则不会, 很好理解.
this
甚至可以传入this指针.
class A {
public:
explicit A(int d) : data(d) {}
void foo(vector &v) {
for_each(v.begin(), v.end(), [this](int i) {
cout << "before: " << i << endl;
i += data;
cout << "after: " << i << endl;
});
}
private:
int data;
};
vector v{1, 2, 3};
A a(3);
a.foo(v);
before: 1
after: 4
before: 2
after: 5
before: 3
after: 6
传入this指针之后, 就可以使用成员变量.
function
然后你会发现一个问题, 我这里写的都是auto, 那具体是什么呢?
function f = [](int a, int b) { return a + b; };
返回一开始的栗子, 看到function里面写了函数的返回值, 然后括号里面是参数类型.
std::function是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。
目的肯定是取代函数指针的, 来看一个栗子:
function add = [](int a, int b) { return a + b; };
cout << add(1, 5) << endl;
function div = [](int a, int b) { return a / b; };
cout << div(5, 1) << endl;
想想之前的写法, 是不是用宏定义一个函数指针, 类似
typedef int(*fp)(int, int);
, 是不是很像, 用function的话, 整体也更直观好操作.
bind
曾经我被问到, 如何实现lambda获取外部参数的功能, 那答案就是利用bind, 先来看个栗子:
function div = [](int a, int b) { return a / b; };
cout << div(5, 1) << endl;
auto divEx = bind(div, placeholders::_2, placeholders::_1);
cout << divEx(5, 1) << endl;
5
0
是不是挺酷的, 什么都没改, 就把参数位置换了.
A a;
auto ret = bind(&A::f, &a, 5, placeholders::_1);
cout << ret(3) << endl;
之前也说了, lambda是不支持默认参数的, 但是通过bind+占位符, 就很好实现了这个点, 甚至可以随意调整参数位置. 而且可以绑定成员函数, 不过你需要传入具体实例在第二个参数.
void print(ostream &os, string &str, char space) {
os << str << space;
}
ostringstream os;
vector data{"cpp", "c", "java"};
char space = ' ';
for_each(data.begin(), data.end(), bind(print, ref(os), placeholders::_1, space));
cout << os.str() << endl;
os.str("");
for_each(data.begin(), data.end(), [&os, space](string &str){os<< str << space;});
cout << os.str() << endl;
os.str("");
auto printL=[&os, space](string&str){os<
最后对比下bind和lambda, 当然二者是可以配合使用的, 怎么用还是看自己. 我的话, 怎么简洁怎么用.
ref简单说下, 主要是有些变量不支持值传递, bind这里就要写成ref(os).
最后
真的是很实用的点, 让c++灵活了很多. 就像设计了while之后, 又设计了for, 设计了for之后又设计了for_each, 语言都是不断发展的, 学习新的真的很有必要.