您的位置:首页 > 编程语言 > C语言/C++

C++11之lambda表达式——C++ Primer

2016-11-27 11:18 218 查看
lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与其他函数类似,lambda具有返回类型、参数列表和函数体。但与函数不同,lambda可能定义在函数内部。lambda表达式具有如下形式:

[capture list] (parameter lisi) -> return type { function body }

其中,capture list(捕获列表)是lambda所在函数中定义的局部变量的列表(通常为空),return type、parameter list和function body与普通函数一样,分别表示返回类型、参数列表和函数体。但是,与普通函数不同,lambda必须使用尾置返回来指定返回类型。参数列表和返回类型可以省略,但必须包含捕获列表和函数体。lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符:()。

在lambda中忽略括号和参数列表等价于指定一个空参数列表。如果忽略返回类型,lambda会根据函数体中的代码推断出返回类型。如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来。否则,返回类型为void。(但是现在编译器也在改进,有些语句也能推断,不绝对,一般来讲,多语句时最好明确指定返回类型)。

与普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。通常,实参和形参的类型必须匹配。但与普通函数不同,lambda不能有默认参数。因此,一个lambda调用的实参永远与形参数目相等。

lambda可以使用其所在函数中的局部变量,但它只能使用那些在捕获列表中明确指明的变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。

我们只对lambda所在函数中定义的(非static)变量使用捕获列表。一个lambda可以直接使用定义在当前函数之外的变量,只要作用域允许。捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的变量。

类似参数传递,变量的捕获方式也可以是值或引用。与传值参数类似,采用值捕获的前提是变量可以拷贝,不同的是,被捕获的变量的值是在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内对应的值。

引用捕获:

void fcn2()
{
size_t v1 = 42; // 局部变量
// 对象f2包含v1的引用
auto f2 = [&v1] { return v1; };
v1 = 0;
auto j = f2(); // j为0;f2保存v1的引用,而非拷贝
}
&指出v1以引用方式捕获。一个以引用方式捕获的变量与其他任何类型的引用的行为类似。当我们在lambda函数体内使用此变量时,实陈上使用的是引用所绑定的对象。如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。

函数的返回值可以是一个lambda。如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda也不能包含引用捕获。

建议:尽量保持lambda的变量捕获简单化

lambda捕获从lambda被创建(即,定义lambda的代码执行时)到lambda自身执行(可能有多次执行)这段时间内保存的相关信息,要确保lambda每次执行的时候这些信息都有预期的意义。

捕获一个普通变量,如int、string或其他非指针类型,通常可以采用简单的值捕获方式。在此情况下,只需关注变量在捕获时是否有我们所需的值就可以了。

如果我们捕获一个指针或迭代器,或采用引用捕获方式,就必须确保在lambda执行时,绑定到迭代器、指针或引用的对象仍然存在。而且,需要保证对象具有预期的值。在lambda从创建到它执行的这段时间内,可能有代码改变绑定的对象的值。也就是说,在指针(或引用)被捕获的时刻,绑定的对象的值是我们所期望的,但在lambda执行时,该对象的值可能已经完全不同了。

一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且,如果可能的话,应该避免捕获指针或引用。

除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。为了让编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用引用捕获方式,=则表示采用值捕获方式。

如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获,此时捕获列表中的第一个元素必须是一个&或=。此符号指定了默认捕获方式为引用或值。

当混合使用隐式捕获和显式捕获时,显式捕获的变量必须使用与隐式捕获不同的方式。即,如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,因此不能在其名字前使用&。类似的,如果隐式捕获采用的是值方式(使用了=),则显式捕获命名变量必须采用引用方式,即在名字前使用&。

lambda捕获列表:

[]  空捕获列表。lambda不能使用所在函数中的变量。只有捕获后才能使用它们。

[names]  names是—个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量。默认情况下,捕获列表中的变量都被拷贝。名字前如果使用了&,则采用引用捕获方式。

[&]  隐式捕获列表,lambda中使用到的所在函数的局部变量都采用引用捕获方式。

[=]  隐式捕获列表,采用值捕获方式。lambda体将拷贝使用到的来自所在函数的变量的值。

[&, names]  names是一个逗号分隔的列表,包含0个或多个来自所在函数的变量。这些变量采用值捕获方式,而任何隐式捕获的变量都采用引用方式捕获。names中的名字前面不能使用&。

[=, names]  names中的变量都采用引用方式捕获,而任何隐式捕获的变量都采用值方式捕获。names中不能包括this,且这变量之前必须使用&。

可变lambda

默认情况下,值捕获时,lambda不能改变其捕获的变量的值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表后加上关键字mutable。一个引用捕获的变量是否可以修改依赖于此引用指向的是否是const类型。

指定lambda返回类型

默认情况下,如果一个lambda体包含return以外的任何语句,则编译器假定此lambda返回void。(现在好像编译器有改进)。当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型。

对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该定义一个函数,而不是多次编写相同的lambda表达式。如果一个操作需要很多语句才能完成,通常使用函数更好。

 

标准库bind函数

它定义在头文件functional中。可以将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

调用bind的一般形式为:

auto newCallable = bind(callable, arglist);

其中,newCallable本身是一个可调用对象,arglist是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arglist中的参数。

arglist中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,依次类推。_n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。为了使用这些名字,两个命名空问都要写上。当然可以using整个命名空间。与bind函数一样,placeholders命名空间也定义在functional头文件中。

可以用bind绑定给定可调用对象中的参数或重新安排其顺序。

默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是,与lambda类似,有时对有些绑定的参数我们希望以引用方式传递,或是要绑定参数的类型无法拷贝。如果我们希望传递给bind一个对象而又不拷贝它,就必须使标准库ref函数。函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库中还有一个cref函数,生成一个保存const引用的类。与bind一样,函数ref和cref也定义在头文件functional中。

旧版本C++提供的绑定函数参数的语言特性限制更多,也更复杂。标准库定义了两个分别名为bind1st和bind2nd的函数。类似bind,这两个函数接受一个函数作为参数,生成一个新的可调用对象,该对象调用给定函数,并将绑定的参数传递给它。但是,这些函数分别只能绑定第一个或第二个参数。由于这些函数局限太强,在新标准中已被弃用。所谓被弃用的特性就是在新版本中不再支持的特性。新的C-++程序应该使用bind。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: