【QT】容器类的迭代
迭代器(iterator)为访问容器类里的数据项提供了统一的方法,Qt有两种迭代器类:Java类型的迭代器和STL类型的迭代器。
Java类型的迭代器更易于使用,且提供一些高级功能,而STL类型的迭代器效率更高。
Qt还提供一个关键字foreach(实际是<QtGlobal>里定义的一个宏)用于方便地访问容器里所有数据项。
1.JAVA类型迭代器
1.1 JAVA类型迭代器总表
对于每个容器类,有两个Java类型迭代器:一个用于只读操作,一个用于读写操作。
Map和QHash等关联容器类的迭代器用法相同,QList和QLinkedList、QSet等容器类的用
法相同,所以下面只以QMap和QList为例介绍迭代器的用法。Java类型的迭代器如表3-3所示。
Java类型迭代器的指针不是指向一个数据项,而是在数据项之间,迭代器指针位置示意图如图所示。
图1-1 JAVA类型迭代器位置示意图
1.2 顺序容器类的迭代器的使用
1.2.1 Java类型QList容器类迭代器
(1)QListIterator常用函数
QListIterator用于移动指针和读取数据的函数见表3-4。
(2)正向遍历QList<QString>容器所有数据
QList<QString>容器对象list作为参数传递给QListlterator<QString>迭代器i的构造函数,i
用于对list作只读遍历。起始时刻,迭代器指针在容器第一个数据项的前面(图1-1中数据项"A”
的前面),调用hasNext()判断在迭代器指针后面是否还有数据项,如果有,就调用next()跳过一个
数据项,并且next()函数返回跳过去的那个数据项。
QList<QString>list;list<<"A"<<"B"<<"C"<<"D";QListIterator<QString>i(list);while(i.hasNext())qDebug()<<i.next();
(2)反向遍历QList<QString>容器所有数据
QListIterator<QString>i(list);i.toBack();while(i.hasprevious())qDebug()<<i.previous;
代码和正序遍历是对称的,我们调用toBack()将迭代器移到最后一项后面的位置。下图描述了在一
个迭代器上调用next()和previous()函数的效果:
(3)QMutableListIterator遍历修改容器数据
QList<int>list;list<<1<<2<<3<<4<<5;QMutableListIterator<int>i(list);while(i.hasNext()){if(i.next()%2 != 0)i.remove();//remove()函数移除next()函数刚刚跳过的一个数据项,不会使迭代器失效。if(i.next() > 128)i.setValue(128);//setValue()函数可以修改刚刚跳过去的数据项的值}
1.3 关联容器类的迭代器的使用
1.3.1 Java类型QMap容器类迭代器
对于关联容器类QMap<KeyT>,使用QMapIterator和QMutableMapIterator迭代器类,它们具
有表3-4所示的所有函数,主要是增加了
key()和value()函数用于获取刚刚跳过的数据项的键和值。
例如,下面的代码将删除键(城市名称)里以“City”结尾的数据项。
QMap<QString,QString>map;map.insert("Paris","France");map.insert("New York","USA");maP.insert("Mexico City","USA");map·insert("Moscow","Russia");......QMutab1eMapIterator<QString,QString>i(map);while(i.hasNext()){if(i.ne×().key().endsWith("City"))i.remove();}
如果是在多值容器里遍历,可以用
findNext()或findPrevious()查找下一个或上一个值,如下面
的代码将删除上一示例代码中map里值为"USA”的所有数据项。
QMutableMapIterator<QString,QString>i(map);while(i.findNext("USA"))i.remove();
2.STL类型迭代器
2.1 STL类型迭代器总表
STL迭代器与Qt和STL的原生算法兼容,并且进行了速度优化。具体类型见表3-5。
对于每一个容器类,都有两个STL类型迭代器:一个用于只读访问,一个用于读写访问。无需修改数据时一定使用只读迭代器,因为它们速度更快。
注意:在定义只读迭代器和读写迭代器时的区别,它们使用了不同的关键字,const_iterator定义只读迭代器,iterator定义读写迭代器。此外,还可以使用const_reverse_iterator和reverse_iterator定义相应的反向迭代器。
STL类型的迭代器是数组的指针,所以“++”运算符使迭代器指向下一个数据项,“*”运算符返回数据项内容。与Java类型的迭代器不同,STL迭代器直接指向数据项,STL迭代器指向位置示意图如图2-1所示。
图2-1 STL类型迭代器位置示意图
begin()函数使迭代器指向容器的第一个数据项,end()函数使迭代器指向一个虚拟的表示结尾的数据项,end()表示的数据项是无效的,一般用作循环结束条件。下面仍然以QList和QMap为例说明sTL迭代器的用法,其他容器类迭代器的用法类似。
2.2 顺序容器类的迭代器的使用
2.2.1 STL类型QList容器类迭代器
(1)正向只读迭代器
QList<QString>list;list<<"A"<<"B"<<"C"<<"D";QList<QString>::const_iterator i;for(i = list.constBegin();i != list.constEnd();++i)qDebug()<<*i;
(2)反向读写迭代器
constBegin()和constEnd()是用于只读迭代器的,表示起始和结束位置。
若使用反向读写迭代器,并将上面示例代码中list的数据项都改为小写,代码如下:
QLIstanbul<QString>::reverse_iterator i;for(i =list.rbegin();i != list.rend();++i)*i = i->toLower();//修改数据
2.3 关联容器类的迭代器的使用
2.3.1 STL类型QMap容器类迭代器
对于关联容器类QMap和QHash,迭代器的“*”操作符返回数据项的值。如果想返回键,使用key()函数。对应的,用value()函数返回一个项的值。
例如,下面的代码将QMap<int,int>map中所有项的键和值输出。
QMap<int,int>map;......QMap<int,int>::const_iterator;for(i = list.constBegin();i != list.constEnd();++i)qDebug()<<i.key()<<":"<<i.value();
Qt API包含很多返回值为QList或QStringList的函数,要遍历这些返回的容器,必须先复制。由于Qt使用了隐式共享,这样的复制并无多大开销。例如下面的代码是正确的。
const QList<int>sizes = splitter->sizes();QList<int>::const_iterator i;for(i = sizes.begin();i !=sizes.end();++i)qDebug()<<*i;
提示:隐式共享(Implicit Sharing)是对象的管理方法。一个对象被隐式共享,只是传递该对象的一个指针给使用者,而不实际复制对象数据,只有在使用者修改数据时,才实质复制共享对象给使用者。如在上面的代码中,splitter->sizes()返回的是一个QList<int>列表对象sizes,但是实际上代码并不将splitter->sizes()表示的列表内容完全复制给变量sizes,只是传递给它一个指针。只有当sizes发生数据修改时,才会将共享对象的数据复制给sizes,这样避免了不必要的复制,减少了资源占用。
下面的代码是错误的。对于STL类型的迭代器,隐式共享还涉及另外一个问题,即当有一个迭代器在操作一个容器变量时,不要去复制这个容器变量。
QList<int>::const_iterator i;for(i =splitter->sizes().begin();i !=splitter->sizes().end();++i)qDebug()<<*i;
2.3.2 STL风格迭代器的API
下面的表概括了STL风格迭代器的API:
表达式
|
用途
|
*i
|
返回当前项
|
++i
|
将迭代器指向下一项
|
i += n
|
迭代器向前移动n项
|
--i
|
将迭代器指向上一项
|
i -= n
|
将迭代器你向后移动n项
|
i - j
|
返回迭代器i和j之间项的数目
|
3.foreach关键字
如果只是想遍历容器中所有的项,可以使用foreach关键字。foreach是<QtGlobal>头文件中定义的一个宏。使用foreach的句法是:
foreach(variable,container)
使用foreach的代码比使用迭代器更简洁。例如,使用foreach遍历一的示例代码如下:
QLinkedList<QString>list;...QString str;foreach(str,list)qDebug()<<str;
用于迭代的变量也可以在foreach语句里定义,foreach语句也可以使用花括号,可以使用break
退出迭代,示例代码如下:
QLinkedList<QString>list;foreach(const QString &str,list){if(str.isEmpty())break;qDebug()<<str;}
对于QMap和QHash,foreach会自动访问“键一一值”对里的值,所以无需调用values()。如果需要访问键则可以调用keys(),示例代码如下:
QMap<QString,int>map;......foreach(const QString &str,map.keys())qDebug()<<str<<":"<<map.value(str);
对于多值映射,可以使用两重foreach语句,示例代码如下:
QMultiMap<QString,int>map;...foreach(const QString &str,map.uniqueKeys())foreach(int i,map.values(str))qDebug()<<str<<":"<<i;
注意:foreach关键字遍历一个容器变量是创建了容器的一个副本,所以不能修改原来容器变量的数据项。