迭代器即是指针,可以是所需要的任意类型,它的最大的好处是可以使容器和算法分离开来。常规思维是每个容器类中有自己的显示、查寻、排序等函数。仔细分析可得出:不同容器中完成相同功能代码的思路大体是相同的,那么能不能把它们抽象出来,多个容器仅对应一个显示、一个查询、一个排序函数 呢?这是泛型思维发展的必然结果。
STL(标准模板库),是目前C++内置支持的library。它的底层利用了C++类模板和函数模板的机制,
由三大部分组成:容器、算法和迭代器。
为数组容器、链表容器编制共同显示函数。
数组类MyArray初始代码如下所示。
//文件名:e3_1.cpp(本示例中所有头文件及源文件内容都在该文件中)
#include <stdio.h>
template<class T>
class MyArray
{
private:
int m_nTotalSize; //数组总长度
int m_nValidSize; //数组有效长度
T *m_pData; //数据
public:
MyArray(int nSize = 3)//数组默认总长度是3
{
m_pData = new T[nSize];
m_nTotalSize = nSize;
m_nValidSize = 0;
}
void Add(T value) //向m_pData添加数据
{
//同例1.1
}
int GetSize() //返回数组有效长度
{
return m_nValidSize;
}
T Get(int pos) //返回某一位置元素
{
return m_pData[pos];
}
virtual ~MyArray()
{
if(m_pData != NULL)
{
delete []m_pData;
m_pData = NULL;
}
}
};
链表类MyLink初始代码如下所示。
template <class T>
struct Unit //链表单元
{
T value;
Unit *next;
};
template <class T>
class MyLink
{
Unit<T> *head; //链表头
Unit<T> *tail; //链表尾
Unit<T> *prev;
public:
MyLink()
{
head = tail = prev = NULL;
}
void Add(T &value)//向链表中添加元素
{
Unit<T> * u = new Unit<T>();
u->value = value;
u->next = NULL;
if(head == NULL)
{
head = u;
prev = u;
}
else
{
prev->next = u;
prev = u;
}
tail = u->next;
}
virtual ~MyLink()
{
if(head != NULL)
{
Unit<T> *prev = head;
Unit<T> *next = NULL;
while(prev != tail)
{
next = prev->next;
delete prev;
prev = next;
}
}
}
};
那么,如何以MyArray、MyLink为基础完成一个共同显示函数呢?其实非常简单,先从需要出发,逆向考虑,先写一个泛型显示数,如下所示。
template<class Init>
void display(Init start, Init end)
{
cout << endl;
for(Init mid=start; start != end; mid++)
{
cout << *mid << "\t" ;
}
cout << end;
}
模板参数类型Init是一个指针,start是起始指针,end是结束指针,指针支持++及*操作。display函数与具体的容器无直接关联,间接关联是必须的。对于MyArray来说,Init相当于对T*的操作,对于MyLink来说,相当于对Unit*的操作。因此就引出了迭代器类,它仍然是一个模板类,对于本示例而言,模板参数是T*或Unit *。
MyArray对应迭代器ArrayIterator类如下所示。
template<class Init>
class ArrayIterator
{
Init *init;
public:
ArrayIterator(Init *init)
{
this->init = init;
}
bool operator!=(ArrayIterator& it)
{
return this->init != it.init;
}
void operator++(int)
{
init ++;
}
Init operator*()
{
return *init;
}
};
另外,还需要在MyArray类中增加两个函数Begin、End,用以获得起止迭代指针 。
T *Begin() //起始迭代指针
{
return m_pData;
}
T *End() //结束迭代指针
{
return m_pData + m_nValidSize;
}
测试函数如下所示。
void main()
{
MyArray<int> ary;
for(int i=0; i<5; i++)
{
ary.Add(i+1);
}
ArrayIterator<int> start(ary.Begin());
ArrayIterator<int> end(ary.End());
cout << "数组元素为:" ;
display(start, end);
}
同理完成链表迭代器类LinkIterator,如下所示。
template<class Init>
class LinkIterator
{
Init *init;
public:
LinkIterator(Init *init)
{
this->init = init;
}
bool operator != (LinkIterator& it)
{
return this->init != it.init;
}
void operator ++(int)
{
init = init->next;
}
Init operator*()
{
return *init;
}
};
可以看出operator !=、operator*重载内容与ArrayIterator中的内容是相同的,只有operator++中的内容不同,对于链表而言不像数组而言内存是连续的,是指针的转向,因此绝对不能写成init = init++,只能是init = init->next。
另外,还需要在MyArray类中增加两个函数Begin、End,用以获得起止迭代指针。
T *Begin() //起始迭代指针
{
return m_pData;
}
T *End() //结束迭代指针
{
return m_pData + m_nValidSize;
}
链表类测试函数如下 所示。
void main()
{
int m = 0;
MyLink<int> ml;
for(int i=0; i<5; i++)
{
m = i+1;
ml.Add(m);
}
LinkIterator<Unit<int> > start(ml.Begin());
LinkIterator<Unit<int> > end(ml.End());
display(start, end);
}
融合迭代器类
自定义数组迭代器、链表迭代器的编制方法,迭代器类位于容器之外。但仔细分析能够发现:数组迭代器只能应用于数组容器,不能应用于链表容器;链表迭代器只能应用于链表容器,不能应用于数组容器。也就是说特定的容器应该有特定的迭代器。因此,把迭代器类做为容器的内部类更符合应用的特点,以MyLink为例,融合LinkIterator后代码如下所示。
class LinkIterator;
template <class T>
class MyLink
{
public:
struct Unit //链表单元
{
T value;
Unit *next;
};
class LinkIterator
{
Unit *init;
public:
LinkIterator(Unit *init)
{
this->init = init;
}
bool operator != (LinkIterator& it)
{
return this->init != it.init;
}
void operator ++(int)
{
init = init->next;
cout<<"asdf";
}
Unit operator*()
{
return *init;
}
friend ostream & operator << (ostream & out, LinkIterator & A);
};
Unit *head; //链表头
Unit *tail; //链表尾
Unit *prev;
public:
MyLink()
{
head = tail = prev = NULL;
}
void Add(T &value)//向链表中添加元素
{
Unit * u = new Unit();
u->value = value;
u->next = NULL;
if(head == NULL)
{
head = u;
prev = u;
}
else
{
prev->next = u;
prev = u;
}
tail = u->next;
}
Unit *Begin()
{
return head;
}
Unit *End()
{
return tail;
}
virtual ~MyLink()
{
if(head != NULL)
{
Unit *prev = head;
Unit *next = NULL;
while(prev != tail)
{
next = prev->next;
delete prev;
prev = next;
}
}
}
};
ostream & operator << (ostream & out, MyLink<int>::LinkIterator & A)
{
//输出s的代码
out << A.init->value;
return out;
}
template<class Init>
void display(Init start, Init mend)
{
cout<<endl;
for(Init mid=start; start != mend; mid++)
{
cout<< mid << "\t" ;
}
cout<<endl;
}
int main()
{
int m = 0;
MyLink<int> ml;
for(int i=0; i<5; i++)
{
m = i+1;
ml.Add(m);
}
MyLink<int>::LinkIterator start = ml.Begin();
MyLink<int>::LinkIterator mend = ml.End();
display(start, mend);
return 0;
}
进一步理解迭代器
容器、迭代器、算法关系示意图如下所示。
每个容器都应有对应的迭代器,容器通过迭代器共享某一具体算法,某一具体算法不依附于某一具体的容器。迭代器起到一个中间媒介的作用,通过它把容器与算法关联起来。换一句更贴切的话来说就是:迭代器思维是编制通用泛型算法发展的必然结果,算法通过迭代器来依次访问容器中的元素。
也可以得出STL标准模板库编程的基本步骤:
- 形成容器元素;
- 取出所需要的迭代指针;
- 调用通用算法。
其实,在生活中有许多“迭代器”的现象,归根结底是需要“通用”的缘故。比如网上资源是一个通用需求,每个家庭都相当于容器的一个结点,那么通讯设备比如电话线等就相当于迭代器。千家万户通过上网就可以查询所需要的信息。再比如生活中用到的自来水是通用需求,每个家庭仍旧相当于一个结点,那么水管线等就相当于迭代器。
既然生活中的通用需求促进了各种通讯事业等的发展,那么对于软件中的通用算法而言,也就一定能促进迭代器的不断进步。因此,希望同学们多观察生活现象,因为软件的许多设计思想就在我们的生活周围。如果你能把软件中的每个设计思想都能找到生活中的实例,那么设计软件你就一定能感到非常快乐,不那么枯燥了。
STL 迭代器共分为5大类型。
- 输入迭代器
- 输出迭代器
- 前向迭代器
- 双向迭代器
- 随机迭代器
输入迭代器
按顺序只读一次。完成的功能有:能进行构造和缺省构造,能被复制或赋值,能进行相等性比较,能进行逐步向前移动,能进行读取值。输入迭代器重载主要操作符 如下所示。
STL提供的主要输入迭代器是istream_iterator,支持表3.1中的所有操作,值得注意的是它的构造函数,有两种形式:
- istream_iterator() 默认的构造器,创建了一个流结束的迭代器。
- istream_iterator(istream &) 参数是输入流。含义是从输入流中读数据,当遇到流结束符时停止。
int main(int argc, char* argv[])
{
cout <<"input data : "<<endl;
istream_iterator<int> a(cin) ; //建立键盘输入流,用istream_iterator枚举整形数据
istream_iterator<int> b; //建立输入流结束迭代器
while(1)
{
cout << *a << endl; //输出整形数据调用operator*()
a ++ ; //迭代器指针指向下一个元素—>调用operator++(int)
if(a == b) //如果当前迭代器等于结束迭代器,则operator==
{ //退出while循环
break;
}
}
return 0;
}
//输出为:
input data :
1 2 3,
1
2
3
到我们只要输入的不是整数就会结束输入。
输出迭代器
只写一次,完成的功能有:能进行构造或缺省构造,能被复制或赋值,能进行相等性比较,能进行逐步前向移动,能进行写入值(*p=x,但不能读出)。输出迭代器重载主要操作符 如下所示。
STL提供的主要输出迭代器是ostream_iterator,支持表2.2中的所有操作,值得注意的是它的构造函数,有两种形式:
- ostream_iterator(ostream& out) 创建了流输出跌代器, 用来迭代out输出流。
- ostream_iterator(ostream& out, const char *delim) 创建了流输出迭代器, 用来向out输出流输出数据,输出的数据之间用delim字符串分割,也即是每向out输出流输出一个数据后,再向out输出流输出一个分隔符delim。
int main(int argc, char* argv[])
{
cout << "输出迭代器演示结果为: " ;
ostream_iterator<int> myout(cout, "\t"); //创建标准输出迭代器
*myout = 1;
myout++;
*myout = 2;
myout++;
*myout = 3;
return 0;
}
执行结果为:
输出迭代器演示结果为:1 2 3
前向迭代器
使用 输入迭代器 和输出迭代器可以基本满足算法和容器的要求。但还是有一些算法需要同时具备两者的功能。 STL本身并没有专为前向迭代器预定义的迭代器 。
双向迭代器:
具有前向迭代器的全部功能,另外它还可以利用自减操作符operator—向后一次移动一个位置。例如双向链表容器中需要的就是双向迭代器。
随机访问迭代器:
具有双向迭代器的所有功能,再加上一个指针所有的功能。包括使用操作符operator[]进行索引,加某个整数值到一个指针就可以向前或向后移动若干个位置,或者使用比较运算符在迭代器之间进行比较。
综观五种 iterator,我们发现从前到后需求越来越多5,也就是所谓的细化。这样在一切情况下都可以使用需求最细的随机迭代器,确实可以,但不好,因为过多的需求自然会降低它的效率,实际编程时应该选择正好合适的iterators 以期得到最高的效率。