根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数。但是,有时我们希望进行的操作需要更多参数,超出了算法对谓词的限制。这就需要用到lambda表达式。
我们可以向一个算法传递任何类别的可调用对象(callable object)。对于一个对象或者表达式,如果可以对其使用调用运算符,则称它为可调用的。即,如果e是一个可调用的表达式,则我们可以编写代码e(args),其中args是一个逗号分隔的一个或多个参数的列表。
四种可调用对象:函数,函数指针,重载了函数调用运算符的类和lambda表达式。
一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型,一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部,一个lambda表达式具有如下形式:
[capture list](parameter list)->return type{function body}
其中capture list是一个lambda所在函数中定义的局部变量的列表(通常为空);return type和function body与任何普通函数一样分别表示返回类型,参数列表和函数体。但是,与普通函数不同,lambda必须使用尾置返回来指定返回类型。我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。
auto f = []{return 42;}
cout << f() << endl;
在lambda中忽略括号和参数列表等价于指定一个空参数列表。如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来。如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void.
与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。通常,实参和形参的类型必须匹配。但与普通函数不同,lambda不能有默认参数。因此,一个lambda调用的实参数目永远与形参数目相等。一旦形参初始化完毕,就可以执行函数体了。下面是一个与isShorter函数完成相同功能的lambda:
[](const string &a, const string &b) {return a.size() < b.size(); }
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b) {return a.size() < b.size();});
一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。
其实,当定义一个lambda时,编译器生成一个与lambda相对应的未命名类的未命名对象,其实这是一个函数对象。捕获列表中获得的局部变量成为了这个函数对象的数据成呀un。默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员,类似任何类的数据成员,lambda的数据成员也在lambda对象创建时被初始化。
类似参数传递,变量的捕获方式也可以是值或引用。首先来说值捕获,与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。随后对其修改不会影响到lambda内对应的值。接着来说引用捕获,引用捕获与引用返回有着相同的问题和限制,如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。lambda捕获的是局部变量,这些变量在函数结束后就不复存在了。如果lambda可能在函数结束后执行,捕获的引用指向的局部变量已经消失。所以建议是尽量保持lambda的变量捕获简单化。
除了显示列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获:
[&, c]... [=, &os]...
当我们混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个&或=。此符号指定了默认捕获方式为引用或值。当混合使用隐式捕获和显式捕获时,显式捕获的变量必须使用与隐式捕获不同的方式。
关于可变lambda,默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表后面加上关键字mutable。因此,可变lambda不能省略参数列表:
void fcn3()
{
size_t v1 = 42; // local variable
// f can change the value of the variables it captures.
auto f = [v1] () mutable {return ++v1;}
v1 = 0;
auto j = f(); // j is 43
}
上例中如果不加() mutable就会出现错误信息error: increment of read-only variable ‘v1’
一个引用捕获变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型:
void fcn4()
{
size_t v1 = 42; // local variable
// v1 is a reference to a nonconst variable
// we can change that variable through the reference inside f2
auto f2 = [&v1] () {return ++v1;}
v1 = 0;
auto j = f2(); // j is 1
}
关于lambda表达式的返回类型,先看一个例子:
transform(vi.begin(), vi.end(), vi.begin(), [](int i) {return i < 0 ? -i : i; });
只有一个return语句,返回一个条件表达式的结果。我们无须指定返回类型,因为可以根据条件运算符的类型推断出来。但是,如果我们将程序改写为看起来是等价的if语句,就会产生编译错误:
//错误:不能推断lambda的返回类型
transform(vi.begin(), vi.end(), vi.begin(), [](int i){ if(i < 0) return -i; else return i;});
编译器推断这个版本的lambda返回类型为void,但它返回了一个int值。
当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型:
transform(vi.begin(), vi.end(), vi.begin(),
[](int i) -> int { if(i < 0) return -i; else return i;});
网友评论