笔者在本文介绍一下迭代器。
对表的一些操作,尤其那些在表的中间进行精密的插入和删除的操作,需要位置的概念。
在STL中,位置由内嵌型类型iterator来表示。特别是,对于list<string>
,位置由类型list<string>::iterator
表示,等等。在描述一些方法时,我们将直接使用iterator作为简记,但在编写代码时还是使用具体的内嵌类名。
首先,有3个问题需要处理:
- 如何获取一个迭代器(iterator);
- 迭代器本身能够执行什么操作;
- 哪些表ADT方法需要迭代器作为参数。
获取迭代器
对于第一个问题,STL表(以及所有其他的STL容器)定义看一对方法:
iterator begin()
:返回一个适当的迭代器,表示容器中的第一项。iterator end()
:返回一个适当的迭代器,表示容器中的尾端(终端)标记(即容器中最后一项之后的位置)。
end方法有些不同寻常,因为它返回一个“越界”的迭代器。为了理解这个概念,考虑系列在引入C++11中基于范围for循环之前通常用于打印vector v中的项的代码:
for(int i = 0; i != v.size(); ++i)
cout << v[i] << endl;
如果我们想使用迭代重新改写这段代码,那么会看到一个与begin方法和end方法自然的对应:
for(vector<int>::iterator itr = v.begin(); itr != v.end(); itr.???)
cout << itr.??? << endl;
在循环终止测试中,i != v.size()和itr != v.end()两者都要测试循环计数器是否“越界”。这段代码也给我们带来第二个话题,即迭代器必须拥有与其相关的方法(这些未知的方法由???表示)。
迭代器方法
基于上面的代码片段,显然,迭代器可以用 != 和 == 进行比较,并且可能需要定义一些拷贝构造函数,和operator=函数。因此,迭代器有些方法,其中许多方法用到运算符重载。除复制外,对迭代器通常使用的大部分操作一般包括如下:
- itr++和++itr:将迭代器推进到写一个位置。前缀形式和后缀形式都是可以使用的。
- *itr:返回对存储在迭代器itr的位置上对象的引用。所返回的引用可以允许修改,也可以不允许修改。
- itr1 == itr2:若itr1和itr2指向同一个位置则返回true,否则返回false。
- itr1 != itr2:若itr1和itr2指向不同的位置则返回true,否则返回false。
for( vector<int>::iterator itr = v.begin(); itr != v.end(); itr++)
cout << *itr << endl;
运算符重载的使用使我们能够访问当前项,此时使用*itr++可以推进到下一项。于是,上面的代码片段又可以写成:
vector<int>::iterator itr = v.begin();
while(itr != v.end())
cout << *itr++ <<endl;
需要迭代器的容器操作
最后一个问题,需要迭代器的3个最流行的方法是那些从表(或vector,或list)的指定位置上进行添加或删除的操作:
iterator insert(iterator pos, const Object & x)
:把x添加到表中由迭代器pos所给指定的位置之前的位置上。这是对list(但不是对vector)的常数时间的操作。返回值是指向被插入项的位置的一个迭代器。
iteratorerase(iterator pos)
:删除由迭代器所给定的位置的对象。这是对list(但不对vector)的常数时间的操作。返回值是调用之前pos的后继元素所在的位置。该操作使pos失效,现在它是多余的了,因为它正在指向的容器项已经被删除。iterator erase(iterator start,iterator end)
:删除从位置start开始直到(但不包括)位置end,为止的所有的项。注意,整个表可以通过调用c.erase(c.begin(),c.end())而被删除。
说明:本文为笔者的学习笔记,可能会出现与书籍或他人博客雷同之处,请谅解。