STL学习(9):迭代器

迭代器即是指针,可以是所需要的任意类型,它的最大的好处是可以使容器和算法分离开来。常规思维是每个容器类中有自己的显示、查寻、排序等函数。仔细分析可得出:不同容器中完成相同功能代码的思路大体是相同的,那么能不能把它们抽象出来,多个容器仅对应一个显示、一个查询、一个排序函数 呢?这是泛型思维发展的必然结果。

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大类型。    

  1. 输入迭代器
  2. 输出迭代器
  3. 前向迭代器
  4. 双向迭代器
  5. 随机迭代器

输入迭代器

按顺序只读一次。完成的功能有:能进行构造和缺省构造,能被复制或赋值,能进行相等性比较,能进行逐步向前移动,能进行读取值。输入迭代器重载主要操作符 如下所示。

STL提供的主要输入迭代器是istream_iterator,支持表3.1中的所有操作,值得注意的是它的构造函数,有两种形式:

  1. istream_iterator() 默认的构造器,创建了一个流结束的迭代器。
  2. 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中的所有操作,值得注意的是它的构造函数,有两种形式:

  1. ostream_iterator(ostream& out)     创建了流输出跌代器, 用来迭代out输出流。
  2. 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 以期得到最高的效率。

猜你喜欢

转载自blog.csdn.net/QQ2558030393/article/details/93383118
今日推荐