一、Qt容器类概述
Qt提供了一套丰富的容器类,这些容器类是基于模板实现的,用于存储和管理一组数据项。与C++标准库中的容器相比,Qt容器类更加方便、安全,并且提供了一些额外的功能。理解这些容器类的特性和适用场景,对于编写高效、可维护的Qt代码至关重要。
1.1 容器类的分类
Qt容器类主要分为两类:
- 顺序容器:按顺序存储元素,如QList、QLinkedList、QVector、QStack、QQueue。
- 关联容器:通过键来存储和访问元素,如QMap、QMultiMap、QHash、QMultiHash、QSet。
1.2 容器类的共同特性
- 类型安全:基于模板实现,确保存储的元素类型一致。
- 隐式共享:采用写时复制(Copy-on-Write)技术,减少内存使用和数据复制。
- 可迭代:支持迭代器,方便遍历容器中的元素。
- 可序列化:可以通过QDataStream进行序列化和反序列化。
- 边界检查:调试模式下提供边界检查,提高代码安全性。
二、顺序容器类的使用
2.1 QList
QList是Qt中最常用的顺序容器,它提供了快速的随机访问和快速的尾部添加操作。在内部,QList使用数组实现,因此随机访问效率高。
示例:使用QList
cpp
#include <QList>
#include <QDebug>
void testQList() {
// 创建QList并添加元素
QList<QString> list;
list.append("apple");
list.append("banana");
list.append("cherry");
// 使用<<操作符添加元素
list << "date" << "elderberry";
// 访问元素
qDebug() << "第一个元素:" << list.first(); // 或list[0]
qDebug() << "最后一个元素:" << list.last(); // 或list[list.size()-1]
// 遍历元素 - 使用索引
for (int i = 0; i < list.size(); ++i) {
qDebug() << "元素" << i << ":" << list[i];
}
// 遍历元素 - 使用迭代器
QList<QString>::const_iterator it;
for (it = list.begin(); it != list.end(); ++it) {
qDebug() << "迭代器元素:" << *it;
}
// 遍历元素 - 使用foreach
foreach (const QString &str, list) {
qDebug() << "foreach元素:" << str;
}
// 修改元素
list[1] = "blueberry";
// 插入元素
list.insert(2, "grape");
// 删除元素
list.removeAt(3);
// 检查元素是否存在
bool exists = list.contains("apple");
// 查找元素位置
int index = list.indexOf("cherry");
}
2.2 QLinkedList
QLinkedList是一个真正的链表实现,提供了快速的插入和删除操作,但随机访问效率较低。
示例:使用QLinkedList
cpp
#include <QLinkedList>
void testQLinkedList() {
QLinkedList<int> linkedList;
// 添加元素
linkedList.append(10);
linkedList.prepend(5);
linkedList.insert(linkedList.end(), 15);
// 遍历元素
for (QLinkedList<int>::const_iterator it = linkedList.begin();
it != linkedList.end(); ++it) {
qDebug() << *it;
}
// 删除元素
linkedList.removeFirst();
linkedList.removeLast();
// 判断链表是否为空
bool isEmpty = linkedList.isEmpty();
}
2.3 QVector
QVector提供了连续的内存存储,类似于C++的std::vector。它在随机访问和尾部添加元素时效率很高。
示例:使用QVector
cpp
#include <QVector>
void testQVector() {
QVector<double> vector;
// 预分配空间
vector.reserve(100);
// 添加元素
for (int i = 0; i < 10; ++i) {
vector.append(i * 0.1);
}
// 访问元素
double value = vector[5];
// 调整大小
vector.resize(15); // 多余的元素将被默认构造
vector.resize(8); // 超出部分将被截断
// 快速填充
vector.fill(1.0);
}
三、关联容器类的使用
3.1 QMap
QMap是一个有序的键值对容器,它基于红黑树实现,保证元素按键的升序排列。
示例:使用QMap
cpp
#include <QMap>
void testQMap() {
QMap<QString, int> map;
// 添加元素
map["apple"] = 1;
map["banana"] = 2;
map["cherry"] = 3;
// 另一种添加元素的方式
map.insert("date", 4);
// 访问元素
int value = map["apple"]; // 如果键不存在,会插入一个默认值
int value2 = map.value("banana"); // 更安全的访问方式
int defaultValue = map.value("grape", 0); // 指定默认值
// 检查键是否存在
bool exists = map.contains("cherry");
// 遍历元素 - 按键的顺序
QMap<QString, int>::const_iterator it;
for (it = map.begin(); it != map.end(); ++it) {
qDebug() << "键:" << it.key() << "值:" << it.value();
}
// 删除元素
map.remove("banana");
// 获取所有键和值
QList<QString> keys = map.keys();
QList<int> values = map.values();
}
3.2 QHash
QHash是一个无序的键值对容器,它基于哈希表实现,提供了更快的查找和插入操作。
示例:使用QHash
cpp
#include <QHash>
void testQHash() {
QHash<QString, int> hash;
// 添加元素
hash["apple"] = 1;
hash["banana"] = 2;
hash["cherry"] = 3;
// 访问元素
int value = hash["apple"];
// 检查键是否存在
bool exists = hash.contains("cherry");
// 遍历元素 - 无序
QHash<QString, int>::const_iterator it;
for (it = hash.begin(); it != hash.end(); ++it) {
qDebug() << "键:" << it.key() << "值:" << it.value();
}
// 删除元素
hash.remove("banana");
// 获取键的数量
int size = hash.size();
}
3.3 QMultiMap和QMultiHash
QMultiMap和QMultiHash是允许一个键对应多个值的容器。
示例:使用QMultiMap
cpp
#include <QMultiMap>
void testQMultiMap() {
QMultiMap<QString, int> multiMap;
// 添加多个值
multiMap.insert("apple", 1);
multiMap.insert("apple", 2);
multiMap.insert("apple", 3);
// 获取所有值
QList<int> values = multiMap.values("apple");
// 遍历所有键值对
QMultiMap<QString, int>::const_iterator it;
for (it = multiMap.begin(); it != multiMap.end(); ++it) {
qDebug() << "键:" << it.key() << "值:" << it.value();
}
// 删除所有"apple"的键值对
multiMap.remove("apple");
}
四、容器类的性能比较与选择
4.1 顺序容器性能比较
操作 | QList | QLinkedList | QVector |
---|---|---|---|
随机访问 | 极快 | 很慢 | 极快 |
头部插入/删除 | 快(如果预分配) | 极快 | 慢 |
尾部插入/删除 | 极快 | 极快 | 极快 |
中间插入/删除 | 慢 | 快 | 慢 |
内存预分配 | 支持 | 不支持 | 支持 |
4.2 关联容器性能比较
操作 | QMap | QHash |
---|---|---|
插入 | 较慢(红黑树) | 极快(哈希表) |
查找 | 较慢(O(log n)) | 极快(平均O(1)) |
删除 | 较慢(红黑树) | 极快(哈希表) |
有序性 | 有序 | 无序 |
键的类型要求 | 必须支持<运算符 | 必须支持==运算符和qHash()函数 |
4.3 容器选择建议
- QList:大多数情况下的首选顺序容器,除非需要频繁在中间插入/删除元素。
- QLinkedList:需要频繁在中间插入/删除元素时使用。
- QVector:需要连续内存存储或与C API交互时使用。
- QMap:需要有序存储或键的类型不支持哈希函数时使用。
- QHash:需要快速查找时使用。
- QMultiMap/QMultiHash:需要一个键对应多个值时使用。
五、容器类的高级用法
5.1 隐式共享与性能
Qt容器类使用隐式共享技术,在对象被复制时并不立即复制数据,而是在修改时才进行复制。这可以提高性能,但在某些情况下需要注意。
示例:隐式共享的影响
cpp
void testImplicitSharing() {
QList<int> list1;
list1 << 1 << 2 << 3;
// 复制list1到list2,此时不会复制数据
QList<int> list2 = list1;
// 此时仍然没有复制数据
qDebug() << "list2在修改前是否与list1共享数据:" << list2.isDetached();
// 修改list2,此时才会复制数据
list2.append(4);
qDebug() << "list2在修改后是否与list1共享数据:" << list2.isDetached();
}
5.2 容器的序列化与反序列化
Qt容器类可以通过QDataStream进行序列化和反序列化。
示例:容器的序列化与反序列化
cpp
#include <QFile>
#include <QDataStream>
void saveContainerToFile(const QMap<QString, int> &map, const QString &fileName) {
QFile file(fileName);
if (file.open(QIODevice::WriteOnly)) {
QDataStream stream(&file);
stream << map;
file.close();
}
}
QMap<QString, int> loadContainerFromFile(const QString &fileName) {
QMap<QString, int> map;
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
QDataStream stream(&file);
stream >> map;
file.close();
}
return map;
}
5.3 使用STL风格的迭代器
Qt容器类支持STL风格的迭代器,方便与STL算法结合使用。
示例:使用STL风格的迭代器
cpp
#include <algorithm>
void testSTLIterators() {
QList<int> list;
list << 5 << 3 << 1 << 4 << 2;
// 使用STL算法排序
std::sort(list.begin(), list.end());
// 使用STL算法查找
QList<int>::iterator it = std::find(list.begin(), list.end(), 3);
if (it != list.end()) {
qDebug() << "找到元素:" << *it;
}
}
六、总结
Qt容器类提供了丰富、高效的数据存储和管理功能,能够满足各种应用场景的需求。顺序容器如QList、QLinkedList和QVector适用于按顺序存储元素的场景,而关联容器如QMap和QHash则适用于通过键来存储和访问元素的场景。在选择容器类时,需要根据具体的应用场景和性能需求来决定。理解容器类的隐式共享机制、序列化功能和STL风格的迭代器等高级用法,可以帮助我们编写更加高效、灵活的代码。掌握了Qt容器类的使用,我们就能在开发过程中更加得心应手地处理各种数据结构和算法问题。