C++11 lambda函数

字数 1168阅读 205

匿名函数是许多编程语言都支持的概念,有函数体,没有函数名。1958年,lisp首先采用匿名函数,匿名函数最常用的是作为回调函数的值。正因为有这样的需求,c++引入了lambda 函数,你可以在你的源码中内联一个lambda函数,这就使得创建快速的,一次性的函数变得简单了。例如,你可以把lambda函数可在参数中传递给std::sort函数。

#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned N) {
    std::sort(x, x + N,
        // Lambda expression begins
        [](float a, float b) {
            return std::abs(a) < std::abs(b);
        });
}

你可能会问,使用函数对象不是也可以吗?是的,函数对象当然没问题,自己写的回调函数,你可以传个函数指针也没有问题。他们有优点也有缺点。函数对象能维护状态,但语法开销大,而函数指针语法开销小,却没法保存范围内的状态。如果你觉得鱼和熊掌不可兼得,那你可错了。lambda函数结合了两者的优点,让你写出优雅简洁的代码。

基本lambda语法
基本形式如下:

[capture](parameters) mutable exception attribute ->return-type {body}

capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下:
[a,&b] a变量以值的方式呗捕获,b以引用的方式被捕获。
[this] 以值的方式捕获 this 指针。
[&] 以引用的方式捕获所有的外部自动变量。
[=] 以值的方式捕获所有的外部自动变量。
[] 不捕获外部的任何变量。

params 指定 lambda 表达式的参数。

mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。
exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f() throw(X, Y)。
attribute 用来声明属性

->return-type表示返回类型,如果没有返回类型,则可以省略这部分。如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。

lambda函数的类型是std:function,并非函数指针。不过C++11标准允许lambda函数向函数指针的转换,但前提是lambda函数没有捕捉任何变量,且函数指针所示的函数原型,必须跟lambda函数有着相同的调用形式。

// 类型为function<int(int,int)>
auto add [](int x, int y) {return x+y;}
add(1,2)  // 3

除去语法层面的不同,lambda和仿函数有着相同的内涵——都可以捕捉一些变量作为初始状态,并接受参数进行运算。而事实上,仿函数是编译器实现lambda的一种方式。在现阶段,编译器都会把lambda函数转化为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式,或者称为仿函数的“语法甜点”。

lambda函数在C++11标准中默认是内联的。

使用lambda函数时,捕获列表按值传递和按引用传递的效果是不一样的。对于按值传递的捕获列表,其传递的值在lambda函数定义的时候就已经决定了。而按引用传递的捕获列表,其传递的值则等于lambda函数调用时的值。

int i = 1;
auto by_val_lambda = [=] { return j+1;}
auto by_ref_lambda = [&] { return j+1;}
cout<<"by_val_lambda: "<<by_val_lambda()<<endl; //12
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl; //12

i++;
cout<<"by_val_lambda: "<<by_val_lambda()<<endl; //12
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl; //13

如果需要捕捉的值称为lambda函数的常量,我们通常会使用按值传递的方式捕捉;而需要捕捉的值称为lambda函数运行时的变量(类似于参数的效果),则应采用按引用传递的方式进行捕捉。

lambda函数的使用场景

  1. 一些短小的函数,但只使用一次的。
  2. 配合STL泛型算法使用。
  3. 回调函数。

lambda函数被设计的目的,就是要就地书写,就地使用。使用lambda的用户,更倾向于在一个屏幕里看到所有的代码,而不是依靠代码浏览工具在文件间找到函数的实现。而在封装的思维层面上,lambda只是一种局部的封装,以及局部的共享。而需要全局共享的代码逻辑,我们还是需要用函数(无状态)或者仿函数(有初始状态)封装起来。

推荐阅读更多精彩内容