一、函数对象概念
在c++中,我们把所有能当作函数使用的对象统称为函数对象。它是实现operator()的任何类型,此运算符被称为调用运算符,当调用此操作符时,其表现形式如同普通函数调用一般,因此取名叫函数对象。C++标准库主要使用函数对象作为容器和算法内的排序条件。在多线程编程中,主要使用函数对象来实现参数的传递。
二、函数对象的优势(相比普通函数)
- 函数对象可包含状态
- 函数对象是一个类型,因此可用作模板参数
三、函数对象分类
3.1 函数类
一个函数类,即重载了()操作符的类。当用该类的对象调用此操作符时,其表现形式如同普通函数一般,因此取名函数类。
//类
class FuncObjType
{
public:
//重载()操作符
void operator()()
{
cout<<"hello C++"<<endl;
}
}
//普通函数
void val()
{
cout<<"Hello C++!"<<endl;
}
int main()
{
//实例化类对象
FuncObjType val;
//调用函数类
val();
//普通函数调用
val();
}
**注:**类FuncObjType中重载了()操作符,因此对于该类对象val,可以调用该操作符val().该调用语句跟调用普通函数完全一样。
**疑问:**既然用函数对象与调用普通函数有相同的效果,为什么还要搞这么麻烦定义一个类来使用函数对象?(主要原因有以下几点)
- 函数对象可以有自己的状态,我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。
- 函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则和定制自己的算法,如排序算法。
3.2 Lambda
3.2.1 Lambda产生背景
使用 STL 时,往往会大量用到函数对象,为此要编写很多函数对象类。有的函数对象类只用来定义了一个对象,而且这个对象也只使用了一次,编写这样的函数对象类就有点浪费,而且,定义函数对象类的地方和使用函数对象的地方可能相隔较远,看到函数对象,想要查看其 operator() 成员函数到底是做什么的也会比较麻烦。对于只使用一次的函数对象类,能否直接在使用它的地方定义呢?Lambda 表达式能够解决这个问题。使用 Lambda 表达式可以减少程序中函数对象类的数量,使得程序更加优雅。C++11的一大亮点就是引入了Lambda表达式。利用Lambda表达式,可以方便的定义和创建匿名函数。Lambda表达式通过在最前面的方括号[ ]来明确指明其内部可以访问的外部变量,这一过程也称过Lambda表达式“捕获”了外部变量。类似参数传递方式(值传递、引入传递、指针传递),在Lambda表达式中,外部变量的捕获方式也有值捕获、引用捕获、隐式捕获。
3.2.2Lambda表达式的声明
格式如下:
//完整声明格式
[capture list] (params list) mutable exception-> return type {
function body }
//忽略某些成分的声明格式
[capture list](params list)->return type{
function body} //格式1
[capture list](params list){
function body} //格式2
[capture list]{
function body} //格式3
各项具体含义:
- capture list: 捕获外部变量列表
- params list: 形参列表
- mutable: 用来说明是否可以修改捕获的变量
- exception: 异常设定
- return type: 返回类型
- function body:函数体
格式说明:
- 格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值
- 格式2省略了返回值类型,但是编译器根据以下规则能够推断出Lambda表达式的返回类型。(1)如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定.(2)如果function body中没有return语句,则返回值为void类型。
- 格式3中省略了参数列表,类似普通函数中的无参函数。
示例:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool cmp(int a, int b)
{
return a < b;
}
int main()
{
vector<int> myvec{
3, 2, 5, 7, 3, 2 };
vector<int> lbvec(myvec);
sort(myvec.begin(), myvec.end(), cmp); // 旧式做法
cout << "predicate function:" << endl;
for (int it : myvec)
cout << it << ' ';
cout << endl;
sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool {
return a < b; }); // Lambda表达式
cout << "lambda expression:" << endl;
for (int it : lbvec)
cout << it << ' ';
}
注:在C++11之前,我们使用STL的sort函数,需要提供一个谓词函数。如果使用C++11的Lambda表达式,我们只需要传入一个匿名函数即可,方便简洁,而且代码的可读性也比旧式的做法好多了。
3.2.3捕获外部变量
Lambda表达式可以使用其可见范围内的外部变量,但必须明确声明。Lambda表达式通过在最前面的方括号[]来明确指明其内部可以访问的外部变量,这一过程也称过Lambda表达式“捕获”了外部变量。
示例:
#include <iostream>
using namespace std;
int main()
{
int a = 123;
auto f = [a] {
cout << a << endl; };
f(); // 输出:123
//或通过“函数体”后面的‘()’传入参数
auto x = [](int a){
cout << a << endl;}(123);
}
**注:**上面这个例子先声明了一个整型变量a,然后再创建Lambda表达式,该表达式“捕获”了a变量,这样在Lambda表达式函数体中就可以获得该变量的值。
类似参数传递方式(值传递、引入传递、指针传递),在Lambda表达式中,外部变量的捕获方式也有值捕获、引用捕获、隐式捕获。
(1)值捕获
值捕获和参数传递中的值传递类似,被捕获的变量的值在Lambda表达式创建时通过值拷贝的方式传入,因此随后对该变量的修改不会影响影响Lambda表达式中的值。
示例:
int main()
{