4.6 打造一个iterator class
为了说明如何对class进行运算符重载操作,让我们体验一下如何实现一个iterator class我们必须提供一下操作方式:
triangular train(1,8);
triangular::iterator
it=train.begin(),
end_it=train.end();
while(it!=end_it)
{
cout<<*it<<' ';
++it;
}
为了让上述程序代码得以工作,我们必须为此iterator class定义!=,*,++等运算符。这应该如何办到呢?我们可以像定义member function那样定义运算符。运算符函数看起来很像普通函数,唯一的差别是它不用指定名称,只需在运算符前加上关键字operator即可。例如:
class triangular_iterator
{
public:
//为了不要在每次访问元素时都执行-1操作
//此处将index的值设为index-1
triangular_iterator(int index):_index(index-1){}
bool operator==(const triangular_iterator&)const;
bool operator!=(const triangular_iterator&)const;
int operator*()const;
triangular_iterator& operator++();//前置版
triangular_iterator&operator++(int);//后置版
private:
void check_integrity()const;
int_index;
}
triangular_iterator维护一个索引值,用以索引triangular中用来存储数列元素的那个static data member,也就是_elems.为了达到这个目的,triangular必须赋予triangular_operator的member function特殊的访问权限。我们将会在4.7中看到如何通过friend机制给予这种特殊的权限。如果两个triangular_operator对象的index相等,我们便说这两个对象相等:
inline bool triangular_iterator::
operator==(const triangular_iterator &rhs)const
{return_index==rhs._index;}
所谓运算符,可以直接作用于其class object:
if(train1==train2)
如果我们希望将运算符作用于指针所指的对象,就得先提取该指针,取出其所指的对象:
if(*ptril==*ptril2)
任何运算符如果和另一个运算符的性质相反,我们通常会以后者实现前者,例如:
inline bool triangular_iterator::
operator!=(const triangular_iterator &rhs)const
{return !(*this==rhs);}
以下是运算符重载的规则:
不可引用新的运算符额,除了,。*::?四个运算符,其他的运算符皆可被重载。
运算符的操作数(operator)个数不可以变,每个二元运算符都需要两个操作数,每个一元运算符都恰好需要一个操作数。因此我们无法定义出一个equality运算符,并令它接受两个以上或两个以下的操作数
运算符的优先级(precedence)不可以改变,例如,除法的运算符永远高于加法的.
运算符函数的参数列表中,必须至少有一个参数为class的类型,也就是说,我们无法为诸如指针之类的non_class类型,重新定义其已存在的运算符,当然更无法为它引进新运算符。
运算符的定义的方式,就像member function一样:
inline int triangular_iterator::
operator*()const
{
check_integrity();
return triangular::_elems[_index];
}
但也可以像non_member function一样:
inline int
operator*(const triangular_iterator &rhs)
{
rhs.check_integrity();
//注意:如果这是一个non_member function,就不具有
//访问no_public member的权利
return triangular::_elems[_index];
}
Non_member运算符的参数列表中,一定会比相应的member运算符多出一个参数,也就是this指针。对于这个member运算符而言,这个this指针隐式代表左操作数。
下面这个check_integrity()member function 可以确保_index不大于_amx_elem,并确保_elems存储了必要的元素。
inline void Triangular_iterator::
check_integrity()const
{
//第七章会介绍throw表达式
if(_index>=Triangular::_max_elems)
throw iterator_overflow();
//必要时扩展vector的容量
if(_index>=Triangular::_elems.size())
Triangular::gen_elements(_index+1);
}
接下来我们必须提供increment(递增)运算符的前置(++train)和后置(trian++)两个版本。
前置版的参数列表是空的:
inline Triangular_iterator& Triangular_iterator::
operator++()
{
//前置版本
++_index;
check_integrity();
return tmp;
}
increment(递增)或decrement(递减)运算符的前置以及后置版本都可以直接作用于class object:
++it//前置版
it__//后置版
另人生疑的是,对后置版本而言,其唯一的那个Int参数从何发生,又到哪里去呢?事实的真相是,编译器会自动为后置版本产生一个int参数(其值必为0)。用户不必为此烦恼。
接下来我们要做的,便是为triangular提供一组begin()/end() member function,并支持前述iterator定义。这需要用到稍后才讨论到的所谓嵌套类型(nested type)。
首先看看我们必须对triangular做的修正:
#include"triangular_iterator.h"
class triangular{
public:
//以下对象,可以让用户不必知晓iterator class的实际名称
typedef triangular_iterator iterator;
triangular_iterator begin() const
{
return triangular_iterator(_beg_pos);
}
triangular_iterator end() const
{
return triangular_iterator(_beg_pos+_length);
}
//......
private:
int _beg_pos;
int _length;
//....
};
嵌套类型(nested type)
typedef可以为某个类型设定另一个不同的名称。其通用形式为
typedef existing_type new_name;
其中的exisiting_type可以使任何一个内置类型,复合类型或class类型。在我们的例子中,我令iterator等同于triangular_iterator,以简化其使用形式。以下是定义一个iterator object的语法:
triangular::iterator it=train.begin();//错误
编译器就不知道在面对iterator这个字时该查看triangular的内容,于是以上声明出现错误。
如果将iterator嵌套放在每个“提供iterator抽象概念”的class内,我们就可以提供有着相同名称的多个定义。但是这样的声明语法有些复杂:
fibonacci::iterator fit=fib.begin();
pell::iterator pit=pel.begin();
vector<int>::iterator vit=_elems.begin();
string::iterator sit=file_name.begin();