本文为《C++ Primer》的读书笔记
#include <iterator>
- 插入迭代器 (insert iterator): 这些迭代器被绑定到一个容器上, 可用来向容器插入元素
- 流迭代器 (stream iterator): 这些迭代器被绑定到输入或输出流上,可用来遍历所关联的 IO 流
- 反向迭代器 (reverse iterator) : 这些迭代器向后而不是向前移动。除了
forward_list
之外的标准库容器都有反向迭代器 - 移动迭代器 (move iterator): 这些专用的迭代器不是拷贝其中的元素, 而是移动它们
插入迭代器
- 插入器是一种迭代器适配器, 它接受一个容器, 生成一个迭代器, 能实现向给定容器添加元素。当我们通过一个插入迭代器进行赋值时, 该迭代器调用容器操作来向给定容器的指定位置插入一个元素
- 插入器有三种类型, 差异在于元素插入的位置:
back_inserter
接受一个指向容器的引用, 返回一个与该容器绑定的插入迭代器。当我们通过此迭代器赋值时, 赋值运算符会调用push_back
将一个具有给定值的元素添加到容器中front_inserter
创建一个使用push_front
的迭代器inserter
创建一个使用insert
的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前
只有在容器支持
push_front
/push_back
/insert
的情况下,我们才可以使用front_inserter
/back_inserter
/inserter
vector<int> vec;
auto it = back_inserter(vec); //通过它赋值会将元素添加到vec中
*it = 42; // vec 中现在有一个元素, 值为 42
// 假设 `it` 是由 `inserter` 生成的插入迭代器
*it = val;
// 等价代码
it = c.insert(it, val); // it 指向新加入的元素
++it; // 递增 it 使它指向原来的元素
iostream
迭代器
- 虽然
iostream
类型不是容器, 但标准库定义了可以用于这些 IO 类型对象的迭代器。这些迭代器将它们对应的流当作一个特定类型的元素序列来处理。通过使用流迭代器, 我们可以用泛型算法从流对象读取数据以及向其写入数据istream_iterator
读取输入流ostrearn_iterator
向一个输出流写数据
istream_iterator
操作
- 当创建一个流迭代器时, 必须指定迭代器将要读写的对象类型。
istream_iterator
要读取的类型必须定义了输入运算符。当创建一个istream_iterator
时, 我们可以将它绑定到一个流 - 当然, 我们还可以默认初始化迭代器, 这样就创建了一个可以当作尾后值使用的空迭代器: 一旦其关联的流遇到文件尾或遇到 IO 错误, 迭代器的值就与尾后迭代器相等
ifstream in("afile");
istream_iterator<string> str_it(in); // 从 "afile" 读取字符串
istream_iterator<int> in_iter(cin); // 从 cin 读取 int
istream_iterator<int> eof; // istream 尾后迭代器
while (in_iter != eof)
// 后置递增运算读取流,返回迭代器的旧值
// 解引用迭代器、获得从流读取的前一个值
vec.push_back(*in_iter++);
// 我们可以将程序重写为如下形式, 这体现了 `istream_iterator` 更有用的地方:
istream_iterator<int> in_iter(cin), eof;
vector<int> vec(in_iter, eof); // 从迭代器范围构造 vec
istream_iterator
允许使用懒惰求值
- 当我们将一个
istream_iterator
绑定到一个流时,标准库并不保证迭代器立即从流读取数据。具体实现可以推迟从流中读取数据, 直到我们使用迭代器时才真正读取。标准库中的实现所保证的是,在我们第一次解引用迭代器之前, 从流中读取数据的操作已经完成了 - 对于大多数程序来说,立即读取还是推迟读取没什么差别。但是,如果我们创建了一个
istream_iterator
, 没有使用就销毁了, 或者我们正在从两个不同的对象同步读取同一个流,那么何时读取可能就很重要了
ostream_iterator
操作
- 我们可以对任何具有输出运算符
<<
的类型定义ostream_iterator
。当创建一个ostream_iterator
时,我们可以提供(可选的) 第二参数, 它是一个 C 风格字符串,在输出每个元素后都会打印此字符串
不允许空的或表示尾后位置的
ostream_iterator
// 输出值的序列
ostream_iterator<int> out_iter(cout, " ");
for (auto e : vec)
*out_iter++ = e;
cout << endl;
- 值得注意的是, 当我们向
out_iter
赋值时, 可以忽略解引用和递增运算即,循环可以重写成下面的样子。但是, 推荐第一种形式。在第一种写法中,流迭代器的使用与其他迭代器的使用保持一致
for (auto e : vec)
out_iter = e; // 赋值语句将元素写到 cout
cout << endl;
- 如果类类型既有输入运算符也有输出运算符, 就可以使用 IO 迭代器:
istream_iterator<Sales_item> item_iter(cin), eof;
ostream_iterator<Sales_item> out_iter(cout, "\n");
// 将第一笔交易记录存在 sum 中, 并读取下一条记录
Sales_item sum = *item_iter++;
while (item_iter != eof) {
if (item_iter->isbn() == sum.isbn())
sum += *item_iter++; // 将其加到 sum 上并读取下一条记录
else (
out_iter = sum; // 输出 sum 当前值
sum = *item_iter++; // 读取下一条记录
}
}
out_iter = sum; // 打印最后一组记录的和
反向迭代器
- 反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。递增一个反向迭代器会移动到前一个元素; 递减一个迭代器会移动到下一个元素
- 除了
forward_list
之外,其他容器都支持反向迭代器。我们可以通过调用rbegin
、rend
、crbegin
和crend
成员函数来获得反向迭代器。这些成员函数返回指向容器尾元素和首元素之前一个位置的迭代器
// 逆序打印 `vec` 中的元素
vector<int> vec = {0,1,2,3,4,5,6,7,8,9};
// 从尾元素到首元素的反向迭代器
for(auto r_iter = vec.crbegin(); r_iter != vec.crend(); ++r_iter)
cout << *r_iter << endl;
反向迭代器需要递减运算符
- 我们只能从既支持
++
也支持--
的迭代器来定义反向迭代器。毕竟反向迭代器的目的是在序列中反向移动 - 除了
forward_list
之外, 标准容器上的其他迭代器都既支持递增运算又支持递减运算。但是, 流迭代器不支持递减运算, 因为不可能在一个流中反向移动。因此, 不可能从一个forward_list
或一个流迭代器创建反向迭代器
反向迭代器和其他迭代器间的关系
// line 为一个保存着一个逗号分隔的单词列表
// 打印 line 中的第一个单词
auto comma = find(line.cbegin(), line.cend(), ',');
cout << string(line.cbegin(), comma) << endl;
// 打印最后一个单词
// 在一个逗号分隔的列表中查找最后一个元素
// 如果 line 中有逗号, 则 rcomma 指向最后一个逗号。如果 line 中没有逗号, 则 rcomma 指向 line.crend()
auto rcomma = find(line.crbegin(), line.crend(), ',');
// 错误:将逆序输出单词的宇符
cout << string(line.crbegin(), rcomma) << endl;
- 上图说明了问题所在: 我们使用的是反向迭代器,会反向处理
string
。而我们希望按正常顺序打印, 因此不能直接使用rcomma
。因为它是一个反向迭代器,意味着它会反向朝string
的开始位置移动。需要做的是,将rcomrna
转换回一个普通迭代器, 能 在line
中正向移动。我们通过调用reverse_iterator
的base
成员函数来完成这一转换, 此成员函数会返回其对应的普通迭代器:
// 正确:得到一个正向迭代器, 从逗号开始读取字符直到line末尾
cout << string(rcomma.base(), line.cend()) << endl;