# 使用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`类型的结果。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/c-17-stl-cook-book/chapter4-0-chinese/chapter4-1-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
