Qt的容器类

本文详细介绍了Qt中的容器类,包括顺序容器如QList、QLinkedList、QVector、QStack和QQueue,以及关联容器如QMap、QMultiMap、QHash和QMultiHash。重点讲解了各类容器的特点、操作函数以及迭代器的使用,同时提到了foreach关键字在遍历容器时的便捷性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Qt的容器类

说白了,这就是一个类。这个类的特别之处就是,用来存储其他的类或者数据。存储基础的int,float类型,也可以是QString,QDate类型。
Qt的容器类分为顺序容器(sequential containers)和关联容器(associative containers)。

顺序容器类

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> list;
list << "one" << "two" << "three";
QString str1=list[1];   //str1=="two"
QString str0=list.at(0);   //str0=="one"

QList的isEmpty()函数在数据项为空时返回true,size()函数返回数据项的个数。

QList是Qt中最常用的容器类,很多函数的参数传递都是采用QList容器类,例如QAudioDeviceInfo的静态函数availableDevices()的函数原型是:

QList<QAudioDeviceInfo> QAudioDeviceInfo::availableDevices(QAudio::Mode mode)

其返回数据就是QAudioDeviceInfo类型的QList列表。

2.QLinkedList

QLinkedList是链式列表(linked-list),数据项不是用连续的内存存储的,它基于迭代器访问数据项,并且插入和删除数据项的操作时间相同。

除了不提供基于下标索引的数据项访问外,QLinkedList的其他接口函数与QList基本相同。

3.QVector

QVector提供动态数组的功能,以下标索引访问数据。

QVector的函数接口与QList几乎完全相同,QVector的性能比QList更高,因为QVector的数据项是连续存储的。

4.QStack

QStack是提供类似于堆栈的后入先出(LIFO)操作的容器类,push()和pop()是主要的接口函数。例如:

QStack<int> stack;
stack.push(10);
stack.push(20);
stack.push(30);
while (!stack.isEmpty())
   cout << stack.pop() << endl;

程序会依次输出30,20,10。

5.QQueue

QQueue是提供类似于队列先入先出(FIFO)操作的容器类。enqueue()和dequeue()是主要操作函数。例如:

QQueue<int> queue;
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
while (!queue.isEmpty())
   cout << queue.dequeue() << endl;

程序会依次输出10,20,30。

关联容器类

Qt还提供关联容器类QMap、QMultiMap、QHash、QMultiHash和QSet。

QMultiMap和QMultiHash支持一个键关联多个值,QHash和QMultiHash类使用散列(Hash)函数进行查找,查找速度更快。

1.QSet

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

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

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

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

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

2.QMap

QMap<Key, T>提供一个字典(关联数组),一个键映射到一个值。QMap存储数据是按照键的顺序,如果不在乎存储顺序,使用QHash会更快。

定义QMap<QString, 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。

3.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类型。

QList<int> values = map.values("plenty");
for (int i = 0; i < values.size(); ++i)
   cout << values.at(i) << endl;

4.QHash

QHash是基于散列表来实现字典功能的模板类,QHash<Key, T>存储的键值对具有非常快的查找速度。

QHash与QMap的功能和用法相似,区别在于以下几点:

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

5.QMultiHash

QMultiHash是QHash的子类,是用于处理多值映射的便利类,其用法与QMultiMap类似。

容器类的迭代

就是我们使用的迭代器。
迭代器(iterator)为访问容器类里的数据项提供了统一的方法,Qt有两种迭代器类:Java类型的迭代器和STL类型的迭代器。

Java类型的迭代器更易于使用,且提供一些高级功能,而STL类型的迭代器效率更高。

Qt还提供一个关键字foreach(实际是< QtGlobal >里定义的一个宏)用于方便地访问容器里所有数据项。

1.Java类型迭代器

1.Java类型迭代器总表

对于每个容器类,有两个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为例介绍迭代器的用法。

2.顺序容器类的迭代器的使用

Java类型迭代器的指针不是指向一个数据项,而是在数据项之间,迭代器指针位置示意图如图所示。

0302.png
图 Java类型迭代器位置示意图

下面是遍历访问一个QList容器的所有数据项的典型代码。

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();

QListIterator用于移动指针和读取数据的函数见表2。

表2 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.关联容器类的迭代器的使用

对于关联容器类QMap,使用QMapIterator和QMutableMapIterator迭代器类,它们具有表2所示的所有函数,主要是增加了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();

2.STL类型迭代器

1.STL类型迭代器总表

STL迭代器与Qt和STL的原生算法兼容,并且进行了速度优化。具体类型见表3。

对于每一个容器类,都有两个STL类型迭代器:一个用于只读访问,一个用于读写访问。无需修改数据时一定使用只读迭代器,因为它们速度更快。

表3 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_iterator和reverse_iterator定义相应的反向迭代器。

STL类型的迭代器是数组的指针,所以“++”运算符使迭代器指向下一个数据项,“*”运算符返回数据项内容。与Java类型的迭代器不同,STL迭代器直接指向数据项,STL迭代器指向位置示意图如图2所示。
在这里插入图片描述
图2 STL类型迭代器位置示意图

begin()函数使迭代器指向容器的第一个数据项,end()函数使迭代器指向一个虚拟的表示结尾的数据项,end()表示的数据项是无效的,一般用作循环结束条件。

下面仍然以QList和QMap为例说明STL迭代器的用法,其他容器类迭代器的用法类似。

2.顺序容器类的迭代器的用法

下面的示例代码将QList list里的数据项逐项输出。

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.关联容器类的迭代器的用法

对于关联容器类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();

Qt API包含很多返回值为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列表对象sizes,但是实际上代码并不将splitter->sizes()表示的列表内容完全复制给变量sizes,只是传递给它一个指针。只有当sizes发生数据修改时,才会将共享对象的数据复制给sizes,这样避免了不必要的复制,减少了资源占用。

而下面的代码是错误的。

QList<int>::const_iterator i;
for (i = splitter->sizes().begin(); i != splitter->sizes().end(); ++i)
   ...

对于STL类型的迭代器,隐式共享还涉及另外一个问题,即当有一个迭代器在操作一个容器变量时,不要去复制这个容器变量。

3.foreach关键字

如果只是想遍历容器中所有的项,可以使用foreach关键字。foreach是头文件中定义的一个宏。使用foreach的句法是:

foreach (variable, container)
使用foreach的代码比使用迭代器更简洁。例如,使用foreach遍历一个QLinkedList的示例代码如下:

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关键字遍历一个容器变量是创建了容器的一个副本,所以不能修改原来容器变量的数据项。

### Qt 容器类概述 Qt 提供了一系列高效的容器类来存储和管理数据集合。这些容器类被设计成模板化,可以容纳任何类型的对象[^1]。 #### QList 类 `QList<T>` 是一个顺序容器,提供了快速的索引操作以及相对快速的追加操作。对于大多数应用场景来说,这是最常用的列表实现方式之一。它内部实现了数组结构,在访问随机位置上的元素时性能较好。 ```cpp // 创建并初始化一个整数型的QList QList<int> numbers; numbers << 1 << 2 << 3; // 访问第一个元素 int firstNumber = numbers.first(); // 追加新元素到末尾 numbers.append(4); ``` #### QLinkedList 类 `QLinkedList<T>` 实现了一个双向链表的数据结构,适合频繁地在中间插入或删除节点的情况。相比于 `QList`, 对于这类操作效率更高;但在通过下标查找特定项的速度上会慢一些。 ```cpp // 初始化字符串类型的QLinkedList QLinkedList<QString> names; names.push_back("Alice"); names.prepend("Bob"); // 遍历整个链接列表打印所有名字 for (auto it = names.begin(); it != names.end(); ++it){ qDebug() << *it; } ``` #### 容器对比 当选择使用哪种容器时,应该考虑具体的应用场景需求: - 如果程序经常执行随机存取或者只需要在一端添加/移除项目,则应优先选用 `QList`. - 当涉及到大量的中部插入与删除动作时,推荐采用 `QLinkedList`. 此外,两种容器都支持迭代器遍历、STL风格算法接口等功能特性.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值