【STL终极奥义❀解耦合思想的实现❀】函数对象、谓词与函数适配器——从for_each、transform、count_if、sort算法源码的角度分析(一)

简介: 【STL终极奥义❀解耦合思想的实现❀】函数对象、谓词与函数适配器——从for_each、transform、count_if、sort算法源码的角度分析

一、概念解析

  • 算法:STL提供的算法主要包含两大类,一类是不改变所操作容器内容的算法比如计数、搜索、比较等算法;另一类是修改所操作容器内容的算法,比如排序、删除等等。使用STL算法需要包含头文件。
  • 函数对象:函数对象是指重载了函数调用操作符()的类,其功能类似于回调函数,函数对象一般用于STL算法中来自定义回调行为。
  • 一元函数对象:重载的operator()函数只有一个参数;
  • 二元函数对象:重载的operator()函数有两个参数;
  • 谓词:谓词可以是仿函数(函数对象),也可以是回调函数,它的返回值是bool类型,作为一个判断式。
  • 一元谓词,只有一个参数的谓词;
  • 二元谓词:含有两个参数的谓词;
  • 函数适配器 :有时候算法中的函数对象只接收一元函数对象,但是我们想要实现的功能需要二元函数对象完成,这时我们就可以通过绑定器把一个二元函数对象和一个参数绑定在一起,适配成一元函数对象。使用函数适配器需要包含头文件。
  • 预定义函数对象:STL标准模板库已经提前预定义好的函数对象,比如greater、less等,使用预定义函数对象需要包含头文件。

二、从源码到实战

1. for_each算法与一元函数对象

本节目标是使用for_each算法实现遍历容器

1.1 搭建测试框架

搭建一个测试框架

1. #include <iostream>
2. using namespace std;
3. 
4. #include <vector>
5. #include <string>
6. #include <algorithm> //使用算法
7. #include <functional> //使用预定义函数对象和适配器
8. 
9. int main()
10. {
11.   vector<string> v1;
12.   v1.push_back("hello");
13.   v1.push_back("C++");
14.   v1.push_back("STL");
15.   v1.push_back("!");
16.   v1.push_back("!");
17.   v1.push_back("!");
18. 
19. /*在此添加测试代码*/
20. 
21.   system("pause");
22.   return 0;
23. }

1.2 for_each源码分析

首先转到for_each算法源码

1. _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
2.    _Adl_verify_range(_First, _Last);
3.    auto _UFirst = _Get_unwrapped(_First);
4.    const auto _ULast = _Get_unwrapped(_Last);
5.    for (; _UFirst != _ULast; ++_UFirst) {
6.      _Func(*_UFirst); //通过源码可知,for_each 的函数对象是一元函数对象
7.    }
8. 
9.    return _Func;
10.   }

根据源码分析可知,for_each算法接收一个一元函数对象,并返回该对象,它会把传入的容器中的元素一个个的放入回调函数_Func作为函数参数。由此可见,for_each只是提供了一个壳子,这个壳子的功能是把一个容器的所有元素(起始和结束都有我们通过迭代器传入)依次作为参数传给函数对象_Func,而具体对这些元素做什么操作,由我们自己通过回调函数_Func实现。这就是算法统一性和解耦合思想的体现。

1.3 根据for_each源码实现一元函数对象

根据源码可以分析出回调函数的接口,一个参数,无返回值(或未用到返回值)。

1. //一元函数对象
2. template<typename _MyType>
3. class _FroEachClass
4. {
5. public:
6.  void operator()(_MyType& t)
7.  {
8.    cout << t << " ";
9.    m_count++;
10.   }
11. public:
12.   _FroEachClass()
13.   {
14.     this->m_count = 0;
15.   }
16. public:
17.   int get_count()
18.   {
19.     return this->m_count;
20.   }
21. private:
22.   int m_count;
23. };
24. 
25. template<typename _MyType>
26. void _FroEachFunc(_MyType& t)
27. {
28.   cout << t << " ";
29. }

回调函数和函数对象(类)的区别已经在另一篇文章中分析过了,函数对象可以有自己的属性和方法,因为类的封装特性,可以把属性方法一并传入作为函数参数,详情可见

【STL算法】for_each源码刨析及函数对象本质刨析https://blog.csdn.net/qq_43471489/article/details/123633464?spm=1001.2014.3001.5501我们在主函数添加如下测试代码

1. _FroEachClass<string> for_each_obj;
2. for_each_obj = for_each(v1.begin(), v1.end(), for_each_obj);
3. cout << "\nvector size: " << for_each_obj.get_count() << endl;
4. for_each(v1.begin(), v1.end(), _FroEachFunc<string>);
5. cout << endl;

编译运行

可以看到,使用函数对象和回调函数都能实现遍历容器,使用函数对象可以通过私有属性记录容器中元素个数。

2. count_if算法与一元谓词

本节目标是使用count_if算法计算某个元素的个数

2.1 count_if源码分析

通过VS转到源码功能

1. template <class _InIt, class _Pr>
2. _NODISCARD _Iter_diff_t<_InIt> count_if(_InIt _First, _InIt _Last, _Pr _Pred) { // count elements satisfying _Pred
3.  _Adl_verify_range(_First, _Last);
4.  auto _UFirst = _Get_unwrapped(_First);
5.  const auto _ULast = _Get_unwrapped(_Last);
6.  _Iter_diff_t<_InIt> _Count = 0;
7.  for (; _UFirst != _ULast; ++_UFirst) {
8.    if (_Pred(*_UFirst)) { //由此可见_Pred返回的应该是一个bool
9.      ++_Count;
10.     }
11.   }
12. 
13.   return _Count;
14. }

通过源码分析可知,count_if算法提供了这样一个壳子,他会把我们传入的容器迭代器范围内的元素依次传递给函数对象_Pred,并且函数对象_Pred返回一个bool类型,由此可知count_if的参数是一个一元谓词

if (_Pred(*_UFirst)) { //由此可见_Pred返回的应该是一个bool

如果这个返回结果为真,即传递给函数对象_Pred的参数(容器元素)符合条件,就进行一次计数。最终count_if返回这个计数值。

2.2 根据count_if源码实现一元谓词

根据上一小节分析可知,我们要写的谓词接口形式为bool类型返回值,一个参数,代码如下:

1. template<typename _MyType>
2. class _CountOfClass
3. {
4. public:
5.  bool operator()(_MyType& t)
6.  {
7.    return (t == this->m_data);
8.  }
9. public:
10.   _CountOfClass(_MyType& t)
11.   {
12.     this->m_data = t;
13.   }
14. private:
15.   _MyType m_data;
16. };
17. 
18. template<typename _MyType>
19. bool _CountOfFunc(_MyType& t1)
20. {
21.   string s = "!";
22.   return (t1 == s);
23. }

然后再主函数继续添加如下测试代码,我们使用count_if计算容器中“!”出现的次数

1. string s("!");
2. int count = count_if(v1.begin(), v1.end(), _CountOfClass<string>(s));
3. cout << "count : " << count << endl;
4. count = count_if(v1.begin(), v1.end(), _CountOfFunc<string>); //使用回调函数也可以编译通过
5. cout << "count : " << count << endl;

编译运行

通过运行结果看到,容器中总共3个叹号。

3. transform算法与二元函数对象

本节目标,使用transform算法实现把两个容器内容相加放入第三个容器。

3.1 transform源码分析

使用VS查看源码

1. template <class _InIt1, class _InIt2, class _OutIt, class _Fn>
2. _OutIt transform(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _First2, _OutIt _Dest, _Fn _Func) {
3.    // transform [_First1, _Last1) and [_First2, ...) with _Func
4.  _Adl_verify_range(_First1, _Last1);
5.  auto _UFirst1 = _Get_unwrapped(_First1);
6.  const auto _ULast1 = _Get_unwrapped(_Last1);
7.  const auto _Count = _Idl_distance<_InIt1>(_UFirst1, _ULast1);
8.  auto _UFirst2 = _Get_unwrapped_n(_First2, _Count);
9.  auto _UDest = _Get_unwrapped_n(_Dest, _Count);
10.   for (; _UFirst1 != _ULast1; ++_UFirst1, (void) ++_UFirst2, ++_UDest) {
11.     *_UDest = _Func(*_UFirst1, *_UFirst2); //把两个参数_UFirst1和_UFirst2的元素传入_Func返回结果放入_UDest
12.   }
13. 
14.   _Seek_wrapped(_Dest, _UDest);
15.   return _Dest;
16. }

这只是transform算法的一个重载模型之一,首先传入参数是两个容器的输入迭代器_First1、_Last1、_First2,其中第一个容器给出了起始和结束位置的迭代器,用来限定操作的容器范围,输出迭代器_Dest。通过这条语句可以看到

*_UDest = _Func(*_UFirst1, *_UFirst2); //把两个参数_UFirst1和_UFirst2的元素传入_Func返回结果放入_UDest

transform算法把两个输入迭代器指示的容器元素传入回调函数_Func作为参数,并返回一个元素装入到输出迭代器_Dest所指示的容器位置中。由此分析可知,transform算法提供了这样一个模型,把两个容器中的元素进行操作,操作结果存放到第三个容器中,所以_Func的返回值应该是和容器类型同类型的一个元素,操作的元素个数由第一个容器的迭代器指定,最终transform返回第三个容器的迭代器位置。transform需要的是一个二元函数对象。


相关文章
|
5天前
|
机器学习/深度学习 自然语言处理 算法
Python遗传算法GA对长短期记忆LSTM深度学习模型超参数调优分析司机数据|附数据代码
Python遗传算法GA对长短期记忆LSTM深度学习模型超参数调优分析司机数据|附数据代码
|
11天前
|
机器学习/深度学习 算法 数据可视化
Matlab决策树、模糊C-均值聚类算法分析高校教师职称学历评分可视化
Matlab决策树、模糊C-均值聚类算法分析高校教师职称学历评分可视化
|
12天前
|
算法 搜索推荐 数据挖掘
MATLAB模糊C均值聚类FCM改进的推荐系统协同过滤算法分析MovieLens电影数据集
MATLAB模糊C均值聚类FCM改进的推荐系统协同过滤算法分析MovieLens电影数据集
|
12天前
|
算法 数据可视化 数据挖掘
数据分享|R语言改进的K-MEANS(K-均值)聚类算法分析股票盈利能力和可视化
数据分享|R语言改进的K-MEANS(K-均值)聚类算法分析股票盈利能力和可视化
|
12天前
|
数据采集 存储 算法
数据分享|Weka数据挖掘Apriori关联规则算法分析用户网购数据
数据分享|Weka数据挖掘Apriori关联规则算法分析用户网购数据
|
14天前
|
搜索推荐 算法 Java
sort-06-shell sort 希尔排序算法详解
这是一个关于排序算法的系列文章摘要。文章汇总了各种排序算法,包括冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序、归并排序、计数排序、桶排序以及大文件外部排序。特别地,希尔排序是一种改进的插入排序,通过使用不同的步长对元素进行分组排序,以提高效率。算法最终以较小的步长进行排序,接近线性时间复杂度。文章还提供了Java代码实现,并举例说明了希尔排序的过程。所有内容可在开源项目[https://github.com/houbb/sort](https://github.com/houbb/sort)中找到。
|
14天前
|
搜索推荐 算法 Java
sort-05-insert sort 插入排序算法详解
这是一个关于排序算法的系列文章总结,包括冒泡排序、快速排序、选择排序、堆排序、插入排序等10种排序算法的详细讲解和Java实现。插入排序是一种简单直观的排序算法,通过构建有序序列并逐个插入新元素来排序。提供的Java代码展示了插入排序的实现过程,并附有测试示例。所有算法已开源在GitHub项目[https://github.com/houbb/sort](https://github.com/houbb/sort)中。
|
14天前
|
人工智能 搜索推荐 算法
sort-04-heap sort 堆排序算法详解
这是一个关于排序算法的系列文章摘要,包括了10篇关于不同排序算法的链接,如冒泡排序、快速排序、选择排序、堆排序等。堆排序是一种基于比较的排序算法,利用了近似完全二叉树的结构并满足最大堆或最小堆的性质。最大堆中,每个节点的值都大于或等于其子节点。文章详细解释了最大堆的概念、节点访问方式以及堆的操作,包括堆调整和堆排序的过程,并通过图解展示了堆排序的具体步骤。此外,还提供了一个Java实现的堆排序代码示例。整个排序系列来源于一个开源项目,读者可以通过链接查看完整内容。
sort-04-heap sort 堆排序算法详解
|
14天前
|
搜索推荐 算法 Java
sort-01-bubble sort 冒泡排序算法详解
这是一个关于排序算法的系列文章摘要。作者整理了10种不同的排序算法,包括冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序、归并排序、计数排序、桶排序和大文件外部排序。文章详细介绍了冒泡排序的工作原理、流程,并提供了代码实现,强调了在实现中考虑的改进点,如统一接口、实用性增强和日志输出。此外,还提供了一个排序接口和工具类以方便使用,并通过测试代码和日志展示了排序过程。整个系列旨在帮助读者理解和掌握排序算法。相关代码已开源在GitHub。
|
15天前
|
机器学习/深度学习 数据采集 算法
共享单车需求量数据用CART决策树、随机森林以及XGBOOST算法登记分类及影响因素分析
共享单车需求量数据用CART决策树、随机森林以及XGBOOST算法登记分类及影响因素分析