首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C++】STL 算法 ③ ( 函数对象中存储状态 | 函数对象作为参数传递时值传递问题 | for_each 算法的 函数对象 参数是值传递 )

【C++】STL 算法 ③ ( 函数对象中存储状态 | 函数对象作为参数传递时值传递问题 | for_each 算法的 函数对象 参数是值传递 )

作者头像
韩曙亮
发布2024-01-07 08:58:44
发布2024-01-07 08:58:44
2.1K00
代码可运行
举报
运行总次数:0
代码可运行
文章目录
  • 一、函数对象中存储状态
    • 1、函数对象中存储状态简介
    • 2、示例分析
  • 二、函数对象作为参数传递时值传递问题
    • 1、for_each 算法的 函数对象 参数是值传递
    • 2、代码示例 - for_each 函数的 函数对象 参数在外部不保留状态
    • 3、代码示例 - for_each 函数的 函数对象 返回值

一、函数对象中存储状态


1、函数对象中存储状态简介

在 C++ 语言中 , 函数对象 / 仿函数 可以像函数一样被调用 , 并且 其 还具有类的特征 , 可以 通过 继承 和 重载 来 修改 重载函数调用操作符函数 的行为 ;

函数对象 / 仿函数 通常是通过 定义一个类 , 然后为这个类 重载 函数调用操作符 () 来实现的 ;

函数对象的一个重要特性是 " 可以存储状态 " ; 这意味着你可以 在类的成员变量中存储数据 , 这些数据可以 在函数调用之间保持不变 ;

普通的函数 是 无法存储状态 的 , 因为 普通函数 中 局部变量 在函数执行完成后 , 自动销毁 ;

函数对象 / 仿函数 的一个主要优势是它们可以拥有状态 , 而普通函数则不能 ;

这使得 " 函数对象 / 仿函数 " 在需要保持 某些数据或状态 在 多次函数调用 之间不变的情况下非常有用 ,

例如 : 在 STL 算法中 , 函数对象经常被用作 谓词 或 用于在容器的每个元素上执行某种操作的函数 , 由于它们可以存储状态 , 因此可以根据算法的需要进行定制 ;

在下面的示例中 , 函数对象 中 维护了一个状态位 , 用于记录该 函数对象 的调用次数 ;

下面的 函数对象 / 仿函数 中 , 存储了状态 n , 每调用一次该仿函数 , 该成员自增 1 ;

代码语言:javascript
代码运行次数:0
运行
复制
//函数对象 类重载了()
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 ;
代码语言:javascript
代码运行次数:0
运行
复制
//函数对象 类重载了()
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 ;

代码语言:javascript
代码运行次数:0
运行
复制
	// 向 foreach 循环中传入函数对象
	// 在函数对象中打印元素内容
	for_each(vec.begin(), vec.end(), PrintT<int>());

代码示例 :

代码语言:javascript
代码运行次数:0
运行
复制
#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 算法的调用代码如下 :

代码语言:javascript
代码运行次数:0
运行
复制
	// 向 foreach 循环中传入函数对象
	// 在函数对象中打印元素内容
	for_each(vec.begin(), vec.end(), PrintT<int>());

for_each 算法的函数原型如下 :

代码语言:javascript
代码运行次数:0
运行
复制
// 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 , 这说明 值传递 改变的是 函数对象实参副本值 , 没有影响外部的 函数对象 值 ;

代码语言:javascript
代码运行次数:0
运行
复制
0 . 666

代码示例 :

代码语言:javascript
代码运行次数:0
运行
复制
#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;
};

执行结果 :

代码语言:javascript
代码运行次数: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 算法的返回值 , 再次执行该 函数对象 调用 , 发现 状态值被保留了下来 , 打印值为 :

代码语言:javascript
代码运行次数:0
运行
复制
3 . 666

代码示例 :

代码语言:javascript
代码运行次数:0
运行
复制
#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;
};

执行结果 :

代码语言:javascript
代码运行次数:0
运行
复制
0 . 1
1 . 3
2 . 5
再次调用函数对象 :
3 . 666
请按任意键继续. . .
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 一、函数对象中存储状态
    • 1、函数对象中存储状态简介
    • 2、示例分析
  • 二、函数对象作为参数传递时值传递问题
    • 1、for_each 算法的 函数对象 参数是值传递
    • 2、代码示例 - for_each 函数的 函数对象 参数在外部不保留状态
    • 3、代码示例 - for_each 函数的 函数对象 返回值
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档