C++: STL迭代器及迭代器失效问题

转载至:http://blog.csdn.net/wangshihui512/article/details/9791517程序员

 

迭代器失效: 典型的迭代器失效. 

首先对于vector而言,添加和删除操做可能使容器的部分或者所有迭代器失效。那为何迭代器会失效呢?vector元素在内存中是顺序存储,试想:若是当前容器中已经存在了10个元素,如今又要添加一个元素到容器中,可是内存中紧跟在这10个元素后面没有一个空闲空间,而vector的元素必须顺序存储一边索引访问,因此咱们不能在内存中随便找个地方存储这个元素。因而vector必须从新分配存储空间,用来存放原来的元素以及新添加的元素:存放在旧存储空间的元素被复制到新的存储空间里,接着插入新的元素,最后撤销旧的存储空间。这种状况发生,必定会致使vector容器的全部迭代器都失效。咱们看到实现上述所说的分配和撤销内存空间的方式以实现vector的自增加性,效率是极其低下的。为了使vector容器实现快速的内存分配,实际分配的容器会比当前所需的空间多一些,vector容器预留了这些额外的存储区,用来存放新添加的元素,而不须要每次都从新分配新的存储空间。你能够从vector里实现capacity和reserve成员能够看出这种机制。capacity和size的区别:size是容器当前拥有的元素个数,而capacity则指容器在必须分配新存储空间以前能够存储的元素总数。算法

 

vector迭代器的失效状况:

1.当插入(push_back)一个元素后,end操做返回的迭代器确定失效。数组

2.当插入(push_back)一个元素后,capacity返回值与没有插入元素以前相比有改变,则须要从新加载整个容器,此时begin和end操做返回的迭代器都会失效。数据结构

3.当进行删除操做(erase,pop_back)后,指向删除点的迭代器所有失效;指向删除点后面的元素的迭代器也将所有失效。 

dom

deque迭代器的失效状况:

1.在deque容器首部或者尾部插入元素不会使得任何迭代器失效。
2.在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。
3.在deque容器的任何其余位置的插入和删除操做将使指向该容器元素的全部迭代器失效。 

函数

List/set/map 迭代器的失效状况:

删除时,指向该删除节点的迭代器失效ui

list<int> intList; 
list<int>::iterator it = intList.begin(); 
while(it != intList.end()) 

    it = intList.erase(it); 
    …… 
}spa

 

总结各类容器特色 

(1) vector

内部数据结构:数组。.net

随机访问每一个元素,所须要的时间为常量。
在末尾增长或删除元素所需时间与元素数目无关,在中间或开头增长或删除元素所需时间随元素数目呈线性变化。
可动态增长或减小元素,内存管理自动完成,但程序员可使用reserve()成员函数来管理内存。
vector的迭代器在内存从新分配时将失效(它所指向的元素在该操做的先后再也不相同)。当把超过capacity()-size()个元素插入vector中时,内存会从新分配,全部的迭代器都将失效;不然,指向当前元素之后的任何元素的迭代器都将失效。当删除元素时,指向被删除元素之后的任何元素的迭代器都将失效。指针

(2)deque

内部数据结构:数组。
随机访问每一个元素,所须要的时间为常量。
在开头和末尾增长元素所需时间与元素数目无关,在中间增长或删除元素所需时间随元素数目呈线性变化。
可动态增长或减小元素,内存管理自动完成,不提供用于内存管理的成员函数。
增长任何元素都将使deque的迭代器失效。在deque的中间删除元素将使迭代器失效。在deque的头或尾删除元素时,只有指向该元素的迭代器失效。

(3)list

内部数据结构:双向环状链表。
不能随机访问一个元素。
可双向遍历。
在开头、末尾和中间任何地方增长或删除元素所需时间都为常量。
可动态增长或减小元素,内存管理自动完成。
增长任何元素都不会使迭代器失效。删除元素时,除了指向当前被删除元素的迭代器外,其它迭代器都不会失效。

(4)slist

内部数据结构:单向链表。
不可双向遍历,只能从前到后地遍历。
其它的特性同list类似。

(5)stack

适配器,它能够将任意类型的序列容器转换为一个堆栈,通常使用deque做为支持的序列容器。
元素只能后进先出(LIFO)。
不能遍历整个stack。

(6)queue

适配器,它能够将任意类型的序列容器转换为一个队列,通常使用deque做为支持的序列容器。
元素只能先进先出(FIFO)。
不能遍历整个queue。

(7)priority_queue

适配器,它能够将任意类型的序列容器转换为一个优先级队列,通常使用vector做为底层存储方式。
只能访问第一个元素,不能遍历整个priority_queue。
第一个元素始终是优先级最高的一个元素。

(8)set

键惟一。
元素默认按升序排列。
若是迭代器所指向的元素被删除,则该迭代器失效。其它任何增长、删除元素的操做都不会使迭代器失效。

(9)multiset 

键能够不惟一。

其它特色与set相同。

(10)map

键惟一。
元素默认按键的升序排列。
若是迭代器所指向的元素被删除,则该迭代器失效。其它任何增长、删除元素的操做都不会使迭代器失效。

(11)multimap

键能够不惟一。
其它特色与map相同。

 

1. 容器的iterator类型 

每种容器类型都定义了本身的迭代器类型,如vector: vector::iterator iter; 
这条语句定义了一个名为iter的变量,它的数据类型是由vector定义的iterator类型。每一个标准库容器类型都定义了一个名为iterator的成员,这里的iterator与迭代器实际类型的含义相同。

2. begin和end操做 

每种容器都定义了一对命名为begin和end的函数,用于返回迭代器。若是容器中有元素的话,由begin返回的迭代器指向第一个元素: vector::iterator iter = ivec.begin(); 
上述语句把iter初始化为由名为begin的vector操做返回的值。假设vector不空,初始化后,iter即指该元素为ivec[0]。 
由end操做返回的迭代器指向vector的"末端元素的下一个"。一般称为超出末端迭代器(off-the-end iterator),代表它指向了一个不存在的元素。若是vector为空,begin返回的迭代器与end返回的迭代器相同。
由end操做返回的迭代器并不指向vector中任何实际的元素,相反,它只是起一个哨兵(sentinel)的做用,表示咱们已处理完vector中全部元素。

3. vector迭代器的自增和解引用运算 

迭代器类型定义了一些操做来获取迭代器所指向的元素,并容许程序员将迭代器从一个元素移动到另外一个元素。 
迭代器类型可以使用解引用操做符(*操做符)来访问迭代器所指向r 元素: 
*iter = 0; 
解引用操做符返回迭代器当前所指向的元素。假设iter指向vector对象ivec的第一个元素,那么*iter和ivec[0]就是指向同一个元素。上面这个语句的效果就是把这个元素的值赋为0。
迭代器使用自增操做符向前移动迭代器指向容器中下一个元素。从逻辑上说,迭代器的自增操做和int型对象的自增操做相似。对 int对象来讲,操做结果就是把int型值"加1",而对迭代器对象则是把容器中的迭代器"向前移动一个位置"。所以,若是iter指向第一个元素,则++iter指向第二个元素。
因为end操做返回的迭代器不指向任何元素,所以不能对它进行解引用或自增操做。 

4. 迭代器的其余运算

 另外一对可执行于迭代器的操做就是比较:用==或!=操做符来比较两个迭代器,若是两个迭代器对象指向同一个元素,则它们相等,不然就不相等。 

5. 迭代器应用的程序示例

 假设已声明了一个vector型的ivec变量,要把它全部元素值重置为0,能够用下标操做来完成: 
// reset all the elements in ivec to 0 
for (vector::size_type ix = 0; ix != ivec.size(); ++ix) 
    ivec[ix] = 0; 
上述程序用for循环遍历ivec的元素,for循环定义了一个索引ix,每循环迭代一次ix就自增1。for循环体将ivec的每一个元素赋值为0


总结:

一、对于关联式容器(map, list, set)元素的删除,插入操做会致使指向该元素的迭代器失效,其余元素迭代器不受影响。
二、对于顺序式容器(vector)元素的删除、插入操做会致使指向该元素以及后面的元素的迭代器失效。

 

关于迭代器

(1)特征与操做 
迭代器的基本特征有: 
解除——支持解除引用(dereference)操做,以即可以访问它引用的值。即,若是p是一个迭代器,则应该对*p和p->进行定义(似指针); 
赋值——可将一个迭代器赋给另外一个迭代器。即,若是p和q都是迭代器,则应该对表达式p=q进行定义; 
比较——可将一个迭代器与另外一个迭代器进行比较。即,若是p和q都是迭代器,则应该对表达式p==q和p!=q进行定义; 
遍历——可使用迭代器来遍历容器中的元素,这能够经过为迭代器p定义++p和p++操做来实现。


迭代器的操做有: 
读——经过解除引用*来间接引用容器中的元素值,例如x = *p; 
写——经过解除引用*来给容器中的元素赋值,例如*p = x; 
访问——经过下标和指向引用容器中的元素及其成员,例如p[2]和p->m 
迭代——利用增量和减量运算(++和--、+和-、+=和-=)在容器中遍历、漫游和跳跃,例如p++、--p、p+五、p-=8 
比较——利用比较运算符(==、!=、<、>、<=、>=)来比较两个迭代器是否相等或谁大谁小,例如if(p < q)……;、wihle(p != c.end())……;


(2)分类 
根据迭代器所支持的操做不一样,在STL中定义了以下5种迭代器: 
输入迭代器(input iterator)——用于读取容器中的信息,但不必定可以修改它。 
  输入迭代器iter经过解除引用(即*iter),来读取容器中其所指向元素之值; 
  为了使输入迭代器可以访问容器中的全部元素的值,必须使其支持(前/后缀格式的)++ 操做符; 
  输入迭代器不能保证第二次遍历容器时,顺序不变;也不能保证其递增后,先前指向的值不变。即,基于输入迭代器的任何算法,都应该是单通(single-pass)的,不依赖于前一次遍历时的值,也不依赖于本次遍历中前面的值。
  可见输入迭代器是一种单向的只读迭代器,能够递增可是不能递减,并且只能读不能写。适用于单通只读型算法。 
输出迭代器(output iterator)——用于将信息传输给容器(修改容器中元素的值),可是不能读取。例如,显示器就是只能写不能读的设备,可用输出容器来表示它。也支持解除引用和++操做,也是单通的。因此,输出迭代器适用于单通只写型算法。
前向迭代器(forward iterator正向迭代器)——只能使用++操做符来单向遍历容器(不能用--)。与I/O迭代器同样,前向迭代器也支持解除引用与++操做。与I/O迭代器不一样的是,前向迭代器是多通的(multi-pass)。即,它老是以一样的顺序来遍历容器,并且迭代器递增后,仍然能够经过解除保存的迭代器引用,来得到一样的值。另外,前向迭代器既能够是读写型的,也能够是只读的。
双向迭代器(bidirectional iterator)——能够用++和--操做符来双向遍历容器。其余与前向迭代器同样,也支持解除引用、也是多通的、也是可读写或只读的。
随机访问迭代器(random access iterator)——可直接访问容器中的任意一个元素的双向迭代器。 

可见,这5种迭代器造成了一个层次结构:I/O迭代器(均可++遍历,可是前者只读/后者只写)最基本、前向迭代器可读写但只能++遍历、双向迭代器也可读写但能++/--双向遍历、随机迭代器除了可以双向遍历外还可以随机访问。


(3)指针与迭代器 既然迭代器是广义的指针,那么指针自己是否是迭代器呢?其实,指针知足全部迭代器的要求,因此,指针就是一种迭代器。 迭代器是泛型算法的接口,而指针是迭代器。因此,各类STL算法,也可使用指针,来对非标准容器(如数组)进行操做。即,利用指针作迭代器,能够将STL算法用于常规数组。 例如排序函数sort: sort(Ran first, Ran last); // Ran表示随机访问迭代器 对容器c为: sort(c.begin(), c.end()); 对数组a能够改成:(const int SIZE = 100; float a[SIZE];) sort(a, a + SIZE); 又例如复制函数copy: copy(In first, In last, Out res); // In和Out分别表示输入和输出迭代器 对容器c<int>可为:(ostream_iterator<int> out_iter(cout);) copy(c.begin(), c.end(), out_iter); 对数组a能够改成:(const int SIZE = 100; float a[SIZE];) copy(a, a + SIZE, c.begin()); 

相关文章
相关标签/搜索