文章目录
- 一、函数对象中存储状态
- 1、函数对象中存储状态简介
- 2、示例分析
- 二、函数对象作为参数传递时值传递问题
- 1、for_each 算法的 函数对象 参数是值传递
- 2、代码示例 - for_each 函数的 函数对象 参数在外部不保留状态
- 3、代码示例 - for_each 函数的 函数对象 返回值
一、函数对象中存储状态
1、函数对象中存储状态简介
在 C++ 语言中 , 函数对象 / 仿函数 可以像函数一样被调用 , 并且 其 还具有类的特征 , 可以 通过 继承 和 重载 来 修改 重载函数调用操作符函数 的行为 ;
函数对象 / 仿函数 通常是通过 定义一个类 , 然后为这个类 重载 函数调用操作符 () 来实现的 ;
函数对象的一个重要特性是 " 可以存储状态 " ; 这意味着你可以 在类的成员变量中存储数据 , 这些数据可以 在函数调用之间保持不变 ;
普通的函数 是 无法存储状态 的 , 因为 普通函数 中 局部变量 在函数执行完成后 , 自动销毁 ;
函数对象 / 仿函数 的一个主要优势是它们可以拥有状态 , 而普通函数则不能 ;
这使得 " 函数对象 / 仿函数 " 在需要保持 某些数据或状态 在 多次函数调用 之间不变的情况下非常有用 ,
例如 : 在 STL 算法中 , 函数对象经常被用作 谓词 或 用于在容器的每个元素上执行某种操作的函数 , 由于它们可以存储状态 , 因此可以根据算法的需要进行定制 ;
在下面的示例中 , 函数对象 中 维护了一个状态位 , 用于记录该 函数对象 的调用次数 ;
下面的 函数对象 / 仿函数 中 , 存储了状态 n , 每调用一次该仿函数 , 该成员自增 1 ;
//函数对象 类重载了()
template <typename T>
class PrintT {
public:
void operator()(T& t) {
cout << n << " . " << t << endl;
// 每调用一次, 自增 1
n++;
}
private:
// 每调用一次, 该成员自增 1
// 该状态一直存储
int n = 0;
};
2、示例分析
在下面的代码示例中 ,
首先 , 定义了 函数对象 / 仿函数 PrintT 类 , 该类 重载了 函数调用操作符 () , 其重载函数是 void operator()(T& t)
;
- 在该 函数对象 中 , 存储了一个状态值 n ,
- 每次调用该 重载函数 , 状态值 n 都会自增 1 ;
//函数对象 类重载了()
template <typename T>
class PrintT {
public:
void operator()(T& t) {
cout << n << " . " << t << endl;
// 每调用一次, 自增 1
n++;
}
private:
// 每调用一次, 该成员自增 1
// 该状态一直存储
int n = 0;
};
然后 , 在 foreach 循环中 , 将该 函数对象 传入 循环算法 中 , 每次遍历 vector 容器中的元素时 , 都会调用 该 函数对象 , 同时 每次调用 时 , 函数对象中的 n 值都会自增 1 ;
// 向 foreach 循环中传入函数对象
// 在函数对象中打印元素内容
for_each(vec.begin(), vec.end(), PrintT<int>());
代码示例 :
#include "iostream"
using namespace std;
#include <vector>
#include <algorithm>
#include "functional"
//函数对象 类重载了()
template <typename T>
class PrintT {
public:
void operator()(T& t) {
cout << n << " . " << t << endl;
// 每调用一次, 自增 1
n++;
}
private:
// 每调用一次, 该成员自增 1
// 该状态一直存储
int n = 0;
};
int main() {
// 创建一个 vector 单端数组容器
vector<int> vec;
// 向容器中插入元素
vec.push_back(1);
vec.push_back(3);
vec.push_back(5);
// 向 foreach 循环中传入函数对象
// 在函数对象中打印元素内容
for_each(vec.begin(), vec.end(), PrintT<int>());
// 控制台暂停 , 按任意键继续向后执行
system("pause");
return 0;
};
执行结果 : 打印时 , 先把状态值 n 打印出来 , 然后跟着打印 vector 容器中的元素 ,
0 . 1
1 . 3
2 . 5
请按任意键继续. . .
二、函数对象作为参数传递时值传递问题
1、for_each 算法的 函数对象 参数是值传递
下面开始分析 for_each 函数中 函数对象 作为参数的 具体细节 ;
for_each 算法的调用代码如下 :
// 向 foreach 循环中传入函数对象
// 在函数对象中打印元素内容
for_each(vec.begin(), vec.end(), PrintT<int>());
for_each 算法的函数原型如下 :
// FUNCTION TEMPLATE for_each
template <class _InIt, class _Fn>
_Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
for (; _UFirst != _ULast; ++_UFirst) {
_Func(*_UFirst);
}
return _Func;
}
上述 for_each 函数的 形参 _Fn _Func 是一个 值 , 不是引用 ;
传递的是 引用 的话 , 那么 外部的对象 和 实参值 是相同的对象 ;
传递的是 值 的话 , 那么 实参 只是 外部的对象 的 副本值 , 在 for_each 函数中 , 无论如何操作改变实参 , 都不会影响到 外部的对象 ;
如果 在 for_each 算法中 调用了 函数对象 , 函数对象中 有 状态改变 ;
在 for_each 算法 外部 继续调用该 函数对象 , 由于 for_each 是 值传递 , 传递的 只是 函数对象副本 , 副本的 状态改变 不会影响到外部函数 ;
如果想要 保留上述 状态改变 , 则需要使用 函数对象 接收 for_each 的返回值 , 这个函数对象 保留了 内部 函数对象参数副本 的状态值 ;
2、代码示例 - for_each 函数的 函数对象 参数在外部不保留状态
如果 在 for_each 算法中 调用了 函数对象 , 函数对象中 有 状态改变 ;
在 for_each 算法 外部 继续调用该 函数对象 , 由于 for_each 是 值传递 , 传递的 只是 函数对象副本 , 副本的 状态改变 不会影响到外部函数 ;
在外部调用 函数对象 时 , 发现状态值 还是 0 , 这说明 值传递 改变的是 函数对象实参副本值 , 没有影响外部的 函数对象 值 ;
0 . 666
代码示例 :
#include "iostream"
using namespace std;
#include <vector>
#include <algorithm>
#include "functional"
//函数对象 类重载了()
template <typename T>
class PrintT {
public:
void operator()(T& t) {
cout << n << " . " << t << endl;
// 每调用一次, 自增 1
n++;
}
private:
// 每调用一次, 该成员自增 1
// 该状态一直存储
int n = 0;
};
int main() {
// 创建一个 vector 单端数组容器
vector<int> vec;
// 向容器中插入元素
vec.push_back(1);
vec.push_back(3);
vec.push_back(5);
// 创建函数对象
PrintT<int> printT;
// 向 foreach 循环中传入函数对象
// 在函数对象中打印元素内容
for_each(vec.begin(), vec.end(), printT);
// 再次调用 函数对象
cout << "再次调用函数对象 : " << endl;
int a = 666;
printT(a);
// 控制台暂停 , 按任意键继续向后执行
system("pause");
return 0;
};
执行结果 :
0 . 1
1 . 3
2 . 5
再次调用函数对象 :
0 . 666
请按任意键继续. . .
3、代码示例 - for_each 函数的 函数对象 返回值
如果 在 for_each 算法中 调用了 函数对象 , 函数对象中 有 状态改变 ;
在 for_each 算法 外部 继续调用该 函数对象 , 由于 for_each 是 值传递 , 传递的 只是 函数对象副本 , 副本的 状态改变 不会影响到外部函数 ;
如果想要 保留上述 状态改变 , 则需要使用 函数对象 接收 for_each 的返回值 , 这个函数对象 保留了 内部 函数对象参数副本 的状态值 ;
使用 PrintT<int> printT;
函数对象 变量 , 接收 for_each 算法的返回值 , 再次执行该 函数对象 调用 , 发现 状态值被保留了下来 , 打印值为 :
3 . 666
代码示例 :
#include "iostream"
using namespace std;
#include <vector>
#include <algorithm>
#include "functional"
//函数对象 类重载了()
template <typename T>
class PrintT {
public:
void operator()(T& t) {
cout << n << " . " << t << endl;
// 每调用一次, 自增 1
n++;
}
private:
// 每调用一次, 该成员自增 1
// 该状态一直存储
int n = 0;
};
int main() {
// 创建一个 vector 单端数组容器
vector<int> vec;
// 向容器中插入元素
vec.push_back(1);
vec.push_back(3);
vec.push_back(5);
// 创建函数对象
PrintT<int> printT;
// 向 foreach 循环中传入函数对象
// 在函数对象中打印元素内容
printT = for_each(vec.begin(), vec.end(), printT);
// 再次调用 函数对象
cout << "再次调用函数对象 : " << endl;
int a = 666;
printT(a);
// 控制台暂停 , 按任意键继续向后执行
system("pause");
return 0;
};
执行结果 :
0 . 1
1 . 3
2 . 5
再次调用函数对象 :
3 . 666
请按任意键继续. . .