使用Lambda表达式定义函数
我们可以使用Lambda表达式来包装代码,为了在之后对其进行调用。我们可以像调用函数那样,给Lambda表达式传入不同的参数,从而得到不同的结果,这样我们就不需要在类中实现这个函数了。
C++11标准正式将Lambda语法加入C++,之后的C++14和C++17标准中对Lambda语法进行了升级。本节我们将看到如何使用Lambda表达式,以及其给我们带来的改变。
How to do it...
现在我们就来使用Lambda表达式完成一个程序,在实践中体验Lambda表达式:
Lambda表达式不需要任何库,不过我们需要将一些字符串打印在屏幕上,所以需要包含必要的的头文件:
这次我们所有内容都会在主函数中完成。我们定义了两个没有参数的函数对象,并且返回整型常量1和2。需要注意的是,返回部分在大括号对
{}
中,就像普通的函数那样,而小括号()
表示没有参数传入,当然也可以像普通函数那样定义函数签名,对于第二个Lambda表达式没有添加小括号对。不过两个表达式都有中括号对[]
:那么现在我们就来调用这两个函数,就像调用普通函数那样:
现在,来定义另一个函数对象,其名为plus,因为它要将两个参数进行加和:
这个函数对象也不难用。使用
auto
类型定义两个参数,只要是作为参数的实参类型支持加法操作,那么就没有任何问题:当然,我们可以不使用变量的方式对Lambda表达式进行保存。我们只需要在使用到的地方对其进行定义即可:
接下来,我们定义一个闭包,包里面装着一个计数器。当我们调用这个计数器时,其值就会自加,并且会将自加后的值返回。为了对计数变量进行初始化,我们(在中括号对中)对
count
进行了赋值。为了能让函数对获取的值进行修改,我们使用mutable
关键字对函数进行修饰,否则在编译时会出问题:现在让我们调用函数对象5次,并且打印其返回值,观察每次调用后计数器增加后的值:
我们也可以通过捕获已经存在的变量的引用,在闭包中进行修改。这样的话,捕获到的值会自加,并且在闭包外部也能访问到这个变量。为了完成这个任务,我们在中括号对中写入
&a
,&
符号就意味着捕获的是对应变量的引用,而非副本:如果这样能行,那我们就可以多次的调用这个函数对象,并且直接在外部对a变量的值进行观察:
最后一个例子是一个多方位展示,这个例子中一个函数对象可以接受参数,并且将其传入另一个函数对象中进行保存。在这个
plus_ten
函数对象中,我们会调用plus
函数对象:编译并运行代码,我们将看到如下的内容打印在屏幕上。我们也可以自己计算一下,看看打印的结果是否正确:
How it works...
上面的例子并不复杂——添加了数字,并对调用进行计数,并打印计数的结果。甚至用一个函数对象来连接字符串,并用这个函数对象对对应字符串进行计数。不过,这些实现对于对Lambda表达式不太了解的人来说,看着就很困惑了。
所以,先让我们了解一下Lambda表达式的特点:
Lambda表达式的最短方式可以写为[]{}
。其没有参数,没有捕获任何东西,并且也不做实质性的执行。
那么其余的部分是什么意思呢?
捕获列表 capture list
指定我们需要捕获什么。其由很多种方式,我们展示两种比较“懒惰”的方式:
将Lambda表达式写成
[=] () {...}
时,会捕获到外部所有变量的副本。将Lambda表达式写成
[&] () {...}
时,会捕获到外部所有变量的引用。
当然,也可以在捕获列表中单独的去写需要捕获的变量。比如[a, &b] () {...}
,就是捕获a
的副本和b
的引用,这样捕获列表就不会去捕获那些不需要捕获的变量。
本节中,我们定义了一个Lambda表达式:[count=0] () {...}
,这样我们就不会捕获外部的任何变量。我们定义了一个新的count
变量,其类型通过初始化的值的类型进行推断,由于初始化为0,所以其类型为int
。
所以,可以通过捕获列表捕获变量的副本和/或引用:
[a, &b] () {...}
:捕获a
的副本和b
的引用。[&, a] () {...}
:除了捕获a
为副本外,其余捕获的变量皆为引用。[=, &b, i{22}, this] () {...}
:捕获b
的引用,this
的副本,并将新变量i
初始化成22,并且其余捕获的变量都为其副本。
Note:
当你需要捕获一个对象的成员变量时,不能直接去捕获成员变量。需要先去捕获对象的
this
指针或引用。
mutable (optional)
当函数对象需要去修改通过副本传入的变量时,表达式必须用mutable
修饰。这就相当于对捕获的对象使用非常量函数。
constexpr (optional)
如果我们显式的将Lambda表达式修饰为constexpr
,编译器将不会通过编译,因为其不满足constexpr
函数的标准。constexpr
函数有很多条件,编译器会在编译时对Lambda表达式进行评估,看其在编译时是否为一个常量参数,这样就会让程序的二进制文件体积减少很多。
当我们不显式的将Lambda表达式声明为constexpr
时,编译器就会自己进行判断,如果满足条件那么会将Lambda表达式隐式的声明为constexpr
。当我们需要一个Lambda表达式为constexpr
时,我们最好显式的对Lambda的表达式进行声明,当编译不通过时,编译器会告诉我们哪里做错了。
exception attr (optional)
这里指定在运行错误时,是否抛出异常。
return type (optional)
当想完全控制返回类型时,我们不会让编译器来做类型推导。我们可以写成这样[] () -> Foo {}
,这样就告诉编译器,这个Lambda表达式总是返回Foo
类型的结果。
Last updated