11.1、概述
如标准库提供的find运算即基于迭代器的,可以find各种类型,list、数组(因为指针的行为与作用在内置数组上的迭代器一样)等。
如果需要传递一个子区间,则传递指向这个子区间的第一个元素以及最后一个元素的下一位置的迭代器(或指针)。(符合前闭后开的原则)
泛型算法用迭代器来解决的要求有:(1)遍历容器,能够从一个元素移到下一个元素;(2)必须能够知道是否到达了集合末尾。用两个迭代器来指出算法操纵的元素范围,第二个迭代器有时被称为超出末端迭代器;(3)元素值的比较。默认find操作要求元素类型定义了相等操作,也可实现自定义的比较函数,传的额外参数即实现元素比较的函数名字。(如快排最后一个参数就可以是compare函数名)。(4)需要一个类型来指出元素在容器中的位置,或者表示找不到该元素。
泛型算法本身从不执行容器操作,只是单独依赖迭代器和迭代器操作实现。
11.2、初窥算法
使用泛型算法必须包含algorithm头文件。标准库还定义了一组泛化的算术算法(generalized numeric algorithm),其命名习惯与泛型算法相同,必须包含numeric头文件。
11.2.1、只读算法
accumulate,在numeric头文件中定义,其带有三个形参,头两个为迭代器,表示需要累加的范围,第三个是累加的初值。容器的类型必须与第三个实参的类型匹配,或者可转换为第三个实参的类型。
如accumulate(v.begin(),v.end(), string(“”));即把v里每个元素连接成一个字符串。这边第三个参数不能传递字符串字面值,如””,将会导致编译时错误,因为此时,累加和的类型将是constchar*,而string的加法操作符所使用的操作数则分别是string和const char*类型,加法的结果将产生一个string对象,而不是constchar*指针。
find_first_of的使用,这个算法带有两对迭代器参数来标记两段元素范围,在第一段范围内查找与第二段范围中任意元素匹配的元素(按容器元素为最小单元,进行精确匹配),然后返回一个迭代器,指向第一个匹配的元素(对其进行不断递增,就可以找出全部能匹配的元素)。如果找不到匹配元素,则返回第一个范围的end迭代器。(而迭代器类型可以不精确匹配,可以使list、vector等等对象,只要这两个序列元素可使用相等操作符进行比较即可)。
11.2.2、写容器元素的算法
写入输入序列的元素:如fill(vec.begin(), vec.begin()+2, 10);fill带有一对迭代器形参,用于指定要写入的范围,所写的值是它第三个形参的副本。如果输入范围有效则安全写入,否则产生运行时错误。
不检查写入操作的算法:fill_n函数带有的参数包括:一个迭代器、一个计数器以及一个值,如fill_n(vec.begin(), 10, 0)。该函数从迭代器指向的元素开始,将指定数量的元素设置为给定的值。初学者常犯的错误:在没有元素的空容器上调用fill_n函数(会产生运行错误)。
引入back_inserter(必须包含iterator头文件):确保算法有足够的元素存储输出数据的一种方法是使用迭代器,插入迭代器是可以给基础容器添加元素的迭代器。back_inserter函数是迭代器适配器,如fill_n(back_inserter(vec), 10, 0);表示fill_n函数每写入一个值,都会通过back_inserter生成的插入迭代器实现,效果相当于在vec上调用push_back,在vec末尾添加10个元素,每个元素都是0。
写入到目标迭代器的算法:copy带有三个迭代器参数:头两个指定输入范围,第三个则指向目标序列的一个元素。如vector<string> b;copy(a.begin(),a.end(),back_inserter(b));copy从输入范围读取元素,然后将它们复制给目标b。但这效率比较差,更好地方法:vector<string> b(a.begin(),a.end());
算法的_copy版本:replace替换操作,将迭代器表示的范围中每一个等于第三个形参的元素替换为第四个形参,如replace(list.begin(), list.end(), 0, 4);如果不想改变原来的序列,则调用replace_copy,这个算法接受第三个迭代器实参,指定保存调整后序列的目标位置,如:replace(list.begin(), list.end(), back_inserter(ivec) , 0,4);
11.2.3、对容器元素重新排序的算法
案例:统计字母大于等于6个以上的单词个数,相同单词只考虑一次。
去除重复:sort算法带有两个迭代器实参,指出要排序的元素范围,这个算法使用小于(<)操作符比较元素。
unique的使用:该算法删除相邻的重复元素,然后重新排列输入范围内的元素,并且返回一个迭代器,表示无重复的值范围的结束。unique实际上并没有删除任何元素,它返回的迭代器指向超出无重复的元素范围末端的下一位置。(算法不直接修改容器大小,要添加或删除元素,则必须使用容器操作)。如unique(words.begin(), words.end());
使用容器操作删除元素,如erase(unique返回值, words.end());
定义需要的实用函数:
bool isShorter(const string&s1, const string &s2){//用于对序列按长度排序
returns1.size()<s2.size();
}
bool GT6(const string&s){//用于判断元素长度是否大于等于6。
returns.size() >= 6;
}
排序算法:stable_sort稳定排序,如stable_sort(woeds.begin(),words.end(),isShorter);
统计长度不小于6的单词:vector<string>::size_type wc =count_if(words.begin(), words.end(), GT6);返回长度不小于6的个数。
11.3、再谈迭代器
(1)插入迭代器:这类迭代器与容器绑定在一起,实现在容器中插入元素的功能;(2)iostream迭代器:这类迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的IO流;(3)反向迭代器:这类迭代器实现向后遍历,而不是向前遍历。
11.3.1、插入迭代器
三种插入器:(1)back_inserter,创建使用push_back实现插入的迭代器;(2)front_inserter,使用push_front实现插入;(3)inserter,使用insert实现插入操作(insert要指明插入的位置,而不是仅仅有容器就可以的),它的第二个实参指向插入起始位置的迭代器。
front_inserter需要使用push_front,在vector或其他没有push_front运算的容器上使用front_inserter,将产生错误。
inserter将产生在指定位置实现插入的迭代器:如inserter(容器, 容器相关的迭代器)。
copy(ilist.begin(),ilist.end(), inserter(list, list.begin()));并不等价于copy(ilist.begin(), ilist.end(), front_inserter(list));,使用front_inserter时,元素始终在容器的第一个元素前面插入。而inserter在该位置插入前插入一个新元素后,插入位置就不再是容器的首元素(可以把inserter看成是定死的迭代器)。front_inserter的使用将导致元素以相反的次序出现在目标对象中。
11.3.2、iostream迭代器
istream_iterator用于读取输入流;ostream_iterator用于写输出流。如ostream_iterator<T> in;表示ostream_iterator对象的超出末端迭代器。流迭代器只定义了最基本的迭代器操作:自增、解引用和赋值。istream提供比较运算,ostream则不提供。ostream_iterator不提供超出末端迭代器。
迭代器的限制:(1)不可能从ostream_iterator对象读入,也不能写到istream_iterator,即两者不能交换;(2)一旦给ostream_iterator对象赋了一个值,写入就提交了。赋值后没有办法再改变这个值。ostream_iterator对象中每个不同的值都只能正好输出一次;(3)ostream_iterator没有à操作符。
用法:如
istream_iterator<int> cin_it(cin);//定义了这个,就算后面没用到它,运行时还是让你输入数据
istream_iterator<int> end_of_stream;//EOF
vector<int>vec(cin_it, end_of_stream);//读入容器元素,可以看成用迭代器初始化vec
sort(vec.begin(), vec.end());//排序
ostream_iterator<int> output(cout,"");//定义输出迭代器
unique_copy(vec.begin(), vec.end(), output);//输出
11.3.3、反向迭代器
反向迭代器是一种反向遍历容器的迭代器。++运算将访问前一个元素,--运算访问下一个元素。vec.rbegin()对应末尾元素,rend()对应第一个元素前面的元素。如想降序排列,可以直接给sort传反向迭代器,这样,你用begin()开始访问就是从大到小的排序。
流迭代器不能创建反向迭代器。
案例:输出列表中用逗号隔开的最后一个单词。即string::reverse_iterator rcomma =find(line.rbegin(), line.rend(), ‘,’);这时rcomma指向最后一个逗号或者第一个元素的前一个位置,这时要用rcomma.base()将其转换成iterator,再正序输出(调用reverse_iterator的base()方法可以获取"对应的"iterator。),即string(rcomma.base(), line.end());
使用普通的迭代器对反向迭代器进行初始化或赋值时,所得到的迭代器并不是指向原迭代器所指向的元素。
11.3.4、const迭代器
const_iterator:find_first_of函数中,第一个参数it不存储为const_iterator是因为容器本身不是const对象,它的end()返回的只是一个普通的迭代器,如果it为const那么指定范围的两个迭代器的类型不相同,则无法编译。
11.3.5、五种迭代器
算法要求的迭代器分为五类:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。
11.4、泛型算法的结构
算法最基本的性质是需要使用的迭代器种类。
泛型算法都含有一组形参规范:如find/find_if(第三个参数为布尔函数,找第一个为true的元素)、reverse/reverse_copy。
11.5、容器特有的算法
list容器特有的算法与其泛型算法版本之间的两个重要差别:(1)remove和unique的list版本修改了其关联的基础容器:真正删除了指定的元素(而泛型前面涉及到,只是放在后面)。(2)list容器提供的merge和splice运算会破坏它们的实参(如合并后实参变为空)。