我们反向迭代器不和正向迭代器放在一起实现?
因为反向迭代器是容器适配器.
list 的反向迭代器模拟实现
先回看一下之前模拟实现list正向迭代器的代码:
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef list_node<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
Node* _node;
__list_iterator(Node* node)
:_node(node)
{}
self& operator++()
{
_node = _node->_next;
return *this;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
单从使用的角度来看,反向迭代器和正向迭代器有什么区别?
其实区别也不是很大,就是正向迭代器的++是从前往后走,而反向迭代器的++是从后往前走,那对于list来说正向++是_node = _node->_next;,那反向就应该是_node = _node->_prev.
当然如果支持- -的话,- -也是反过来的。
然后反向迭代器其它的功能如判断相等,解引用等就和正向迭代器是一样的操作了。
版本1:
直接把正向迭代器的代码拷贝一份,然后把名字,++和- -的操作改一下。
然后在list里添加反向迭代器和对应的rbegin,rend:
rbegin是尾结点,也就是list头结点的前一个结点,rend还是头结点不需要变
版本2:
虽然这样也可以, 但是这样写的话代码比较冗余:
通过上面的复写可以发现两个迭代器在实现上没有太大的差别
源码:
stl_list.h:
通过源码可以看到,这里的反向迭代器它们的模板参数传的是正向迭代器。
而且我们在当前stl_list.h这个头文件里并没有找到reverse_iterator这个类模板的定义!
那reverse_iterator这个类模板的实现其实是在另一个头文件stl_iterator.h:
成员变量是Iterator current,而reverse_iterator的模板参数iterator在list类里是正向迭代器
那reverse_iterator 这个类呢,其实是一个适配器,是一个迭代器适配器。即reverse_iterator 是对正向迭代器进行了一个封装。但是库里面实现的是比较复杂的,涉及一个迭代器萃取的东西,这个可以先不用管。
先大致看一下它是怎么实现的:
它封装正向迭代器的话,反向的++就是正向的- -,反向的- -就是正向的++。
它的解引用怎么不是返回当前结点的值啊,为什么返回的是- -之后再解引用的值?
那这就要去看它里面的实现了,正如刚才在stl_list.h中展示过的,它的rbegin和rend是这样的:
rbegin()返回的是正向迭代器的end(),也就是头结点
rend()返回的是正向迭代器的begin(),也就是头结点的next
那这样我们就看出来了,库里面应该是想追求一个对称。
但是如果这样实现的话:
反向迭代器在解引用的时候如果还是直接去它对应的那个位置是不是就出问题了,就拿rbeign来说,*rbegin()就返回的是头结点的值,正确的情况rbegin解引用应该拿到的是最后一个元素的值。
所以它的解引用就应该返回的是--之后的值,正向迭代器--就是取prev,头结点的prev就正好是最后一个元素。
但是为什么不直接返回*(current-1)的值呢?而是先创建临时变量再--
因为current是正向迭代器对象, 类中并没有重载减号运算符, 如果重载的话在语法上是可以通过的,但是在语义上没有意义.
反向迭代器模拟实现的改进——适配器模式
接下来按照库里面的方式来模拟实现:
先实现++,--,判断相等:
template<class Iterator>
struct reverse_iterator
{
typedef reverse_Iterator<Iterator> self;
Iterator _cur;
reverse_iterator(Iterator it)
:_cur(it)
{}
self& operator++()
{
_cur--;
return *this;
}
self operator++(int)
{
self tmp(*this);
_cur--;
return tmp;
}
self& operator--()
{
++_cur;
return *this;
}
self operator--(int)
{
self tmp(*this);
++_cur;
return tmp;
}
bool operator!=(const self& s)
{
return _cur != s._cur;
}
bool operator==(const self& s)
{
return _cur == s._cur;
}
};
实现一下解引用*
:
经过上面我们的分析我们知道这里解引用返回的是- -之后的值。
但是这里的返回值怎么办呢,普通对象解引用返回引用,const对象解引用返回const引用。那这个问题我们之前是不是提出了比较好的解决方式啊,可以增加两个模板参数。
但库里并没有这样做,但这里我们不按库里面的方法去走了,它里面实现的比较复杂,其实它用我们这个方法也能搞,但它考虑到其它的一些原因搞的比较复杂,我们不用管。
template<class Iterator,class Ref,class Ptr>
struct reverse_iterator
{
typedef reverse_Iterator<Iterator,Ref,Ptr> self;
Iterator _cur;
reverse_iterator(Iterator it)
:_cur(it)
{}
self& operator++()
{
_cur--;
return *this;
}
self operator++(int)
{
self tmp(*this);
_cur--;
return tmp;
}
self& operator--()
{
++_cur;
return *this;
}
self operator--(int)
{
self tmp(*this);
++_cur;
return tmp;
}
Ref operator*()
{
Iterator tmp = _cur;
return *(--tmp);
}
Ptr operator->()
{
return &(operator*());
}
bool operator!=(const self& s)
{
return _cur != s._cur;
}
bool operator==(const self& s)
{
return _cur == s._cur;
}
};
测试一下:
适配器模式的优点
参照stl进行的改进:按照适配器模式去重新实现,与之前我们自己的方法相比,好像也没有优秀多少?不还是需要我们自己手动去写一个类。
这样做真正的优秀之处:
大家想一下,对于我们的list来说,我们使用最开始我们自己的方法去实现反向迭代器(拷贝一份正向迭代器的代码,进行一些简单修改),确实也可以。
但是,如果是vector呢?我们还可以用那种方法去实现吗?不行,因为我们vector的迭代器是直接使用的原生指针(因为原生指针的行为完全符合我们的需求),只不过进行了typedef
但是对于适配器的实现方式:
给我一个list的正向迭代器,我可以给你适配出list的反向迭代器,那如果给一个vector的正向迭代器,能否适配出vector的反向迭代器呢?
当然也是可以的。
回想我们stack和queue容器适配器,它们对应的底层容器仅限一种吗?
不是,只要支持指定的操作就可以作为其底层的适配容器。
那我们这里的迭代器适配器Reverse_Iterator是不是只要对应容器的迭代器支持++和--操作就可以进行适配(因为里面反向的++需要复用正向的- -,反向- -复用正向++)
所以,对于任何一个容器,只要迭代器至少是双向的,都可以用Reverse_Iterator这个类模板去适配出其反向的迭代器。那我们常见的这些容器什么vector,list,string的反向迭代器不就都可以搞定了。
int main()
{
test::vector<int>d1;
d1.push_back(1);
d1.push_back(2);
d1.push_back(3);
d1.push_back(4);
test::vector<int>::reverse_iterator it = d1.rbegin();
while (it != d1.rend())
{
cout << *it;
it++;
}
return 0;
}
当我们还停留在思考去如何实现list的迭代器的时候,人家考虑的已经是如何做到一劳永逸,搞定所有容器的反向迭代器。