迭代器思维
在STL中,容器和算法是分开设计的,彼此独立,然后再以一帖胶着剂将他们撮合在一起,迭代器就扮演了胶着剂的角色。
例如find()算法:
template <class InputIterator, class T>
InputIterator find(InputIterator first,
InputIterator last,
const T& value)
{
while (first != last && *first != value)
++first;
return first;
}
该算法接受两个迭代器和一个搜寻目标,只要给予不同的迭代器,find()便能够对不同的容器进行查找操作。
迭代器的设计
迭代器是一种行为类似指针的对象,所以至少要重载operator*,operator->和operator++三个操作符。
我们以list迭代器为例,来看迭代器的内部定义。
list的节点定义如下:
template <typename T>
class ListItem
{
public:
ListItem(T value) : _value(value), _next(0) { }
T value() const { return _value; }
ListItem* next() const { return _next; }
void setnext(ListItem* val) { _next = val; }
private:
T _value; //节点值
ListItem* _next; //指向下一个节点的指针
};
list的定义如下:
//List链表
template <typename T>
class List {
public:
List() : _front(0), _end(0), _size(0) { }
private:
ListItem<T>* _end; //指向最后一个节点
ListItem<T>* _front;//指向第一个节点
long _size; //链表节点数目
};
list迭代器的定义如下:
template <class Item>
struct ListIter
{
Item *ptr;
ListIter(Item* p = 0)
: ptr(p) { }
Item& operator*() const { return *ptr; }
Item* operator->() const { return ptr; }
ListIter operator++()
{
ptr = ptr->next();
return *this;
}
ListIter operator++(int)
{
ListIter tmp = *this;
ptr = ptr->next();
return tmp;
}
bool operator==(const ListIter& i) const
{
return ptr == i.ptr;
}
bool operator!=(const ListIter& i) const
{
return ptr != i.ptr;
}
};
如果要在list的迭代器上使用find()函数,还需要重载operator!=操作符,并以int和ListItem<int>作为它的两个参数型别(因为find()函数内以*iter != value来检查元素值是否吻合)
template <typename T>
bool operator!=(const ListItem<T>& item, T n)
{
return item.value() != n;
}
现在就可以以ListIter为参数调用find()函数了
ListIter<ListItem<int> > begin(mylist.front()); //需要初始化begin迭代器
ListIter<ListItem<int> > end;
ListIter<ListItem<int> > iter;
iter = find(begin, end, 3);
迭代器的基本设计思路如下图所示:
Traits编程技法
首先得明白一点,为什么需要traits。traits的实现很简单,但要清楚traits的作用!
STL定义的迭代器中定义了很多类型,例如value_type(迭代器所指对象的类型),difference_type(表示两个迭代器之间的距离)。
我们在定义使用迭代器的模板函数时,可以直接使用这些类型,例如:
template <class I>
typename I::value_type
func(I ite)
{
return *ite;
}
但是有一个问题,traits就是为了解决这个问题:如果I是普通的指针(例如int*),那么该函数实例化就会出错,因为普通的指针并不是类类型,自然也就没有value_type成员。
解决办法是加一个中间类,这个中间类统一处理迭代器和普通指针。
template <class I>
struct iterator_traits {
typedef typename I::value_type type;
};
//特化类型
template <class I>
struct iterator_traits<I*>
{
typedef I value_type;
};
template <class I>
struct iterator_traits<const I*>
{
typedef I value_type;
};
需要迭代器中的类型时,需要通过traits间接使用:
template <class I>
typename iterator_traits<I>::value_type
func(I ite)
{
return *ite;
}
这样无论I是普通迭代器类型还是指针类型,都可以调用func()函数。
traits就是这个作用。
//榨汁机traits
template <class Iterator>
struct iterator_traits {
typedef typename Iterator::iterator_category iterator_category;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
};
//特化版本
template <class T>
struct iterator_traits<T*> {
typedef random_access_iterator_tag iterator_category; //普通指针的迭代器类型是random_access_iterator_tag,后面会有解释!!
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
template <class T>
struct iterator_traits<const T*> {
typedef random_access_iterator_tag iterator_category; //普通指针的迭代器类型是random_access_iterator_tag
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
};
其中前四个类型都容易理解,关键是第五个类型(iterator_category也就是迭代器类型)
迭代器类型
STL定义了五种迭代器类型如下图所示:
下面定义的五个classes,代表五种迭代器类型:
struct input_iterator_tag { };
struct output_iterator_tag { };
struct forward_iterator_tag : public input_iterator_tag { };
struct bidirectional_iterator_tag : public forward_iterator_tag { };
struct random_access_iterator_tag : public bidirectional_iterator_tag { };
每种类型的迭代器支持不同的操作,例如input_iterator只支持迭代器的++操作,bidirectional_iterator支持++和--操作,而random_access支持+n和-n操作。
如果要定义一个函数,把迭代器前移n步 void advance(iterator& i, Distance n)
如果使用iput_iterator迭代器实现,如下所示:
void advance(InputIterator& i, Distance n)
{
while (n--)
++i;
}
如果使用bidirectional_iterator迭代器实现:
void advance(DidiectionalIterator& i, Distance n)
{
if (n >= 0)
while (n--)
++i;
else
while (n++)
--i;
}
最好的实现方式是定义一个模板函数,能自动根据迭代器参数的类型,调用不同的函数,定义如下:
//重载函数模板
template <class InputIterator, class Distanace>
inline void __advance(InputIterator& i, Distanace n, input_iterator_tag)
{
while (n--)
++i;
}
template <class InputIterator, class Distanace>
inline void __advance(InputIterator& i, Distanace n, bidirectional_iterator_tag)
{
if (n >= 0) {
while (n--)
++i;
} else {
while (n++)
--i;
}
}
template <class InputIterator, class Distanace>
inline void __advance(InputIterator& i, Distanace n, random_access_iterator_tag)
{
i += n;
}
template <class InputIterator, class Distanace>
inline void advance(InputIterator& i, Distanace n)
{
__advance(i, n, iterator_traits<InputIterator>::iterator_category()); //iterator_category是对应的迭代器类型
}
这样advance函数就会根据输入迭代器的类型,调用不同的实现函数!
为了符合规范,任何迭代器都应该提供五个内嵌相应类型,以利于traits萃取,为了防止忘记某一个类型,所以最好定义一个基类,在基类中定义这五个类型,
具体的迭代器实现类继承这个基类,如下所示:
//为避免写代码时挂一漏万,自行开发的迭代器最小继承下面这个iterator
template <class Category, class T, class Distanace = ptrdiff_t,
class Pointer = T*, class Reference = T&>
struct iterator {
typedef Category iterator_category;
typedef T value_type;
typedef Distanace difference_type;
typedef Pointer pointer;
typedef Reference reference;
};
定义迭代器时,如下所示:
template <class Item>
struct ListIter :
public iterator<forward_iterator_tag, Item>//迭代器类型是forward_iterator_tag
{
......
};
STL中还有个__type_traits定义,定义方式和traits一样,很容易看明白,作用则是用于函数重载。
掌握了traits编程技法,就掌握了stl源码的钥匙。