STL解耦合思想 | 函数对象、谓词与函数适配器(源码分析)
【摘要】 🎈🎈🎈🎈🎈🎈🎈前言🎈🎈🎈🎈🎈🎈🎈📣STL算法为我们提供了一些统一的算法模型,在这些算法模型中,只提供了一个统一的壳子,具体实现什么样的功能由我们通过函数对象或回调函数来实现。这是一种非常重要的思想,统一性思想,而统一的实现就是解耦合,如果不理解这个思想,那么学习STL就像背英语单词,将变得毫无意义。下面将通过for_each、transform、count_...
🎈🎈🎈🎈🎈🎈🎈前言🎈🎈🎈🎈🎈🎈🎈
📣STL算法为我们提供了一些统一的算法模型,在这些算法模型中,只提供了一个统一的壳子,具体实现什么样的功能由我们通过函数对象或回调函数来实现。这是一种非常重要的思想,统一性思想,而统一的实现就是解耦合,如果不理解这个思想,那么学习STL就像背英语单词,将变得毫无意义。下面将通过for_each、transform、count_if、sort四个算法实例来一步步深入理解这种思想。
🎮文章目录🎮
一、概念解析
- 算法:STL提供的算法主要包含两大类,一类是不改变所操作容器内容的算法比如计数、搜索、比较等算法;另一类是修改所操作容器内容的算法,比如排序、删除等等。使用STL算法需要包含头文件<algorithm>。
- 函数对象:函数对象是指重载了函数调用操作符()的类,其功能类似于回调函数,函数对象一般用于STL算法中来自定义回调行为。
- 一元函数对象:重载的operator()函数只有一个参数;
- 二元函数对象:重载的operator()函数有两个参数;
- 谓词:谓词可以是仿函数(函数对象),也可以是回调函数,它的返回值是bool类型,作为一个判断式。
- 一元谓词,只有一个参数的谓词;
- 二元谓词:含有两个参数的谓词;
- 函数适配器 :有时候算法中的函数对象只接收一元函数对象,但是我们想要实现的功能需要二元函数对象完成,这时我们就可以通过绑定器把一个二元函数对象和一个参数绑定在一起,适配成一元函数对象。使用函数适配器需要包含头文件<functional>。
- 预定义函数对象:STL标准模板库已经提前预定义好的函数对象,比如greater、less等,使用预定义函数对象需要包含头文件<functional>。
二、从源码到实战
1. for_each算法与一元函数对象
本节目标是使用for_each算法实现遍历容器
1.1 搭建测试框架
搭建一个测试框架
1.2 for_each源码分析
首先转到for_each算法源码
根据源码分析可知,for_each算法接收一个一元函数对象,并返回该对象,它会把传入的容器中的元素一个个的放入回调函数_Func作为函数参数。由此可见,for_each只是提供了一个壳子,这个壳子的功能是把一个容器的所有元素(起始和结束都有我们通过迭代器传入)依次作为参数传给函数对象_Func,而具体对这些元素做什么操作,由我们自己通过回调函数_Func实现。这就是算法统一性和解耦合思想的体现。
1.3 根据for_each源码实现一元函数对象
根据源码可以分析出回调函数的接口,一个参数,无返回值(或未用到返回值)。
回调函数和函数对象(类)的区别已经在另一篇文章中分析过了,函数对象可以有自己的属性和方法,因为类的封装特性,可以把属性方法一并传入作为函数参数,
我们在主函数添加如下测试代码编译运行
可以看到,使用函数对象和回调函数都能实现遍历容器,使用函数对象可以通过私有属性记录容器中元素个数。
2. count_if算法与一元谓词
本节目标是使用count_if算法计算某个元素的个数
2.1 count_if源码分析
通过VS转到源码功能
通过源码分析可知,count_if算法提供了这样一个壳子,他会把我们传入的容器迭代器范围内的元素依次传递给函数对象_Pred,并且函数对象_Pred返回一个bool类型,由此可知count_if的参数是一个一元谓词
如果这个返回结果为真,即传递给函数对象_Pred的参数(容器元素)符合条件,就进行一次计数。最终count_if返回这个计数值。
2.2 根据count_if源码实现一元谓词
根据上一小节分析可知,我们要写的谓词接口形式为bool类型返回值,一个参数,代码如下:
然后再主函数继续添加如下测试代码,我们使用count_if计算容器中“!”出现的次数
编译运行
通过运行结果看到,容器中总共3个叹号。
3. transform算法与二元函数对象
本节目标,使用transform算法实现把两个容器内容相加放入第三个容器。
3.1 transform源码分析
使用VS查看源码
这只是transform算法的一个重载模型之一,首先传入参数是两个容器的输入迭代器_First1、_Last1、_First2,其中第一个容器给出了起始和结束位置的迭代器,用来限定操作的容器范围,输出迭代器_Dest。通过这条语句可以看到
transform算法把两个输入迭代器指示的容器元素传入回调函数_Func作为参数,并返回一个元素装入到输出迭代器_Dest所指示的容器位置中。由此分析可知,transform算法提供了这样一个模型,把两个容器中的元素进行操作,操作结果存放到第三个容器中,所以_Func的返回值应该是和容器类型同类型的一个元素,操作的元素个数由第一个容器的迭代器指定,最终transform返回第三个容器的迭代器位置。transform需要的是一个二元函数对象。
3.2 根据transform源码实现函数对象
根据源码分析,回调函数接口应为二元函数对象,函数返回值为一个元素,代码如下:
继续在主函数添加测试代码如下:
编译运行
通过输出结果看到,已经实现了把容器1和2相加存入第三个容器的功能。我们在源码分析中说到,transform返回容器3的输出迭代器,那么返回的迭代器位置究竟在哪呢,我们在程序进行了测试,也就是这几句程序
通过对返回的迭代器指向的内容打印可以得出结论,transform算法返回的是v1.end()减去v1.begin()后面的一个位置,加入容器v1有6个元素,那么transform返回的应该是v3的第7个位置,具体可见程序中的说明。
4. sort算法与二元谓词
本节目标是通过sort算法实现排序功能。
4.1 sort算法源码分析
源码如下:
首先分析源码,sort算法实现的是排序功能,我们可以按自己的排序规则进行排序,比如对一个people类按照成员变量年龄进行排序,一个成绩管理系统,按照成绩排序等,另外STL还提供了预定义函数对象供我们使用,比如less、greater等。sort算法接收的是一个二元谓词。
4.2 根据sort源码实现二元谓词
我们实现从大到小排序,代码如下:
在主函数添加如下测试代码:
编译运行
我们还可以使用预定义函数对象实现从大到小排序
通过自定义的谓词,我们可以实现自己的数据类型的排序,这就是sort算法为我们提供的统一个排序模型。
5. 函数适配器
有时候算法中的函数对象只接收一元函数对象,但是我们想要实现的功能需要二元函数对象完成,这时我们就可以通过绑定器把一个二元函数对象和一个参数绑定在一起,适配成一元函数对象。比如,我们要用count_if实现计算int型容器中大于5的元素的个数,我们可以借用预定义函数对象greater,但是greater是一个二元谓词,我们在第二节分析过count_if源码,count_if接收的是一个一元函数对象,这时我们就可以使用绑定器bind2nd把元素5绑定到greater的第二个参数位置,把他适配为一元函数对象。
在主函数添加如下测试代码:
编译运行
大于5的个数为5,结果正确。
通过这个例子我们可以再一次体会到STL算法为我们提供的统一性,我们使用count_if算法完成了两个自己的算法,一个是计算容器中某元素的个数,一个是计算容器中大于某元素的个数。只要是我们的回到调函数功能和参数模型能匹配算法提供的模型,就可以套用该算法,这就是统一性。
三、STL提供的统一性思考
算法的统一性延伸至STL的统一性思考
🧧附:完整代码🧧
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)