1 前言
前面已经详细阐述了Qt 中QtGlobal 包含的常用数据类型和方法还有一些宏定义,因为篇幅的问题,所以做了上下两章来讲。方便各位消化和耐看!那么今天这章主要就是讲解Qt 独有的迭代器使用了!
话说使用迭代器必定少不了容器,没有容器,迭代器就无法现行,所有下面我们从容器开始入手,先将Qt的容器过一遍眼,然后详细讲解各种容器的迭代器使用方式!
公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。
公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。
官方店:https://shop114595942.taobao.com//
2 容器类
2.1 容器类概述
Qt提供了多个基于模板 的容器类,这些容器类可以用于存储指定类型的数据项,例如常用的字符串列表类 QStringList 就是从容器类 QList<QString> 继承的,实现对字符串列表的添加、存储、删除等操作。
Qt的容器类比标准模板库(STL)中的容器类更轻巧、安全和易于使用。这些容器类是隐式共享和可重入的,而且它们进行了速度和存储优化,因此可以减少可执行文件的大小。此外,它们还是线程安全的,也就是说它们作为只读容器时可被多个线程访问。
容器类是基于模板的类,如常用的容器类QList<T>, T是一个具体的类型,可以是int、float等简单类型,也可以是QString、QDate 等类,但不能是QObject或任何其子类。 T 必须是一个可赋值的类型,即T 必须提供一个缺省的构造函数,一个可复制构造函数和–个赋值运算符。
例如用 QList<T> 定义一个字符串列表的容器,其定义方法是:
QList<QString> mList;
这样定义了一个QList容器类的变量aList,它的数据项是QString,所以aList可以用于处理
字符串列表,例如:
mList.append ("小明");
mList.append ("小红");
mList.append ("小刚");
//还有一种用法直接使用 << 操作符更加方便
mList<< "小明" << "小红" << "小刚";
QString str=mList[0];
Qt的容器类分为有序容器( sequential containers) 和关联容器( associative containers)。
容器迭代类用于遍历容器里的数据项,有Java 类型的迭代类和STL类型的迭代类。Java 类型的迭代类易于使用,提供高级功能,而STL 类型的迭代类效率更高一些。
Qt还提供了 foreach 宏用于遍历容器内的所有数据项。
2.2 有序容器类
Qt的有序容器类有QList、QLinkedList、 QVector、 QStack 和QQueue。
- QList
QList是最常用的容器类,虽然它是以数组列表( array-list)的形式实现的,但是在其前或后添加数据非常快,QList 以下标索引的方式对数据项进行访问。
QList用于添加、插入、替换、移动、删除数据项的函数有: insert()、 replace()、 removeAt()、move()、swap()、 append()、 prepend()、 removeFirst() 和 removeLast() 等。
QList提供下标索引方式访问数据项,如同数组一样,也提供 at() 函数,例如:QList<QString> mlist; mlist<< "小明" << "小红" << "小刚"; QString str1=mlist[1]; //str1=="小红' QString str0=mlist.at(0); //str0=="小明'
QList的isEmpty()函数在数据项为空时返回true,size()函数返回数据项的个数。
QList是Qt中最常用的容器类,很多函数的参数传递都是采用QList 容器类,例如
QAudioDeviceInfo的静态函数availableDevices()的函数原型是:
QList<QAudioDeviceInfo> QAudioDeviceInfo::availableDevices(QAudio::Mode mode)
其返回数据就是QAudioDeviceInfo 类型的QList列表。
-
QLinkedList
QLinkedList<T> 是链式列表(linked-list), 数据项不是用连续的内存存储的,它基于迭代器访问数据项,并且插入和删除数据项的操作时间相同。
除了不提供基于下标索引的数据项访问外,QLinkedList 的其他接口函数与QList 基本相同。 -
QVector
QVector<T> 提供动态数组的功能,以下标索引访问数据。
QVector的函数接口与QList 几乎完全相同,QVector<T> 的性能比 QList<T> 更高,因为 QVector<T> 的数据项是连续存储的。 -
QStack
QStack<T> 是提供类似于堆栈的后进先出(LIFO) 操作的容器类,push()和 pop()是主要的接口函数。例如:
QStack<int> stack;
stack.push(1);
stack.push(2);
stack.push(3);
while (!stack.isEmpty())
cout << stack.pop() << endl;
程序会依次输出3,2,1。
- QQueue
QQueue<T> 是提供类似于队列先进先出(FIFO)操作的容器类。enqueue()和dequeue()是主要操作函数。例如:
QQueue<int> queue;
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
while (!queue.isEmpty())
qDebug()<< queue.dequeue()<< endl;
程序会依次输出1,2,3。
2.3 关联容器类
Qt 还提供关联容器类QMap、QMultiMap、QHash、 QMultiHash 和QSet。
QMultiMap和QMultiHash支持一个键关联多个值,QHash 和 QMultiHash 类使用散列(Hash)函数 进行查找,查找速度更快。
- QSet
QSet是基于散列表的集合模板类,它存储数据的顺序是不定的,查找值的速度非常快。QSet<T> 内部就是用QHash 实现的。.
定义QSet<T> 容器和输入数据的实例代码如下:
QSet<QString> set;
set <<"dog"<<"cat"<<"tiger";
测试一个值是否包含于这个集合,用 contains() 函数,示例如下:
if(!set.contains("cat"))
...
- QMap
QMap<Key, T> 提供一一个字典(关联数组),一个键映射到一个值。QMap存储数据是按照键的顺序,如果不在乎存储顺序,使用QHash会更快。.
定义OMap<OString, int> 类型变量和赋值的示例代码如下:
QMap<QString, int> map;
map["one"]=1;
map["two"]=2;
map["three"]=3;
也可以使用 insert() 函数赋值,或 remove() 移除一个键值对,示例如下:
map.insert("four",4);
map.remove("two");
要查找一个值,使用运算符 [ ] 或 value() 函数,示例如下:
int num1 = map["one"];
int num2 = map.value("two");
如果在映射表中没有找到指定的键,会返回一个缺省构造值(default-constructed values), 例如,如果值的类型是字符串,会返回一个空的字符串。.
在使用 value() 函数查找键值时,还可以指定一个缺省的返回值,示例如下:
timeout = map.value("TIMEOUT",30);
这表示如果在map里找到键 TIMEOUT ,就返回关联的值,否则返回值为30。
- QMultiMap
QMultiMap是QMap的子类,是用于处理多值映射的便利类。
多值映射就是一个键可以对应多个值。QMap 正常情况下不允许多值映射,除非使用
QMap::insertMulti() 添加键值对。
QMultiMap是QMap的子类,所以QMap的大多数函数在QMultiMap都是可用的,但是有几个特殊的,QMultiMap::insert() 等效于 QMap::insertMulti() ,QMultiMap::replace() 等效于QMap::insert()。
QMultiMap 使用示例如下:
QMultiMap<QString,int> map1, map2, map3;
map1.insert("plenty", 100);
map1.insert("plenty", 2000); // map1.size() == 2
map2.insert("plenty", 5000); // map2.size() == 1
map3 = map1 + map2; // map3.size() == 3
QMultiMap不提供 [ ] 操作符,使用 value() 函数访问最新插入的键的单个值。如果要获取一个键对应的所有值,使用 values() 函数,返回值是 QList<T> 类型。
QList<int> values = map.values("plenty");
for(int i = 0; i < values.size(); ++i)
qDebug()<<values.at(i);
4. QHash
QHash是基于 散列表 来实现字典功能的模板类,QHash<Key,T> 存储的键值对具有非常快的查找速度。.
QHash与QMap 的功能和用法相似,区别在于以下几点:
- QHash比QMap的查找速度快;
- 在 QMap 上遍历时,数据项是按照键排序的,而 QHash 的数据项是任意顺序的;
- QMap 的键必须提供 < 运算符,QHash 的键必须提供 == 运算符和一个名称为 qHash() 的全局散列函数。
5. QMultiHash
QMultiHash是QHash的子类,是用于处理多值映射的便利类,其用法与QMultiMap类似。
3 迭代器
终于将容器给讲解的差不多了,可以来到我们的重点——讲解 迭代器 的使用
迭代器(iterator) 为访问容器类里的数据项提供了统一的方法,Qt 有两种迭代器类: Java 类型的迭代器和STL类型的迭代器。
Java类型 的迭代器更易于使用,且提供一些高级功能,而STL类型的迭代器效率更高。
Qt 还提供一个关键字 foreach() (实际是 <QtGlobal> 里定义的一个宏) 用于方便地访问容器里所有数据项。
3.1 Java类型迭代器
3.1.1 Java类型迭代器集合总览
java风格的迭代器是Qt 4中的新元素,是Qt应用程序中使用的标准迭代器。它们使用起来比STL风格的迭代器更方便,但效率稍低一些。它们的API模仿Java的迭代器类。
对于每个容器类 ,有两个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> |
QMap和QHash等关联容器类的迭代器用法相同,QList和QLinkedList、QSet等容器类的用法相同,所以下面只以QMap和QList为例介绍迭代器的用法。
3.1.2 Java类型有序容器类的迭代器的使用
Java类型迭代器的指针不是指向一个数据项,而是在数据项之间,迭代器指针位置示意图如下图所示。
下面是遍历访问一个 QList<QString> 容器的所有数据项的典型代码。
QList<QString> list;
list << "A" << "B" << "C" << "D";
QListIterator<QString> i(list);
while (i.hasNext())
qDebug() << i.next();
QList<QString> 容器对象list作为参数传递给 QListIterator< QString > 迭代器i 的构造函数,i 用于对 list 作只读遍历。起始时刻,迭代器指针在容器第一个数据项的前面(上图中 数据项A 的前面),调用 hasNext() 判断在迭代器指针后面是否还有数据项,如果有,就调用 next() 跳过一个数据项,并且 next() 函数返回跳过去的那个数据项。
也可以反向遍历,示例代码如下:
QListIterator<QString> i (list) ;
i.toBack();
while (i.hasPrevious())
qDebug() << i.previous () ;
该代码与前向迭代是对称的,除了我们首先调用toBack()将迭代器移动到列表中最后一项之后。
下面的图表说明了在迭代器上调用 next() 和 previous() 的效果:
QListIterator 用于移动指针和读取数据的函数见下表:
函数名 | 功能描述 |
---|---|
void toFront() | 迭代器移动到列表的最前面(第一个数据项之前) |
void toBack() | 迭代器移动到列表的最后面(最后一个数据项之后) |
bool hasNext() | 如果迭代器不是位于列表最后位置,返回true |
const T & next() | 返回下一个数据项,并且迭代器后移-一个位置 |
const T & peekNext() | 返回下一个数据项,但是不移动迭代器位置 |
bool hasPrevious() | 如果迭代器不是位于列表的最前面,返回true |
const T & previous() | 返回前一个数据项,并且迭代器前移-一个位置 |
const T & peekPrevious() | 返回前一个数据项,但是不移动迭代器指针 |
QListIterator是只读访问容器内数据项的迭代器,若要在遍历过程中对容器的数据进行修改,需要使用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)函数刚刚跳过的一一个 数据项,不会使迭代器失效。setValue() 函数可以修改刚刚跳过去的数据项的值。
3.1.3 Java类型关联容器类的迭代器的使用
对于关联容器类QMap<Key T>,使用QMapIterator 和QMutableMapIterator迭代器类,它们具有前面表中所示的所有函数,主要是增加了 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");
QMutableMapIterator<QString, QString> i(map);
while (i.hasNext()){
if (i.next().key().endsWith("City"))
i.remove() ;
}
如果是在多值容器里遍历,可以用findNext()或findPrevious()查找下一个或上一个值,如下面的代码将删除上一示例代码中 map 里值为 USA 的所有数据项。
QMutableMapIterator<QString, QString> i(map);
while (i.findNext("USA"))
i.remove();
3.2 STL类型迭代器
3.2.1 STL类型迭代器集合总览
STL风格的迭代器在Qt 2.0发布后就可用了。它们与Qt和STL的通用算法兼容,并针对速度进行了优化
对于每一个容器类,都有两个STL类型迭代器: 一个用于只读访问,一个用于读写访问。无需修改数据时一定使用只读迭代器,因为它们速度更快。具体类型见下表:
容器类 | 只读迭代器 | 读写迭代器 |
---|---|---|
QList<T>, QQueue<T> | QList<T>::const_iterator | QList<T>::iterator |
QLinkedList<T> | QLinkedList<T> :const_iterator | QLinkedList<T>::iterator |
QVector<T>, QStack<T> | QVector<T>::const_iterator | QVector<T> :iterator |
QSet<T> | QSet<T>::const_iterator | QSet<T>::iterator |
QMap<Key, T>,QMultiMap<Key,T> | QMap<Key, T>:const_iterator | QMap<Key, T>::iterator |
QHash<Key, T>,QMultiHash<Key, T> | QHash<Key, T>::const_iterator | QHash<Key, T>::iterator |
注意:在定义只读迭代器和读写迭代器时的区别, 它们使用了不同的关键字,const_iterator 定义只读迭代器,iterator定义读写迭代器。此外,还可以使用const_reverse_iterator 和reverse_iterator 定义相应的反向迭代器。
STL类型 的迭代器是数组的指针,所以 ++ 运算符使迭代器指向下一个数据项,* 运算符返回数据项内容。与Java类型 的迭代器不同,STL 迭代器 直接指向数据项,STL 迭代器指向位置示意图如下图所示。
begin() 函数使迭代器指向容器的第一个数据项,end() 函数使迭代器指向一个虛拟的表示结尾的数据项,end() 表示的数据项是无效的,一般用作循环结束条件。
下面仍然以QList和QMap为例说明STL迭代器的用法,其他容器类迭代器的用法类似。
3.2.2 STL有序容器类的迭代器用法
下面的示例代码将QList<QString> list 里的数据项逐项输出。
iterator 用法
QList<QString> list;
list << "A" << "B" << "C" << "D";
QList<QString>::iterator i;
for (i = list.begin(); i != list.end(); ++i)
*i = (*i).toLower();
const_iterator 用法
QList<QString> list;
list << "A" << "B" <<"C" << "D";
QList<QString>::const_iterator i;
for (i = list.constBegin() ; i != list.constEnd(); ++i)
qDebug() << *i;
constBegin()和constEnd() 是用于只读迭代器的,表示起始和结束位置。若使用反向读写迭代器,并将上面示例代码中list 的数据项都改为小写,代码如下:
QList<QString>::reverse_iterator i;
for (i = list.rbegin(); i != list.rend(); ++i)
{
*i = i->toLower () ;
}
3.2.3 STL关联容器类的迭代器的用法
对于关联容器类QMap和QHash,迭代器的 * 操作符返回数据项的值。如果想返回键,使用key() 函数。对应的,用 value() 函数返回一个项的值。
例如,下面的代码将QMap<int,int>map中所有项的键和值输出:
QMap<int,int> map;
QMap<int,int>::const_iterator i;
for (i = map.constBegin() ; i != map.constEnd() ; ++i)
qDebug() << i.key() << ":"<< i.value();
3.3 Qt的隐式共享
QtAPI包含很多返回值为QList或QStringList的函数,要遍历这些返回的容器,必须先复制。由于Qt使用了隐式共享,这样的复制并无多大开销。例如下面的代码是正确的。
const QList<int> sizes = splitter->sizes() ;
QList<int>::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
...
隐式共享 ( Implicit Sharing) 是对象的管理方法。一个对象被隐式共享,只是传递该对象的一个指针给使用者,而不实际复制对象数据,只有在使用者修改数据时,才实质复制共享对象给使用者。如在上面的代码中,splitter->sizes()返回的是一个QList<int>列表对象sizes, 但是实际上代码并不将splitter->sizes()表示的列表内容完全复制给变量sizes,只是传递给它一个指针。只有当sizes 发生数据修改时,才会将共享对象的数据复制给sizes,这样避免了不必要的复制,减少了资源占用。
而下面的代码是错误的。
QList<int>::const_iterator i;
for (i = splitter->sizes().begin(); i != splitter->sizes().end(); ++i)
...
对于STL类型的迭代器,隐式共享还涉及另外一个问题,即当有- -个迭代器在操作- - 个容器变量时,不要去复制这个容器变量。
3.4 foreach关键字
目前foreach已经可以被C++11 标准库for循环代替了。所以代码实用时不建议使用foreach,直接使用for即可,下面还是介绍下foreach的大概用法
如果只是想遍历容器中所有的项,可以使用 foreach 关键字。foreach 是<QtGlobal>头文件中定义的一个宏。使用foreach的句法是:
foreach(variable,container )
使用foreach的代码比使用迭代器更简洁。例如,使用foreach 遍历一个QStringList的示例代码如下:
QStringList list;
list<<"A"<<"B"<<"C";
QString str;
foreach (str, list)
qDebug() < str;
替代的C++11for循环用法,(仅限C++11)
for(str:list)
qDebug() < str;
输出结果:A,B,C
用于迭代的变量也可以在foreach语句里定义,foreach 语句也可以使用花括号,可以使用break 退出迭代,示例代码如下:
QStringList strlist;
strlist<<"A"<<"B"<<"C";
foreach (const QString &str, strlist) {
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 关键字遍历一个容器变量是创建了容器的一个副本,所以不能修改原来容器变量的数据项。