【C++11特性】Lambda表达式(匿名函数)
一、函数对象
在C++中,我们把所有能当作函数使用的对象当作函数对象。
一般来说,如果我们列出一个对象,而它的后面又跟有由花括号包裹的参数列表,就像fun(arg1, arg2, …),这个对象就被称为函数对象。函数对象大致可分为以下几类:
1)函数类
函数类,即重载了()括号操作符。
其可以直接通过该类对象 val()调用,其与直接定义val()效果一致。
优势:
a) 函数对象的类是我们自定义的,所以类中可以自定义状态变量,该函数对象多次调用过程中可以共享该状态,灵活度比函数调用更高。
b) 函数对象有自己特有的类型,而普通函数无类型可言。因此我们在使用STL的时候,可以传递相应类型作为参数来实例化模版,实现我们自己的规则。
2)Lambda
2.1、概述:
C++ 11 中的 Lambda 表达式用于定义并创建匿名的函数对象,以简化编程工作。
在QT之前的总结中,我们可以知道其是通过外部的 [ ] 来明确函数体内部可以访问的外部变量,这个过程称为Lambda表达式 “捕获” 了外部变量。
类似参数传递方式(值传递、引入传递、指针传递),在Lambda表达式中,外部变量的捕获方式也有值捕获、引用捕获、隐式捕获。
语法格式:
[函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}
a.函数对象参数:
表示Lambda表达式开始,这部分必须存在。[ ] 中引用的变量,称作闭包。
函数对象参数具体有以下形式:
b.操作符重载函数参数:
标识重载的 () 操作符的参数,没有参数时,这部分可以省略。参数可以通过
按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递。
c.mutable 或 exception 声明
这部分可以省略。添加mutable修饰,可以修改传递进来的拷贝。而
exceptions声明用来指定函数抛出异常,感觉用得比较少。
d.-> 返回值类型
标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的
地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
e.{函数体}
可为空,但不可省略。
2.2、实例应用:
【注:图中实例暂未运行成功,不知道是不是没有按照C++11编译】
这样的代码编译的时候,lambda会自动生成一个类,其的成员变量对应每一个捕获到的变量,这跟上面说道的函数类类似,如下图:
3)std : : function类
当需要一个非模版函数对象作为类的成员或函数参数时,你必须指定函数对象的具体类型,C++没有这样的基类,但标准库中提供了一个模版类std::function来代表所有函数对象。
std::function抽象了函数对象的类型和参数,因此无论普通函数,还是函数指针、lambdas,又或是任何可以被当做函数使用的对象,只要拥有相同参数和返回值,均可以用同一类std::function表示。
尽管std::function非常有用,但是它也带来了性能损失。这是因为为了隐藏包含的函数对象类型,提供通用的调用接口,std::function使用了叫做type erasure(类型擦除)的技术。
简单来说是通过虚函数的调用在运行期来决定具体调用,因此编译器无法内联(inline)函数调用,也无法进行更多优化。
【注:类型擦除在后续文章中总结。】
实例:
4)std : : bind(参数绑定)和闭包
大多数的编程范式都提供了提高代码重用的方式,比如在面向对象编程中,我们可以通过抽象出特定类来将复杂系统拆分成小的组件,在降低耦合的同时也可以分开设计、测试代码。
而在函数式编程中,我们可以通过标准库std::bind来创造闭包。
std::bind返回一个基于f的函数对象,其参数被绑定到args上。f的参数要么被绑定到值,要么被绑定到placeholders(占位符,如_1, _2, …, _n)。
通过std::bind,我们可以为同一个类的不同对象分配不同的实现,从而实现不同的行为。这种方式使得我们不需要设计继承和多态的架构,为程序库设计提供了新方式。
【注:程序库的设计不应该给使用者带来不必要的耦合,而继承是排名第二的耦合方式,最强耦合是友元…】