我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++精要分析lambda表达式的使用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++精要分析lambda表达式的使用

引言

C++要走向现代语言,如果不支持lambda表达式,你很难认为这门语言和现代有什么关系。幸好,从C++11标准起,它就实现了对lambda表达式的支持。

那么,什么是lambda表达式呢?

lambda表达式是匿名函数,就是说不用定义函数名,函数实现可以直接嵌入在业务逻辑代码中。诸如python、java、C#等语言,都将其作为基础特性。

其优点是提高了代码的可读性,对于一些无需重用的方法特别适合。例如在容器的迭代中实现特定的查询逻辑。

语法与示例

C++11标准中,对于lambada表达式的定义如下:

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

  • [captures] —— 捕获列表,它用于捕获当前函数作用域的零个或多个变量,变量之间用逗号分隔。
  • {params} —— 可选参数列表,其语法与普通函数参数列表一致。如果不需要参数,则可以忽略此项。
  • specifiers —— 可选限定符,可选值为mutable。其意义是可以在函数体内修改按值捕获的变量。
  • exception —— 可选异常说明符,可以使用noexcept来指明lambda是否会抛出异常。
  • ret —— 可选返回值类型,lambda使用返回类型后置的语法来表示返回类型。如果没有返回值 ,则可忽略此部分。如果不指定返回类型,则编译器会根据代码实现为函数推导一个返回类型。
  • {body} —— 表达式的函数体,此部分与实现普通函数体一致。

从上面的定义可以看到,lambda表达式的语法多少有些与我们以往的认知不太一样。所以,我们直接上代码来体会吧。

#include <iostream>
int main() {
    int x = 10;
    auto foo = [x](int y)->int { return x + y; };
    std::cout << foo(7) << std::endl;
}

各位不要手懒,务必打开IDE将这段代码运行一下,看看结果。然后再尝试修改一下参数类型,或者返回类型。

增加mutable之后,对x变量进行修改,看看会发生什么?

int main() {
    int x = 10;
    auto foo = [x](int y) mutable ->int { x++; return x + y; };
    std::cout << foo(7) << std::endl;
    std::cout << foo(7) << std::endl;
    std::cout << foo(7) << std::endl;
}

而一个最简形式的lambda表达式,可以是 auto foo = [] { return 0; };

所以大家以后看到类似语法,可不要大惊小怪了,还以为这是什么另类的数组访问方式,或者琢磨->这个指针指向了个什么东西。

捕获列表

毫无疑问,lambda表达式中,最反直觉的就是捕获列表的定义。毕竟在我们的认知里,中括号是用来定义数组并访问数组元素的。

而且捕获列表的诸多特性,也是面试中挖坑的好地方。我们先从作用域开始说起。

关于lambda表达式的作用域,有四个重点:

  • 第一,捕获列表的变量有两个作用域,一是lambda定义的函数作用域,二是表达式函数体所在代码的作用域;
  • 第二,在表达式函数体内,默认情况下,被捕获变量是只读属性。如需修改,则要添加mutable标识;
  • 第三,在表达式函数体内,修改被捕获变量的值,不影响原始变量的值;
  • 第四,被捕获变量必须是非静态局部变量。

好,为了加强印象,我们先从面试挖坑场景开始。请先看第一坑:

int main() {
    int x = 10;
    auto foo = [x](int y) ->int { x++; return x + y; };
    std::cout << foo(8) << std::endl;
}

问:上述代码能通过编译吗?如果不能,是为什么?

答:不能。因为根据规则一和二,在表达式函数体内,x变量是只读,不能被修改。

好,上面的只是开胃菜,诡异的第二坑来了:

int main() {
    int x = 10;
    auto foo = [x](int y) mutable ->int {
        x++;
        std::cout << x << std::endl;
        return x + y;
    };
    foo(8);
    foo(8);
    std::cout << x << std::endl;
}

问:在上述代码执行完后,请说出所有std::cout语句输出什么内容?

答:根据规则三,分别输出11,12,10。

所以大家可以看到,前两次输出是在表达式体内对x值进行修改,x的状态是保留的了。但在函数体外,x变量的值仍然保持不变。千万要记住这个规则,这就是存在两个作用域所得到的结果。

后面我们会讲解其实现原理,这样就可以方便记忆这条规则了。

再来第三坑:

int y = 100;
int main() {
    static int x = 10;
    auto foo = [x, y](int z) ->int { return x + y + z; };
    std::cout << foo(8) << std::endl;
}

问:上述代码有什么问题,应该如何调整?

答:根据作用域规则四,x与y都不能作为lambda表达式的捕获列表变量。在表达式函数体内可以直接使用静态或者全局变量,所以只要修改为auto foo = [](int z) ->int { return x + y + z; };即可。

虽然上面这段代码放在gcc编译器下不会报错,只是出警告:capture of variable ‘x’ with non-automatic storage duration。但编译器实际上是作了一次选择,即认为你的意图是作为全局或静态变量来使用。可如果你只是手误或者忘了要调整代码,那就会出现预料之外的运行结果。

实际上,笔者所在公司就严格要求不能出现编译警告,这对于防止运行期出现偏差是非常重要的。

捕获引用

上一节我们所讲的,都是捕获值的用法。那么想将变量作为引用传递进lambda表达式,是否可以呢?

答案是肯定的,示例如下:

int main() {
    int x = 10;
    int y = 11;
    auto foo = [&x, &y](int z) mutable ->int {
        x++;
        y++;
        return x + y + z;
    };
    std::cout << foo(8) << std::endl;
    std::cout << x << std::endl;
    std::cout << y << std::endl;
}

可以看到,捕获引用就是在变量前加上&符号即可。切记不要与取地址相混淆。被引用变量在函数体内修改,也会影响函数体外同名变量的值,这一点与我们以往的认知相同。

至于上述代码结果,请各位先在脑海里执行一遍,然后上机验证。

特殊用法

捕获列表支持三种特殊情况,下面分别说明:

  • [this] —— 捕获this指针,可以在类内的lambda表达式函数体内使用,以访问成员变量和方法;
  • [=] —— 捕获当前作用域内全部变量的值,包括this。当变量较多时,可使用此符号;
  • [&] —— 捕获当前作用域内所有变量的引用,包括this。

先看[this]的示例:

class CaptureOne {
    public:
        void test() {
            auto foo = [this] { std::cout << x << std::endl; };
            foo();
        }
    private:
        int x = 100;
};
int main() {
    CaptureOne one;
    one.test();
}

建议大家在上面这个例子里继续扩展,例如增加一个类方法,看看在表达式函数中如何调用,以及执行结果是什么。

再看[=]的示例:

int main() {
    int x = 10;
    int y = 11;
    int z = 12;
    auto foo = [=]() mutable ->int {
        x++;
        y++;
        return x + y + z;
    };
    std::cout << foo() << std::endl;
    std::cout << x << std::endl;
    std::cout << y << std::endl;
}

可以看到,这的确是方便了代码书写。如果表达式函数体内的变量增加或减少,都不必再费心思去调整捕获列表了。

另外,不知道大家还注意到一个细节没有,就是当参数列表为空时,如果存在mutable标识,则()是不能省略的。

最后,[&]还是烦请各位自己去实践并体验。

实现原理

C++多年老手,在上手lambda表达式的时候,应该会马上在脑海里跟函数对象关联起来。

我们先从定义一个函数类开始说起。所谓函数类,就是定义一个类,然后重载operator ()。这样可以将类对象作为函数一样来调用。

示例如下:

class FuncOne {
    public:
        FuncOne(int x, int y) : valx(x), valy(y){}
        int operator () () {
            return valx + valy;
        }
    private:
        int valx;
        int valy;
};
int main() {
    FuncOne one(10, 20);
    std::cout << one() << std::endl;
}

上述代码就是函数类的定义与函数对象的使用方法。而C++11标准中的lambda表达式,其实就是在编译器在编译期生成一个闭包类(可以理解为类似于FuncOne的类),然后在运行时由这个闭包类生成一个对象,称之为闭包。

而闭包就是一个匿名的函数对象,它包含了定义时作用域的上下文。这样我们就会发现lambda表达式,其实就是C++11给我们提供的一个语法糖。

现在再回头去理解捕获列表的四条原则,是否有恍然大悟之感?理解了原理,对于原则就不用死记硬背了,祝各位面试好运!

应用

当然,我们不能满足于只过面试关,还应当在工作中用好它才行。做C++开发的,用好STL是基本功,而lambda表达式与STL相结合,可以简化代码,发挥出强大的威力来。

先看一个需求:我们要在一组vector列表中,打印可以整除4的数。

示例代码:

#include        <iostream>
#include        <vector>
#include        <algorithm>
int main() {
    std::vector<int> num_list = {1, 3, 9, 10, 12, 17, 18};
    std::cout << *std::find_if(num_list.cbegin(), num_list.cend(),
            [](int i) { return (i % 4) == 0;}) << std::endl;
}

大家可以看到,如果是在以前,要实现这个功能,需要额外写多少代码。

也希望各位能从这个简单示例开始,回头去梳理自己以前写的代码,可以怎样优化结构并提高效率。并且在将来的开发过程中,不断修炼功夫,成为C++大咖!

到此这篇关于C++精要分析lambda表达式的使用的文章就介绍到这了,更多相关C++lambda内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++精要分析lambda表达式的使用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java的Lambda表达式使用实例分析

这篇文章主要讲解了“Java的Lambda表达式使用实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java的Lambda表达式使用实例分析”吧!Lambada 简介lambda 表达
2023-07-05

为什么要使用lambda表达式

这篇文章给大家分享的是有关为什么要使用lambda表达式的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。前言lambda表达式的语法结构:#!/usr/bin/python3 # 可写函数说明sum = lambd
2023-06-02

C++11的lambda表达式怎么使用

这篇文章主要讲解了“C++11的lambda表达式怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++11的lambda表达式怎么使用”吧!可变lambda假设有如下vector,保
2023-06-19

C#中如何使用Lambda表达式

本篇文章为大家展示了C#中如何使用Lambda表达式,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。C# Lambda表达式我们从“所有字符串查找包含YJingLee子字符串”说起。在C# 2.0中,
2023-06-17

Java的Lambda表达式实例分析

这篇“Java的Lambda表达式实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java的Lambda表达式实例分析
2023-06-29

C++ 中如何使用lambda表达式?

lambda 表达式是 c++++ 中的匿名函数,用于创建一次性的函数。它们通过捕获列表访问外部作用域变量,并可以接收参数和定义返回类型。lambda 表达式通常用于快速创建或在运行时传递函数。它们可以访问 lvalue 和 rvalue,
C++ 中如何使用lambda表达式?
2024-04-12

C#中Lambda表达式如何使用

本篇内容介绍了“C#中Lambda表达式如何使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、介绍1、"Lambda表达式"是一个特殊的
2023-06-30

Linq lambda表达式的示例分析

小编给大家分享一下Linq lambda表达式的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Linq lambda表达式了解过C# 3.0的新特性的话应
2023-06-17

C++11中的lambda表达式怎么使用

本篇内容介绍了“C++11中的lambda表达式怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!可调用对象对于一个表达式e,如果可以编
2023-06-19

C++11中lambda表达式如何使用

本篇文章为大家展示了C++11中lambda表达式如何使用,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。首先这个lambda就是罗马字母λ,lambda表达式即λ表达式。数学上有一个概念叫λ演算,其
2023-06-19

C++ lambda 表达式如何定义和使用?

c++++ lambda 表达式是匿名函数,用于内联定义函数,使用语法:[capture list](parameters) -> return_type {函数体}。它们可以捕获外部范围内的变量,传递参数,并指定返回类型。lambda 表
C++ lambda 表达式如何定义和使用?
2024-04-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录