泛型算法
1. 简介
1.1定义
泛型:可以操作在不同的容器类型之上。
算法:实现共同的操作。
注意:大多数算法是通过遍历由两个迭代器标记的一段元素来实现其功能。
1.泛型算法本身从不执行容器提供的操作,只是单独依赖迭代器和迭代器操作实现。
2.使用“普通”的迭代器时,算法从不修改基础容器的大小。正如我们看到的,算法也许会改变存储在容器中元素的值,也许会在容器内移动元素,但是,算法从不直接添加或删除元素。
2.根据对元素的操作将算法分类
头文件:
#include <algorithm>
#include <numeric>
2.1 只读算法
find()
accumulate(b, e, t) b, e均为迭代器,指定要累加的元素范围,t为累加的起始值
注意:accumulate算法返回累加的结果,其返回类型就是其第三个实参的类型
1. 调用该函数时必须传递一个起始值,否则,accumulate将不知道使用什么起始值
2. 容器内的元素类型必须与第三个实参的类型匹配,或者可以转换为第三个实参的类型。
find_first_of(b1, e1, b2, e2) b1,e1和b2,e2为两组迭代器,分别标记两段元素范围,在第一段范围内查找与第二段范围中任意元素匹配的元素,然后返回一个迭代器,指向第一个匹配的元素。
注意:每对迭代器中,两个实参的类型必须精确匹配,但不要求两对之间的类型匹配,特别是,元素可存储在不同类型的序列中,只要这两个序列的元素可以比较。
2.2 写容器元素的算法
注意:在使用写入元素的算法时要当心,必须确保算法缩写的序列至少足以存储要写入的元素。
2.2.1 写入输入序列的元素
fill(b, e, t) b,e为一对迭代器,指定要写入的范围,t为将范围内的元素设定成的值。
注意:如果输入范围有效,则可安全写入。这个算法只会对输入范围内已存在的元素进行写入操作。
2.2.2 不检查写入操作的算法
fill_n(b, n, t) 从迭代器b指向的元素开始,将指定数量n的元素设置成给定的值t。
注意:fill_n函数假定对指定数量的元素做写操作时安全的,如果容器为空,或者不足以存放指定数量n的元素,那么将会发生灾难性的后果。
确保算法有足够的元素存储输入数据的一种方法是使用插入迭代器(insert iterator)
头文件:
#include<iterator>
fill_n(back_inserter(vec),n, t) 通过back_inserter实现,相当于调用vec.push_back(n)实现。
2.2.3 写入到目标迭代器的算法
copy(ilist.begin(),ilist.end(), back_inserter(ivec)) 从输入范围中读取元素,然后将它们复制给目标ivec。
2.2.4 算法的_copy版本
这些算法对输入序列的元素做出处理,但不修改原来的元素,而是创建一个新序列存储元素的处理结果。
举例:
replace(b, e, t1, t2) 将迭代器b,e范围内的t1全部替换成t2。
replace_copy(b, e,back_inserter(ivec), t1, t2)
将迭代器b,e范围内的t1全部替换成t2,但是不修改原来的序列,而是将修改后的序列存储到迭代器指定的副本中。
unique(b, e) 去除迭代器b, e范围内重复的元素
unique_copy(b, e, back_inserter(ivec))
将迭代器b,e范围内重复的元素去掉,但是不修改原来的序列,而是将修改的序列存储到迭代器指定的副本中。
2.3 对容器元素重新排序的算法
sort(b, e) b,e标记要排序的范围
stable_sort(b, e, predicate) b,e标记要排序的范围,predicate谓词。
注意:谓词是做某些检测的函数,返回用于条件判断的类型,支出条件是否成立。
3.另外三种迭代器介绍
头文件:
#include <iterator>
3.1 插入迭代器(insert iterator)
定义:这类迭代器与容器绑定在一起,实现在容器中插入元素的功能。
back_inserter(C) 创建使用push_back实现插入的迭代器。
front_inserter(C) 使用push_front实现插入。
注意:只有在容器提供了push_front操作的时候才能使用front_inserter,否则会发生错误。inserter(C,it) 将产生在指定位置实现插入的迭代器。
3.2 iostream迭代器
定义:这类迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的IO流。
这些迭代器将他们所对应的流是为特定类型的元素序列。使用流迭代器是,可以用泛型算法从流对象中读数据(或将数据写入到流对象中)。
3.2.1 iostream迭代器的构造函数
istream_iterator<T> in(strm); 创建从输入流strm中读取T类型对象的istream_iterator对象。
istream_iterator<T> in; istream_iterator对象的超出末端迭代器
ostream_iterator<T> in(strm); 创建将T类型的对象写到输出流strm的ostream_iterator对象
ostream_iterator<T > in(strm, delim); 创建将T类型的对象写到输出流strm的ostream_iterator对象,在写入过程中使用delim作为元素的分隔符。delim是以空字符结束的字符数组。
3.2.2 istream对象和ostream对象的使用
使用方法,概括来说就是将流对象iostream看做一种容器,然后用iostream迭代器进行操作。
3.2.3 流迭代器的限制
1. 不可能从ostream_iterator对象读入,也不可能写到istream_iterator对象中。
2. 一旦给ostream_iterator对象服了一个值,写入就提交了。赋值后,没有办法再改变这个值。此外,ostream_iterator对象中每个不同的值都只能正好输出一次。
3. ostream_iterator没有-> 操作。
3.3 反向迭代器(reverse iterator)
定义:这类迭代器实现向后遍历,而不是向前遍历。所有容器类型都定义了自己的reverse_iterator类型,由rbegin和rend成员函数返回。
所有容器都定义了begin和end成员,分别返回指向容器首元素和尾元素下一位置的迭代器。
容器还定义了rbegin和rend成员,分别返回指向容器尾元素和首元素前一位置的反向迭代器。
使用:类比普通迭代器。
4.迭代器的分类
算法要求的迭代器操作分为五个类别,对应如下列出的五种迭代器:
输入迭代器 读,不能写;只支持自增运算
输出迭代器 写,不能读;只支持自增运算
前向迭代器 读和写;只支持自增运算
双向迭代器 读和写;支持自增和自减运算
随机访问迭代器 读和写;支持完整的迭代器算术运算
注意:对于每一个形参,迭代器必须保证最低功能。将支持更少功能的迭代器传递给函数是错误的,而传递更强功能的迭代器则没有问题。
5.根据需要使用的迭代器种类对算法进行分类
5.1 算法的形参模式
分为四种:
alg(beg, end, other parms);
alg(beg, end, dest, other parms);
alg(beg, end, beg2, other parms);
alg(beg, end, beg2, end2, other parms);
alg是算法的名字,beg和end指定算法操作的元素范围。通常称为算法的“输入范围”,
其它形参:dest 、beg2、end2它们都是迭代器。
5.1.1 带有单个目标迭代器的算法
dest形参是一个迭代器,用于指定存储输出数据的目标对象。
注意:算法假定无论需要写入多少个元素都是安全的。
调用这些算法时,必须确保输出容器有足够大的容量存储输出数据,这正是通常要使用插入迭代器或者ostream_iterator来调用这些算法的原因。
5.1.2带第二个输入序列的算法
这类算法通常将联合两个输入范围的元素来完成计算功能。
带beg2和end2的算法,完整地指定了两个输入范围:(beg, end)和(beg2, end2)。
不带end2的算法,只指定了首元素,而没有指定该范围的最后一个元素。
注意:与写入dest的算法一样,只带有beg2的算法也假定以beg2开始的序列与beg和end标记的序列一样大。
5.2 算法的命名规范
5.2.1第一种模式:测试输入范围内元素的算法
区别带有一个值或一个谓词函数参数的算法版本
举例:
当参数个数不同的时候,使用谓词函数的算法多一个参数:
sort(beg,end) 普通排序,用到标准库的关系操作符:==和<
sort(beg,end, comp) 谓词函数comp(),来提供比较来取代操作符的使用
注意:当参数个数相同的时候,使用谓词函数的算法后面加上_if来加以区别:
find(beg,end, val)
find_if(beg,end, pred)
5.2.2第二种模式:应用于对输入范围内元素重新排序的算法
举例:
reverse(beg, end) 将重新排列的算法写回其输入范围
reverse_copy(beg, end, dest) 将重新排列后的元素写入到指定的输出目标
注意:是否改变原来的输入序列的元素通过_copy来区别。