lambda是c++0x提供的重大新特性。通过lambda表达式能很方便的创建简单的函数对象(你也可以创建复杂的函数对象,没人能阻止你),这在很多需要提供回调函数的场合非常有用。vc10及以上(gcc4.5及以上)版本的编译器都实现了lambda表达式。貌似c++11、c++14、c++17对lambda的规范有细微调整,这从侧面也能说明C++这些年在实现“现代化”方面很努力。(虽然有点晚了),很多时候我们无法选择使用哪个标准,因为项目中的编译器我们无法选择,所以,个人意见就是:大胆去用,看编译错误来调整。
1、hello,lambda
在C++0x中,lambda表达式隐式定义和构造无名函数对象(行为类似手写的函数对象):
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
template<typename T>
class print
{
public:
void operator()(T n)
{
cout<<n <<" ";
}
};
int main()
{
vector<int> vec;
for(int i=0; i<10; i++)
vec.push_back(i);
cout<<"print from lambda:";
for_each(vec.begin(), vec.end(), [](int n){cout<<n<<" ";});
cout<<endl<<"print from function object:";
print<int> p;
for_each(vec.begin(), vec.end(), p);
cout<<endl;
return 0;
}
程序运行结果:
print from lambda:0 1 2 3 4 5 6 7 8 9
print from function object:0 1 2 3 4 5 6 7 8 9
对于简单的处理,使用lambda能简化代码。
[]是lambda的前导符号,告诉编译器一个lambda表达式要开始了。(int n)是lambda的参数声明,而{cout<<n<<" ";}是无名函数对象被调用操作的函数体。
问题:如何主动调用lambda?
就跟你调用函数对象一样:
int num = [](int num)->int{return ++num;}(10);
cout<<num<<endl;
问题:如何“保存”已定义的lambda?
这涉及到c++0x的另外一个关键字auto,这里对它就不细说了。
auto lambda = [](int num)
{
cout<<++num<<endl;
};
lambda(10);
2、lambda的语法
前导 声明(可选) 大括弧(函数体)
(lambda-introducer lambda-delcarator compound-statement)
下面分别对其进行简单的说明:
lambda前导(lambda-introducer):
[lambda-capture(可选)],捕获(capture)参数是可选的,默认([])就是啥都不捕获
int num =10;
[]
{
cout<<num<<endl;//这里行不通
}();
如果想在lambda表达式内访问外部的数据,可使用以下方式传递变量:
[=]//值传递所有变量
[&]//引用所有变量
[var]//值传递变量
[&var]//引用传递变量
int num =10;
int another = 11;
[=]//所有传递的变量为const
{cout<<"num:"<<num<<" "<<"another:"<<another<<endl;}();
[&]//引用所有变量
{
num++;
another++;
}();
//num 和 another的值发生了改变
cout<<"num:"<<num<<" "<<"another:"<<another<<endl;
[num]//只值传递num变量
{
cout<<num<<endl;
cout<<another<<endl; //错误,封闭函数体内的局部变量如果不在捕获列表中就不能被引用
}();
[&num]//只引用传递num变量
{
num++;
another++;//错误,封闭函数体内的局部变量如果不在捕获列表中就不能被引用
}();
lambda声明(lambda-declarator):
( 参数列表 ) attribute-specifier(可选) mutable(可选)
exception-specification(可选) trailing-return-type(可选)
int num =10;
[=]()mutable//当使用mutable关键字时,可以对值传递的变量进行“写”操作
{cout<<++num<<endl;}();
cout<<num<<endl;//调用lambda后num 其实没变化
下面的lambda返回值为bool,判断两个参数是否都小于某个变量的l,throw()表示没有异常抛出:
bool f = [num](int a, int b) throw()->bool
{return num>a && num >b;}(9, 11);
需要注意的一点是,如果你指定了返回类型,那么必须在前导[]后面使用圆括弧,即使你没有参数声明。
[]()->bool{ return false;}//[]后面必须有()
对于lambda的语法,画了个图,很形象:
但还是建议大家参考一下:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2927.pdf
msdn也有形象的说明:
- 捕获子句
- 参数列表(可选)
- mutable关键字(可选)
- 异常关键字(可选)
- 返回类型(可选)(trailing-return-type)
- lambda函数体
3、注意事项
lambda前导中指定的捕获选项组合多变,但默认的捕获策略必须在前面,也就是说&、=只能在前面,特化的捕获在后面:
int fst =1;
int sec = 2;
int third = 3;
[=,&fst]{};//除了fst,别的值传递
[&, fst]{};//除了fst,别的引用传递
[&fst,sec,third]{};//等同[=,fst]
[&fst,sec,&third]{};//等同[&,sec]
[fst,=]{};//非法
[fst,&]{};//非法
lambda表达式可以嵌套,但嵌套在内的表达式不能捕获外层作用域内的局部变量(全局变量可以):
struct test
{
test(){num =0;}
int num;
void print()
{
cout<<num<<endl;
}
};
test g_o;
int main()
{
test local;
[&local]
{
local.num = 100;
[=](int num)->void
{
//local.num //错误,lambda体内不能引用外面作用域内的局部变量
g_o.num = num;
}(local.num);
}();
g_o.print();
local.print();
return 0;
}
关于mutable关键字:如果以值传递的方式传递变量,在表达式内是无法对这个变量进行修改的(或者调用此变量的非const成员函数,如下图的print函数):
如果不想以引用的方式来传递变量又想不受const限制,可以使用mutable来修饰表达式。