算法函数
STL包含了很多处理容器的非成员函数。对于算法函数,它们都是用模板来提供通用类型,并且它们都是用迭代器来提供访问容器中数据的通用表示。
算法组
STL将算法库分成4组:
(1) 非修改式序列操作;
(2) 修改式序列操作;
(3) 排序和相关操作;
(4) 通用数字运算。
前3组在algorithm(以前为algo.h)头文件中定义。
第4组是专用于数值数据的,在numeric(以前为algol.h)头文件中定义。
非修改式序列操作对区间中的每个元素进行操作。这些操作不修改容器的内容。
修改式序列操作也对区间中的每个元素进行操作。它们可以修改容器的内容。可以修改值,也可以修改值的排序顺序。
排序和相关操作包括多个排序函数(包括sort())和其他各种函数,包括集合操作。
数字操作包括将区间的内容累积、计算两个容器的内部乘积、计算小计、计算相邻对象差的函数。通常,这些都是数组的操作特性,因此vector是最有可能使用这些操作的容器。
算法的通用特征
STL函数使用迭代器和迭代器区间。从函数原型可知有关迭代器的假设。
例如,copy()函数的原型:
template<class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result);
InputIterator和OutputIterator都是模板参数。不过,STL文档使用模板参数名称来表示参数模型的概念。
由上述声明可知,区间参数必须是输入迭代器或更高级别的迭代器,而指示结果存储位置的迭代器必须是输出迭代器或更高级别的迭代器。
对算法进行分类的方式之一是按结果放置的位置进行分类。
就地算法(in-place algorithm),例如,在sort()函数完成时,结果被存放在原始数据的位置上。
复制算法(copying algorithm),例如,copy()函数将结果发送到另一个位置。
有些算法有两个版本:就地版本和复制版本,例如,transform()函数。
STL约定,复制版本的名称以 _copy 结尾。
复制版本将接受一个额外的输出迭代器参数,该参数指定结果的放置位置。
例如,函数replace()的原型如下:
template<class ForwardIterator, class T>
void replace(ForwardIterator first, ForwardIterator last, const T & old_value, const T & new_value);
它将所有的old_value替换为new_value,这就是就地发生的。由于这种算法同时读写容器元素,因此迭代器类型必须是ForwardIterator或更高级别的。
复制版本的原型如下:
template<class InputIterator, class OutputIterator, class T>
OutputIterator replace_copy(InputIterator first, InputIterator last, OutputIterator result,
const T & old_value, const T & new_value);
此时,结果被复制到result指定的新位置,因此对于指定区间而言,只读输入迭代器足够了。
replace_copy()的返回值类型为OutputIterator。对于复制算法,统一的约定是: 返回一个迭代器,该迭代器指向复制的最后一个值后面的一个位置。
另一个常见的变体: 有些函数有这样的版本,即根据将函数应用于容器元素得到的结果来执行操作。这些版本的名称通常以 _if 结尾。
例如,如果将函数用于旧值时,返回的值为true,则replace_if()将把旧值替换为新的值。原型如下:
template<class ForwardIterator, class Predicate, class T>
void replace_if(ForwardIterator first, ForwardIterator last, Predicate pred, const T & new_value);
Predicate(断言)也是模板参数名称。STL选用Predicate来提醒用户,实参应是Predicate概念的一个模型。
同样,STL使用诸如Generator(自适应生成器)和BinaryPredicate(自适应二元断言)等术语来提示必须是其他函数对象概念模型的参数。
STL和string类
string类虽然不是STL的组成部分,但它设计时考虑到了STL。例如,它包含begin()、end()、rbegin()和rend()等成员,因此可以使用STL接口。
函数和容器方法
使用STL方法还是使用STL函数。通常方法是更好的选择。它更适合于特定的容器;其次,作为成员函数,它可以使用模板类的内存管理工具,从而在需要时调整容器的长度。
例如,假设有一个由数字组成的链表,并要删除链表中某个特定值(例如4)的所有实例。如果la是一个list<int>对象,则可以使用链表的remove()方法:
la.remove(4); // remove all 4s from the list
调用该方法后,链表中所有值为4的元素都被删除,同时链表的长度将被自动调整。
如果用一个同名的remove()的STL算法,它不是由对象调用,而是接受区间参数:
remove(la.begin(), la.end(), 4);
效果一样,但它不能调整链表的长度。它将没被删除的元素放到链表的开始位置,并返回一个指向新的超尾值的迭代器。
可以用该迭代器来修改容器的长度。
例如,用链表的erase()方法来删除一个区间,该区间描述了链表中不再需要的部分:
list<int>::iterator last;
last = remove(la.begin(), la.end(), 4);
la.erase(last,la.end());
尽管方法通常更适合,但非方法函数更通用。因为可以将它们用于数组、string对象、STL容器,还可以用它们来处理混合的容器类型,例如,将矢量容器中的数据存储到链表或集合中。