- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
18.4 Lambda 函数
见到术语 lambda 函数(也叫 lambda 表达式,常简称为 lambda)时,您可能怀疑 C++11 添加这项新功能旨在帮助编程新手。看到下面的 lambda 函数示例后,您可能坚定了自己的怀疑:
但 lambda 函数并不像看起来那么晦涩难懂,它们提供了一种有用的服务,对使用函数谓词的 STL 算法来说尤其如此。
18.4.1 比较函数指针、函数符和 Lambda 函数
来看一个示例,它使用三种方法给 STL 算法传递信息:函数指针、函数符和 lambda。出于方便的考虑,将这三种形式通称为函数对象,以免不断地重复“函数指针、函数符或 lambda”。假设您要生成一个随机整数列表,并判断其中多少个整数可被 3 整除,多个少整数可被 13 整除。
生成这样的列表很简单。一种方案是,使用 vector<int>存储数字,并使用 STL 算法 generate( ) 在其中填充随机数:
函数 generate( ) 接受一个区间(由前两个参数指定),并将每个元素设置为第三个参数返回的值,而第三个参数是一个不接受任何参数的函数对象。在上述示例中,该函数对象是一个指向标准函数 rand( ) 的指针。
通过使用算法 count_if( ),很容易计算出有多少个元素可被 3 整除。与函数 generate( ) 一样,前两个参数应指定区间,而第三个参数应是一个返回 true 或 false 的函数对象。函数 count_if( ) 计算这样的元素数,即它使得指定的函数对象返回 true。为判断元素能否被 3 整除,可使用下面的函数定义:
同样,为判断元素能否被 13 整除,可使用下面的函数定义:
定义上述函数后,便可计算复合条件的元素数了,如下所示:
下面复习一下如何使用函数符来完成这个任务。第 16 章介绍过,函数符是一个类对象,并非只能像函数名那样使用它,这要归功于类方法 operator( ) ( )。就这个示例而言,函数符的优点之一是,可使用同一个函数符来完成这两项计数任务。下面是一种可能的定义:
这为何可行呢?因为可使用构造函数创建存储特定整数值的 f_mod 对象:
而这个对象可使用方法 operator( ) 来返回一个 bool 值:
构造函数本身可用作诸如 count_if( ) 等函数的参数:
参数 f_mod(3) 创建一个对象,它存储了值 3;而 count_if( ) 使用该对象来调用 operator( ) ( ),并将参数 x 设置为 numbers 的一个元素。要计算有多少个数字可被 13(而不是 3)整除,只需将第三个参数设置为 f_mod(3)。
最后,来看看使用 lambda 的情况。名称 lambda 来自 lambda calculus(λ演算)—一种定义和应用函数的数学系统。这个系统让您能够使用匿名函数—即无需给函数命名。在 C++11 中,对于接受函数指针或函数符的函数,可使用匿名函数定义(lambda)作为其参数。与前述函数 f3( ) 对应的 lambda 如下:
这与 f3( ) 的函数定义很像:
差别有两个:使用[]替代了函数名(这就是匿名的由来);没有声明返回类型。返回类型相当于使用 decltyp 根据返回值推断得到的,这里为 bool。如果 lambda 不包含返回语句,推断出的返回类型将为 void。就这个示例而言,您将以如下方式使用该 lambda:
也就是说,使用使用整个 lambad 表达式替换函数指针或函数符构造函数。
仅当 lambad 表达式完全由一条返回语句组成时,自动类型推断才管用;否则,需要使用新增的返回类型后置语法:
程序清单 18.4 演示了前面讨论的各个要点。
程序清单 18.4 lambda0.cpp
下面是该程序的输出示例:
输出表明,样本很小时,得到的统计数据并不可靠。
18.4.2 为何使用 lambda
您可能会问,除那些表达式狂热爱好者,谁会使用 lambda 呢?下面从 4 个方面探讨这个问题:距离、简洁、效率和功能。
很多程序员认为,让定义位于使用的地方附近很有用。这样,就无需翻阅多页的源代码,以了解函数调用 count_if( ) 的第三个参数了。另外,如果需要修改代码,涉及的内容都将在附近;而剪切并粘贴代码以便在其他地方使用时,涉及的内容也在一起。从这种角度看,lambda 是理想的选择,因为其定义和使用是在同一个地方进行的;而函数是最糟糕的选择,因为不能在函数内部定义其他函数,因此函数的定义可能离使用它的地方很远。函数符是不错的选择,因为可在函数内部定义类(包含函数符类),因此定义离使用地点可以很近。
从简洁的角度看,函数符代码比函数和 lambda 代码更繁琐。函数和 lambda 的简洁程度相当,一个显而易见的例外是,需要使用同一个 lambda 两次:
但并非必须编写 lambda 两次,而可给 lambda 指定一个名称,并使用该名称两次:
您甚至可以像使用常规函数那样使用有名称的 lambda:
然而,不同于常规函数,可在函数内部定义有名称的 lambda。mod3 的实际类型随实现而异,它取决于编译器使用什么类型来跟踪 lambda。
这三种方法的相对效率取决于编译器内联那些东西。函数指针方法阻止了内联,因为编译器传统上不会内联其地址被获取的函数,因为函数地址的概念意味着非内联函数。而函数符和 lambda 通常不会阻止内联。
最后,lambda 有一些额外的功能。具体地说,lambad 可访问作用域内的任何动态变量;要捕获要使用的变量,可将其名称放在中括号内。如果只指定了变量名,如[z],将按值访问变量;如果在名称前加上&,如[&count],将按引用访问变量。[&]让您能够按引用访问所有动态变量,而[=]让您能够按值访问所有动态变量。还可混合使用这两种方式,例如,[ted, &ed]让您能够按值访问 ted 以及按引用访问 ed,[&, ted]让您能够按值访问 ted 以及按引用访问其他所有动态变量,[=, &ed]让您能够按引用访问 ed 以及按值访问其他所有动态变量。在程序清单 18.4 中,可将下述代码:
替换为如下代码:
[&count13]让 lambda 能够在其代码中使用 count13。由于 count13 是按引用捕获的,因此在 lambda 对 count13 所做的任何修改都将影响原始 count13。如果 x 能被 13 整除,则表达式 x % 13 == 0 将为 true,添加到 count13 中时,true 将被转换为 1。同样,false 将被转换为 0。因此,for_each( ) 将 lambda 应用于 numbers 的每个元素后,count13 将为能被 13 整除的元素数。
通过利用这种技术,可使用一个 lambda 表达式计算可被 3 整除的元素数和可被 13 整除的元素数:
在这里,[&]让您能够在 lambad 表达式中使用所有的自动变量,包括 count3 和 count13。
程序清单 18.5 演示了如何使用这些技术。
程序清单 18.5 lambda1.cpp
下面是该程序的示例输出:
输出表明,该程序使用的两种方法(两个独立的 lambda 和单个 lambda)的结果相同。
在 C++中引入 lambda 的主要目的是,让您能够将类似于函数的表达式用作接受函数指针或函数符的函数的参数。因此,典型的 lambda 是测试表达式或比较表达式,可编写为一条返回语句。这使得 lambda 简洁而易于理解,且可自动推断返回类型。然而,有创意的 C++程序员可能开发出其他用法。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论