前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++:Lambda表达式

C++:Lambda表达式

作者头像
王强
发布2019-03-04 16:27:11
2.2K0
发布2019-03-04 16:27:11
举报
文章被收录于专栏:Python爬虫实战

1. 匿名函数概念2. Lambda 表达式的表示3. Lambda 表达式各部分3.1 Capture 子句3.1.1 引用捕获3.1.2 值捕获3.1.3 不捕获3.1.4 捕获方式总结3.2 参数列表3.3 可变规范3.4 异常规范3.5 返回类型3.6 函数体4. 嵌套 Lambda 表达式

1. 匿名函数概念

在计算机编程中,匿名函数(英语:anonymous function)是指一类无需定义标识符(函数名)的函数或子程序,普遍存在于多种编程语言中。

在 C++11 和更高的版本中,lambda 表达式通常称为 lambda —— 是一种在调用它或作为参数传递给函数时定义匿名函数对象(闭包)的简便方法。Lambda 通常用于封装传递给算法或异步方法的少量代码。

注:** 本文只讨论 C++11 中的 lambda特性。

2. Lambda 表达式的表示

ISO C++ 标准展示了作为第三个参数传递给 std::sort()函数的简单 lambda:

代码语言:javascript
复制
#include <algorithm>
#include <cmath>

void abssort(float* x, unsigned n) {
    std::sort(x, x + n,
        // Lambda expression begins
        [](float a, float b) {
            return (std::abs(a) < std::abs(b));
        } // end of lambda expression
    );
}

lambda 表达式的完整语法如下:

[captures] (params) specifiers(可选) exception -> ret { body }

下图通过实例展示了 lambda 的组成部分:

01_constituent_part_of_lambda

解释:

  1. captures - 零或更多捕获的逗号分隔列表,可选地以 capture-default 起始。 捕获的详细描述见下方。 若变量满足下列条件,则 lambda 表达式能使用而不捕获它
  • 为非局部变量,或拥有静态或线程局域存储期(该情况下不能捕获该变量),或
  • 为以常量表达式初始化的引用。 若变量满足下列条件,则 lambda 表达式能读取其值而不捕获它
  • 拥有 const 而非 volatile 的整数或枚举类型,并已用常量表达式初始化,或
  • constexpr 且为可平凡复制构造。
  1. params - 参数列表 (也称为lambda 声明符,可选) 若以 auto 为参数类型,则该 lambda 为泛型 lambda 。 (C++14 起)
  2. specifiers - 可变规范(可选)。 可选的指定符序列。允许下列指定符:
  • mutable :允许 body 修改以复制捕获的参数,及调用其非 const 成员函数
  • constexpr :显式指定函数调用运算符为 constexpr 函数。此指定符不存在时,若函数调用运算符恰好满足所有 constexpr 函数要求,则它也会是 constexpr(C++17 起)
  • consteval :指定函数调用运算符为立即函数。不能同时使用 constevalconstexpr 。(C++20 起)
  1. exception - 异常规范(可选)。 为闭包类型的 operator() 提供异常规定或 noexcept 子句。
  2. ret - 返回类型(可选)。若缺失,则由函数的 return 语句所隐含(或若函数不返回任何值则为 void )。
  3. body - lambda 函数体。

Lambda 表达式是纯右值表达式,其类型是独有的无名非联合非聚合类类型,被称为闭包类型,它声明于含有该 lambda 表达式的最小块作用域、类作用域或命名空间作用域。

3. Lambda 表达式各部分

3.1 Capture 子句

Lambda 以 capture 子句开头,指定哪些变量被捕获,以及是通过值还是引用捕获。Lambda 通过在最前面的方括号 [] 来明确指明其内部可以访问的外部变量,这一过程也称为 Lambda 表达式“捕获”了外部变量。

3.1.1 引用捕获

使用引用捕获一个外部变量,只需要在捕获列表变量前面加引用说明符 & 即可,如果捕获列表只有一个 引用说明符但没有变量名称,则表示可以引用访问所有其可以访问到的变量。

示例 3.1.1:

代码语言:javascript
复制
void CaptureByRef()
{
    int total = 20;
    auto func = [&]() {
        cout << "The total num of sutdents is: " << total << endl;

        cout << "There comes a new student. " << endl;
        total++;
    };

    func();

    cout << "Now, the total number of sutdents is: " << total << endl;
}

上述示例在 func 匿名函数中对 total 采用引用访问,并在该函数中对 total 进行加一操作,输出如下:

代码语言:javascript
复制
The total num of sutdents is: 20
There comes a new student.
Now, the total number of sutdents is: 21

3.1.2 值捕获

使用值捕获一个外部变量,只需要在捕获列表变量前面加一个等号 = 即可,如果捕获列表只有一个等号但没有变量名称,则表示可以使用值捕获的方式访问所有其可以访问到的变量。如果我们仅将上述示例的引用访问改为值访问,会怎样?

示例 3.1.2:

代码语言:javascript
复制
void CaptureByValue()
{
    int total = 20;
    auto func = [=]() {
        cout << "The total num of sutdents is: " << total << endl;

        cout << "There comes a new student. " << endl;
        total++;
    };

    func();

    cout << "Now, the total number of sutdents is: " << total << endl;
}

Visual Studio 会提示如下错误,也就是值传递的变量不可以被修改,所以需要将total++; 删掉或移出 lambda 函数。

02_capture_by_value_error

3.1.3 不捕获

空 capture 子句 [] 指示 lambda 表达式的主体不访问封闭范围内的变量。

如果将上述代码中代表值传递的=去掉,就是空 capture 子句:

示例 3.1.3:

代码语言:javascript
复制
void CaptureDefault()
{
    int total = 20;
    auto func = []() {
        cout << "The total num of sutdents is: " << total << endl;
    };

    func();

    cout << "Now, the total number of sutdents is: " << total << endl;
}

此代码会报如下错误封闭函数局部变量不能在lambda体内引用,除非其位于捕获列表中

03_capture_default_error1

假如我们把 total 放入参数列表中,情况如何?

示例 3.1.4:

代码语言:javascript
复制
void CaptureDefault()
{
    int total = 20;
    auto func = [](int total) {
        cout << "The total num of sutdents is: " << total << endl;

        cout << "There comes a new student. " << endl;
        total++;    
        cout << "Now, the total number of sutdents is: " << total << endl;
    };

    func(20);

    cout << "Actually, the total number of sutdents is: " << total << endl;
}

运行结果为:

代码语言:javascript
复制
=========== CaptureDefault test =============
The total num of sutdents is: 20
There comes a new student.
Now, the total number of sutdents is: 21
Actually, the total number of sutdents is: 20

可以看到:虽然参数列表和外部的 total 同名,但是匿名函数并未改变外部 total 的值,这和全局变量与局部变量的差别类似。

3.1.4 捕获方式总结

通过以上示例,基本的捕获方式已经介绍完毕,我们来总结一下:

捕获方式

说明

[]

不捕获任何外部变量

[x1, x2, …]

默认以值捕获的方式捕获指定外部变量 x1, x2, …

[&x1, &x2, …]

以引用捕获的方式捕获指定外部变量x1, x2, …

[this]

以值捕获的方式捕获this指针

[=]

以值捕获的方式捕获所有外部变量

[&]

以引用捕获的方式捕获所有外部变量

[=, &x]

外部变量x以引用捕获方式捕获,其余变量以值捕获的方式捕获

[&, x]

外部变量x以值捕获方式捕获,其余变量以引用捕获的方式捕获

3.2 参数列表

Lambda表达式的参数和普通函数的参数类似,但是在 Lambda 表达式中传递参数还有一些限制,主要有以下几点:

  • 参数列表中不能有默认参数(C++14 起, lambda 能拥有自身的默认参数)
  • 不支持可变参数
  • 所有参数必须有参数名

虽然参数列表中不支持默认参数,但是可以通过 lambda 函数体 后面加一个小括号,在小括号中指定默认值。

代码语言:javascript
复制
void ParamList()
{
    // 指定参数值
    cout << "The total number of sutdents is: " << [](int total) -> int {return total; } (20) << endl;
}

执行结果为:

代码语言:javascript
复制
The total number of sutdents is: 20

3.3 可变规范

通常情况下,lambda 的函数调用运算符是常量的值,但使用的 mutable 关键字就可以改变其值。它不会生成可变的数据成员。 利用可变规范,lambda 表达式的主体可以修改通过值捕获的变量。

示例3.1.2 中在 lambda 函数体内对值捕获的外部变量进行修改会提示错误,我们可以通过加入 mutable 关键字来解决。

示例 3.3.1

代码语言:javascript
复制
void MutableSpecifier()
{
    int total = 20;
    auto func = [=]() mutable {
        cout << "The total number of sutdents is: " << total << endl;

        cout << "There comes a new student. " << endl;
        total++;
        cout << "Now, the total number of sutdents is: " << total << endl;
    };

    func();

    cout << "Actually, the total number of sutdents is: " << total << endl;
}

执行结果:

代码语言:javascript
复制
The total number of sutdents is: 20
There comes a new student.
Now, the total number of sutdents is: 21
Actually, the total number of sutdents is: 20

从输出结果可以看出,加上 mutable 关键字之后,在 lambda 函数体内可以对外部变量进行修改,但是其修改的有效作用域限制于函数体内,在函数体外部该变量并没有修改。

3.4 异常规范

为闭包类型的 operator() 提供异常规定(https://zh.cppreference.com/w/cpp/language/except_spec)或 noexcept 子句(https://zh.cppreference.com/w/cpp/language/noexcept_spec)。

3.5 返回类型

Lambda 表达式的返回类型是自动推导的。 如果不指定返回类型,可以使用 auto 关键字 。 trailing-return-type 类似于普通方法或函数的返回类型部分。 但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含 trailing-return-type 关键字 ->

如果 lambda 函数体仅包含一个返回语句或其表达式不返回值,则可以省略 lambda 表达式的返回类型部分。 如果 lambda 函数体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。 否则,编译器会推导返回类型为void。

3.6 函数体

Lambda 函数体可以包含普通方法或函数的主体可以包含的任何内容。 普通函数和 lambda 表达式的主体均可访问以下变量类型:

  • 从封闭范围捕获变量
  • 参数
  • 本地声明变量
  • 当在类中声明类数据成员,this 可以被捕获
  • 具有静态存储持续时间的任何变量(例如,全局变量)

4. 嵌套 Lambda 表达式

Lambda 表达式可以嵌套以实现复杂的应用场景。

示例 4.1:

代码语言:javascript
复制
void NestedLambda()
{
    auto result = [](int x) { 
        return [](int y) { 
            return y * 2; 
        }(x)+3; 
    }(5);

    cout << "result: " << result << endl;
}

该表达式用来计算 5*2+3 的值,其输出结果为:

代码语言:javascript
复制
result: 13

参考链接:

  1. https://zh.cppreference.com/w/cpp/language/lambda
  2. https://docs.microsoft.com/zh-cn/cpp/cpp/lambda-expressions-in-cpp?view=vs-2017
  3. https://www.cnblogs.com/DswCnblog/p/5629165.html
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 C与Python实战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 匿名函数概念
  • 2. Lambda 表达式的表示
  • 3. Lambda 表达式各部分
    • 3.1 Capture 子句
      • 3.1.1 引用捕获
      • 3.1.2 值捕获
      • 3.1.3 不捕获
      • 3.1.4 捕获方式总结
    • 3.2 参数列表
      • 3.3 可变规范
        • 3.4 异常规范
          • 3.5 返回类型
            • 3.6 函数体
            • 4. 嵌套 Lambda 表达式
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档