上一篇中我们介绍了迭代器的分类,下面我们将集中精力分析一个小程序,通过修改使用迭代器的类型,让这个程序做不同的事情。
template <class In, class Out>
Out copy(In start, In end, Out dest)
{
while(start != end) {
*dest = *start;
dest++; start++;
}
return dest; // 返回dest已表明复制结束时处在原序列中的位置。
}
迭代器类型
上面copy
函数使用的In
和Out
分别对应于上一篇中介绍的输入迭代器和输出迭代器。分别表明了In
和Out
应该满足什么需求。例如:
- 必须支持
operator++
。 - 必须支持
operator!=
。 - 必须支持
operator*
,且*out = *in;
是正确的,但是*in = *out
却不一定正确。 -
Out
每次赋值以后就不能在回去赋值了,In
在访问过一个元素后也不能在访问之前的。因为不支持operator--
我们也不能做这个假设。
虚拟序列
我们的输入迭代器的表现因为跟内建指针类似,所以也会给人一种输入迭代器指向了一个无穷的序列,为了解决这个问题,我们需要在输入迭代器的内部增加一个成员count
用来记录现在已经访问过多少元素。
考虑下面的需求:
int x[100];
Iterator it(0);
copy(it, it+100, x);
通过copy
将x[100]
这个数组的元素赋值为0,此时start
和end
需要进行!=
的比较操作,所以我们需要重载operator+
,以便将end
的count
成员初始化为真实的值。同时我们也知道operator==
的实现应该是:
template<class T>
bool operator==(const Iterator<T>& op1, const Iterator<T>& op2)
{
return op1.data == op2.data && op1.count == op2.count;
}
// 同理给出operator!=
template<class T>
bool operator!=(const Iterator<T>& op1, const Iterator<T>& op2)
{
return !(op1 == op2);
}
下面我们来处理operator+
的问题,上面的使用需求表明我们的operator+
应该支持与整型的操作。所以我们的operator+
实现如下:
template<class T>
Iterator<T>(const Iterator<T>& it, int n)
{
Iterator<T> ret = it;
ret.count += n;
return ret;
}
// 同样也要支持 n+It的形式
template<class T>
Iterator<T>(int n, const Iterator<T>& it)
{
Iterator<T> ret = it;
ret.count += n;
return ret;
}
// 这里采用了const引用是为了可以接收右值,书中不是这样写的
输出流迭代器
针对输出流迭代器我们的类一定需要一个ostream
的成员,同时如果执行*out++ = x;
的操作,我们的期望是将x
输出到ostream
中,但是如果该迭代器的ostream
被初始化为cout
则与直接调用cout << x
没有区别了。所以我们的迭代器应支持一种可以间隔输出的方式。
由上我们得到这个迭代器的声明:
template <class T>
class ostream_iterator{
public:
ostream_iterator(ostream& os, const char* s): strm(&os), str(s) {}
ostream_iterator& operator++()
{
//注意这里应该返回一个左值
// 因为ostream每次写入后会自动向后移动,所以这里不需要什么操作
return *this;
}
ostream_iterator& operator++(int)
{
//注意这里应该返回一个左值
return *this;
}
ostream_iterator& operator*()
{
// 注意这里也要返回一个左值,同时我们将打印的操作放在赋值操作符中,所以这里也不需要操作。
return *this;
}
ostream_iterator& operator=(const T& t)
{
*strm << t << str; // 这里打印
return *this;
}
private:
ostream* strm;
const char* str;
};
输入流迭代器
实现输入流迭代器面临两个问题:
- 输入流迭代器怎样去比较。
- 输入流不进行读取操作就不知道是否已经到达尾部。
我们采用类似惰性求值的方式:
template <class T>
class istream_iterator
{
public:
istream_iterator(istream& is):strm(&is),full(false),eof(false) {}
istream_iterator():strm(nullptr),full(false),eof(true) {} // 默认构造函数默认为到达尾部
private:
T data; // 只有一个元素的缓冲区
istream* strm;
bool full; // 缓冲区已满
bool eof; // 已经到达尾部
};
因为是输入流迭代器,所以他应该支持:
-
operator*
可以返回一个右值 -
operator++
可以进行遍历 -
operator==
判断是否相等 - 赋值操作符。这里可以使用默认实现。
template <class T>
class istream_iterator
{
public:
istream_iterator& operator++() {
full = false;
return *this;
}
istream_iterator operator++(int) {
istream_iterator ret = *this;
full = false;
return ret; // 这里返回的是一个右值,与基本类型保持一致
}
private:
T data; // 只有一个元素的缓冲区
istream* strm;
bool full; // 缓冲区已满
bool eof; // 已经到达尾部
};
由于我们采用惰性求值的方案,所以在operator*
和operator=
的实现中,我们必须要去读取istream
,只有这样我们才能更新data,full,eof这三个成员变量。
所以我们的实现如下:
template<class T>
void istream_iterator::read() {
if (!full && !eof) {
if (*strm >> data) {
full = true;
}
else
eof = true;
}
}
template<class T>
T istream_iterator::operator*() {
read();
assert(full);
return data;
}
template<class T>
bool operator==(const istream_iterator& op1, const istream_iterator& op2)
{
if (op1.eof && op2.eof) { // 都处于尾部
return true;
}
if (!op1.eof && !op2.eof) {
return &op1 == &op2; // 地址相同
}
op1.read();op2.read();
return op1.eof == op2.eof; // 都处于尾部
}
总结
- istream_iterator和ostream_iterator分别实现了输入迭代器和输出迭代器的需求,所以他们也是可以使用在
copy
函数中的。 - 进一步说明了将迭代器进行模版化抽象对于算法库的重要性。
- 重点关注
istream_iterator
和ostream_iterator
的实现。