在C++11之后终于有了lambda,这个在很多需要传一个函数作为参数的时候方便了很多,比STL里面的for_eachsort。原来是要单独定义一个函数,然后当做参数传进去。比如,我要打印一个vector:

void print_elem(const int x) {
    cout << x << " ";
}

或者定义一个函数对象:

struct print_elem_class {
    void operator() (int x) {
        cout << x << " ";
    }
}print_elem;

然后像这样调用:

vector<int> v{1,2,3,4,5};
for_each(v.begin(), v.end(), print_elem);

有了lambda之后就不需要单独定义函数了,可以in-palce定义一个匿名函数,lambda就是干这个的嘛。

vector<int> v{1,2,3,4,5};
for_each(v.begin(), v.end(), [](const int x){ cout << x << " ";});

下面来看一下C++ lambda的语法,这个相比Ruby此类动态语言还是多很多细节的。看到上面的例子中有一个中括号,一个小括号,一个花括号,都用全了。我们来看看没一部分都是什么。首先看下lambda语法的定义:

[ capture-list ] ( params ) mutable(optional) exception attribute -> ret { body }

capture-list

中括号里的叫做capture-list,表示从当前的context获取的变量列表,这在动态语言中是自动获取的吧。capture-list有几种形式:

  • [] 空的就把表示不获取任何东西
  • [=] 放一个等号表示按值获取所有当前context的变量
  • [&] 这个和上面类似,按引用获取
  • [this] 按值获取this指针
  • [a, &b] 列出所要capture的变量,带&是按引用capture,不带的是按值

params

这个就是lambda函数所要用的参数,这个比较简单。

mutable(optional)

这个 mutable是可选的,如果写上的话,表示lambda函数体内可以修改按值capture的变量,并且可以调用它们的非const函数。举个例子:

vector<int> v{1,2,3,4,5};
int y = 10;
for_each(v.begin(), v.end(), [y] (const int x) mutable { y++; cout << x + y << " ";});

因为这个y是按值capture的,所以如果不加上mutable,会报错cannot assign to a variable captured by copy in a non-mutable lambda

exception

给出调用时的异常情况。

attributes

定义函数的attribute,attribute就像Java的annotations。

ret

定义lambda的返回类型,也可以省略,会从return语句的返回类型推导出来。如果没有return语句,就是返回void。上面的例子我都省略了ret

body

body就是函数体,和普通函数没有区别,函数体内可以访问传进去的参数和capture到的变量。

lambda的递归调用

如果想在lambda里面调用自己,就需要给lambda一个名字,还有一点需要注意,看一个例子:

auto fib = [](int x) {
    if(x == 0 || x == 1) return x;
    else return fib(x - 1) + fib(x - 2);
};
cout << fib(5) << endl;

lambda的变量可以直接用auto类型,从lambda的参数和返回值推导出来具体的函数类型,但是在递归定义lambda的时候这样是不行的,比如上面的例子会报错variable 'fib' declared with 'auto' type cannot appear in its own initializer。因为在lambda函数体内还不知道这个这个lambda的类型,就去调用了它,这样让编译器无所适从,所以要明确写出lambda的类型。

function<int(int)> fib = [](int x) {
    if(x == 0 || x == 1) return x;
    else return fib(x - 1) + fib(x - 2);
};
cout << fib(5) << endl;

这样就可以了吗,运行一下你会看到另一个错误 variable 'fib' cannot be implicitly captured in a lambda with no capture-default specified,因为lambda没有capture到自己,所以在函数体内没法调用到自己。那我们就给它,拿去。

function<int(int)> fib = [&fib](int x) {
    if(x == 0 || x == 1) return x;
    else return fib(x - 1) + fib(x - 2);
};
cout << fib(5) << endl;

现在终于运行成功了,当然如果要写递归定义的lambda,其实都可以写成普通函数,只不过lambda可以capture当前的context。