# 使用Lambda表达式定义函数

我们可以使用Lambda表达式来包装代码，为了在之后对其进行调用。我们可以像调用函数那样，给Lambda表达式传入不同的参数，从而得到不同的结果，这样我们就不需要在类中实现这个函数了。

C++11标准正式将Lambda语法加入C++，之后的C++14和C++17标准中对Lambda语法进行了升级。本节我们将看到如何使用Lambda表达式，以及其给我们带来的改变。

## How to do it...

现在我们就来使用Lambda表达式完成一个程序，在实践中体验Lambda表达式：

1.  Lambda表达式不需要任何库，不过我们需要将一些字符串打印在屏幕上，所以需要包含必要的的头文件：

    ```cpp
    #include <iostream>
    #include <string>
    ```
2.  这次我们所有内容都会在主函数中完成。我们定义了两个没有参数的函数对象，并且返回整型常量1和2。需要注意的是，返回部分在大括号对`{}`中，就像普通的函数那样，而小括号`()`表示没有参数传入，当然也可以像普通函数那样定义函数签名，对于第二个Lambda表达式没有添加小括号对。不过两个表达式都有中括号对`[]`：

    ```cpp
    int main()
    {
        auto just_one ( [](){ return 1; } );
        auto just_two ( []  { return 2; } );
    ```
3.  那么现在我们就来调用这两个函数，就像调用普通函数那样：

    ```cpp
        std::cout << just_one() << ", " << just_two() << '\n';
    ```
4.  现在，来定义另一个函数对象，其名为plus，因为它要将两个参数进行加和：

    ```cpp
        auto plus ( [](auto l, auto r) { return l + r; } );
    ```
5.  这个函数对象也不难用。使用`auto`类型定义两个参数，只要是作为参数的实参类型支持加法操作，那么就没有任何问题：

    ```cpp
        std::cout << plus(1, 2) << '\n';
        std::cout << plus(std::string{"a"}, "b") << '\n';
    ```
6.  当然，我们可以不使用变量的方式对Lambda表达式进行保存。我们只需要在使用到的地方对其进行定义即可：

    ```cpp
        std::cout
            << [](auto l, auto r){ return l + r; }(1, 2)
            << '\n';
    ```
7.  接下来，我们定义一个闭包，包里面装着一个计数器。当我们调用这个计数器时，其值就会自加，并且会将自加后的值返回。为了对计数变量进行初始化，我们(在中括号对中)对`count`进行了赋值。为了能让函数对获取的值进行修改，我们使用`mutable`关键字对函数进行修饰，否则在编译时会出问题：

    ```cpp
        auto counter (
            [count = 0] () mutable { return ++count; }
        );
    ```
8.  现在让我们调用函数对象5次，并且打印其返回值，观察每次调用后计数器增加后的值：

    ```cpp
        for (size_t i {0}; i < 5; ++i) {
            std::cout << counter() << ", ";
        }
        std::cout << '\n';
    ```
9.  我们也可以通过捕获已经存在的变量的引用，在闭包中进行修改。这样的话，捕获到的值会自加，并且在闭包外部也能访问到这个变量。为了完成这个任务，我们在中括号对中写入`&a`，`&`符号就意味着捕获的是对应变量的引用，而非副本：

    ```cpp
        int a {0};
        auto incrementer ( [&a] { ++a; } );
    ```
10. 如果这样能行，那我们就可以多次的调用这个函数对象，并且直接在外部对a变量的值进行观察：

    ```cpp
       incrementer();
       incrementer();
       incrementer();

       std::cout
           << "Value of 'a' after 3 incrementer() calls: "
           << a << '\n';
    ```
11. 最后一个例子是一个多方位展示，这个例子中一个函数对象可以接受参数，并且将其传入另一个函数对象中进行保存。在这个`plus_ten`函数对象中，我们会调用`plus`函数对象：

    ```cpp
        auto plus_ten ( [=] (int x) { return plus(10, x); } );
        std::cout << plus_ten(5) << '\n';
    }
    ```
12. 编译并运行代码，我们将看到如下的内容打印在屏幕上。我们也可以自己计算一下，看看打印的结果是否正确：

    ```cpp
    1, 2
    3
    ab
    3
    1, 2, 3, 4, 5,
    Value of a after 3 incrementer() calls: 3
    15
    ```

## How it works...

上面的例子并不复杂——添加了数字，并对调用进行计数，并打印计数的结果。甚至用一个函数对象来连接字符串，并用这个函数对象对对应字符串进行计数。不过，这些实现对于对Lambda表达式不太了解的人来说，看着就很困惑了。

所以，先让我们了解一下Lambda表达式的特点：

```cpp
[capture list] (parameters)
    mutable            (optional)
    constexpr        (optional)
    exception attr    (optional)
    -> return type    (optional)
{
    body
}
```

Lambda表达式的最短方式可以写为`[]{}`。其没有参数，没有捕获任何东西，并且也不做实质性的执行。

那么其余的部分是什么意思呢？

**捕获列表 capture list**

指定我们需要捕获什么。其由很多种方式，我们展示两种比较“懒惰”的方式：

* 将Lambda表达式写成`[=] () {...}`时，会捕获到外部所有变量的副本。
* 将Lambda表达式写成`[&] () {...}`时，会捕获到外部所有变量的引用。

当然，也可以在捕获列表中单独的去写需要捕获的变量。比如`[a, &b] () {...}`，就是捕获`a`的副本和`b`的引用，这样捕获列表就不会去捕获那些不需要捕获的变量。

本节中，我们定义了一个Lambda表达式：`[count=0] () {...}`，这样我们就不会捕获外部的任何变量。我们定义了一个新的`count`变量，其类型通过初始化的值的类型进行推断，由于初始化为0，所以其类型为`int`。

所以，可以通过捕获列表捕获变量的副本和/或引用：

* `[a, &b] () {...}`：捕获`a`的副本和`b`的引用。
* `[&, a] () {...}`：除了捕获`a`为副本外，其余捕获的变量皆为引用。
* `[=, &b, i{22}, this] () {...}`：捕获`b`的引用，`this`的副本，并将新变量`i`初始化成22，并且其余捕获的变量都为其副本。

> Note:
>
> 当你需要捕获一个对象的成员变量时，不能直接去捕获成员变量。需要先去捕获对象的`this`指针或引用。

**mutable (optional)**

当函数对象需要去修改通过副本传入的变量时，表达式必须用`mutable`修饰。这就相当于对捕获的对象使用非常量函数。

**constexpr (optional)**

如果我们显式的将Lambda表达式修饰为`constexpr`，编译器将不会通过编译，因为其不满足`constexpr`函数的标准。`constexpr`函数有很多条件，编译器会在编译时对Lambda表达式进行评估，看其在编译时是否为一个常量参数，这样就会让程序的二进制文件体积减少很多。

当我们不显式的将Lambda表达式声明为`constexpr`时，编译器就会自己进行判断，如果满足条件那么会将Lambda表达式隐式的声明为`constexpr`。当我们需要一个Lambda表达式为`constexpr`时，我们最好显式的对Lambda的表达式进行声明，当编译不通过时，编译器会告诉我们哪里做错了。

**exception attr (optional)**

这里指定在运行错误时，是否抛出异常。

**return type (optional)**

当想完全控制返回类型时，我们不会让编译器来做类型推导。我们可以写成这样`[] () -> Foo {}`，这样就告诉编译器，这个Lambda表达式总是返回`Foo`类型的结果。
