lambda表达式

借鉴自:《c++ primer》

一个lambda表达式表示一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数,与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体,但与函数不同,lambda可能定义在函数内部,而且lambda必须使用尾置返回

lambda表达式形式:

[capture list] (parameter list) opt-> return type {function body}

1. capture list:捕获列表,是一个lambda所在函数中定义的局部变量的列表(通常为空)

2. parameter list:参数列表

3. opt是函数选项,可以填写mutable,exception,attribute(选填写)

mutable:说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法
exception:说明lambda表达式是否抛出异常以及何种异常
attribute:用来声明属性

4.return type:返回类型

5.function body:函数体

我们可以忽略参数列表返回类型、函数选项(和参数列表共存),但必须永远包含捕获列表函数体,忽略括号和参数列表等价于指定一个空参数列表,忽略返回类型lambda根据函数体中的代码推断出返回类型,如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void

	auto f = [] {return 42; };
	cout << f() << endl;  //42

lambda表达式和普通函数的区别

1. lambda可能定义在函数内部
2. lambda必须使用尾置返回
3. lambda不能有默认参数(因此,一个lambda调用的实参数目永远与形参数目相等)

捕获列表

1.[]不捕获任何变量。
2.[&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)
3.[=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)
4.[=,&foo] [&,foo] 混合捕获
5.[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量

一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量(非static),才能在函数体中使用该变量。捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字。

	//错误:sz未捕获
	[](const string& a)
	{return a.size() >= sz; };
int i = 100;  //全局变量

int main()
{
	//int i = 100; 在内部定义i,则不能在lambda中使用i
	[](string& s)
	{return s.size() >= i; };
}
int main()
{
	static int i = 100;
	[](string& s)
	{return s.size() >= i; };  //可以使用static修饰的i
}

值捕获

值捕获的方式与传值参数类似,采用值捕获前提变量可以拷贝,与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝因此随后对其修改不会影响到lambda内对应的值

void foo()
{
	size_t v1 = 42;  //局部变量
	//将v1拷贝到名为f的可调用对象
	auto f = [v1] {return v1; };
	v1 = 0;
	auto j = f(); //j为42 f保存了我们创建它时v1的拷贝
}

引用捕获

一个以引用方式捕获的变量与其他任何类型的引用的行为类似,当我们在lambda函数体内使用此变量时,实际上使用的是引用所用绑定的对象,引用捕获与返回引用有着相同的问题和限制,如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的, lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了,如果lambda可能在函数结束后执行,捕获的引用指向的局部变量就已经消失(指针捕获同理)。一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型。

void foo()
{
	size_t v1 = 42;  //局部变量
	//对象f2包含v1的引用
	auto f = [&v1] {return ++v1; };
    cout<< v1 <<endl;  //43
	v1 = 0;
	auto j = f(); //j为1 f保存v1的引用,而非拷贝
}
void biggies(vector<string>& words, vector<string>::size_type sz, ostream& os = cout, char c =' ')
{
	//打印count的语句改为打印到os
	for_each(words.begin(), words.end(), [&os, c](const string& s) {os << s << c; }); //向函数传递一个lambda
}

我们不能拷贝ostream对象,因此捕获os的唯一方法就是捕获其引用(或指向os的指针)

返回的lambda不能包含引用捕获(悬挂引用)

我们可以向一个函数传递一个lambda,也可以从一个函数返回lambda,如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda也不能包含引用捕获。因为引用捕获不会延长引用的变量的生命周期。

std::function<int(int)> add_x(int x)
{
    return [&](int a) { return x + a; };
}

隐式捕获

除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量,为了指示编译器推断捕获列表,应该捕获列表中写一个&或=。&告诉编译器采用捕获引用方式,=则表示采用值捕获方式

int main()
{
	int sz = 10;
	auto f = [=](string& s) {return s.size() > sz; };
}

混合捕获(混合使用隐式捕获和显式捕获)

如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获,当混合使用隐式捕获和显式捕获时捕获列表中的第一个元素必须是一个&或=此符号指定了默认捕获方式为引用或值且显式捕获的变量必须使用与隐式捕获不同的方式

void biggies(vector<string>& words, vector<string>::size_type sz, ostream& os = cout, char c = ' ')
{
	//os隐式捕获  c显式捕获
	for_each(words.begin(), words.end(), [&, c](const string& s) {os << s << c; });
	//os显式捕获,c隐式捕获
	for_each(words.begin(), words.end(), [=, &os](const string& s) {os << s << c; }); //向函数传递一个lambda
}

捕获this指针

捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限

class A
{
public:
	int _a = 0;

	void func(int x, int y)
	{
		auto f = [] { return _a; }; //错误 没有捕获外部变量
		auto f1 = [this] { return _a; };             
		auto f2 = [this, x, y] { return _a + x + y; };  
		auto f3 = [this] { return ++_a; };     
		cout << f3() << endl; //1
	}
};

int main()
{
	A a;
	cout << a._a<< endl; //0
	a.func(1, 2);
	cout << a._a << endl;//1
}

可变lambda

默认情况下,对于个值被拷贝的变量,lambda不会改变其值,如果我们希望能改变一个被捕获的变量的值,就必须在参数列表上加关键字mutable

int main()
{
	int i = 0;
	//f可以改变它所捕获的变量的值
	auto f = [=]()mutable {return ++i; };
	cout << f() << endl; //1
	cout << i << endl;   //0
}

原因:lambda表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终会变为闭包类型的成员变量。按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量的值的。而mutable的作用,就在于取消operator()的const

指定lambda返回类型(尾置返回)

如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void,与其他void的函数类似,被推断返回void的lambda不能返回值。当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型

int main()
{
	transform(vi.begin(), vi.end(), vi.begin(), [](int i) {return i < 0 ? -i : i; });
	//错误:不能推断lambda的返回类型
	transform(vi.begin(), vi.end(), vi.begin(), [](int i) {if (i < 0) return -i; else return i; });
	//修改为尾置返回
	transform(vi.begin(), vi.end(), vi.begin(), [](int i)->int {if (i < 0) return -i; else return i; });
}

lambda表达式的大致原理

以下转载自:https://blog.csdn.net/qq_43313035/article/details/98533582

  • 每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类重载了()运算符),我们称为闭包类型(closure type)。这个类不含默认构造函数、赋值运算符及默认析构函数,它是否含有默认的拷贝/移动构造函数则通常要视捕获的数据成员类型而定。
  • 那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,是一个右值。
  • 所以,我们上面的lambda表达式的结果就是一个个闭包。
  • 对于复制传值捕捉方式,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。
  • 对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。
  • 至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,与具体实现有关
  • lambda表达式是不能被赋值的,闭包类型禁用了赋值操作符,但是没有禁用复制构造函数,所以你仍然可以用一个lambda表达式去初始化另外一个lambda表达式而产生副本。
auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; };

a = b;   // 非法,lambda无法赋值
auto c = a;   // 合法,生成一个副本

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值