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类型,不能写入,所以将关联容器的迭代器视为支持自减运算的输入迭代器,而不是完整的双向迭代器;
算法中用于标记范围的一对迭代器必须是同种类型。