Skip to content
Leo的技术分享
Go back

C++ Lambda 学习笔记

C++ 中,对于一个对象或一个表达式,如果可以对其使用调用运算符(()),则称它是可调用的。即,如果 e 是可调用的,则可以这样使用:

e(args)

其中,args 是一个逗号分隔的一个或多个参数的列表。

C++ 中可调用对象除了我们熟悉的函数或函数指针外,还包括函数对象以及 lambda 表达式。

本文重点讲述 lambda 表达式。

lambda 表达式表示一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数。一个 lambda 表达式具有如下形式:

[capture list](parameter list) -> return type {function body}

lambda 表达式可以忽略参数列表和返回类型,但必须包含捕获列表和函数体:

auto f = [] {return 10;};

上述代码中,我们定义了一个可调用对象 f,它不接受参数,返回 10。

lambda 的调用方式与普通函数的调用方式一样:

cout << f() << endl;

输出:

10

向 lambda 传递参数

lambda 可以带上参数,例如:

[](const string &s1, const string &s2) {
     return s1.size() < s2.size();
};

上述定义的 lambda 带上两个 const string & 类型的参数,lambda 函数体比较两个 string 的长度并返回 bool 值。

可以使用此 lambda 来调用 std::stable_sort 排序。当 std::stable_sort 需要比较两个元素时,它就会调用指定的这个 lambda。

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

int main() {
    vector<string> words{"leehao.me", "lee", "hao", "leehao"};
    std::stable_sort(words.begin(), words.end(),
                     [](const string &s1, const string &s2) {
                         return s1.size() < s2.size();
                     });

    for (const auto &word : words) {
        cout << word << endl;
    }

    return 0;
}

输出:

lee hao leehao leehao.me

lambda 捕获列表

一个 lambda 可以使用其所在函数中的局部变量,但需要明确指明这些变量。lambda 捕获列表可以用来声明这些需要使用的局部变量。例如:

[sz](const string &s) {
    return s.size() >= sz;
};

由于此 lambda 捕获 sz,因此,可以在函数体中使用 sz。如果未捕获 sz,则会报编译错误:

// 编译错误:sz 未捕获
[](const string &s) {
    return s.size() >= sz;
};

lambda 本质上是函数对象。当向一个函数参数传递一个 lambda 时,同时定义了一个新的函数对象类型并生成一个此类型的未命名对象。

因此,从 lambda 生成的函数对象类型包含此 lambda 所捕获的变量的数据成员,且 lambda 的数据成员在 lambda 对象创建时被初始化。

值捕获

类似参数传递,变量的捕获方式可以是值或引用。与传递参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在 lambda 创建时拷贝,而不是调用时拷贝:

void fcn1()
{
    size_t v1 = 42;  // 局部变量
    // 将 v1 拷贝到名为 f 的可调用对象
    auto f = [v1] {return v1;};
    v1 = 0;
    auto j = f();  // j 为 42,f 保存了 lambda 创建时 v1 的拷贝,即 42
    cout << j << endl;
}

输出:

42

由于被捕获变量的值是在 lambda 创建时拷贝,因此,随后对其修改不会影响到 lambda 内对应的值。

引用捕获

定义 lamda 时可以采用引用方式捕获变量:

void fcn2()
{
    size_t v1 = 42;  // 局部变量
    // 对象 f2 包含 v1 的引用
    auto f2 = [&v1] {return v1;};
    v1 = 0;
    auto j = f2();  // j 为 0,f2 保存 v1 的引用,而非拷贝
    cout << j << endl;
}

输出:

0

v1 前的 & 表示 v1 以引用方式捕获。当我们在 lambda 函数体内使用此变量,实际上使用的是引用所绑定的对象。因此,当 v1 在修改后,对象 f2 引用的 v1 也发生修改。

当以引用方式捕获一个变量时,必须保证在 lambda 执行时,变量是存在的。

lambda 返回类型

上述例子中,我们并没有为 lambda 指定返回类型,这是由于编译器可以正常推断出 lambda 的返回类型。

例如,使用标准库算法 transform 和一个 lambda 来将整型向量中每个负数转化为其绝对值:

vector<int> vi {1, -2, 3, 2, -4, 5};
std::transform(vi.begin(), vi.end(), vi.begin(),
        [](int i) {return i < 0 ? -i : i;});

transform 第 4 个参数是一个 lambda。 lambda 函数体是单一的 return 语句,我们没有指定返回类型,这是由于编译器可以根据条件运算符来推断出返回类型。

也可以指定 lambda 的返回类型:

vector<int> vi {1, -2, 3, 2, -4, 5};
std::transform(vi.begin(), vi.end(), vi.begin(),
        [](int i) -> int
        {if (i < 0) return -i; else return i;});

transform 第 4 个参数是一个 lambda,它的捕获列表为空,接受一个 int 参数,返回一个 int 值。

可以输出 vi 元素的值:

for (auto i : vi) {
    cout << i <<  endl;
}

输出:

1 2 3 2 4 5

参考资料


Share this post on:

Previous Post
prometheus 发送企业微信
Next Post
C++ 函数对象学习笔记