首先我们在刚开始学习stl的时候,知道stl中含有6大组件,其中容器,迭代器和算法都是stl的六大组件之一。那么这里我们思考一个问题:迭代器的意义是什么呢?
我们知道容器的意思是储存数据,而算法的作用是对数据进行处理,例如对vector进行排序,对list进行排序。
如果我们将算法写到容器实现中,那么就会出现很多重复的代码。所以容器和算法是分离的。除此之外,我们还可能想要遍历一下容器中的数据,但是不同类型的容器,其底层的实现逻辑是不同的,例如树形结构的遍历和普通线性结构的遍历那就是截然不同的,即使最后遍历打印出的数据是一样的。那么假设我们是自己想要遍历容器中的数据,我们本身肯定是要理解这个容器底层是怎样储存数据的,就拿数组举例,我们知道要遍历数组那么使用下标和for循环是很方便的,对于链表我们知道我们通过指针去寻找下一个节点,并且知道最后一个节点最后指向的是NULL。
那么如果我们要使用stl也要和数组和链表一样的话,那么对于某些人来说,成本是很高的,要学会使用一个stl就必须理解stl的底层实现,但是对于某些人来说,它使用stl就只是需要使用到它的功能而已,而不关心它的底层实现逻辑。所以就出现了封装,将容器底层的实现逻辑包起来,提供能让外人访问的接口,而外人只需要知道对应接口的功能就可以了。
那么迭代器的设计也就是这样。
由此不管你的容器是什么不管你底层的结构是链表还是树,甚至于是哈希表等等,我们都能够通过一个类似的方法来访问容器。
当然迭代器底层有可能就是一个指针(vector),也有可能是一个类模板(list)。
下面我们再来理解一下下面的这两个代码
首先从底层来说上面的两个东西是一样的,因为listiterator的底层实现就是一个list<int>*的变量,但是两者一个是自定义类型,一个是内置类型。并且都对两者进行++的结果也是不一样的,前者的++是直接调用的函数,而后者的++则只是从当前地址往后移动了一个指针大小。
而这也正是类型的力量。
反向和正向迭代器的不同
首先我们来分析一下正向迭代器的不同点只有2个,一个是正向迭代器的加是反向迭代器的减,一个是反向迭代器的减是正向迭代器的加。那么我在之前的博客是如何实现这个反向迭代器的呢?是通过模仿正向迭代器的方法,再写了一个方向迭代器的模板类。那么我们回想一个那些设计stl的大佬是如何设计栈的呢?是通过对一个已有容器(dequeue)的包装来实现的。这中方法就是通过一个复用之前所写的代码来达成的,那么我们能否将这个思想运用到我们反向迭代器的实现上呢?即通过复用正向迭代器的功能来完成反向迭代器。
实现反向迭代器模板
下面我们来实现一个基本的反向迭代器模板
template<class Iterator, class Ref, class Ptr>
class Reverse_iterator
{
public:
typedef Reverse_iterator<Iterator, Ref, Ptr> Self;
Reverse_iterator(Iterator _b)
:_a(_b)
{}//通过封装正向迭代器来完成反向迭代器
Ptr operator*()
{
return *_a;
}
Ref operator->()
{
return _a.operator->();
}//以上两个函数都是通过正向迭代器来完成反向迭代器,因为正向迭代器
//和反向迭代器是存在相同点的,除了指针移动的方向不同以外
Self& operator++()
{
--_a;
return *this;
}
Self& operator--()
{
++_a;
return *this;
}
bool operator!=(const Self& s)
{
return _a != s._a;
}
private:
Iterator _a;
};
从上面的代码可以看到我们的反向迭代器的模板参数有三个,其中第一个就是我们要使用的是那一个容器的正向迭代器,而第二个参数和第三个参数则是为了重载解引用(*)和(->)的时候,能够有返回值,而传递过去的参数,这样写还有一个好处就是能够直接完成非const类型的反向迭代器以及const类型的反向迭代器。
现在我们只需要去想要使用的容器那里去修改一下容器的typedef即可。
因为要实现一整个list的代码过于长了所以这里我就只展示需要修改的地方了
list中需要修改的地方:
typedef Reverse_iterator <iterator, T*, T&> reverse_iterator;
typedef Reverse_iterator <const_iterator, const T*, const T&> const_reverse_iterator;
需要详细的list的实现代码可以去我之前所写的博客,但是那个代码的反向迭代器并不是复用的正向迭代器而是直接对正向迭代器的复制拷贝。
下面是适配到vector后的要修改的代码
typedef Reverse_iterator<iterator, T*, T&> reserve_iterator;
typedef Reverse_iterator<const_iterator, const T*, const T&> const_reserve_iterator;
详细代码也请看我往期的博客。
但是在实现vector的rbegin()和rend()以及list的rbegin和rend()的时候,可以看出vs编译器的一个特殊处理
首先我们来看list的rbegin和rend
reverse_iterator rbegin()
{
return reverse_iterator(--end());
}
const_reverse_iterator rbegin() const
{
return const_reverse_iterator(--end());
}
reverse_iterator rend()
{
return reverse_iterator(end());
}
const_reverse_iterator rend() const
{
return const_reverse_iterator(end());
}
然后是vector的rbegin和rend()
reserve_iterator rbegin()//返回反向迭代器的开头
{
return (--end());//因为_end指向的是有效数据的下一位
}//这里的end()函数返回的就是_end
reserve_iterator rend()//反向迭代器的结束
{
return _start-1;//这里的_start也需要减一,因为循环结束的条件是it和rend相等如果方向迭代器的rend返回的是开头元素的指针
//那么当it和rend相等的时候不会进入循环,也就会导致原本开头的元素会被漏掉
}
const_reserve_iterator rbegin() const
{
return (--end());
}
const_reserve_iterator rend() const
{
return _start-1;
}
但是如果你只运行list的代码那么不会报错,但是如果你运行了vector的代码就会出现下面的报错。
那么这个报错是什么原因呢?
首先我们来分析一下这个list和vector的rbegin代码
可以看到除了_finish这代表的是一个内置的类型而iterator值一个自定义类型以外没有其它区别。
那么我们继续分析,end()函数(两个)是一个传值返回的函数,那么这个传值返回,返回的是_finish和iterator的临时拷贝,对于临时拷贝的对象是具有常属性的。而常数是不能进行++和--的操作。
按照上面的规则来说list和vector都应该报错才对但是,c++对于自定义类型做了一个特殊的处理对于这种具有常属性的自定义类型一般只显示在引用上。
下面就可以验证一下:
同时我在上面写的show函数很明显是一个非const函数,按照一般的规则,const对象不能调用非const函数,但是这里却可以。
这就是c++在对于自定义类型具有常属性时做出的一些改动。这也是为什么在上面vector的end()返回就不可以,而list的end()返回就可以。所以在上面的--end(),list是可以完成的,因为list的迭代器是一个自定义类型。而vector的迭代器是一个指针而指针是一个内置类型。
所以上面的vector的内置类型的数据可以修改成end()-1就可以了。
然后下面是使用我自己所写的方向迭代器后的运行代码和运行图像。
#include<iostream>
using namespace std;
#include"STLlist.h"
#include"vector.h"
#include"Reserve_iterator.h"
void func(const LHY::List<int>& nums)
{
for (LHY::List<int>::const_reverse_iterator it = nums.rbegin(); it != nums.rend(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
void func2(const LHY::vector<int>& nums)
{
for (LHY::vector<int>::const_reserve_iterator it = nums.rbegin(); it != nums.rend(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
int main()
{
LHY::List<int> a;
a.push_back(1);
a.push_back(2);
a.push_back(3);
a.push_back(4);
a.push_back(5);
for (auto it = a.rbegin(); it != a.rend(); ++it)
{
cout << *it << " ";
}
cout << endl;
func(a);
LHY::vector<int> nums;
nums.push_back(1);
nums.push_back(2);
nums.push_back(3);
nums.push_back(4);
nums.push_back(5);
for (LHY::vector<int>::reserve_iterator it = nums.rbegin(); it != nums.rend(); ++it)
{
cout << *it << " ";
}
cout << endl;
func2(nums);
cout << endl;
return 0;
}
在源码中的反向迭代器的实现方式
我们下面去看一下在vector的源码中是如何实现rbegin和rend的
其中的current就是rbegin。
那么这里很妙的就是,虽然rbegin指向的是无效数据,但是rbegin的前一个指向的就是有效的数据。所以在*那里是先让tmp--指向上一个数据,再解引用。那么当it = rend()的时候,循环结束也能打印出rend()指向的数据,因为再rend()+1的位置就已经打印出rend()指向的数据了。同理对于list也是一样的。
希望这篇博客能对你有所帮助,如果觉得写的不好请见谅,如果发现了任何错误,欢迎指出。