概述
对于每个容器类,都有两种stl风格的迭代器类型:一种提供只读访问,另一种提供读写访问。应该尽可能使用只读迭代器,因为它们比读写迭代器快。
STL迭代器的API以数组中的指针为模型。例如,++操作符将迭代器推进到下一项,*操作符返回迭代器指向的项。实际上,对于QVector和QStack(它们将元素存储在相邻的内存位置),迭代器类型只是T *的一个typedef,而const_iterator类型只是const T *的一个typedef。
示例
在本文档中,将集中讨论QList和QMap。QLinkedList、QVector和QSet的迭代器类型与QList的迭代器具有完全相同的接口;类似地,QHash的迭代器类型与QMap的迭代器具有相同的接口。
下面是按顺序遍历QList<QString>
中的所有元素并将它们转换为小写的示例:
QList<QString> list;
list << "A" << "B" << "C" << "D";
QList<QString>::iterator i;
for (i = list.begin(); i != list.end(); ++i)
*i = (*i).toLower();
与java风格的迭代器不同,stl风格的迭代器直接指向项。容器的begin()函数返回一个迭代器,该迭代器指向容器中的第一项。容器的end()函数返回一个迭代器,指向容器中最后一项后面一个位置的虚项。End()标记一个无效的位置;它永远不能被取消引用。它通常用于循环的中断条件。如果列表为空,begin()等于end(),因此我们永远不会执行循环。
下图用红色箭头显示了包含四个元素的vector的有效迭代器位置:
使用stl风格的迭代器进行向后迭代可以使用反向迭代器完成:
QList<QString> list;
list << "A" << "B" << "C" << "D";
QList<QString>::reverse_iterator i;
for (i = list.rbegin(); i != list.rend(); ++i)
*i = i->toLower();
}
在到目前为止的代码片段中,使用一元*
操作符检索存储在某个迭代器位置的项(QString类型),然后对其调用QString::toLower()
。大多数c++编译器也允许编写i->toLower()
,但有些编译器不允许。
对于只读访问,可以使用const_iterator
、constBegin()
和constEnd()
。例如:
QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug() << *i;
stl风格API
下表总结了stl
风格迭代器的API:
方法 | 行为 |
---|---|
*i | 返回当前项。 |
++i | 将迭代器推进到下一项 |
i += n | 将迭代器向前移动n个元素 |
–i | 将迭代器向后移动一项 |
i -= n | 将迭代器向后移动n个元素 |
i - j | 返回迭代器i和j之间的项数 |
++
和--
操作符都可以作为前缀(++i
,--i
)和后缀(i++
,i--
)操作符使用。前缀版本修改迭代器并返回对修改后迭代器的引用;后缀版本在修改迭代器之前获取该迭代器的副本,并返回该副本。在忽略返回值的表达式中,我们建议您使用前缀操作符(++i
,--i
),因为这些操作符略快一些。
对于非常量迭代器类型,一元*
操作符的返回值可用于赋值操作符的左侧。
对于QMap
和QHash
, *
操作符返回项的值组件。如果要检索键,请在迭代器上调用key()
。为了对称,迭代器类型还提供了一个value()
函数来检索值。例如,下面是将QMap
中的所有项目打印到控制台:
QMap<int, int> map;
...
QMap<int, int>::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
qDebug() << i.key() << ':' << i.value();
由于隐式共享,函数为每个值返回一个容器的成本非常低。Qt API包含几十个函数,每个值返回一个QList或QStringList(例如,QSplitter::sizes())。如果希望使用STL迭代器遍历这些对象,则应该始终获取容器的一个副本,并遍历该副本。例如:
// RIGHT
const QList<int> sizes = splitter->sizes();
QList<int>::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
...
// WRONG
QList<int>::const_iterator i;
for (i = splitter->sizes().begin();
i != splitter->sizes().end(); ++i)
...
对于返回const或非const对容器的引用的函数,不会出现此问题。
隐式共享迭代器问题
隐式共享对于stl风格的迭代器还有另一个后果:当迭代器在容器上处于活动状态时,应该避免复制容器。迭代器指向内部结构,如果复制容器,则应该非常小心地使用迭代器。例句:
QVector<int> a, b;
a.resize(100000); // make a big vector filled with 0.
QVector<int>::iterator i = a.begin();
// WRONG way of using the iterator i:
b = a;
/*
Now we should be careful with iterator i since it will point to shared data
If we do *i = 4 then we would change the shared instance (both vectors)
The behavior differs from STL containers. Avoid doing such things in Qt.
*/
a[0] = 5;
/*
Container a is now detached from the shared data,
and even though i was an iterator from the container a, it now works as an iterator in b.
Here the situation is that (*i) == 0.
*/
b.clear(); // Now the iterator i is completely invalid.
int j = *i; // Undefined behavior!
/*
The data from b (which i pointed to) is gone.
This would be well-defined with STL containers (and (*i) == 5),
but with QVector this is likely to crash.
*/
上面的例子只显示了QVector的问题,但这个问题存在于所有隐式共享的Qt容器中。
结论
努力奔跑吧,反正没有一片属于你的风景
。