我经常看到C ++ 11 lambda是使开发人员感到困惑的话题。 他们的奇怪语法可能会让以前从未遇到过的人反感。 那些经常使用它们的人也可能不确定它们的实际工作原理-它们昂贵吗? 我可以复制它们吗? 他们会肿我的代码库吗? 但是,与友谊不同,lambda并不是魔术:它们是用C ++编写特定类型的类(称为函子)的简化方法。
在本文中,我旨在揭开lambda真正背后的神秘面纱,但是请注意,这并不是lambda的初学者指南。 我假设您以前以某种能力使用过C ++ lambda,并且您了解C ++类和模板编程的基础。
- 制作受公共领域启发的游戏
- 通过蓝图使用UMG实现无状态UI
- 如何获取YouTube或Twitch频道的免费游戏键
- Phaser 3中的模块化游戏世界(Tilemaps#1)—静态地图
- 如何奖励用户以最大程度地利用视频广告获利
乍看上去:
- Lambda是常规C ++类
- 他们利用了
operator()
成员函数 - 这些实际上在C ++中被称为“ functors”
- 使用它们没有额外的开销或性能损失
这是关于lambda的两部分中的第一篇,请在此之后阅读第二部分:
C ++ Lambda不是魔术,第2部分🎆
关于lambda有很多神话和奇怪的问题。 本指南将使您对他们的规则有更深入的了解。
medium.com
这是lambda的基本用法。 在给定人员列表的情况下,我们只想过滤那些年龄足以被视为成年人的人。 由于我们可以编写一个lambda内联函数,因此它是非常简洁的代码。
请注意,在lambda声明中,我们如何捕获变量cutoffAge
,接受const Person&
类型的参数并返回布尔值(尽管从未显式定义返回类型)。
在深入研究lambda之前,首先让我们讨论一些自然会引导我们进入lambda的概念。
贵族sort
在职业生涯的某个时刻,所有程序员都需要以一种简单的方式对对象列表进行排序。 假设您的老板刚刚给您完成了一项重要任务:我们有一个学生列表,需要按字母顺序对他们进行排序! 确实,这是很久以来的任务。
为了执行排序,我们需要一个比较器功能 ,让我们知道哪个学生比另一个学生“更大”,例如:
我们还需要一个可以接受此比较器的排序功能。 这是一个简单的例子。 我实际上没有编写任何排序逻辑,因为那将是不必要的噪音。
请注意,我们如何使用模板,该模板将使我们将所需的任何内容传递给第二个参数-在这种情况下,我们将传递一个函数。 然后,我们可以根据需要编写排序,并在需要时使用此comparator
函数。
如果将一个函数传递给另一个函数的想法对您来说并不陌生,请不要惊慌-这是功能编程语言(如Haskell和许多JavaScript)的核心部分。
调用studentSort
很简单:
请注意,我们实际上是如何在不使用任何复杂语法的情况下键入要传递给studentSort
的函数的名称。 现在,我们有了将功能传递给其他功能的方法。 这确实是一个非常强大的概念,但是我们可以走得更远。
让我们来谈谈函子🦗
在C ++中,函子是“可以充当函数的类”的花哨词。 这样做的主要优点是我们可以在函数运行之前向函子提供状态-我们将在稍后再讲到这一点。
首先,让我们将isFirstStudentGreaterThanSecond
转换为一个名为StudentComparator
的函子。 我们必须更新我们的studentSort
函数,以调用新函子类的成员函数。
我们的比较器现在是一个带有.compare
函数的类。 为了使用它,我们需要实例化它。
使它成为“适当的”函子几乎完成了。 我说过它们是“像函数一样起作用的类”,但是现在看起来像是普通类。 输入operator()
。
我们可以对StudentComparator
进行以下调整,使其看起来像常规函数:
好的,我们使类看起来像一个函数。 这给我们带来了什么额外的力量?
您的老板突然出现了一个新问题:亲爱的老亲爱的米金斯夫人(Miggins),把所有学生的名字都输入了系统,却忘了把其中一些大写。 您的排序区分大小写,这意味着所有未大写的名称都已排在列表末尾!
我们不要简单地使排序不区分大小写。 让我们为这类用户添加确定他们想要的功能。 我们可以通过将isCaseSensitive
布尔值添加到StudentComparator
类并在构造函数中传递值来实现。
现在,我们可以通过将配置值传递给构造函数来修改预设函数的行为。 可以编写一个现有的比较器,而不是为区分大小写和不区分大小写的排序写一个单独的比较器。
对于一个简单的概念, StudentComparator
开始看起来像很多代码。 在这种情况下就可以了,但是如果我们需要创建许多任意分类器怎么办? 还是其他函子,例如用于转换或过滤列表的函子?
让我们将在过去几段中学到的所有知识精简为几行:
是的:lambda是用于声明函子的语法糖,其中函子是重载operator()
。 Lambda的捕获列表是指将传递给构造函数的变量。
请注意,我们对类型使用了auto
,因为类型实际上是在编译时生成的,因此我们不知道它是什么。 我们也没有在lambda中提及返回类型-在这种情况下,它会自动推断为bool
。
等等,就是这样吗?
是的 尽管存在关于如何声明lambda以及在某些情况下捕获如何工作的进一步规则,但从根本上讲,lambda只能编译为一个简单的类,该类会使operator()
重载,并且可以通过或不可以通过其构造函数“捕获”变量。
因此,当您说lambda就像函子类时,那只是一个隐喻,对吧?
在手动编写函子类和编写lambda之间,由编译器生成的代码中有99%是相同的。 (我们将在第2部分中对此进行详细介绍。)
术语速记
C ++函子与函子的功能编程定义非常不同。 尽管在这里我不会谈论它们之间的区别,但是请知道它们不是同一回事 ,如果将它们混为一谈,可能会导致很多混乱。
以这种方式将这些变量传递给函子称为“捕获”它们。 在JavaScript和Rust等其他语言中,它被称为“关闭”变量。 如果您之前听过“ closure”一词,则变量comparator
为闭包。 请注意,在这种情况下,函子不一定是闭包,而闭包是函子。
Lambda本身不是函子或闭包-它是创建一个的表达式。 您可以将lambda视为类,将functor视为实际的实例化对象。
希望我已经用lambda揭开了幕后的神秘面纱。
从这里,您已经可以做出更明智的决策,例如何时安全地传递lambda以及是否将其分配在堆栈或堆上。 (在堆栈上!)
但是,lambda的细微之处比我在这里描述的要多。 在第2部分中,我们将简要介绍汇编器的输出,考虑复制lambda时发生的情况,并了解为什么默认情况下您不能对按值捕获的变量进行突变。