【C++】STL 算法 ③ ( 函数对象中存储状态 | 函数对象作为参数传递时值传递问题 | 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
请按任意键继续. . .

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/han1202012/article/details/135421912
今日推荐