Qt系列文章010-Qt容器类介绍

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。

  1. 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列表。

  1. QLinkedList
      QLinkedList<T> 是链式列表(linked-list), 数据项不是用连续的内存存储的,它基于迭代器访问数据项,并且插入和删除数据项的操作时间相同。
      除了不提供基于下标索引的数据项访问外,QLinkedList 的其他接口函数与QList 基本相同。

  2. QVector
      QVector<T> 提供动态数组的功能,以下标索引访问数据。
      QVector的函数接口与QList 几乎完全相同,QVector<T> 的性能比 QList<T> 更高,因为 QVector<T> 的数据项是连续存储的。

  3. 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

  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支持一个键关联多个值,QHashQMultiHash 类使用散列(Hash)函数 进行查找,查找速度更快。

  1. QSet

  QSet是基于散列表的集合模板类,它存储数据的顺序是不定的,查找值的速度非常快。QSet<T> 内部就是用QHash 实现的。.

  定义QSet<T> 容器和输入数据的实例代码如下:

QSet<QString> set;
set <<"dog"<<"cat"<<"tiger";

  测试一个值是否包含于这个集合,用 contains() 函数,示例如下:

if(!set.contains("cat"))
	...

  1. 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

  1. QMultiMap

  QMultiMapQMap的子类,是用于处理多值映射的便利类。

  多值映射就是一个键可以对应多个值。QMap 正常情况下不允许多值映射,除非使用
QMap::insertMulti() 添加键值对。

  QMultiMapQMap的子类,所以QMap的大多数函数在QMultiMap都是可用的,但是有几个特殊的,QMultiMap::insert() 等效于 QMap::insertMulti()QMultiMap::replace() 等效于QMap::insert()

  QMultiMap 使用示例如下:

QMultiMap<QStringint> 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> 存储的键值对具有非常快的查找速度。.
  QHashQMap 的功能和用法相似,区别在于以下几点:

  • QHashQMap的查找速度快;
  • QMap 上遍历时,数据项是按照键排序的,而 QHash 的数据项是任意顺序的;
  • QMap 的键必须提供 < 运算符,QHash 的键必须提供 == 运算符和一个名称为 qHash() 的全局散列函数。

5. QMultiHash
  QMultiHashQHash的子类,是用于处理多值映射的便利类,其用法与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>

  QMapQHash等关联容器类的迭代器用法相同,QListQLinkedListQSet等容器类的用法相同,所以下面只以QMapQList为例介绍迭代器的用法

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>,使用QMapIteratorQMutableMapIterator迭代器类,它们具有前面表中所示的所有函数,主要是增加了 key()和value() 函数用于获取刚刚跳过的数据项的键和值。
  例如,下面的代码将删除键(城市名称)里以City结尾的数据项。

QMap<QStringQString> 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_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

注意:在定义只读迭代器和读写迭代器时的区别, 它们使用了不同的关键字,const_iterator 定义只读迭代器,iterator定义读写迭代器。此外,还可以使用const_reverse_iteratorreverse_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关联容器类的迭代器的用法

  对于关联容器类QMapQHash,迭代器的 * 操作符返回数据项的值。如果想返回键,使用key() 函数。对应的,用 value() 函数返回一个项的值。
  例如,下面的代码将QMap<int,int>map中所有项的键和值输出:

QMap<intint> map;
QMap<intint>::const_iterator i;

for (i = map.constBegin() ; i != map.constEnd() ; ++i)
	qDebug() << i.key() << ":"<< i.value();

3.3 Qt的隐式共享

  QtAPI包含很多返回值为QListQStringList的函数,要遍历这些返回的容器,必须先复制。由于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++11for(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;
}

  对于QMapQHashforeach 会自动访问 键一一值 对里的值,所以无需调用values()。 如果需要访问键则可以调用keys() ,示例代码如下:

QMap<QStringint> map;

foreach (const QString &str, map.keys() )
	qDebug() << str << ":" << map.value(str) ;

注意: foreach 关键字遍历一个容器变量是创建了容器的一个副本,所以不能修改原来容器变量的数据项。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值