在 C++11 中新增了一个特性 —— Lambda 表达式,它提供了一个简便的方法来创建一个函数对象。
至于函数对象,它的用途通常是在调用标准库算法的时候作为核心部分传入的,比如说 std::sort 默认是从小到大排序,然而如果我们要从大到小排序在 C++ 98/03 中有三种方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// solution 1 bool cmp(int a, int b) { return b < a; } std::sort(iter_begin, iter_end, cmp); // solution 2 struct comparer { bool operator () (int a, int b) const { return b < a; } }; std::sort(iter_begin, iter_end, comparer()); // solution 3 std::sort(iter_begin, iter_end, std::greater<int>()); |
第一种是给 std::sort 一个函数,第二种是给一个函数对象,第三种是用标准库 functional 里面已经定义好了的函数对象
你可能会说,第三种多快多方便,但是,如果比较算法一变得复杂起来,如果标准库没有提供这样的函数对象,那你就不得不选择前两种方法了,肯定,很多人会选择第一种。
但是…… 如果你的比较函数要依赖于一个外部的,比较函数调用之时才确定的,那么第一种就无能为力了,考虑下面这个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class between { double low, high; public: between(double l, double u) : low(l), high(u) {} bool operator () (const employee& e) { return e.salary() >= low && e.salary() <= high; } }; // some codes double min_salary; // some codes std::find_if(employees.begin(), employees.end(), between(min_salary, 1.1 * min_salary)); |
这种写法无疑会消耗很多时间,增加很多代码量,于是 Lambda 表达式便在 C++11 标准中诞生了。
1 2 3 4 5 6 |
double min_salary; // some codes std::find_if(employees.begin(), employees.end(), [&] (const employee& e) { return e.salary() >= min_salary && e.salary() <= max_salary; } ); |
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
1 2 |
[](){}; [](int v) { return v * v; }; |
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。
1 2 3 |
auto x1 = [](int i) { return i; }; // OK: return type is int auto x2 = [] { return {1, 2}; }; // error: the return type is void // (a braced-init-list is not an expression) |
Lambda 表达式的闭包类型(closure type)有一个公有(public)的内连的(inline)函数调用操作符号,它的参数和返回类型和 lambda 表达式声明中的一样。并且这个函数调用操作符默认被声明为 const 成员函数,如果要取消其常量性,必须如上面语法描述一样添加 mutable 关键字。
Lambda 表达式的闭包类型(closure type)如果不带有任何捕获(lambda-cpture),那么,它会拥有一个公有的(public),非虚的(non-virtual),非显式的(non-explicit)常量转换函数,可以让你把它转换到一个函数指针。
1 2 3 4 5 6 7 |
using func_t = int(int); int x; auto f = [](int x) { return x * x; }; auto g = [x](int t) { return t * t; }; func_t* ff = f; // OK, f with no capture func_t* gg = g; // ERROR, f has a capture |
Lambda 表达式的捕获可以捕捉外部的变量,因为默认在 lambda 表达式内部,外层的变量是不可见的,如果想要使用就需要捕获。
如果使用了 & 捕获,那么外层所有变量都可以访问,并且默认是以引用形式存在,在 lambda 表达式内部用到外部变量的时候就会被以引用方式隐式地捕获。如果使用了 = 捕获,那么外层所有变量都可以访问,并且默认会进行复制,lambda 函数的闭包类型里有其副本。如果 lambda 表达式是在一个类中的成员函数声明的,那么还有一种 this 捕获,这个捕获使得 this 指针可见(如果捕获方法是 =,那么默认会有 this 捕获,因此在其后不可以声明 this 捕获)
简单来说
- 如果一个变量是被隐式捕捉(implicitly captured)(也就是没有在捕获列表中写出变量但是写了 = 或 &)并且其捕获列表默认捕获方式是 =,或者它是显式(explicitly captured)地被捕获(也就是在捕获列表中写出了这个变量的名字),并且前面没有 &,那么它通过复制方式捕捉(captured by copy)。
- 如果一个变量显式或隐式地被捕获,但是不是通过复制方式,那么它就是通过引用方式捕获(captured by reference)。
1 2 3 4 5 6 7 8 9 |
int x = 10, y = 10, n = 10, m = 10; auto f = [&] { x *= x; }; // x is captured by reference implicitly auto g = [=]() mutable { y *= y; }; // y is captured by copy implicitly // n is captured by copy explicitly // m is captured by reference explicitly auto t = [n, &m]() mutable { n *= n, m *= m; }; // (x, y, m, n) = (10, 10, 10, 10) f(); g(); t(); // (x, y, m, n) = (100, 10, 100, 10) |
如果两个变量,一个想要按值捕获,一个想要按引用捕获,那么可以在捕获列表里分别写出例如 [x, &y] 表示 x 按照值捕获,y 按照引用捕获。如果最开头声明了 =,那么之后只能声明某个变量按照引用捕获,例如 [=, &y] 表示除了 y 之外的所有变量按照值捕获,y 按照引用捕获。一个变量不能在捕获列表中出现超过一次。
1 2 3 4 5 6 7 8 |
struct S2 { void f(int); }; void S2::f(int i) { [&, i]{}; // OK,i 按值捕获 [&, &i]{}; // ERROR,i 按照引用捕获,可是引用捕获是默认的 [=, this]{}; // ERROR,默认按值捕获时,不需要 this [i, i]{}; // ERROR,i 重复 } |
只有在一个 lambda 表达式的最小作用域(smallest enclosing scope)是块作用域(block scope)之时,捕获才是允许的,否则不允许有捕获的出现。
在缺省实参(default argument)中的 lambda 表达式不允许捕捉(显式或隐式)任何变量。
1 2 3 4 5 6 7 8 9 |
void f2() { int i = 1; void g1(int = [i]{ return i; }()); // ill-formed void g2(int = [i]{ return 0; }()); // ill-formed void g3(int = [=]{ return i; }()); // ill-formed void g4(int = [=]{ return 0; }()); // OK void g5(int = []{ return sizeof(i); }()); // OK } |
如果一个 lambda 表达式 m2 捕获了一个变量,并且这个变量被外部的一个 lambda 表达式 m1 捕获,那么 m2 的捕获会进行如下转换:
- 如果 m1 以复制方式捕获那个变量,那么 m2 捕获 m1 的闭包类型相关联的数据成员。
- 如果 m1 以引用方式捕获那个变量,那么 m1 会捕获与 m1 相同的实体。
具体的可以看下面这个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// output: 123234 int a = 1, b = 1, c = 1; auto m1 = [a, &b, &c]() mutable { auto m2 = [a, b, &c]() mutable { std::cout << a << b << c; a = 4, b = 4, c = 4; }; a = 3, b = 3, c = 3; m2(); }; a = 2, b = 2, c = 2; m1(); std::cout << a << b << c; |
最后,在可变参数模板(variadic template)中,如果一个捕获后跟着 ...,那么意味着这是一个包展开(pack expansion),例如
1 2 3 4 5 6 |
template<typename... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); } |
不得不说cxx11的lambda语法挺难看的……不过特性够全
你可以看下coffeescript的lambda语法
好简洁…… 说起来我好像想起来 cxx lambda 第一个版本是长这样的看起来更难看 = = <>(int a) -> int { return a * a; }
不对 想了想还是推荐看Ruby的代码块语法
看起来好奇怪【其实是没学过没看懂、
后面这个do...end其实本质可以看做是一个语法糖包装的lambda 等价于cxx11
看起来好帅!