list arr(5, 3);
list::iterator it = arr.begin();
//这里就是申请一个迭代器,
while (it != arr.end())
{
cout << *it << " ";
++it;
}
当我们对普通iterator类型解引用时,得到对某个元素的非const引用。而如果我们对const_iterator类型解引用时,可以得到
一个指向const对象的引用,如同任何常量一样,该对象不能进行重写。例如,如果text是vector
历它,输出每个元素,可以这样编写程序:
// use const_iterator because we won't change the elements
for (vector
iter != text.end(); ++iter)
cout << *iter << endl; // print each element in text
除了是从迭代器读取元素值而不是对它进行赋值之外,这个循环与前一个相似。由于这里只需要借助迭代器进行读,不需要写,这里
把iter定义为 const_iterator类型。当对const_iterator类型解引用时,返回的是一个const值。不允许用const_iterator进行赋值:
for (vector
iter != text.end(); ++ iter)
*iter = " "; // error: *iter is const
使用const_iterator类型时,我们可以得到一个迭代器,它自身的值可以改变,但不能用来改变其所指向的元素的值。可以对迭代
器进行自增以及使用解引用操作符来读取值,但不能对该元素值赋值.
迭代器的实现原理
当你创造出来一个东西的时候,那么你对他的使用或者对于他的bug可能烂熟于心了吧.首先我们知道迭代器就是通过一定的封装
然后暴露出接口,再然后进行使用. 我们不需要知道底层的封装,我们记住它的方法如何使用就可以了. 但是我今天偏偏就要明
白迭代器的底层实现,通过我最近查看源代码,其实跟iterator跟指针是非常相似的,拥有 *-> 操作,并且具有
++,--,== ,!= ,目前为止我们就只实现这几个就够了.现在我要写一个山寨版的list容器的iterator的定义.
其他容器大概都是这么一个框架实现,只是实现具体方法不同.
代码实现:
template
struct __ListIterator
{
typedef __ListNode Node;
typedef __ListIterator Self;
Node* _node;
__ListIterator(Node* x)
:_node(x)
{}
Ptr operator->()
{
return &(operator*())
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->() const
{
return &(operator*()) const;
}
Ref operator*() const
{
return _node->_data const;
}
bool operator != (const Self& s)
{
return _node != s._node;
}
inline Self& operator++()//前置++
{
_node = _node->_next;
return *this;
}
Self& operator++(int) //后置++
{
//第一种先初始化再返回.
/*Self tmp(*this);
_node = _node->_next;
return tmp;*/
//第二种返回的时候再初始化.
Node* cur = _node;
_node = _node->_next;
return Self(cur);
//第一种做了三件事情. 首先调用了构造函数构造一个tmp,返回的时候再调用拷贝构造把自己拷给外部存储,最后调用析构函数让自己析构.
//第二种做了一件事情,在外部存储初始化一个tmp就Ok了. 所以效率来说第二种方法是最优解决方案.
}
inline Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self& operator--(int)
{
Node* cur = _node;
_node = _node->_prev;
return Self(cur);
}
bool operator != (const Self& s) const
{
return this->_node != s._node;
}
bool operator ==(const Self& s) const
{
return !(operator!=(s));
}
};
我们看到迭代器的构造函数是一个单参数构造函数,其实我们发现迭代器跟智能指针蛮相似的,都是底层封装一个指针,最后对
这个类进行各种各样的运算符重载. 让它成为一个拥有指针功能的特殊类. 接下来我们在list容器当中定义它.
template
class List
{
typedef __ListNode Node;
public:
typedef __ListIterator Iterator;
typedef __ListIterator ConstIterator;
}
可能上面有的朋友看不懂为什么会有三个模板参数? 现在明白了吧.这些都是为了代码的复用.要不然当你要实现const_iterator
的时候又得重新写一个const__ListIterator的类了. T& T* 这些都是要在代码中高频出现所以我们也给他们开出来一个模板参数
的位置,到时候iterator和const_iterator都要使用相同的模板框架,直接嵌套进去就ok. 如图所示:
因为iterator指向节点所以他就可以访问并操作节点节点的内容,而const_iterator只能访问到元素不能够操作节点. 这是迭代器
的定义.有木有觉得它其实没有你想的那么抽象. 仔细想一想我相信你可以想明白了.接下来是一个迭代器很重要的问题> 迭代器
失效. 接下来我们引出来这个问题吧. 首先我们开始完善我们的山寨List,什么push_back,popback这些我们写过很多遍. 我下面
会有完整代码的. 我们今天着重研究一下erase删除函数. 像我们正常实现的erase函数只是将参数改为迭代器即可.
下面是我对该函数的实现;
void erase(Iterator& it)
{
assert(it != End() && it._node);
Node* cur = it._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
}
这个删除对于双向循环链表应该是没有bug的. 然后这个时候我做一个操作, 我利用迭代器删掉元素当中所有偶数:
void Test()
{
List l;
l.PushBack(1);
l.PushBack(2);
l.PushBack(3);
l.PushBack(4);
List::Iterator it = l.Begin();
while (it != l.End())
{
if (*it % 2 == 0)
{
l.erase(it);
}
++it;
}
it = l.Begin();
while (it != l.End())
{
cout << *it << " ";
++it;
}
cout << endl;
}
运行结果:
正常情况就是最后窗口打印出来1 3. 好的那我们运行程序.结果程序崩了,居然还是越界. 经过我调试之后我发现程序在调用
earse之前没问题,调用earse之后就出现问题. 我再继续调试,发现第一次删除后,节点被删除了是没有问题的,而且连接也
没有问题,但是接下来的一次*it就访问越界了,
所以我有理由相信这里的问题出在it上面,所以我返回第一次删除前调试.
我们发现第一次earse之前,It的值还没问题.
但是earse之后,我们发现it变成一个随机值,原来这个就是我们程序崩溃的罪魁祸首.当删除掉该节点之后,该节点的iterator
变为随机值,所以后面的++it,生成的还是一个随机值,最后下次*it是后就会发生访问越界. 这个就是我们的迭代器失效问题.
突然一个很重要的问题就被引出来了,这就是迭代器失效,就是你删除该节点后节点的迭代器因为变成一个随机值,没有办
法跳转到下一个节点. 这个时候你的迭代器++之后就是一个随机值,最后程序再次使用到it时,程序崩溃. 这就类似于野指
针的问题.
那么问题来了,我们该如何解决它? 既然节点被删了,它的迭代器也就成随机值了,那我每次删除完就要对iterator重新定
义一次,我们每次删除结束之后我们把就是删除节点的上一个节点的Iterator,赋给it,这时候++it的时候,迭代器就会跳
转到下一个节点的位置. 具体如何实现呢? 那我们就来尝试一下.
void earse(Iterator& it)
{
assert(it != End() && it._node);
Node* cur = it._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
it = prev;//这里发生了隐式转换 类似于 it = Iterator(prev); 单参数的构造函数. 就会发生隐式转换.
}
我把it的引用传进去,然后在删除cur之后,把it赋值为cur前面的节点的迭代器. 接下来我们来看程序结果:
从结果上面来看,我们应该是成功了,迭代器失效每个容器导致的原因好像都不一样,但是他们的原理都是因为不当操作使得
iterator失去它自己的内容变为随机值,我们应该避免这些东西. 就能够熟练掌握迭代器. 下面我把整个山寨List的全套代码
贴在下面,有空我们可以去尝试山寨别的容器,提高自己对容器的理解和认识.
List实现代码:
template
struct __ListNode
{
__ListNode* _next;
__ListNode* _prev;
T _data;
__ListNode(const T& x)
:_data(x)
, _next(NULL)
, _prev(NULL)
{}
};
template
struct __ListIterator
{
typedef __ListNode Node;
typedef __ListIterator Self;
Node* _node;
__ListIterator(Node* x)
:_node(x)
{}
//T& operator*()
Ptr operator->()
{
return &(operator*())
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->() const
{
return &(operator*()) const;
}
Ref operator*() const
{
return _node->_data const;
}
bool operator != (const Self& s)
{
return _node != s._node;
}
inline Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator++(int)
{
//第一种先初始化再返回.
/*Self tmp(*this);
_node = _node->_next;
return tmp;*/
//第二种返回的时候再初始化.
Node* cur = _node;
_node = _node->_next;
return Self(cur);
//第一种做了三件事情. 首先调用了构造函数构造一个tmp,返回的时候再调用拷贝构造把自己拷给外部存储,最后调用析构函数让自己析构.
//第二种做了一件事情,在外部存储初始化一个tmp就Ok了. 所以效率来说第二种方法是最优解决方案.
}
inline Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self& operator--(int)
{
Node* cur = _node;
_node = _node->_prev;
return Self(cur);
}
bool operator != (const Self& s) const
{
return this->_node != s._node;
}
bool operator ==(const Self& s) const
{
return !(operator!=(s));
}
};
template
class List
{
typedef __ListNode Node;
public:
typedef __ListIterator Iterator;
typedef __ListIterator ConstIterator;
List()
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
}
Iterator Begin()
{
return Iterator(_head->_next);
}
ConstIterator Begin() const
{
return ConstIterator(_head->_next);
}
Iterator End()
{
return Iterator(_head);
}
ConstIterator End() const
{
return ConstIterator(_head);
}
T& Front()
{
return _head->next;
}
T& back()
{
return _head->_prev;
}
void PushBack(const T& x)
{
Insert(End(), x);
}
void PushFront(const T& x)
{
Insert(Begin(), x);
}
void popFront()
{
earse(Begin());
}
void popBack()
{
earse(--End());
}
void Insert(Iterator pos, const T& x)
{
assert(pos._node);
Node* next = pos._node;
Node* prev = next->_prev;
Node* cur = new Node(x);
prev->_next = cur;
cur->_prev = prev;
cur->_next = next;
next->_prev = cur;
}
void earse(Iterator& it)
{
assert(it != End() && it._node);
Node* cur = it._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
it = prev;//这里发生了隐式转换 类似于 it = Iterator(prev); 单参数的构造函数. 就会发生隐式转换.
}
void Clear()
{
Iterator it = Begin();
while (it != End())
{
Node* del = it._node;
++it;
delete del;
}
_head->_next = _head;
_head->_prev = _head;
}
Iterator Find(const T& x)
{
Iterator it = Begin();
while (it != End())
{
if (*it == x)
return it;
++it;
}
return End();
}
protected:
Node* _head;
};