什么是Lambda表达式呢?λ演算(λ-calculus)是一个形式化的数学逻辑系统,它基于函数的抽象和应用,使用变量绑定和替换来表示计算。具体请参考:λ-calculus 从C++11开始可以使用Lambda表达式创建匿名函数对象(闭包),它可以作为一个参数传递给另一个函数。语法如下:
1.捕获从句
1.[ ]是捕获从句,lambda可以访问或捕获周围作用域的变量。在c++ 14中,还可以在其函数体中引入新变量。lambda表达式必须以[]开始。用它来指定捕获哪些变量并指明捕获是通过值还是通过引用。有&前缀的变量会以引用的方式被访问,否则就是以值的方式来访问。
- [ ],表示lambda表达式的函数体内不访问任何外部变量。如果此时在函数体内访问了lambda表达式的函数体作用域外的变量就会报错。如:
-[arg1,arg2,…],我们也可以指定要捕获的变量和方式(引用或值,若是引用方式就是在变量前加&,函数体对引用变量的改变会影响原值),如
- [&]和[=],我们指定一个默认模式去捕获外部变量。[&]表示以引用方式捕获所有外部变量,在函数体中对变量做的操作会影响原值,[=]就是以值方式捕获所有外部变量,在函数体中对变量做的操作不会影响原值。
当指定了默认捕获模式后,也可以用相反模式指定某个变量的捕获,如:
这里还要注意一点,当指定了默认捕获模式,只有在lambda函数体用到的外部变量才会被捕捉。
如果捕获从句(即[ ])中已指定了默认捕获模式为&,即引用,那么[ ]中就不能再指定任何& identifier
,因为指定的默认模式&中,已包含此。例子:
同理,如果指定了默认捕获模式为=
,那么也不能在[]指定任何= identifier
:
一个标识符或this指针在[]捕获从句中只能出现一次,例子:
对于可变参数的捕获:一个捕获加上一个省略号是一个包的扩展:
要在类成员函数体中使用lambda表达式,就要将this指针传递给capture子句,以提供对封闭类的成员函数和数据成员的访问。
在c++17之后,this 指针可以通过值的方式被捕获,即[*this]。通过值方式的捕获会拷贝整个闭包到lambda调用处。 所谓的闭包就是匿名函数对象,这个对象封装了lambda表达式。当lambda表达式并行执行或异步操作时,通过值方式的捕获非常有用。
在多线程使用lambda表达式时,要注意:
- 引用型捕获可以改变外部变量,但是值捕获不会。mutable关键字表明允许修改副本,但不允许修改原始副本
- 引用型捕获会引入生命周期依赖,但是值方式的捕获没有生命周期依赖问题。特别重要的一点是当lambda表达式异步执行,如果在异步lambda中通过引用捕获局部变量, 那么那个局部变量随着lambda的运行很容易就被销毁了。我们的代码可能就会在运行时导致访问冲突。
在c++ 14中,可以在capture子句中引入和初始化新变量,而不需要让这些变量存在于lambda函数的封闭作用域中。初始化可以表示为任意表达式,新变量的类型由表达式产生的类型推导而来。该特性允许从周围的作用域捕获仅需移动的变量,并在lambda中使用它们。
2.参数列表
Lambdas不仅可以捕获变量,还可以接受输入参数. 参数列表对于lambda表达式来说是可选,就是说不一定要有。
在c++ 14中,如果参数类型是泛型的,可以使用auto关键字作为类型说明符。该关键字告诉编译器将函数调用操作符创建为模板。参数列表中auto的每个实例等效于一个不同的类型参数。
另外,lambda表达式也可以将另一个lambda表达式作为它的参数。因为参数列表是可选的,所以如果lambda表达式不用传参数,并且lambda表达式的声明符中不包含异常声明、返回类型或mutable关键字,那么括号可以省略。
3.mutable
lambda以值方式捕获的变量,lambda函数体内默认是const的,即不能修改它们。mutable关键字就是取消这一点。加上mutable关键字,能够在函数体内修改以值方式捕获到的变量的副本。
加上mutable关键字就可以修改n的副本的值了。
4.exception
我们以用noexcept表明lambda表达式不会抛出任何异常。
5.Return type
指定lambda表达式的返回类型。lambda表达式的返回类型是自动推导的。如果函数体只有一条return语句,那么会自动推导出返回类型。
6.Lambda body
lambda函数体实现。函数体可以访问以下这些变量:
- 从封闭区域捕获到的变量
- 参数
- lambda函数内部本地定义的变量。
- 类成员,当在一个类里定义了lambda表达式,且表达式里捕获了this。
- 任何有静态存储期的变量,如全局变量