标准库容器并没有提供大量的操作,而是提供了一组算法,这些算法大都独立于特定的容器,这些算法是通用的(generic,或称泛型),它们可用于不同类型的容器和不同类型的元素。
算法永不执行容器提供的操作,泛型算法本身从不执行容器操作,只是单独依赖迭代器和迭代器操作实现。使用“普通”的迭代器时,算法从不修改基础容器的大小。正如我们所看到的,算法也许会改变存储在容器中的元素的值,也许会在容器内移动元素,但是,算法从不直接添加或删除元素。
使用泛型算法必须包含 algorithm
头文件,标准库还定义了一组泛化的算术算法(generalized numeric algorithm),其命名习惯与泛型算法相同。使用这些算法则必须包含 numeric
头文件
除了少数例外情况,所有算法都在一段范围内的元素上操作,我们将这段范围称为“输出范围(input range)”。带有输入范围参数的算法总是使用头两个形参标记该范围。这两个形参是分别指向要处理的第一个元素和最后一个元素的下一位置的迭代器。
算法最基本的性质是需要使用的迭代器种类。所有算法都指定了它的每个迭代器形参可使用的迭代器类型。
- 只读算法,不改变元素的值顺序
- 给指定元素赋新值的算法
- 将一个元素的值移给另一个元素的算法
任何其他的算法分类都含有一组形参规范。理解这些形参规范有利于学习新的算法——只要知道形参的含义,就可专注于了解算法实现的操作。大多数算法采用下面四种形式之一:
alg (beg, end, other parms);
alg (beg, end, dest, other parms);
alg (beg, end, beg2, other parms);
alg (beg, end, beg2, end2, other parms);
- beg 和 end 指定算法操作的元素范围,算法上称为“输入范围”
- 尽管几乎所有算法都有输入范围,但算法是否使用其他形参取决于它所执行的操作,比较常用的其他形参:dest、beg2 和 end2,它们都是迭代器。这些迭代器在使用时,充当类似的角色。
- 除了这些迭代器形参之外,有些算法还带有其他的非迭代器形参,它们是这些算法特有的。
- 向算法传递函数
- lambda表达式
- 参数绑定
标准库为每一种标准容器定义了一种迭代器类型。迭代器类型提供了比下标操作更通用化的方法。所有的标准库容器都定义了相应的迭代器类型, 而只有少数的容器支持下标操作。
一个迭代器的典型用法是编写循环:
for (vector<int>::iterator iter = ivec.begin(); iter != ivec.end(); ++iter)
*iter = 0;
每种容器类型都定义了自己的迭代器类型:
vector<int>::iterator iter;
这符语句定义了一个名为 iter 的变量,它的数据类型是 vector<int>
定义的 iterator 类型。每个标准库容器类型都定义了一个名为 iterator 的成员,
这里的 iterator 与迭代器实际类型的含义相同
参考迭代器
C++ 定义的容器类型中,只有 vector 和 deque 容器提供下面两种重要的运算集合:迭代器算术运算,
以及使用除了 ==
和!=
之外的关系操作符来比较两个迭代器(==
和 !=
这两种关系运算适用于所有容器)。
运算符 | 说明 |
---|---|
iter + n 、iter - n |
在迭代器上加(减)整数值 n,将产生指向容器中前面(后面)第 n 个元素的迭代器。新计算出来的迭代器必须指向容器中的元素或超出容器末端的下一位置 |
iter1 += iter2 |
这是迭代器加减法的复合赋值运算:将 iter1 加上或减去 iter2 的运算结果赋给 iter1 |
iter1 - iter2 、iter1 -= iter2 |
两个迭代器的减法,其运算结果加上右边的迭代器即得左边的迭代器。这两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置。只适用于 vector 和 deque 容器 |
>, >=, <, <= |
迭代器的关系操作符。当一个迭代器指向的元素在容器中位于另一个迭代器指向的元素之前,则前一个迭代器小于后一个迭代器。关系操作符的两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置。只适用于 vector 和 deque 容器 |
关系操作符只适用于 vector 和 deque 容器,这是因为只有这种两种容器为其元素提供快速、随机的访问。它们确保可根据元素位置直接有效地访问指定的容器元素。
这两种容器都支持通过元素位置实现的随机访问,因此它们的迭代器可以有效地实现算术和关系运算。而list 容器的迭代器既不支持算术运算(加法或减法),
也不支持关系运算(<=, <, >=, >
),它只提供前置和后置的自增、自减运算以及相等(不等)运算。
除了为每个容器提供迭代器之外,C++ 语言还在iterator头文件中提供了另外几种迭代器:
- 插入迭代器(insert iterator):这类迭代器与容器绑定在一起,实现在容器中插入元素的功能。
- 流迭代器(stream iterator):这类迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的 IO 流。
- 反向迭代器(reverse iterator):这类迭代器实现向后遍历,而不是向前遍历。所有容器类型都定义了自己的 reverse_iterator 类型,由 rbegin 和 rend 成员函数返回。
- 移动迭代器(move iterator) :这类迭代器不是拷贝其中的元素,而是移动它们
C++ 语言提供了三种插入器,其差别在于插入元素的位置不同
back_inserter
,创建使用 push_back 实现插入的迭代器front_inserter
,使用 push_front 实现插入inserter
,使用 insert 实现插入操作。除了所关联的容器外,inserter
还带有第二实参:指向插入起始位置的迭代器
list<int>::iterator it = find (ilst.begin(), ilst.end(), 42);
replace_copy (ivec.begin(), ivec.end(), inserter (ilst, it), 100, 0);
虽然 iostream 类型不是容器,但标准库同样提供了在 iostream 对象上使用的迭代器:istream_iterator
用于读取输入流,而 ostream_iterator
则用于写输出流。
流迭代器有下面几个重要的限制:
- 不可能从
ostream_iterator
对象读入,也不可能写到istream_iterator
对象中。 - 一旦给
ostream_iterator
对象赋了一个值,写入就提交了。赋值后,没有办法再改变这个值。此外,ostream_iterator
对象中每个不同的值都只能正好输出一次。 - ostream_iterator` 没有 -> 操作符。
从标准输入读取一些数,再将读取的不重复的数写到标准输出:
istream_iterator<int> cin_it(cin);
// reads ints from cin
istream_iterator<int> end_of_stream; // end iterator value
// initialize vec from the standard input:
vector<int> vec(cin_it, end_of_stream);
sort(vec.begin(), vec.end());
// writes ints to cout using " " as the delimiter
ostream_iterator<int> output(cout, " ");
// write only the unique elements in vec to the standard output
unique_copy(vec.begin(), vec.end(), output);
反向迭代器是一种反向遍历容器的迭代器。从最后一个元素到第一个元素遍历容器。反向迭代器将自增(和自减)的含义反过来了, 对于反向迭代器,++ 运算将访问前一个元素,而 -- 运算则访问下一个元素。
Input iterator
(输入迭代器) 读,不能写;只支持自增运算Output iterator
(输出迭代器) 写,不能读;只支持自增运算Forward iterator
(前向迭代器) 读和写;只支持自增运算Bidirectional iterator
(双向迭代器) 读和写;支持自增和自减运算Random access iterator
(随机访问迭代器) 读和写;支持完整的迭代器算术运算
很多算法通过检查其输入范围内的元素实现其功能。这些算法通常要用到标准关系操作符:==
或 <
。
其中的大部分算法会提供第二个版本的函数,允许程序员提供比较或测试函数取代操作符的使用。
有些算法提供所谓的“复制(copying)”版本。这些算法对输入序列的元素做出处理,但不修改原来的元素,而是创建一个新序列存储元素的处理结果。
但不修改原来的元素,而是创建一个新序列存储元素的处理结果。比如replace_copy
是replace
的_copy
版本。
- list
- forward_list