8.lambda表达式
一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。 与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。
但与函数不同,lambda可能定义在函数内部。 一个lambda表达式具有如下形式:
[capture list](parameter list) -> return type {
function body
}
其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空)。 return type、parameter list和function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。 但是,与普通函数不同,lambda必须使用尾置返回来指定返回类型。
我们可以忽略参数列表和返回类型,但必须包含捕获列表和函数体:
auto f = [] {return 42; };
此例中,我们定义了一个可调用对象f,它不接受参数。
lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符:
cout << f() << endl;
在lambda中忽略括号和参数列表等价于指定一个空参数列表。在此例中,当调用f时,参数列表是空的。
如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。
如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来。否则,返回类型为void。
如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void。
例如:
[] (const string &a, const string &b) {
return a.size() < b.size();
}
空捕获列表表明此lambda不使用它所在函数中的任何局部变量。 当stable_sort需要比较两个元素时,它就会调用给定的这个lambda表达式:
stable_sort(words.begin(), words.end(), [] (const string &a, const string &b) {
return a.size() < b.size();
}
使用捕获列表
虽然一个lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。 一个lambda通过将局部变量包含在其捕获列表中来指出将会使用这些变量。 捕获列表指引lambda在其内部包含访问局部变量所需的信息。 在本例中,我们的lambda会捕获sz,并只有单一的string参数。起函数体会将string的大小与捕获的sz的值进行比较:
void biggies(vector<string> &words, vector<string>::size_type sz) {
auto wc = find_if(words.begin(), words.end(), [sz] (const string &a) {
return a.size() >= sz;
}
}
值捕获
类似参数传递,变量的捕获方式也可以是值或引用。
与传值参数类似,采用值捕获的前提是变量可以拷贝。 与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝:
void fcn1() {
size_t v1 = 42; // 局部变量
// 将v1拷贝到名为f的可调用对象
auto f = [v1] { return v1; };
v1 = 0;
auto j = f(); // j为42,f保存了我们创建它时v1的靠背
由于被捕获变量的值是在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值。
引用捕获
我们定义lambda时可以采用引用方式捕获变量,例如:
void fcn2() {
size_t v1 = 42;
// 对象f2包含v1的引用
auto f2 = [&v1] { return v1; };
v1 = 0;
auto j = f2(); // j为0,f2保存v1的引用,而非拷贝
v1之前的&指出v1应该以引用方式捕获。 一个以引用方式捕获的变量与其他任何类型的引用的行为类似。当我们在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象。 在本例中,当lambda返回v1时,它返回的是v1指向的对象的值。
隐式捕获
除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体重的代码来推断我们要使用哪些变量。
为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。
- &告诉编译器采用捕获引用方式
- =则表示采用值捕获方式。
例如,可以重写传递给find_if的lambda:
// sz为隐式捕获,值捕获方式
wc = find_if(words.begin(), words,end(), [=](const string &s) {
return s.size() >= sz;
});
如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获:
```c++
void biggers(vector<string> &words, vector<string>::size_type sz,
ostream &os = cout, char c = '') {
// os隐式捕获,引用捕获方式;c显式捕获,值引用方式
for_each(words.begin(), words.end(), [&, c](const string &s) {
os << s << c;
});
}
返回值
当我们需要为一个Lambda定义返回类型时,必须使用尾置返回类型:
transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int {
if (i < 0) {
return -i;
} else {
return i;
}
});
在此例中,传递给transform的第四个参数是一个lambda,它的捕获列表是空的,接受单一int参数,返回一个int值。