lambda表达式形式上比较让人难以理解,但假如我们把它整体当作一个对象,且该对象所对应的类型重载了()运算符。即把lambda表达式的调用与函数对象类比,会容易理解很多。
仿函数的极简形式如下:
//lambda表达式
[]{cout << "hello lambda" << endl;};
VS
//重载()的类
class Functor{
public:
void operator()(){
cout << "hello functor"<<endl;
}
};
众所周知,对象+()即可实现对()运算符函数的调用,故而:
...
//lambda表达式执行调用
[]{cout << "hello lambda" << endl; }();
...
//函数对象执行调用
Functor functor;
functor();
上面展示的lambda表达式的极简形式的使用非常简单,但实际上一般我们并不这样使用,我们需要在此基础上做如下操作:
注释:
auto:lambda表达式的返回值类型一般就直接写成关键字auto,即让编译器自己去推导,因为若要用户给出相对比较麻烦。
lambda:lambda表达式的别名,用户通过给出表达式别名方便后面的调用,调用形式就是别名+()
[…]:用以指明当前作用域内表达式对外部变量的访问权限及访问形式
[=]:表示可以访问当前作用域内表达式外部的所有变量,且都是以值的形式访问
[&]:表示可以访问当前作用域内表达式外部的所有变量,且都是以引用的形式访问
[x,&y]:表示可以访问当前作用域内表达式外部的变量x、y,且x是以值的形式进行访问,y以引用的形式访问
[x,&]:表示可以访问当前作用域内表达式外部的所有变量,其中x以值的形式访问,其他变量以引用的形式访问
其他形如[&y,=]、[x,y]、[&x,&y]等的意义可以类推。
注意:为了提高效率,我们一般使用引用的形式访问表达式外部的变量;根据最小权限原则,一般我们需要在表达式中使用外部的哪个变量就在[]中添加哪个变量,务必不要为了图方便大量使用[=]或[&]。
(…):指明向表达式中传入的参数(形参)
注意:在表达式的极简式里,()可以省略,那是因为一方面()中没有参数,另一方面()后面的三个可选参数也省略掉了。假如()后面的三个参数有一个没有省略,则无论()中是否有参数,()都不能省略。
mutable:辅助指明当前作用域内表达式对外部变量的访问形式
注意:这里的辅助是真的辅助。mutable的意义非常有限,主要用于当出现形式[=]、[x]这样以值的形式访问表达式外部变量时,加上mutable表明这些变量是可写的,不加mutable表明这些值是只读的。进一步地,通过[]访问外部变量我们可以将这些变量类比做类地静态成员变量,最大地相似点就是这些变量具有记忆性,即在表达式中这些变量的改变可以累积。但假如以[&x]、[&]的形式访问外部变量,加不加mutable都可以对外部的变量做改变。
throwSpec:指明表达式对异常的处理(throw)
这一点没什么好讲的,和普通函数的异常抛出没什么两样,通过throw(Type...)抛出指定类型的异常。
->returnType:指明向表达式外传出的参数类型(返回值)
这里我一直觉得很多余,因为lambda表达式最前面的auto其实已经承接了返回值的类型,所幸这个参数一般也是省略掉的
具体实例如下:
......
int b=0;
auto lambda=[b](int a)mutable throw() ->string{//[b]:以值的形式访问外界变量b,
//mutable:b在表达式内可写
//throw():不抛出任何类型的异常
//->string:表达式返回值为string类型
b+=1;//可写,且变化可以累积
cout << "b=" << b << endl;
string output="hello lambda";
for(int i=0;i<a;i++)
cout << output << endl;
return output;
};
......
//调用
lambda(3);//第一次调用,传参
lambda(2);//第二次调用,传参
cout << typeid(lambda).name() << endl;//lambda表达式相当于一个对象,其类型就是lambda类型
cout << "b="<<b << endl;//lambda表达式以值的形式访问b,b在表达式内部改变,外部不受影响
......
class Functor {
private:
static int b;
public:
auto operator()(int a) {
b += 1;
cout << "b=" << b << endl;
string output = "hello functor";
for (int i = 0; i<a; i++)
cout << output << endl;
return output;
}
};
int Functor::b = 0;
......
//调用
Functor functor;
functor(3);
functor(2);
大多时候,我们把lambda表达式当作仿函数的替代品来用,因为有时候它比仿函数使用起来更方便。
......
//复数类
class MyComplex {
private:
int real;
int image;
public:
MyComplex(int r, int i) :real(r), image(i) {}
int realValue()const { return real; }
int imageValue()const { return image; }
friend ostream& operator<<(ostream& os, const MyComplex& mc);
};
ostream& operator<<(ostream& os, const MyComplex& mc)
{
os <<"("<< mc.real<<","<<mc.image <<")"<< endl;
return os;
}
void main()
{
//使用lambda表达式指定排序规则
auto cmp = [](const MyComplex& p1, const MyComplex& p2) {//从大到小排序
return p1.realValue()>p2.realValue()||(p1.realValue()==p2.realValue()&&p1.imageValue()>p2.imageValue());
};
//set coll;//erro:没有对应构造函数
set<MyComplex, decltype(cmp)> coll(cmp);//创建set容器,并使用lambda表达式指定容器中元素的排序规则
//在容器中插入元素
coll.insert(MyComplex(1, 2));
coll.insert(MyComplex(3, 4));
coll.insert(MyComplex(5, 6));
for (MyComplex mc : coll) {
cout << mc;
}
}
......
在上面的例子中我们用lambda表达式指定排序规则,首先decltype获取lambda表达式的类型并作为模板参数,同时将lambda表达式作为实参传递给set容器类对象的构造函数,真是很神奇了。进一步地 ,我们来探究以下参数是怎么传的,这就需要看看set容器类地源码了:
template<class Key,class Compare=less<Key>,class Alloc=alloc>
class set{
public
......
typedef Compare key_compare;
typedef Compare value_compare;
private:
tyepdef rb_tree<key_type,value_type,identity<value_type>,key_compare,Alloc> rep_type;
rep_type t;//红黑树
public:
set():t(Compare()){}
explicit set(const Compare& comp):t(comp){}//上面例子中调用地就是这个构造函数
};
我们需要着重注意地是,set
可进一步说明lambda表达式虽然和普通的重载了()运算符的类很相似,但还是有一些区别的。
有一种说法是lambda表达式随对应的类型没有默认构造函数,也没有重载赋值运算符。若套用这一种说法,上面的情况就可以得到解释。我们虽然使用decltype获取了lambda表达式的类型,但却不可以通过类型+()调用构造函数创建一个匿名对象,这就是为什么无法调用构造函数set():t(Compare()){},因为Compare()相当于在创建匿名对象,这一点lambda表达式所对应的类型无法实现。
一般来说,都是先创建类,再创建对象。lambda表达式却正好相反,我们先有了lambda表达式对象,然后通过这个对象反推它的类。真是神奇了!!!
对于普通的重载了()运算符的类,若cmp为函数对象,则decltype(cmp)为其所对应的类型,那么set
vector<int> vec{ 1,5,2,7,4,9 };
int x = 3, y = 6;
vec.erase(remove_if(vec.begin(), vec.end(), [x, y](int n) {return x<n&&y>n; }), vec.end());
for (int v : vec) {
cout << v << " ";
}