Roson的Qt之旅#73 Qt容器的迭代器

158 篇文章 30 订阅

1.迭代器类(Iterator Classes)概述

迭代器提供了一种统一的方法来访问容器中的项。Qt的容器类提供了两种类型的迭代器:java风格的迭代器和stl风格的迭代器。由于调用非const成员函数,当容器中的数据被修改或从隐式共享副本分离时,这两种类型的迭代器都将失效。

2.java风格的迭代器

java风格的迭代器是Qt 4中的新内容,也是Qt应用程序中使用的标准迭代器。它们比stl样式的迭代器更方便使用,但代价是效率略低。它们的API是基于Java的迭代器类建模的。

对于每个容器类,都有两种java风格的迭代器数据类型:一种提供只读访问,另一种提供读写访问。

容器只读容器迭代器可读可写迭代器
QList<T>, QQueue<T>QListIterator<T>QMutableListIterator<T>
QLinkedList<T>QLinkedListIterator<T>QMutableLinkedListIterator<T>
QVector<T>, QStack<T>QVectorIterator<T>QMutableVectorIterator<T>
QSet<T>QSetIterator<T>QMutableSetIterator<T>
QMap<Key, T>, QMultiMap<Key, T>QMapIterator<Key, T>QMutableMapIterator<Key, T>
QHash<Key, T>, QMultiHash<Key, T>QHashIterator<Key, T>QMutableHashIterator<Key, T>

在本讨论中,我们将集中讨论QList和QMap。QLinkedList、QVector和QSet的迭代器类型与QList的迭代器具有完全相同的接口;类似地,QHash的迭代器类型具有与QMap的迭代器相同的接口。

与STL风格的迭代器(下面将介绍)不同,java风格的迭代器指向项目之间,而不是直接指向项目。因此,它们要么指向容器的最开始(在第一项之前),要么指向容器的最末端(在最后一项之后),要么指向两个项之间。下图显示了包含四项的list的有效迭代器位置(红色箭头):

 下面是一个典型的循环,按顺序遍历QList<QString>的所有元素,并将它们打印到控制台:

  QList<QString> list;
  list << "A" << "B" << "C" << "D";

  QListIterator<QString> i(list);
  while (i.hasNext())
      qDebug() << i.next();

它的工作原理如下:要迭代的QList被传递给QListIterator构造函数。 此时,迭代器正好位于列表中第一项的前面(在项“A”之前)。 然后调用hasNext()检查迭代器后面是否有项。 如果有,则调用next()来跳过该项。 函数的作用是:返回所跳过的项。 对于一个QList<QString>,该条目的类型是QString。  

下面是如何在QList中向后迭代:  

  QListIterator<QString> i(list);
  i.toBack();
  while (i.hasPrevious())
      qDebug() << i.previous();

该代码与向前迭代是对称的,只是我们首先调用toBack()将迭代器移动到列表的最后一项之后。  

下图说明了在迭代器上调用next()和previous()的效果:  

下表总结了QListIterator API:

FunctionBehavior
toFront()Moves the iterator to the front of the list (before the first item)
toBack()Moves the iterator to the back of the list (after the last item)
hasNext()Returns true if the iterator isn't at the back of the list
next()Returns the next item and advances the iterator by one position
peekNext()Returns the next item without moving the iterator
hasPrevious()Returns true if the iterator isn't at the front of the list
previous()Returns the previous item and moves the iterator back by one position
peekPrevious()Returns the previous item without moving the iterator

QListIterator不提供在迭代时从列表中插入或删除项目的函数。 要做到这一点,必须使用QMutableListIterator。 下面是一个使用QMutableListIterator从QList<int>中移除所有奇数的例子:  

  QMutableListIterator<int> i(list);
  while (i.hasNext()) {
      if (i.next() % 2 != 0)
          i.remove();
  }

每次都会执行循环中的下一个()调用。 它跳过列表中的下一项。 函数的作用是:删除列表中最后一个跳过的条目。 调用remove()不会使迭代器失效,所以继续使用它是安全的。 这在向后迭代时同样有效:  

  QMutableListIterator<int> i(list);
  i.toBack();
  while (i.hasPrevious()) {
      if (i.previous() % 2 != 0)
          i.remove();
  }

如果我们只想修改现有项的值,可以使用setValue()。 在下面的代码中,我们用128替换任何大于128的值:  

  QMutableListIterator<int> i(list);
  while (i.hasNext()) {
      if (i.next() > 128)
          i.setValue(128);
  }

就像remove()一样,setValue()对我们跳过的最后一项进行操作。 如果向前迭代,这是迭代器前面的项; 如果向后迭代,这就是迭代器后面的项。  

函数的作用是:返回对列表项的非const引用。 对于简单操作,我们甚至不需要setValue():  

  QMutableListIterator<int> i(list);
  while (i.hasNext())
      i.next() *= 2;

如上所述,QLinkedList、QVector和QSet的迭代器类具有与QList完全相同的API。 现在我们将转向QMapIterator,它有些不同,因为它对(键、值)对进行迭代。  

和QListIterator一样,QMapIterator提供了toFront()、toBack()、hasNext()、next()、peekNext()、hasPrevious()、previous()和peekPrevious()。 键和值组件是通过对next()、peekNext()、previous()或peekPrevious()返回的对象调用key()和value()提取的。  

下面的例子删除了所有以“City”结尾的(capital, country)元素对:  

  QMap<QString, QString> map;
  map.insert("Paris", "France");
  map.insert("Guatemala City", "Guatemala");
  map.insert("Mexico City", "Mexico");
  map.insert("Moscow", "Russia");
  ...

  QMutableMapIterator<QString, QString> i(map);
  while (i.hasNext()) {
      if (i.next().key().endsWith("City"))
          i.remove();
  }

QMapIterator还提供了一个key()和value()函数,它们直接对迭代器进行操作,并返回迭代器所跳过的最后一项的键和值。 例如,下面的代码将QMap的内容复制到QHash中:  

  QMap<int, QWidget *> map;
  QHash<int, QWidget *> hash;

  QMapIterator<int, QWidget *> i(map);
  while (i.hasNext()) {
      i.next();
      hash.insert(i.key(), i.value());
  }

如果希望遍历具有相同值的所有项,可以使用findNext()或findPrevious()。 下面是一个移除所有具有特定值的条目的例子:  

  QMutableMapIterator<int, QWidget *> i(map);
  while (i.findNext(widget))
      i.remove();

3.STL风格的迭代器

stl样式的迭代器自Qt 2.0发布以来就已经可用了。 它们与Qt和STL的通用算法兼容,并对速度进行了优化。  

对于每个容器类,都有两种stl风格的迭代器类型:一种提供只读访问,另一种提供读写访问。 应该尽可能地使用只读迭代器,因为它们比读写迭代器更快。  

Containers只读迭代器可读可写迭代器
QList<T>, QQueue<T>QList<T>::const_iteratorQList<T>::iterator
QLinkedList<T>QLinkedList<T>::const_iteratorQLinkedList<T>::iterator
QVector<T>, QStack<T>QVector<T>::const_iteratorQVector<T>::iterator
QSet<T>QSet<T>::const_iteratorQSet<T>::iterator
QMap<Key, T>, QMultiMap<Key, T>QMap<Key, T>::const_iteratorQMap<Key, T>::iterator
QHash<Key, T>, QMultiHash<Key, T>QHash<Key, T>::const_iteratorQHash<Key, T>::iterator

STL迭代器的API是以数组中的指针为模型的。 例如,++操作符将迭代器向前推进到下一项,而*操作符返回迭代器所指向的项。 事实上,对于QVector和QStack,它们将它们的项存储在相邻的内存位置,迭代器类型只是T *的类型定义,而const_iterator类型只是const T *的类型定义。  

在本讨论中,我们将集中讨论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:  

ExpressionBehavior
*iReturns the current item
++iAdvances the iterator to the next item
i += nAdvances the iterator by n items
--iMoves the iterator back by one item
i -= nMoves the iterator back by n items
i - jReturns the number of items between iterators i and j

++和——操作符既可以作为前缀(++i,——i),也可以作为后缀(i++, i——)。 前缀版本修改迭代器并返回对修改后迭代器的引用; 后缀版本在修改迭代器之前获取该迭代器的副本,并返回该副本。 在忽略返回值的表达式中,我们建议您使用前缀操作符(++i,——i),因为这些操作略快一些。  

对于非const迭代器类型,可以在赋值操作符的左侧使用一元*操作符的返回值。  

对于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引用,则不会出现此问题。  

4.隐式共享迭代器问题  

隐式共享对于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容器中。  

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Allen Roson

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值