[c++11]Lambda 表达式

在 C++11 中新增了一个特性 —— Lambda 表达式,它提供了一个简便的方法来创建一个函数对象。

至于函数对象,它的用途通常是在调用标准库算法的时候作为核心部分传入的,比如说 std::sort 默认是从小到大排序,然而如果我们要从大到小排序在 C++ 98/03 中有三种方法

第一种是给 std::sort 一个函数,第二种是给一个函数对象,第三种是用标准库 functional 里面已经定义好了的函数对象

你可能会说,第三种多快多方便,但是,如果比较算法一变得复杂起来,如果标准库没有提供这样的函数对象,那你就不得不选择前两种方法了,肯定,很多人会选择第一种。

但是…… 如果你的比较函数要依赖于一个外部的,比较函数调用之时才确定的,那么第一种就无能为力了,考虑下面这个例子

这种写法无疑会消耗很多时间,增加很多代码量,于是 Lambda 表达式便在 C++11 标准中诞生了。

Lambda 的语法是这样的,首先以 [] 打头,之后是和普通函数一样的参数列表,然后是函数体,如果有返回值(假定为 T),那么在参数列表后加上 -> T,它语法大概就写成这样,最简单的 lambda 表达式就是 []{} 它什么都不做

[ capture ] ( argument-list ) -> return-type { statement }

  • [ capture ]:捕获列表。它总是出现在 lambda 函数开头,并且不能省略,事实上,它是 lambda 函数的引出符。捕获列表可以捕捉上下文中的变量以供 lambda 函数内部使用,具体用法请看下文。
  • ( argument-list ):参数列表。和普通函数参数列表一样,如果没够参数可以连同括号一起省略。
  • mutable:默认情况下 lambda 函数总是 const 函数,也就是说捕获的变量不可修改。mutable 可以取消其常量性,若有这个修饰符,参数列表不可省略。
  • return-type:函数返回类型。详见下文
  • { statement }:函数体。

或者按照标准给出其全部语法(看不懂就跳过吧)

lambda-expression:
    lambda-introducer lambda-declaratoropt compound-statement

lambda-introducer:
    [ lambda-captureopt ]

lambda-captrue:
    capture-default
    capture-list
    capture-default , capture-list

capture-default:
    &
    =

captrue-list:
    captrue ...opt
    capture-list , captrue ...opt

capture:
    identifier
    & identifier
    this

lambda-declarator:
    ( parameter-declaration-clause ) mutableopt
        exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt

Lambda 表达式求值的结果是个 prvalue 的临时对象,我们叫做闭包对象(closure object),lambda 表达式不能出现在不需要求值的操作数中。

Lambda 表达式的类型也是由编译器决定的,它是独有的、未命名的类对象,我们叫做闭包类型(closure type),要存储它可以用 auto 或者 std::function 这个类。

如果一个 lambda 表达式没有包含 lambda-declarator(见上面的语法),那么 lambda-declarator 就像是 () 。如果一个 lambda 表达式没有指定返回类型,那么它的返回类型会按照下面这个方法来确定

  • 如果 lambda 表达式函数体(就是上面的 compound-statement)有下面这个格式
    • { return expression; }
    • { attribute-specifier-seq return expression; }

    那么其返回类型是 expression 的返回类型(这个类型经过 lvalue-to-rvalue 转换、array-to-pointer 转换、function-to-pointer 转换)。

  • 其余情况返回类型都是 void。

Lambda 表达式的闭包类型(closure type)有一个公有(public)的内连的(inline)函数调用操作符号,它的参数和返回类型和 lambda 表达式声明中的一样。并且这个函数调用操作符默认被声明为 const 成员函数,如果要取消其常量性,必须如上面语法描述一样添加 mutable 关键字。

Lambda 表达式的闭包类型(closure type)如果不带有任何捕获(lambda-cpture),那么,它会拥有一个公有的(public),非虚的(non-virtual),非显式的(non-explicit)常量转换函数,可以让你把它转换到一个函数指针。

Lambda 表达式的捕获可以捕捉外部的变量,因为默认在 lambda 表达式内部,外层的变量是不可见的,如果想要使用就需要捕获。

如果使用了 & 捕获,那么外层所有变量都可以访问,并且默认是以引用形式存在,在 lambda 表达式内部用到外部变量的时候就会被以引用方式隐式地捕获。如果使用了 = 捕获,那么外层所有变量都可以访问,并且默认会进行复制,lambda 函数的闭包类型里有其副本。如果 lambda 表达式是在一个类中的成员函数声明的,那么还有一种 this 捕获,这个捕获使得 this 指针可见(如果捕获方法是 =,那么默认会有 this 捕获,因此在其后不可以声明 this 捕获)

简单来说

  • 如果一个变量是被隐式捕捉(implicitly captured)(也就是没有在捕获列表中写出变量但是写了 =&)并且其捕获列表默认捕获方式是 =,或者它是显式(explicitly captured)地被捕获(也就是在捕获列表中写出了这个变量的名字),并且前面没有 &,那么它通过复制方式捕捉(captured by copy)。
  • 如果一个变量显式或隐式地被捕获,但是不是通过复制方式,那么它就是通过引用方式捕获(captured by reference)。

如果两个变量,一个想要按值捕获,一个想要按引用捕获,那么可以在捕获列表里分别写出例如 [x, &y] 表示 x 按照值捕获,y 按照引用捕获。如果最开头声明了 =,那么之后只能声明某个变量按照引用捕获,例如 [=, &y] 表示除了 y 之外的所有变量按照值捕获,y 按照引用捕获。一个变量不能在捕获列表中出现超过一次。

只有在一个 lambda 表达式的最小作用域(smallest enclosing scope)是块作用域(block scope)之时,捕获才是允许的,否则不允许有捕获的出现。

在缺省实参(default argument)中的 lambda 表达式不允许捕捉(显式或隐式)任何变量。

如果一个 lambda 表达式 m2 捕获了一个变量,并且这个变量被外部的一个 lambda 表达式 m1 捕获,那么 m2 的捕获会进行如下转换:

  • 如果 m1 以复制方式捕获那个变量,那么 m2 捕获 m1 的闭包类型相关联的数据成员。
  • 如果 m1 以引用方式捕获那个变量,那么 m1 会捕获与 m1 相同的实体。

具体的可以看下面这个例子

最后,在可变参数模板(variadic template)中,如果一个捕获后跟着 ...,那么意味着这是一个包展开(pack expansion),例如

Miskcoo's Space,版权所有丨如未注明,均为原创
转载请注明转自:http://blog.miskcoo.com/2014/06/cxx11-lambda-expression

miskcoo

顺利从福州一中毕业!感觉大学周围都是聚聚十分可怕QAQ 想要联系的话欢迎发邮件:miskcoo [at] gmail [dot] com

7 thoughts on “[c++11]Lambda 表达式

        1. 后面这个do...end其实本质可以看做是一个语法糖包装的lambda 等价于cxx11

Leave a Reply

Your email address will not be published. Required fields are marked *

NOTE: If you want to add mathematical formulas, use $$ to wrap them. For example, use $$x_0$$ to get $$x_0$$.

If you want to get a newline, hit Enter twice, that is, use double newlines to get a newline.