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)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效。

deque迭代器的失效情况:

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

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

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

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

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

总结各种容器特点

(1) vector

内部数据结构:数组。

随机访问每个元素,所需要的时间为常量。
在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,但程序员可以使用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

总结:

1、对于关联式容器(map, list, set)元素的删除,插入操作会导致指向该元素的迭代器失效,其他元素迭代器不受影响。
2、对于顺序式容器(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+5、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());

时间: 2024-10-24 14:48:02

C++: STL迭代器及迭代器失效问题的相关文章

C++笔记(6):标准模板库STL:容器、迭代器和算法

STL(Standard Template Library)是C++标准库的一部分.STL的代码从广义上讲分为三类:容器.迭代器和算法. 1.容器 2.迭代器 3.算法  -------------------------------------------------------------------------------------------------------------------------- 1.容器 顺序容器容器是特定类型对象的集合.顺序容器为程序员提供控制元素存储和访问

一步一步认识C++STL中的迭代器

一步一步认识C++STL中的迭代器 "指针"对所有C/C++的程序员来说,一点都不陌生.在接触到C语言中的malloc函数和C++中的new函数后,我们也知道这两个函数返回的都是一个指针,该指针指向我们所申请的一个"堆".提到"堆",就不得不想到"栈",从C/C++程序设计的角度思考,"堆"和"栈"最大的区别是"栈"由系统自动分配并且自动回收,而"堆&quo

STL标准库-迭代器

摘要: 摘要: 技术在于交流.沟通,本文为博主原创文章转载请注明出处并保持作品的完整性 本节主要介绍STL六大部件中的Iterators迭代器. 在语言方面讲,容器是一个class template, 算法是一个仿函数, 分配器class template, 迭代器是一个class template, 适配器class template, 分配器class template 从图中我们可以看出算法是看不到容器的,容器跟算法的交互必要用迭代器做桥梁,那么迭代器是怎样让容器和算法满足各自的需求的呢?

3.STL简单的迭代器实现(含源码)

我使用vs2015写的程序(源码下载) STL的中心思想在于将容器(container)和算法(algorithms)分开,彼此独立设计,最后再以一贴胶着剂将它们撮合在一起,而这个胶着剂就是迭代器(iterator). 迭代器是访问容器的工具.注意,先有容器,才有访问容器的工具.迭代器需要了解容器的特性才能实现,这决定了迭代器必须要深入到容器内部,于是STL干脆把迭代器的开发交给容器的设计者. 迭代器的开发分为三步: 1.      元素的设计: 2.      装载元素的容器的设计: 3.  

课时10:迭代器、迭代器失效分析

1.迭代器简介迭代器是一种遍历容器元素的数据类型,这种数据类型类似指针,理解的时候可以理解为迭代器用来指向容器中的某个元素2.容器的迭代器类型vector<int> iv ={100,200,300};vector<int>::iterator iter; //定义迭代器 3.迭代器begin()/end()操作,反向迭代器rbegin()/rend()操作//begin()/end()用来返回迭代类型,rbegin()/rend()用来返回迭代类型a.begin()返回一个迭代器

C++STL容器和迭代器

1.简介 容器(container)用于存放数据的类模板.可变长数组.链表.平衡二叉树等数据结构在 STL 中都被实现为容器.程序员使用容器时,就是将容器类模板实例化为容器类,需要指明容器中存放的元素是什么类型的. 容器中可以存放基本类型的变量,也可以存放对象.对象或基本类型的变量被插入容器中时,实际插入的是对象或变量的一个复制品. 1.1 容器分类 容器分为2类:顺序容器和关联容器 顺序容器:可变长动态数组 vector.双端队列 deque.双向链表 list.之所以被称为顺序容器,是因为元

C++STL:流迭代器

流迭代器是一种迭代器适配器.istream_iterator用于读取输入流,ostream_iterator用于写输出流.这些迭代器将它们所对应的流视为特定类型的元素序列.使用流迭代器时,可以用泛型算法从流对象中读数据或将数据写入到流对象中. 流迭代器的构造函数如下: 流迭代器的基本操作如下: 1.流迭代器的定义. 流迭代器都是类模板:任何已定义输入操作符(>>操作符)的类型都可以定义istream_iterator,任何已定义输出操作符(<<操作符)的类型都可以定义ostream

STL之iterator(迭代器)

3.迭代器简介 除了使用下标来访问vector对象的元素外,标准库还提供了访问元素的方法:使用迭代器.迭代器是一种检查容器内元素并且遍历元素的数据类型. 百科释义: 迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址.迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器. 1.容器的iterator类型 每种容器类型都定义了自己的迭代器类型,如vector: vector<int

STL中,迭代器的分类

五类迭代器如下: 1.输入迭代器:只读,一次传递    为输入迭代器预定义实现只有istream_iterator和istreambuf_iterator,用于从一个输入流istream中读取.一个输入迭代器仅能对它所选择的每个元素进行一次解析,它们只能向前移动.一个专门的构造函数定义了超越末尾的值.总是,输入迭代器可以对读操作的结果进行解析(对每个值仅解析一次),然后向前移动.   2.输出迭代器:只写,一次传递    这是对输入迭代器的补充,不过是写操作而不是读操作.为输出迭代器的预定义实现