这些文章其实都是在网上参考前人的博客,有些自己整理,有些不需要修改,本意是为自己学习,以备以后查阅之用。如有侵权,联系我即可。
9.2. 迭代器和迭代器范围
在整个标准库中,经常使用形参为一对迭代器的构造函数。在深入探讨容器
操作之前,先来了解一下迭代器和迭代器范围。
第 3.4 节首次介绍了 vector 类型的迭代器。每种容器类型都提供若干共
同工作的迭代器类型。与容器类型一样,所有迭代器具有相同的接口:如果某种
迭代器支持某种操作, 那么支持这种操作的其他迭代器也会以相同的方式支持这
种操作。例如,所有容器迭代器都支持以解引用运算从容器中读入一个元素。类
似地,容器都提供自增和自减操作符来支持从一个元素到下一个元素的访问。表
9.3 列出迭代器为所有标准库容器类型所提供的运算。
表 9.3. 常用迭代器运算
*iter 返回迭代器 iter 所指向的元素的引用
iter->mem 对 iter 进行解引用,获取指定元素中名为 mem 的成员。等效于
(*iter).mem
*iter 返回迭代器 iter 所指向的元素的引用
++iter
iter++
给 iter 加 1,使其指向容器里的下一个元素
--iter
iter--
给 iter 减 1,使其指向容器里的前一个元素
iter1 ==
iter2
iter1 !=
iter2
比较两个迭代器是否相等(或不等)。当两个迭代器指向同一个
容器中的同一个元素,或者当它们都指向同一个容器的超出末端
的下一位置时,两个迭代器相等
vector 和 deque容器的迭代器提供额外的运算
C++ 定义的容器类型中,只有 vector 和 deque 容器提供下面两种重要的
运算集合:迭代器算术运算(第 3.4.1 节),以及使用除了 == 和 != 之外的
关系操作符来比较两个迭代器(== 和 != 这两种关系运算适用于所有容器)。
表 9.4 总结了这些相关的操作符。
表 9.4. vector 和 deque 类型迭代器支持的操作
iter + n
iter - n
在迭代器上加(减)整数值 n,将产生指向容器中前面(后面)第 n
个元素的迭代器。新计算出来的迭代器必须指向容器中的元素或超出
容器末端的下一位置
iter1 +=
iter2
iter1 -=
iter2
这里迭代器加减法的复合赋值运算:将 iter1 加上或减去 iter2 的
运算结果赋给 iter1
iter1 -
iter2
两个迭代器的减法,其运算结果加上右边的迭代器即得左边的迭代
器。这两个迭代器必须指向同一个容器中的元素或超出容器末端的下
一位置
只适用于 vector 和 deque 容器
iter + n
iter - n
在迭代器上加(减)整数值 n,将产生指向容器中前面(后面)第 n
个元素的迭代器。新计算出来的迭代器必须指向容器中的元素或超出
容器末端的下一位置
>, >=,
<, <=
迭代器的关系操作符。当一个迭代器指向的元素在容器中位于另一个
迭代器指向的元素之前,则前一个迭代器小于后一个迭代器。关系操
作符的两个迭代器必须指向同一个容器中的元素或超出容器末端的
下一位置
只适用于 vector 和 deque 容器
关系操作符只适用于 vector 和 deque 容器,这是因为只有这种两种容器
为其元素提供快速、随机的访问。它们确保可根据元素位置直接有效地访问指定
的容器元素。这两种容器都支持通过元素位置实现的随机访问,因此它们的迭代
器可以有效地实现算术和关系运算。
例如,下面的语句用于计算 vector 对象的中点位置:
vector
另一方面,代码:
// copy elements from vec into ilist
list
ilist.begin() + ilist.size()/2; // error: no addition on list
iterators
是错误的。list 容器的迭代器既不支持算术运算(加法或减法),也不支
持关系运算(<=, <, >=,>),它只提供前置和后置的自增、自减运算以及相等
(不等)运算。
第十一章中,我们将会了解到迭代器提供运算是使用标准库算法的基础。
Exercises Section 9.2
Exercise
9.7:
下面的程序错在哪里?如何改正。
list
list
iter2 = lst1.end();
while (iter1 < iter2)
Exercise
9.8:
假设 vec_iter 与 vector 对象的一个元素捆绑在一起,该
vector 对象存放 string 类型的元素,请问下面的语句实现什么
功能?
if (vec_iter->empty())
Exercise
9.9:
编写一个循环将 list 容器的元素逆序输出。
Exercise
9.10:
下列迭代器的用法哪些(如果有的话)是错误的?
const vector< int > ivec(10);
vector< string> svec(10);
list< int> ilist(10);
(a)vector
(b)list
(c) vector
(d) for(vector
it = svec.begin(); it != 0; ++it)
// ...
9.2.1. 迭代器范围
迭代器范围这个概念是标准库的基础。
C++ 语言使用一对迭代器标记迭代器范围(iterator range),这两个迭代
器分别指向同一个容器中的两个元素或超出末端的下一位置, 通常将它们命名为
first 和 last,或 beg 和 end,用于标记容器中的一段元素范围。
尽管 last 和 end 这两个名字很常见,但是它们却容易引起误解。其实第
二个迭代器从来都不是指向元素范围的最后一个元素, 而是指向最后一个元素的
下一位置。该范围内的元素包括迭代器 first 指向的元素,以及从 first 开始
一直到迭代器 last 指向的位置之前的所有元素。如果两个迭代器相等,则迭代
器范围为空。
此类元素范围称为左闭合区间(left-inclusive interval),其标准表示
方式为:
// to be read as: includes first and each element up to butnot
including last
[ first, last )
表示范围从 first 开始,到 last 结束,但不包括 last。迭代器 last 可
以等于 first,或者指向 first 标记的元素后面的某个元素,但绝对不能指向
first 标记的元素前面的元素。
对形成迭代器范围的迭代器的要求
迭代器 first 和 last 如果满足以下条件,则可形成一个迭代器范围:
• 它们指向同一个容器中的元素或超出末端的下一位置。
• 如果这两个迭代器不相等, 则对 first 反复做自增运算必须能够
到达 last。换句话说,在容器中,last 绝对不能位于 first 之
前。
编译器自己不能保证上述要求。编译器无法知道迭代器
所关联的是哪个容器,也不知道容器内有多少个元素。
若不能满足上述要求,将导致运行时未定义的行为。
使用左闭合区间的编程意义
因为左闭合区间有两个方便使用的性质,所以标准库使用此烦区间。假设
first 和 last 标记了一个有效的迭代器范围,于是:
1. 当 first 与 last 相等时,迭代器范围为空;
2. 当 first 与不相等时,迭代器范围内至少有一个元素,而且 first 指向
该区间中的第一元素。此外,通过若干次自增运算可以使 first 的值不
断增大,直到 first == last 为止。
这两个性质意味着程序员可以安全地编写如下的循环,通过测试迭代器处理
一段元素:
while (first != last) {
// safe to use *first because we know there is at least one
element
++first;
}
假设 first 和 last 标记了一段有效的迭代器范围,于是我们知道要么
first == last,这是退出循环的情况;要么该区间非空,first 指向其第一个
元素。因为 while 循环条件处理了空区间情况,所以对此无须再特别处理。当
迭代器范围非空时,循环至少执行一次。由于循环体每次循环就给 first 加 1,
因此循环必定会终止。而且在循环内可确保 *first 是安全的:它必然指向
first 和 last 之间非空区间内的某个特定元素。
Exercises Section 9.2.1
Exercise
9.11:
要标记出有效的迭代器范围,迭代器需要满足什么约束?
Exercise
9.12:
编写一个函数,其形参是一对迭代器和一个 int 型数值,
实现在迭代器标记的范围内寻找该 int 型数值的功能,
并返回一个 bool 结果,以指明是否找到指定数据。
Exercise
9.13:
重写程序,查找元素的值,并返回指向找到的元素的迭代
器。确保程序在要寻找的元素不存在时也能正确工作。
Exercise
9.14:
使用迭代器编写程序,从标准输入设备读入若干 string
对象,并将它们存储在一个 vector 对象中,然后输出该
vector 对象中的所有元素。
Exercise
9.15:
用 list 容器类型重写习题 9.14 得到的程序,列出改变
了容器类型后要做的修改。
9.2.2. 使迭代器失效的容器操作
在后面的几节里,我们将看到一些容器操作会修改容器的内在状态或移动容
器内的元素。这样的操作使所有指向被移动的元素的迭代器失效,也可能同时使
其他迭代器失效。使用无效迭代器是没有定义的,可能会导致与悬垂指针相同的
问题。
例如,每种容器都定义了一个或多个 erase 函数。这些函数提供了删除容
器元素的功能。任何指向已删除元素的迭代器都具有无效值,毕竟,该迭代器指
向了容器中不再存在的元素。
使用迭代器编写程序时,必须留意哪些操作会使迭代器失效。
使用无效迭代器将会导致严重的运行时错误。
无法检查迭代器是否有效,也无法通过测试来发现迭代器是否已经失效。任
何无效迭代器的使用都可能导致运行时错误,但程序不一定会崩溃,否则检查这
种错误也许会容易些。
使用迭代器时,通常可以编写程序使得要求迭代器有效的代
码范围相对较短。然后,在该范围内,严格检查每一条语句,
判断是否有元素添加或删除,从而相应地调整迭代器的值。
9.3. 每种顺序容器都提供了一组有用的类型定义以及以下操作:
每种顺序容器都提供了一组有用的类型定义以及以下操作:
• 在容器中添加元素。
• 在容器中删除元素。
• 设置容器大小。
• (如果有的话)获取容器内的第一个和最后一个元素。
9.3.1. 容器定义的类型别名
在前面的章节里,我们已经使用过三种由容器定义的类型:size_type、
iterator 和 const_iterator。所有容器都提供这三种类型以及表 9.5 所列出
的其他类型。
表 9.5. 容器定义的类型别名
size_type 无符号整型,足以存储此容器类型的最大可能容器长
度
iterator 此容器类型的迭代器类型
const_iterator 元素的只读迭代器类型
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 元素的只读(不能写)逆序迭代器
difference_type 足够存储两个迭代器差值的有符号整型,可为负数
value_type 元素类型
reference 元素的左值类型,是 value_type& 的同义词
const_reference 元素的常量左值类型,等效于 constvalue_type&
我们将在第 11.3.3 节中详细介绍逆序迭代器。简单地说,逆序迭代器从后
向前遍历容器, 并反转了某些相关的迭代器操作: 例如, 在逆序迭代器上做 ++ 运
算将指向容器中的前一个元素。
表 9.5 的最后三种类型使程序员无须直接知道容器元素的真正类型,就能
使用它。需要使用元素类型时,只要用 value_type 即可。如果要引用该类型,
则通过 reference 和 const_reference 类型实现。 在程序员编写自己的泛型程
序(第十六章)时,这些元素相关类型的定义非常有用。
使用容器定义类型的表达式看上去非常复杂:
// iter is the iterator type defined bylist
list
// cnt is the difference_type type defined byvector
vector
iter 所声明使用了作用域操作符,以表明此时所使用的符号 :: 右边的类
型名字是在符号 iter 左边指定容器的作用域内定义的。 其效果是将 iter 声明
为 iterator 类型,而 iterator 是存放 string 类型元素的 list类的成员。
Exercises Section 9.3.1
Exercise
9.16:
int 型的 vector 容器应该使用什么类型的索引?
Exercise
9.17:
读取存放 string 对象的 list 容器时,应该使用什么类
型?
9.3.2. begin 和 end 成员
begin 和 end 操作产生指向容器内第一个元素和最后一个元素的下一位置
的迭代器,如表 9.6 所示。这两个迭代器通常用于标记包含容器中所有元素的
迭代器范围。
表 9.6. 容器的 begin 和 end 操作
c.begin() 返回一个迭代器,它指向容器 c 的第一个元素
c.end() 返回一个迭代器,它指向容器 c 的最后一个元素的下一位置
c.rbegin() 返回一个逆序迭代器,它指向容器 c 的最后一个元素
c.rend() 返回一个逆序迭代器,它指向容器 c 的第一个元素前面的位置
上述每个操作都有两个不同版本:一个是 const 成员(第 7.7.1 节),另
一个是非 const 成员。这些操作返回什么类型取决于容器是否为 const。如果
容器不是 const,则这些操作返回 iterator 或 reverse_iterator 类型。如果
容器是 const,则其返回类型要加上 const_ 前缀,也就是 const_iterator 和
const_reverse_iterator 类型。 我们将在第 11.3.3节中详细介绍逆序迭代器。
9.3.3. 在顺序容器中添加元素
第 3.3.2 节介绍了添加元素的一种方法:push_back。所有顺序容器都支持
push_back 操作(表 9.7),提供在容器尾部插入一个元素的功能。下面的循环
每次读入一个 string 类型的值,并存放在 text_word: 对象中:
// read from standard input putting each word onto the end of
container
string text_word;
while (cin >> text_word)
container.push_back(text_word);
调用 push_back 函数会在容器 container 尾部创建一个新元素,并使容器
的长度加 1。新元素的值为 text_word 对象的副本,而 container 的类型则可
能是 list、vector 或 deque。
除了 push_back 运算,list 和 deque 容器类型还提供了类似的操作:
push_front。这个操作实现在容器首部插入新元素的功能。例如:
list
// add elements at the end of ilist
for (size_t ix = 0; ix != 4; ++ix)
ilist.push_back(ix);
使用 push_back 操作在容器 ilist 尾部依次添加元素 0、1、2、3。
然后,我们选择用 push_front 操作再次在 ilist 中添加元素:
// add elements to the start of ilist
for (size_t ix = 0; ix != 4; ++ix)
ilist.push_front(ix);
此时,元素 0、1、2、3 则被依次添加在 ilist 的开始位置。由于每个元
素都在 list 的新起点插入, 因此它们在容器中以逆序排列, 循环结束后, ilist
内的元素序列为:3、2、1、0、0、1、2、3。
关键概念:容器元素都是副本
在容器中添加元素时,系统是将元素值复制到容器里。类似地,使用一
段元素初始化新容器时,新容器存放的是原始元素的副本。被复制的原
始值与新容器中的元素各不相关,此后,容器内元素值发生变化时,被
复制的原值不会受到影响,反之亦然。
表 9.7 在顺序容器中添加元素的操作
c.push_back(t) 在容器 c 的尾部添加值为 t 的元素。返回 void 类型
c.push_front(t) 在容器 c 的前端添加值为 t 的元素。返回 void 类型
只适用于 list 和 deque 容器类型.
c.insert(p,t) 在迭代器 p 所指向的元素前面插入值为 t 的新元素。返回
指向新添加元素的迭代器
c.insert(p,n,t) 在迭代器 p 所指向的元素前面插入 n 个值为 t 的新元素。
返回 void 类型
c.insert(p,b,e) 在迭代器 p 所指向的元素前面插入由迭代器 b 和 e 标记
的范围内的元素。返回 void 类型