参考资料:
- 《C++ Primer》第5版
- 《C++ Primer 习题集》第5版
10.4 再探迭代器(P357)
除了为每个容器定义的迭代器外,头文件 iterator
中还定义了额外的几种迭代器:
- 插入迭代器(insert iterator):这些迭代器被绑定到一个容器上,可以向容器中插入元素。
- 流迭代器(stream iterator):这些迭代器被当顶到输入或输出流上,可用来遍历所关联的 IO 流。
- 反向迭代器(reverse iterator):除
forward_list
外的所有标准库容器都有反向迭代器。 - 移动迭代器(move iterator):用于移动元素的迭代器。
10.4.1 插入迭代器(P358)
插入器是一种迭代器适配器,接受一个容器,生成一个迭代器,能实现向容器中插入元素:
插入迭代器有三种类型,区别在于插入元素的位置:
back_inserter
:使用push_back
。front_inserter
:使用push_front
。inserter
:使用insert
,第二个参数为指向给定容器的迭代器。
vector<int> vi = { 0,1,2,3 };
list<int> v1, v2;
// 注意体会inserter和front_inserter的区别
auto it1 = inserter(v1, v1.begin());
auto it2 = front_inserter(v2);
for (auto i : vi) it1 = i, it2 = i;
for (auto i : v1) cout << i << ' '; // 输出0 1 2 3
cout << endl;
for (auto i : v2) cout << i << ' '; // 输出3 2 1 0
10.4.2 iostream
迭代器(P359)
这部分不是很懂
流迭代器将对应的流当作一个特定类型元素的序列。创建一个流迭代器时,必须指定迭代器要读写的类型。
istream_iterator
操作
istream_iterator
通过 >>
来读取流,所以其要读取的数据类型必须定义了 >>
运算符:
istream_iterator<int> int_it(cin); // 从cin读取int
istream_iterator<int> eof; // 定义尾后迭代器
//
vector<int> vi(int_it, eof);
使用算法操作流迭代器
istream_iterator<int> in(cin), eof;
cout << accumulate(in, eof, 0) << endl;
istream_iterator
允许懒惰求值
当我们将一个 istream_iterator
绑定到一个流时,标准库不保证迭代器立即从流读取数据,具体实现可以知道我们使用迭代器时才读取读取数据。
ostream_iterator
操作
我们可以对具有 <<
的类型定义 ostream_iterator
。在创建 ostream_iterator
时,我们可以提供第二个参数,类型为 C 风格字符串,在输出每个元素后都会打印此字符串。必须将 ostream_iterator
绑定到一个流。
此处应有图片
vector<int> vi = { 1,2,3,4,5 };
ostream_iterator<int> out(cout, ", ");
for (auto i : vi) {*out++ = i;
}
cout<<endl;
// 输出:1, 2, 3, 4, 5,
简单写法:
copy(vi.begin(), vei.end(), out);
cout<<endl;
10.4.3 反向迭代器(P363)
除了 forward_list
外,其他容器都支持反向迭代器:
vector<int> vi = { 1,2,3,4,5 };
for (auto r_iter = vi.crbegin(); r_iter != vi.crend(); r_iter++) {cout << *r_iter << ' ';
}
// 输出5 4 3 2 1
利用反向迭代器和 sort
实现降序排序:
sort(vi.rbegin(), vi.rend());
反向迭代器需要递减运算符
反向迭代器的实现依赖于普通迭代器的 --
运算符,除 forward_list
外,标准库中的所有容器都同时支持递增和递减操作。
反向迭代器和其他迭代器间的关系
假设我们有一个名为 line
的 string
,保存着一个逗号分隔的单词序列:
string line("FIRST,MIDDLE,LAST");
如果我们想要打印第一个单词,使用 find
可以很容易实现:
auto comma = find(line.cbegin(), line.cend(), ',');
cout << string(line.cbegin(), comma) << endl; // 输出FIRST
如果我们想要打印最后一个单词,可以借助反向迭代器:
auto rcomma = find(line.crbegin(), line.crend(), ',');
cout << string(line.crbegin(), rcomma) << endl; // 输出TSAL
可以发现,使用反向迭代器会导致我们的实际输出也是反过来的,所以我们需要使用 reverser_iterator
的 base
函数成员,将反向迭代器转变成正向迭代器:
cout << string(rcomma.base(), line.cend()) << endl; // 输出LAST
这里需要注意,反向迭代器 rcomma
指向 ','
,而 rcomma
对应的普通迭代器 rcomma.base()
指向 'L'
,这一设计反映了“左闭右开区间”的特性:
10.5 泛型算法结构(P365)
算法所要求的迭代器操作可以分为 5 个迭代器类别(iterator category):
10.5.1 5类迭代器(P365)
C++ 标准指明了算法的每个迭代器参数的最小类别,向算法传递一个能力更差的迭代器会产生错误。
对于向算法传递错误类别迭代器的问题,很多编译器不会给出警告信息。
迭代器类别
输入迭代器(input iterator):可以读取序列中的元素,必须支持:
- 用于比较两个迭代器的
==
和!=
。 - 用于推进迭代器的前置和后置
++
。 - 用于读取元素的解引用运算符
*
,解引用只会出现在赋值运算符的右侧。 - 箭头运算符
->
。
对于一个输入迭代器,*it++
保证是有效的,但递增后可能导致其他指向流的迭代器失效,导致不能保证输入迭代器的状态可以保存下来用来访问元素。因此,输入迭代器只适用于单遍扫描算法。istream_iterator
是一种输入迭代器。
输出迭代器(output iterator):只写而不读元素,必须支持:
- 用于推进迭代器的前置和后置
++
。 - 解引用运算符
*
,解引用只会出现在赋值运算符的左侧。
输出迭代器只能用于单遍扫描算法,ostream_iterator
是一种输出迭代器。
前向迭代器(forward iterator):只能在序列中沿一个方向移动,可以读写元素,支持所有输入和输出迭代器的操作,可以多次读写同一个元素。因此,前向迭代器可以用于多遍扫描算法,forward_list
上的迭代器是前向迭代器。
双向迭代器(bidirectional iterator):可以双向移动,支持前向迭代器所有操作,支持前置和后置 --
运算符。除 forward_list
外,所有标准库容器都提供符合双向迭代器要求的迭代器。
随机访问迭代器(random-access iterator):提供在常量时间内访问序列内任意元素的能力。除支持双向迭代器的所有功能,还支持:
- 用于比较两个迭代器相对位置关系的运算符
>
、>=
、<
、<=
。 - 迭代器和一个整数值的加减运算
+
、+=
、-
、-=
。 - 用于两个迭代器的减法运算符,得到两个迭代器的距离。
- 下标运算符
iter[n]
,与*(iter[n])
等价。
array
、deque
、string
、vector
的迭代器都是随机访问迭代器,访问内置数组元素的指针也是。
10.5.2 算法形参形式(P367)
大多数算法具有如下 4 种形式之一:
alg(beg, end, args);
alg(beg, end, dest, args);
alg(beg, end, beg2, args);
alg(beg, end, beg2, end2, args);
beg
和 end
表示算法操作的输入范围。
接受单个目标迭代器的算法
dest
参数表示算法写入的目的位置的迭代器,并假定目标空间足够容纳写入的数据。比较常见的情况是,dest
绑定到一个插入迭代器或 ostream_iterator
。
接受第二个输入序列的算法
接受单独 beg2
或 beg2
和 end2
的算法用这些迭代器表示第二个输入范围 ,并假定 beg2
开始的范围至少和beg
和 end
的范围一样大。
10.5.3 算法命名规范(P368)
一些算法使用重载形式传递一个谓词
unique(beg, end);
unique(beg, end, comp);
_if
版本的算法
find(beg, end, val);
find_if(beg, end, pred);
由于可能产生重载歧义,所以标准库选择提供不同名字而非重载。
区分拷贝元素的版本和不拷贝的版本
reverse(beg, end);
reverse_copy(beg, end, dest);
vector<int> v1 = { 0,1,2,3,4,5 };
vector<int> v2;
// 同时提供_copy和_if版本
remove_copy_if(v1.begin(), v1.end(), back_inserter(v2),[](int i) {return i % 2; });
for (auto i : v1) cout << i << ' '; // 输出0 1 2 3 4 5
cout << endl;
for (auto i : v2) cout << i << ' '; // 输出0 2 4
10.6 特定容器算法(P369)
链表类型 list
和 forward_list
定义了几个成员函数形式的算法:
由于通用版本的 sort
要求随机访问迭代器,所以链表类型 list
和 forward_list
只能使用专用版本;其他算法的通用版本虽然可以用于 list
和 forward_list
,但这些算法需要交换序列中的元素,而链表可以通过改变元素间的链接方式实现交换,所以专用版本的算法效率往往更高。
splice
成员
该算法是链表类型独有的:
链表特有操作会改变容器
通用算法不会改变容器,而链表特有版本会改变底层容器。