Lambda 函数与表达式
C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。
Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量和作为参数传递,还可以像函数一样对其求值。
Lambda 表达式本质上与函数声明非常类似。Lambda 表达式具体形式如下:
声明Lambda表达式
Lambda表达式完整的声明格式如下:
[capture list] (params list) mutable exception-> return type { function body }
capture list:捕获外部变量列表
params list:形参列表
mutable指示符:用来说用是否可以修改捕获的变量
exception:异常设定
return type:返回类型
function body:函数体
[capture](parameters)->return-type{body}
例如:
[](int x, int y){ return x < y ; }
如果没有返回值可以表示为:
[capture](parameters){body}
例如:
[]{ ++global_x; }
在一个更为复杂的例子中,返回类型可以被明确的指定如下:
[](int x, int y) -> int { int z = x + y; return z + x; }
本例中,一个临时的参数 z 被创建用来存储中间结果。如同一般的函数,z 的值不会保留到下一次该不具名函数再次被调用时。
如果 lambda 函数没有传回值(例如 void),其返回类型可被完全忽略。
在Lambda表达式内可以访问当前作用域的变量,这是Lambda表达式的闭包(Closure)行为。 与JavaScript闭包不同,C++变量传递有传值和传引用的区别。可以通过前面的[]来指定:
[] // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。
auto是c++程序设计语言的关键字。用于两种情况
(1)声明变量时根据初始化表达式自动推断该变量的类型
(2)声明函数时函数返回值的占位符
auto关键字更适用于类型冗长复杂、变量使用范围专一时,使程序更清晰易读。如:
std::vector<int> vect; for(auto it = vect.begin(); it != vect.end(); ++it){ //it的类型是std::vector<int>::iteratorstd::cin >> *it;}
或者保存lambda表达式类型的变量声明:
auto ptr = [](double x){return x*x;};//类型为std::function<double(double)>函数对象
四、优势
(1)拥有初始化表达式的复杂类型变量声明时简化代码。
比如:
#include <string>
#include <vector>
void loopover(std::vector<std::string>&vs)
{ std::vector<std::string>::iterator i=vs.begin(); for(;i<vs.end();i++) { } }
变为:
#include <string>
#include <vector>
void loopover(std::vector<std::string>&vs)
{ for( auto i=vs.begin();;i<vs.end();i++) { } }
使用std::vectorstd::string::iterator来定义i是C++常用的良好的习惯,但是这样长的声明带来了代码可读性的困难,因此引入auto,使代码可读性增加。并且使用STL将会变得更加容易
(2)可以避免类型声明时的麻烦而且避免类型声明时的错误。
但是auto不能解决所有的精度问题。比如:
#include <iostream>
using namespace std;
int main()
{ unsigned int a=4294967295;//最大的unsigned int值 unsigned int b=1; auto c=a+b; cout<<"a="<<a<<endl; cout<<"b="<<b<<endl; cout<<"c="<<c<<endl;
}
上面代码中,程序员希望通过声明变量c为auto就能解决a+b溢出的问题。而实际上由于a+b返回的依然是unsigned int的值,姑且c的类型依然被推导为unsigned int,auto并不能帮上忙。这个跟动态类型语言中数据hi自动进行拓展的特性还是不一样的。
五、注意的地方
(1)可以用valatile,pointer(*),reference(&),rvalue reference(&&) 来修饰auto
auto k = 5; auto* pK = new auto(k); auto** ppK = new auto(&k); const auto n = 6;
(2)用auto声明的变量必须初始化
(3)auto不能与其他类型组合连用
(4)函数和模板参数不能被声明为auto
(5)定义在堆上的变量,使用了auto的表达式必须被初始化
int* p = new auto(0); //fine
int* pp = new auto(); // should be initialized
auto x = new auto(); // Hmmm … no intializer
auto* y = new auto(9); // Fine. Here y is a int*
auto z = new auto(9); //Fine. Here z is a int* (It is not just an int)
(6)以为auto是一个占位符,并不是一个他自己的类型,因此不能用于类型转换或其他一些操作,如sizeof和typeid
(7)定义在一个auto序列的变量必须始终推导成同一类型
auto x1 = 5, x2 = 5.0, x3='r'; /<span style="font-family: Arial, Helvetica, sans-serif;">/错误,必须是初始化为同一类型</span>
(8)auto不能自动推导成CV-qualifiers (constant & volatile qualifiers)
(9)auto会退化成指向数组的指针,除非被声明为引用
1、值捕获
值捕获和参数传递中的值传递类似,被捕获的变量的值在Lambda表达式创建时通过值拷贝的方式传入,因此随后对该变量的修改不会影响影响Lambda表达式中的值。
int main()
{int a = 123;auto f = [a] { cout << a << endl; }; a = 321;f(); // 输出:123
}
这里需要注意的是,如果以传值方式捕获外部变量,则在Lambda表达式函数体中不能修改该外部变量的值。
2、引用捕获
使用引用捕获一个外部变量,只需要在捕获列表变量前面加上一个引用说明符&。如下:
int main()
{int a = 123;auto f = [&a] { cout << a << endl; }; a = 321;f(); // 输出:321
}
从示例中可以看出,引用捕获的变量使用的实际上就是该引用所绑定的对象。
3、隐式捕获
上面的值捕获和引用捕获都需要我们在捕获列表中显示列出Lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。
隐式值捕获示例:
int main()
{int a = 123;auto f = [=] { cout << a << endl; }; // 值捕获f(); // 输出:123
}
隐式引用捕获示例:
int main()
{int a = 123;auto f = [&] { cout << a << endl; }; // 引用捕获a = 321;f(); // 输出:321
}