一、概念解析
- 算法: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需要的是一个二元函数对象。