C++11——lambda表达式详解

引入

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表达式构造

上面展示的lambda表达式的极简形式的使用非常简单,但实际上一般我们并不这样使用,我们需要在此基础上做如下操作:

  • 为表达式起别名(为了方便调用,就像普通函数的函数名一样)
  • 指明向表达式中传入的参数(形参)
  • 指明向表达式外传出的参数类型(返回值)
  • 指明表达式对异常的处理(throw)
  • 指明当前作用域内表达式对外部变量的访问权限以及访问形式
    其中1、2、3、4点普通函数也都有对应,且意义基本相同,唯一特别的是第5点。针对这五点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在表达式内部改变,外部不受影响

在这里插入图片描述
VS

......
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表达式的应用

大多时候,我们把lambda表达式当作仿函数的替代品来用,因为有时候它比仿函数使用起来更方便。

应用1:

......
//复数类
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<MyComplex, decltype(cmp)> 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<MyComplex, decltype(cmp)> coll(cmp) 调用构造函数explicit set(const Compare& comp):t(comp){}set<MyComplex, decltype(cmp)> coll却无法调用构造函数set():t(Compare()){}
可进一步说明lambda表达式虽然和普通的重载了()运算符的类很相似,但还是有一些区别的。
有一种说法是lambda表达式随对应的类型没有默认构造函数,也没有重载赋值运算符。若套用这一种说法,上面的情况就可以得到解释。我们虽然使用decltype获取了lambda表达式的类型,但却不可以通过类型+()调用构造函数创建一个匿名对象,这就是为什么无法调用构造函数set():t(Compare()){},因为Compare()相当于在创建匿名对象,这一点lambda表达式所对应的类型无法实现。
一般来说,都是先创建类,再创建对象。lambda表达式却正好相反,我们先有了lambda表达式对象,然后通过这个对象反推它的类。真是神奇了!!!
对于普通的重载了()运算符的类,若cmp为函数对象,则decltype(cmp)为其所对应的类型,那么set<MyComplex, decltype(cmp)> coll就可以编译通过,因为它会调用构造函数set():t(Compare()){}

应用2:

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 << " ";
	}

在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值