本周课程主要内容为STL6大部件中的迭代器、算法、泛函数和适配器。其中算法与其他STL部件的区别之一是算法是函数模板,其他的是类模板。
1、各部件的关系
STL的6大部件是相互联系的。算法虽然对容器一无所知,但是它通过问答迭代器,通过迭代器实现了对容器的操作。当迭代器无法回答迭代器的问题时,编译就会报错。算法也是泛函数的应用场合之一。适配器则是在容器、迭代器、泛函数的基础上再次封装,用这三大部件实现所需功能。
2、迭代器
本节主要分两部分:iterator_category和iterator_category对算法的影响。
2.1 iterator_category
共有五种迭代器类型,其中input——farward——bidirectional——random_access,从右向左是依次继承的关系,还有一种单独的类型为output_iterator。
本小节内容小结如下:
(1)使用随机访问迭代器的容器:array, vector,deque;
使用双向迭代器的容器:list,红黑树作为底层支撑的容器;
使用单向迭代器的容器:forward_list;
两个特殊的迭代器类型:input迭代器(istream_iterator)和output迭代器(ostream_iterator);
(2)五种迭代器均是class,是通过迭代器的萃取器来得到类型;
(3)istream_iterator和ostream_iterator的迭代器种类的格式相同,G2.9版中有两个模板参数,G3.3和G4.9版本中有四个模板参数。
2.2 iterator_category对算法的影响
本小节内容小结如下:
(1)distance:计算两个迭代器间的距离时,内部首先会识别iterator的类型,之后根据不同的迭代器类型调用不同的__distance(first, second, category())函数计算距离,category()是个临时对象,表示iterator的类型;
(2)若可以直接相减则选用上述图2.4中的方法2,尾-头,速度快;若不能直接相减则选用上述图2.4中的方法1,一步一步走,计算总共走多少步;
(3)advance:实现iterator移动n个元素的位置时,其内部会调用__advance(i, n, iterator_category(i)), iterator_category函数用于获取iterator的类型,根据不同的迭代器类型调用不同的__advance函数,如图2.5所示,有3中情况,如果是input迭代器,那么会将iterator逐个移动n个位置;如果是双向迭代器,那么会先判断n是正整数还是负整数,再决定是向前还是向后逐个移动;如果是随机访问迭代器,那么直接将指针加n个位置;
(4)在调用distance,advance等这类重载的函数时,如果没有特别对这种迭代器类型的实现函数,那么会根据迭代器类型的父类找到重载的函数,例如,若iterator是forward_iterator_tag,返回类型为inline,用方法1(原因为forward_iterator_tag继承input);
(5)copy:实现复制内容时,内部有多出检查,首先会检查first和last的类型,如果是const char类型,那么会调用memmove();如果是const wchar_t类型,那么也会调用memmove();如果是InputIterator类型,那么就调用__copy_dispatch(),再去判断是const T, 还是T,还是迭代器,如果是迭代器,那么会再去检查是随机的还是其他类型的迭代器,如果是指针类型,那么会利用type traits询问它的拷贝构造重不重要(has trivial op=(), has non-trivial op=(),对于不需要自己定义,可以使用编译器给的默认拷贝构造函数,才叫做不重要的拷贝构造);
(6)destroy和copy类似,都是会将传入的类型做判断,不同的类型采用不同的处理方式;
(7)unique_copy:(first, last, result)函数中result如果是outputIterator,由于output iterator无法read, 无法执行result !=first, 对于这种情况的处理就不同于result传入的是forward iterator。
3、算法
总共介绍了12种算法,包括算法accumulate、算法for_each、算法replace,replace_if,replace_copy、算法count,cout_copy、算法find,find_if、算法sort以及算法binary_search。
本节内容总结如下:
(1)accumulate(first, last, init),元素直接相加;
accumulate(first, last, init, binary_op), 元素累计,binary_op可以传自定义的函数,或是泛函数,binary_op(init,first)这个函数定义了初值init和first之间的计算方法;
(2)replace()是将first到last内等于old_value的元素替换为new_value;
replace_if()这里predicate表示要传一个能返回真假的判断函数pred(*first), 返回真的话,会做替换;
replace_copy()是将将first到last区间内的值拷贝给res, 如果是old_value,替换为new_value再拷贝给res,原有的值不做替换;
(3)count(first, last, value) 对于其中等于value的元素进行计数;
count_if(first, last, pred) 对于其中符合pred(*first)的元素进行计数.;
如果容器中有自己的版本,要用自己的,效率更高;
(4)sort(first, last, op); op可以是自定义比较大小的函数,也可以是泛函数;
sort要求容器可跳跃,而list和forword_list不能跳跃;
(5)r.begin()和r.end()是通过reverse iterator实现的。
4、仿函数
总共分为三大类仿函数:算术类、逻辑运算类和相对关系类。标准库提供的仿函数都有继承关系。
本节内容总结如下:
(1)identify、select1st、select2nd是GUN C++独有的仿函数,是非标准的,identify传入什么元素就传出什么元素,select1st传入pair, 传出pair中第一个元素,select2nd传入pair, 传出pair中第二个元素;
G4.9版本中名称有所改变,_Identify、_Select1st、_Select2nd;
(2)3种仿函数的共同点:都是继承自unary_function或是binary_function类,他们内部定义了一些typedef, 共适配器询问,他们的内部并没有数据成员,对于自定义的泛函数只有遵循STL的体系架构,继承于unary_function或是binary_function才能作为泛函数适配器。
5、适配器
适配器可以改造泛函数、迭代器和容器。适配器中可以拥有泛函数、迭代器或容器。
本节内容总结如下:
(1)A改造B,A需要取用B的功能,有两种实现方法:A继承B或者A拥有B,适配器中使用的是A拥有B这种方法;
(2)binder2nd(泛函数适配器):2nd指第二参数,bind2nd(less(), 40)给less函数绑定第二个参数为40,使得第一个参数和40进行比较,这里less()是一个临时对象,并没有被调用 ;
(3) not1(泛函数适配器):not1(pred), 对pred的结果取非;
(4)bind (新型适配器):C++11标准中才有,可以绑定函数、泛函、成员函数(_1必须是某个object地址)、数据成员(_1必须是某个object地址);
(5)inserter(迭代器适配器):是函数模板,将元素插入到另一个容器中。copy(bar.begin(), bar.end(), inserter(foo, it)); 将bar的元素全部插入到foo容器的it位置,并不覆盖foo原有的元素,如果是copy(bar.begin(), bar.end(), foo.begin());这样写会覆盖foo中原有的元素;
(6)ostream_iterator(X适配器,三大类之外的适配器):其内部定义了拷贝赋值函数,将value赋值给标准输出,所以copy会调用ostream_iterator的拷贝赋值,输出各个元素;
ostream_iterator相当于count(输出),istream_iterator相当于cin(输入)。
6、课堂难点知识理解
(1)inserter
C++ 语言提供了三种插入器,其差别在于插入元素的位置不同。
back_inserter,创建使用push_back实现插入的迭代器。
front_inserter,使用push_front实现插入。
inserter,使用insert实现插入操作。
也许有人认为可使用inserter和容器的begin迭代器来模拟front_inserter的效果。然而,inserter的行为与front_inserter的有很大差别。在使用front_inserter时,元素始终在容器的第一个元素前面插入。而使用inserter时,元素则在指定位置前面插入。即使此指定位置初始化为容器中的第一个元素,但是,一旦在该位置前插入一个新元素后,插入位置就不再是容器的首元素了。
(2)ostream_iterator
ostream_iterator是流迭代器,流迭代器是标准模板库中的,因此是类模板。
ostream_iterator<int>指定了类型,就是迭代器读写的类型。通过这个流迭代器可以把你要输入的写入到指定的流中。cout就是指定的流,就是标准输出。可以改成一个输出流就可以,比如一个文件。通俗的一点说,你把它看成一个指向输出流的指针,通过这个指针你可以把东西写的输出流中。
copy (v.begin(),v.end(),output);这个意思就是说,把向量V中的数据放到cout输出流中,通过流迭代器output.
ostream_iterator output(cout ,"*");这个的意思说,放到输出流的时候,没放一个整数,就末尾添加一个*.