迭代器设计思维
在学习STL的时候,迭代器扮演着重要的角色,STL的中心思想在于:将容器和算法独立分开,之后再设计一种胶着剂将他们撮合在一起。而迭代器起到了重要的作用。
迭代器是一种智能指针
迭代器是一种行为类似指针的对象,没错,迭代器是一个对象,而指针最基本的功能有“解引用”,“成员访问”,所以迭代器最重要的就是对这两个操作符进行运算符重载。
这里我们以STL的容器list为例子演示一下:
list是一个双向带头链表,所以首先要明白一点:list不支持通过下标随机访问表中元素,在模拟实现list的时候,我们首先创建一个list_node的类,类成员变量即为每个节点所含的信息。
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
list_node(const T& x=T())
:_data(x),
_next(nullptr),
_prev(nullptr)
{}
};
其次创建一个list的类,但是我们会发现list的许多操作都涉及到了迭代器。list这个类只有唯一的一个类成员,就是list的头节点head。
template<class T>
class list
{
typedef list_node<T> Node;
public:
typedef __list_iterator<T,T&,T*> iterator;
typedef __list_iterator<T,const T&, const T*> const_iterator;
typedef myiterator::__reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef myiterator::__reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator rbegin()
{
return reverse_iterator(end());
}
list()
{
empty_init();
}
void empty_init()
{
//创建并初始化哨兵位的头节点
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
cur->_next = newnode;
newnode->_next = cur;
newnode->_prev = prev;
cur->_prev = newnode;
return iterator(newnode);
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
list<T>& operator = (list<T> lt)
{
swap(lt);
return *this;
}
void pop_back()
{
erase(--end());
}
void pop_head()
{
erase(begin());
}
template<class InputIterator>
list(InputIterator first,InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
void swap(list<T>& x)
//void swap(list x) 在类里面也可以不写<T>
{
std::swap(_head, x._head);
}
//拷贝构造
list(const list<T>& x)
{
empty_init();
list<T> tmp(x.begin(), x.end());
swap(tmp);
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
private:
Node* _head;
};
基本的接口这里就不做详细解释了,增删查改的结构之前的博客中也有。
list的构造函数
template<class InputIterator>
list(InputIterator first,InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
在官方的原文档中有很多list的构造函数的重载,但是这里只模拟实现了一种迭代器分别指向头尾的方法,这里就要提到一个迭代器的知识点了:前闭后开
在list中,存储的节点并不像vector一样是连续存放的,所以判断的时候要使用!=,其次就是为什么要设计为前闭后开(first指向第一个元素,last指向最后一个元素的下一个地方),这里就要说一下一个查找函数find,当没有找到的时候就会返回指定范围的最后一个元素的下一个位置。
list的拷贝构造以及swap函数的应用
void swap(list<T>& x)
//void swap(list x) 在类里面也可以不写<T>
{
std::swap(_head, x._head);
}
//拷贝构造
list(const list<T>& x)
{
empty_init();
list<T> tmp(x.begin(), x.end());
swap(tmp);
}
list<T>& operator = (list<T> lt)
{
swap(lt);
return *this;
}
这里的拷贝构造先将当前对象初始化,初始化之后再创建一个tmp对象,并对其进行初始化,之后进行交换,应为list类只有一个成员变量就是_head,所以也只需要将这一个元素进行交换即可。还有一个可以使用swap就是上面代码中的=运算符重载,函数传入的参数是一个拷贝,所以可以直接进行交换后返回。
list中的迭代器
首先我们知道了迭代器是一个类,所以首先写一下这个类的代码
//迭代器
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef list_node<T> Node;
Node* _node;
typedef __list_iterator iterator;
__list_iterator(Node* node)
:_node(node)
{}
//运算符重载
bool operator !=(const iterator& it) const
{
return _node != it._node;
}
bool operator ==(const iterator& it) const
{
return _node == it._node;
}
Ref operator *()
{
return _node->_data;
}
//重载->
Ptr operator->()
{
return &(operator*());//<=> return &(_node->_data);
}
//后置++
iterator& operator ++()
{
_node = _node->_next;
return *this;
}
//前置++
iterator& operator++(int)
{
iterator tmp(*this);
_node = _node->_next;
return tmp;
}
//后置--
iterator& operator--()
{
_node = _node->_prev;
return *this;
}
//前置--
iterator& operator--(int)
{
iterator tmp(*this);
_node = _node->_prev;
return tmp;
}
};
上面的代码我们实现了一个正向的普通迭代器,之后会介绍反向迭代器。上面的代码介绍了基本的迭代器封装。list实现的迭代器实现的功能有正常的++,--的移动,由于list不支持通过 [] 来随机访问节点元素,其他的一些封装我们上面也提到过了。但是迭代器的类的实现最终要的是和list合起来:
这里的一个细节值得我们注意:迭代器的类模板中有三个参数,在list类模板中使用的时候,因为要在设计普通迭代器的同时也要设计一个const迭代器,但是如果再去设计一个const迭代器的类明显有些冗余了,所以这里C++源码给出的答案就像上面代码中的内容:
typedef __list_iterator<T,T&,T*> iterator;
typedef __list_iterator<T,const T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end() const
{
return const_iterator(_head);
}
begin和end分别设计了普通版本和const版本,返回的方式是调用迭代器的构造函数,传入一个指向链表的节点的指针,构造出一个迭代器对象。
在迭代器的类成员函数中也有很多细节的东西:
在重载运算符" * "的时候,返回值为引用,这就避免了返回值拷贝的问题。因为我们并不知道list的每个成员的成员类型是什么。
有一个运算符的重载有点意思,“->” 实际就是先通过对象找到成员变量的地址,之后对其进行返回
通过算法中的find函数,以及list的模拟实现,可以发现迭代器在这些功能的实现中起到了粘着剂的关键作用。本篇的iterator的讲解偏向基础,之后会有更深入的讲解,敬请期待!