简介
lambda表达式(lambda expression)基于数学中的λ演算得名。在计算机科学中,我们可以将其理解为一个匿名内联函数,一个lambda表达式表示一个可调用的代码单元。
与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。C++的lambda表达式的基本语法如下:
[capture list] (parameter list) -> return type {function body}
其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(可以为空);return type、parameter list和function body与普通函数一样,分别表示返回类型、参数列表和函数体。一个lambda中的捕获列表和函数体是不可缺少的,其余部分可以省略。特别的,lambda必须使用尾置返回来指定返回类型。
在lambda中,忽略括号和参数列表等价于指定一个空参数列表。如果忽略返回类型,则返回类型从返回的表达式的类型推断而来。否则,返回类型为void。
此外,由于lambda的类型特殊且唯一,不能通过类型名来显式声明对应的对象,但可以利用auto关键字来进行类型推导:
auto f = [](int a, int b){return a>b;};
在C++14中,还支持基于类型推断的泛型lambda表达式:
auto f = [](auto a, auto b){return a>b;};
先通过一个简单的程序来初窥lambda表达式的作用。我们使用std::sort写一个简单的数组排序:
#include <iostream>
#include <vector>
#include <algorithm>
bool compare(int &a, int &b) {
return a>b;//降序排序
}
int main()
{
std::vector<int> iv = {3, 24, 8};
std::sort(iv.begin(), iv.end(), compare);
for (auto i : iv)
std::cout << i << " ";
std::cout << std::endl;
return 0;
}
为调用泛型算法sort实现降序排列,我们需要另写一个compare函数传递给sort,而引入lambda表达式后,我们只需要这么写:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> iv = {3, 24, 8};
std::sort(iv.begin(), iv.end(), [](int a, int b){return a>b;});
for (auto i : iv)
std::cout << i << " ";
std::cout << std::endl;
return 0;
}
代码简洁了不少。
向lambda传递参数
与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。通常,实参和形参的类型必须匹配。但与普通函数不同,lambda不能有默认参数。因此,一个lambda调用的实参数目永远与形参数目相等,编译器根据传递给lambda的实参来初始化形参,然后执行函数体。
使用捕获列表
虽然一个lambda可以出现在一个函数中,使用其局部变量或函数体外的全局变量,但对于lambda所处的函数中的局部变量,它只能使用那些明确指明的变量。一个lambda通过将局部变量包含在其捕获列表中来指出将会使用这些变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。例如:[b](int a){return a>b}这条语句是错误的,b并不在捕获列表中,除非b是static或全局变量,不然都需要在捕获列表中指明。
总结起来,捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。
值捕获、引用捕获和隐式捕获
类似参数传递,变量的捕获方式也可以是值或引用。当未指明捕获方式时,编译器将根据lambda中的代码来推断我们要使用哪种变量。我们可以显式指明编译器对哪些变量进行何种捕获:
在使用引用捕获时,我们必须确保被引用的对象在lambda执行的时候是存在的,否则会像使用一个无效指针一样带来不可预知的后果。并且,一般在lambda中使用引用捕获并非以改变捕获变量为主要目的,而是对于一些不能拷贝的对象(如IO流)或复合类型,特别是不能拷贝的对象,我们只能捕获其引用或指针。
可变lambda
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获变量的值,就必须在参数列表首加上关键字mutable。
void fun() {
int i = 3;
auto f = [i]() mutable {return ++i;};
return;
}
一个引用捕获的变量是否可以修改引用所绑定的变量依赖于此引用指向的是一个const类型还是一个非const类型。
指定lambda返回类型
前面说过,在没有指定返回类型时编译器根据返回语句来推断返回类型,这只是在lambda函数体只包含单一的return语句的情况下。实际上,默认情况下如果一个lambda函数体包含return之外的任何语句,则编译器都假定此lambda返回void。与其他返回void的函数类似,被推断返回void的lambda不能返回值,否则会编译错误。
当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型:
sort(iv.begin(), iv.end(), [](int a, int b)->bool{if (a>b) return ture;else return false;});
lambda表达式主要在某一简单函数只在另一个函数中被调用的情况下使用,这个简单函数就可以定义为lambda。一来简化了代码,二来不用费心给这个函数取一个合适的函数名。掌握这个语法糖对改善代码应该会有不少帮助。
本文部分内容摘自《C++ Primer(中文版)第五版》