lambda表达式:
1:相比于c++老标准,c++新标准增加了lambda表达式。它的存在主要是为了替代函数对象。
可以看如下例子:比如说vector<string>
中存储了一系列字符串,现在我要统计在这个vector
中,长度分别大于等于0到10的字符串个数是多少个。在c++老标准中,我们需要先定义一个类,在这个类中重载调用操作符。代码如下:
//先定义类,在类中重载调用操作符;
class GT_cls{
public:
GT_cls(size_t val=0):bound(val){}
bool operator()(const string& s){ return s.size()>=bound;}
private:
size_t bound;
};
//输出在vector<string>这个对象中,长度分别大于等于0到10的字符串的个数
void print(const vector<string>& words)
{
for(int i=0;i<=10;++i)
cout<<"there are "<<count_if(words.begin(),words.end(),GT_cls(i))<<" words whose size is equal or larger than "<<i<<" characters."<<endl;
}
但是有了lambda表达式,我们就可以略去定义类的麻烦,实现相同功能的代码如下:
void print(const vector<string>& words)
{
for(int i=0;i<=10;++i)
cout<<"there are "<<count_if(words.begin(),words.end(),[i](const string& s)->bool{ return s.size()>=i))<<" words whose size is equal or larger than "<<i<<" characters."<<endl;
}
2: lambda表达式具有如下标准形式:
`[capture list](parameter list) -> return type { function body }`
在上面代码中定义的lambda表达式[i](const string& s) -> bool { return s.size()>=i)
就具有这种标准形式,其中 i
就是 capture list
, const string& s
就是 parameter list
,-> bool
就相当于 -> return type
,return s.size()>=i
就是 function body
。当然我们在实际写程序过程中,没必要严格按照这种标准形式,有些项可以省略,比如[i](const string& s) -> bool { return s.size()>=i)
可以写成[i](const string& s){ return s.size()>=i)
。
下面就lambda标准表达式中各组成成分分别加以详细解释:
(1):参数列表(通过参数列表向lambda表达式传递参数):
与一个普通函数类似,调用一个lambda表达式时给定的实参被用来初始化lambda的形参。通常实参和形参类型必须匹配,但与普通函数不同的是,lambda不能有默认参数,因此一个lambda调用的实参数目永远与形参数目相等。比如在这个语句中count_if(words.begin(),words.end(),[i](const string& s)->bool{ return s.size()>=i))
,向lambda表达式传递的参数是words
这个vector<string>
中存储的一系列字符串。
如果lambda不需要任何参数的话,参数列表可以略去;
(2):俘获列表:
lambda表达式可以出现在任何函数中,并且可以在表达式的函数体(function body) 部分使用该函数中的局部变量,但它只能使用那些明确指明的局部变量。一个lambda表达式通过将局部变量包含在其俘获列表中来指出将会使用这些局部变量。
比如在例子代码中,我们写出的lambda表达式会俘获函数中的局部变量i,函数体会将string的大小与i做比较。
void print(const vector<string>& words)
{
for(int i=0;i<=10;++i)
cout<<"there are "<<count_if(words.begin(),words.end(),[i](const string& s)->bool{ return s.size()>=i))<<" words whose size is equal or larger than "<<i<<" characters."<<endl;
}
但如果我们让俘获列表为空,写成[](const string& s)->bool{ return s.size()>=i)
则代码会编译错误,[]表明该lambda不俘获任何局部变量。对于参数列表来说,如果lambda没有任何参数,则参数列表那一项可以省略,但对于俘获列表来说,就算不俘获任何变量,俘获列表也不能略去,要写成[]的形式。
切记:一个lambda只有在其俘获列表中俘获了它所在函数的局部变量,才能够在函数体中使用该变量!
但同时我们也需要注意,我们没必要俘获命名空间中的变量(包括全局变量),因为这些变量总是可以得到的,如cout,cin这些,如下面代码例子所示:
void print(const vector<string>& words)
{
for_each(words.begin(),words.end(),[](const string& s){cout<<s<<" ";});//cout是全部变量,因此没必要被俘获。
cout<<endl;
}
类似于参数的传递,变量的俘获方式可以是值也可以是引用
i):值俘获:我们在上面例子中写出的lambda表达式是值俘获。与传值参数类似,采用值俘获的前提是变量可以拷贝,但与参数不同的是,被俘获的变量的值是在lambda创建时拷贝的,而不是调用时拷贝的。如下面例子所示:
void fcn1()
{
size_t v1=42;
auto f=[v1]{return v1;}; // 参数列表和返回类型被省略了。
v1=0;
auto j=f(); // j为42不是0,因为f保存了我们创建它是v1的拷贝;
}
(ii):引用俘获:如下面代码例子所示:
void fcn2()
{
size_t v1=42;
auto f=[&v1]{return v1;}; // 参数列表和返回类型被省略了。
v1=0;
auto j=f(); // j是0不是42!因为lambda表达式f是在引用v1;
}
(3):返回类型:
下面两种情况下,返回类型可以省略:如果一个lambda的函数体没有return语句,则lambda的返回类型默认是void;如果一个lambda的函数体仅仅包含了一个return 语句,则lambda的返回类型默认是那个return语句的返回类型。但是如果不是上述这两种情况,我们需要明确地指出返回类型。例子如下:
void g(double y)
{
auto z0=[&y]{f(y);}; // 返回类型是void;
auto z1=[y](int x){return x+y;}; //返回类型是double;
auto z2=[y]{if(f(y)) return 1; else return 2;} //compile-time error! 有两个return语句,不能推断出返回类型。
auto z3=[y]{ return f(y)?1:2; } 返回类型是int;
auto z4=[y]()->int{ if(f(y)) return 1; else return 2;} //okay!明确指定了返回类型。
3:可变lambda;
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值,如果我们希望能改变一个被俘获的变量的值,就必须在参数列表首加上关键字mutable。例子如下:
void fcn()
{
size_t v1=42;
auto f=[v1]() mutable { return ++v1; }
v1=0;
auto j=f() //j=43!