C++学习之迭代器

  • Post author:
  • Post category:其他


C++标准库中的3个重要概念就是容器,迭代器,算法,标准库中的常用容器有vector,list,deque,map,set,multimap,multiset(C++11中新增了array,forward_list,unordered_map,unordered_set,unordered_multimap,unordered_multiset),这些容器大家都经常用到,相对比较熟悉。但是使用C++,就必须得熟悉其提供给我们的100多个泛型算法(参见


C++学习之标准库泛型算法_STL算法


),这样我们就可以避免重复造轮子。泛型算法不是调用容器已有的操作实现,而是借助于迭代器来实现,所以调用泛型算法的时候,我们需要传递迭代器参数来指明输入范围和输出范围,所以在学习泛型算法之前,必须先了解迭代器的概念,类型等。

下面是我在看了C++primer第11章泛型算法后自己的理解:

一、什么是迭代器?

我的理解是迭代器提供了一种遍历容器内元素的方法,比如遍历数组可以通过指针实现,指针就可以认为是一种迭代器,可以参见C++ Primer的p84 。

我认为迭代器没有责任去检测越界,比如当某个迭代器位于超出末端下一位置,如果再对它进行自增运算,那么就会出现运行时错误:“iterator not incrementable”。

二、迭代器种类

1.从迭代器的类型上对迭代器分类

(1)普通迭代器:就是我们经常使用的 容器::iterator,比如std::vector<int>::iterator,一般都支持++、–操作,其他算术操作看具体的容器类型;

(2)反向迭代器:这一种迭代器也是经常用到的,比如std::list<int>::reverse_iterator,一般都支持++、–操作,其他算术操作看具体的容器类型;


反向迭代器的通过使用普通迭代器的自减操作实现的,所以从一个支持–同时也支持++的迭代器就可以定义反向迭代器

*上面两种类型的迭代器都有相应的const迭代器,const迭代器不能修改其指向的元素,比如std::list<int>::const_iterator,std::list<int>::const_reverse_iterator,const迭          代器类似于const int* p。

(3)流迭代器:输入输出流不是容器,但是标准库将它看作了



容器



,流迭代器只支持向前遍历(++)这是流的特点决定的,因此流迭代器不能创建反向迭代器。流迭代器分为了istream_iterator和ostream_iterator,分别用于遍历输入、输出流,并且我们只能从istream_iterator读入输入流的内容,通过ostream_iterator写出到输出流。

istream_iterator提供的操作

it1==it2 比较两个迭代器是否相同。

it1!=it2  如果两个都指向end,那么it1==it2;如果均不指向end,且由同一个输入流构造,那么也相等。


比较运算符必须是 读取相同类型的两个流迭代器

*it  返回从流中读取的值,

作为右值使用

it->v  或 (*it).v 返回读取对象的成员v

++it

it++

ostream_iterator 只提供*it (

作为左值使用

),++it ,it ++,不提供比较和成员选择操作。


流迭代器的构造函数

istream_iterator<T> in(stream)    创建从输入流stream读入T类型对象的 istream_iterator对象

istream_iterator<T> in                   istream_iterator对象的超出末端迭代器

ostream_iterator<T> out(stream)    创建将T类型对象写出到输出流stream的ostream_iterator对象,可以指名输出时的分隔符,如下

ostream_iterator<T> out(stream,delim)


*ostream_iterator 不提供比较函数,所以就没必要定义其对应的超出末端的迭代器了

任何定义了输入操作符(>>)的类型都能定义都可以定义 istream_iterator,同时任何定义了输出操作符(<<)的类型都能定义都可以定义 ostream_iterator

下面是流迭代器的例子

	//将input.txt 内容拷贝到output.txt,以换行符分隔输出
	ifstream fileIn("input.txt");
	istream_iterator<string> iit(fileIn);
	istream_iterator<string> eof;
	ofstream fileOut("output.txt");
	ostream_iterator<string>oit(fileOut,"\n");
	copy(iit,eof,oit);
	fileOut.clear();
	fileOut.close();
	fileIn.clear();
	fileIn.close();

(4)插入迭代器:插入迭代器实际是迭代器适配器,它又分为了首插入迭代器、尾插入迭代器、指定位置插入迭代器,即front_inserter,back_inserter,inserter.

适配器的概念可以参见

http://kingphp.blog.163.com/blog/static/200423244201272953254508/

,这里插入适配器,带有一个容器形参,并生成一个在此容器上的一个迭代器,用于向容器中插入元素,首插入迭代器、尾插入迭代器、指定位置插入迭代器的位置分别是容器首部、超出末端第一个位置、指定位置,(

C++STL插入的意思是在指定位置前添加元素

),并且在多次使用某个插入迭代器的时候,其位置永远是容器首部、尾部、指定的位置。比如下面的代码:

#include<iostream>
#include<iterator>
#include<list>
#include<algorithm>
int main(int argc,char**argv){
	std::list<int> vInt1,vInt2;
	for(int i=0;i<5;++i){
		vInt1.push_back(i);
		vInt2.push_back(i+5);
	}
	//vInt1 0,1,2,3,4
	//vInt2 5,6,7,8,9
	//使用inserter vInt1 copy到 vInt2的7的前面
	//结果 5 6 0 1 2 3 4 7 8 9
	std::copy(vInt1.begin(),vInt1.end(),std::inserter(vInt2,std::find(vInt2.begin(),vInt2.end(),7)));
	for(std::list<int>::iterator it=vInt2.begin();it!=vInt2.end();++it)
		std::cout<<*it<<" ";
	std::cout<<std::endl;
	//使用front_inserter vInt1 copy到 vInt2 前面
	//结果 4 3 2 1 0 5 6 0 1 2 3 4 7 8 9
	std::copy(vInt1.begin(),vInt1.end(),std::front_inserter(vInt2));
	for(std::list<int>::iterator it=vInt2.begin();it!=vInt2.end();++it)
		std::cout<<*it<<" ";
	std::cout<<std::endl;
	return EXIT_SUCCESS;
}


值得注意的是front_inserter,back_inserter,inserter分别调应其参数类型容器的push_front,push_back,insert操作,所以得要求容器具有相应的操作才行,比如不能在vector上构造front_inserter。

2.通过STL中泛型算法对迭代器参数类型的要求对迭代器分类

(1)输入迭代器  只读(一次),不写,支持自增 ,如istream_iterator

(2)输出迭代器  只写(一次),不读,支持自增,如ostream_iterator

(3)前向迭代器  支持同一元素的多次读写

(4)双向迭代器  从两个方向读写,如list<t>::iterator

(5)随机访问迭代器   在常量时间内读写任意位置, vector,deque,string可以提供,常用的指针也是。

这几种迭代器具有层次结构(具体层次结构请参见

C++学习之深入理解迭代器

),下层具有上层的功能,并且添加了添加一些额外的操作,如下图。



注意:所有标准库提供的迭代器都至少达到双向迭代器的要求;


由于关联容器map,set的键是const类型,不能写入,所以将关联容器的迭代器视为支持自减运算的输入迭代器,而不是完整的双向迭代器;


算法中用于标记范围的一对迭代器必须是同种类型。



版权声明:本文为xhu_eternalcc原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。