一步一步认识C++STL中的迭代器
“指针”对所有C/C++的程序员来说,一点都不陌生。在接触到C语言中的malloc函数和C++中的new函数后,我们也知道这两个函数返回的都是一个指针,该指针指向我们所申请的一个“堆”。提到“堆”,就不得不想到“栈”,从C/C++程序设计的角度思考,“堆”和“栈”最大的区别是“栈”由系统自动分配并且自动回收,而“堆”则是由程序员手动申请,并且显示释放。如果程序员不显示释放“堆”,便会造成内存泄漏,内存泄漏的危害大家知道,严重时会导致系统崩溃。
既然“指针”的使用者一不小心就可能导致内存泄漏,那么我们如何能够使得指针的使用变得更安全呢?从C++面向对象的角度分析,我们有没有可能将“指针”封装起来,使得用户不直接接触指针,而使用一个封装后的对象来替代指针的操作呢?
答案是显然的,“智能指针”(smart pointer)正解决这类问题,尤其是在防止内存泄漏方面做得非常突出。C++标准库std中提供了一种“智能指针类”名为"auto_ptr",先看下面的代码:
void f()
{
int *p=new int(42);
////////此处发生异常////
delete p;
}
正如上面的代码所示,如果在两条语句中间发生异常,会导致指针p所指的那块内存泄漏,因为在执行delete之前发生异常,就不会自动释放堆。然而,如果使用auto_ptr对象代替常规指针,将会自动释放内存,因为编译器能够保证提前运行其析构函数。这样,我们就可以将上述代码改为:
#include
//auto_ptr的头文件
void f()
{
auto_ptr
p(new int (42)); }
通过以上的分析,我们便对智能指针有了一定的了解。迭代器iterator就是一种智能指针,它对原始指针进行了封装,并且提供一些等价于原始指针的操作,做到既方便又安全。
一提到STL,必须要马上想到其主要的6个组成部件,分别是:容器、算法、迭代器、仿函数、适配器和空间分配器,本文主要介绍迭代器。迭代器是连接容器和算法的一种重要桥梁。为什么呢?我们举个例子来说明原因。
#include
#include
//用到find函数 #include
using namespace std; int main() { vector
vec; int i; for(i=0;i<10;i++) { vec.push_back(i); } if(vec.end()!=find(vec.begin(),vec.end(),7)) { cout<<"find!"<
上述代码中,值得注意的是用到的find函数,find函数的函数原型为:template
_InIt find(_InIt _First, _InIt _last, const _Ty & _val),从find函数原型可以看出find函数是一个函数模板,函数的形参中前两个参数为_InIt,该参数是InputIterator的缩写,最后一个参数则是一个任意类型变量的引用。而我们的程序在使用find函数实,传入的实参为find(vec.begin(),vec.end(),7),
这样我们的形参迭代器就将算法find和容器vector联系起来了,从这个角度我们可以很容易理解为什么说迭代器是算法和容器的联系的桥梁了。
为什么上面的形参是一个InputIterator,而能够接受实参vec.begin()呢?为了回答这个问题,需要从迭代器的分类说起。
在STL中,迭代器主要分为5类,分别是:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。
输入迭代器:只读,支持++、==、!=;
输出迭代器:只写,支持++;
前向迭代器:读写,支持++、==、!=;
双向迭代器:读写,支持++、--,C++的所有标准库容器都至少在双向迭代器的层次上。;
随机访问迭代器:读写,支持++、--、[n]、-n、<、<=、>、>=;
输入迭代器和输出迭代器是最低级的迭代器,后三种迭代器都是对该迭代器的一种派生,回到刚刚那个问题,为什么实参vec.begin()能够与形参_InIt“虚实结合”呢?我们先看下面的代码:
#include
using namespace std;
class A{};
class A1:public A{};//类A1继承A
class B{};
void print(A)//只需指定参数类型
{
cout<<"This is Base class A!"<
从上面的代码可以看出,在main函数中,我们调用print(A1()),即可以用派生类对象作为实参传递给以基类类型作为形参的函数,所以find函数中的vec.begin()作为实参,以输入迭代器类型作为形参,两者可以达到虚实相结合的目的而不会出错。所以说,迭代器为各类算法和和各类容器提供了一个相互沟通的平台。
接着,我们从容器本身的角度和算法本身的角度对迭代器做进一步的分析。
容器的迭代器都是定身制作的,什么意思呢?所有容器都内置一个迭代器,该内置迭代器由容器的设计者实现。由于现有的容器比较多,不可能每种容器的迭代器都详细介绍下,但是有一点可以确定的是,每个容器对应的迭代器都是根据容器的特点来实现的,以求达到最高效率。我们所关心的问题是:哪些操作会使容器的迭代器失效呢?
迭代器失效?没错,就是迭代器失效。迭代器失效指的是迭代器原来所指向的元素不存在了或者发生了移动,此时如果不更新迭代器,将无法使用该过时的迭代器。迭代器失效的根本原因是对容器的某些操作修改了容器的内存状态(如容器重新加载到内存)或移动了容器内的某些元素。
使vector迭代器失效的操作
1.向vector容器内添加元素(push_back,insert)
向vector容器添加元素分以下两种情况:
1)若向vector添加元素后,整个vector重新加载,即前后两次vector的capacity()的返回值不同时,此时该容器 内的所有元素对应的迭代器都将失效。
2)若该添加操作不会导致整个vector容器加载,则指向新插入元素后面的那些元素的迭代器都将失效。
2,删除操作(erase,pop_back,clear)
vector执行删除操作后,被删除元素对应的迭代器以及其后面元素对应的迭代器都将失效。
3.resize操作:调整当前容器的size
调整容器大小对迭代器的影响分如下情况讨论:
A.若调整后size>capacity,则会引起整个容器重新加载,整个容器的迭代器都将失效。
B.若调整后size
B1.若调整后容器的size>调整前容器的size,则原来vector的所有迭代器都不会失效;
B2.若调整后容器的size<调整前容器的size,则容器中那些别切掉的元素对应的迭代器都将失效。
4.赋值操作(v1=v2 v1.assign(v2))
会导致左操作数v1的所有迭代器都失效,显然右操作数v2的迭代器都不会失效。
5.交换操作(v1.swap(v2))
由于在做交换操作时,v1,v2均不会删除或插入元素,所以容器内不会移动任何元素,故v1,v2的所有迭代器都不会失效。
使deque迭代器失效的操作
1.插入操作(push_front,push_back,insert)
deque的插入操作对迭代器的影响分两种情况:
A.在deque容器首部或尾部插入元素不会是任何迭代器失效;
B.在除去首尾的其他位置插入元素会使该容器的所有迭代器失效。
2.删除操作
A.在deque首、尾删除元素只会使被删除元素对应的迭代器失效;
B.在其他任何位