跳到主要内容C++ STL 容器与 Qt 容器对比及选型建议 | 极客日志C++算法
C++ STL 容器与 Qt 容器对比及选型建议
本文对比了 C++ STL 容器与 Qt 容器的特性、性能及使用场景。STL 作为标准库提供通用数据结构,Qt 容器则深度集成于 GUI 框架并支持隐式共享。文章详细列举了 vector、list、map 等 STL 容器及 QList、QMap、QHash 等 Qt 容器的用法与时间复杂度,并给出了基于项目类型(GUI vs 纯 C++)、性能需求及线程安全性的选型建议。
静心2 浏览 一、前言
在软件开发中,选择合适的容器是实现高效和易于维护代码的关键。C++ 标准模板库(STL)和 Qt 框架都提供了各自的容器类,用以存储和管理数据。这两种容器各有优势,适用于不同的场景和需求。
下面将比较 C++ STL 容器和 Qt 容器的一些关键方面:
1.1 标准模板库(STL)容器
STL 容器主要包括以下几种:
- vector:动态数组,支持快速随机访问。
- list:双向链表,支持快速插入和删除。
- deque(双端队列):支持在两端快速插入和删除。
- set 和 :基于红黑树的集合,自动排序。
multiset
map 和 multimap:基于红黑树的关联容器,存储键值对并自动排序。unordered_set 和 unordered_multiset:基于哈希表的集合,无序但查找速度快。unordered_map 和 unordered_multimap:基于哈希表的关联容器,无序但查找速度快。1.2 Qt 容器
Qt 框架提供了自己的容器类,主要用于跨平台开发,特别是在 GUI 应用程序中:
- QList:类似于 STL 的
vector,但专为 Qt 设计,支持 Qt 的信号和槽机制。
- QVector:类似于 STL 的
vector,但在某些情况下比 QList 更高效。
- QLinkedList:类似于 STL 的
list,但在某些操作上可能比 QList 或 QVector 更高效。
- QMap 和 QMultiMap:类似于 STL 的
map 和 multimap,但提供了更丰富的功能,如迭代器类别转换等。
- QHash 和 QMultiHash:类似于 STL 的
unordered_map 和 unordered_multimap,提供快速查找功能。
1.3 比较
通用性 vs 特定功能
- STL 容器是 C++ 标准的一部分,广泛用于非 GUI 应用程序中。它们提供了广泛的通用数据结构和算法支持。
- Qt 容器则更专注于 GUI 应用程序开发,提供了与 Qt 框架更紧密集成的特性,如信号和槽机制、元对象系统等。
性能
- 对于某些操作(如频繁的插入和删除),Qt 容器的表现可能优于 STL 容器,特别是当使用
QList、QLinkedList 或 QVector 时。
- 在某些情况下,使用 STL 容器(如
std::vector 或 std::list)可能更合适,尤其是当需要与标准库的其他部分(如算法)无缝集成时。
使用场景
- 如果你的项目主要是 GUI 应用程序,并且希望充分利用 Qt 的其他特性(如信号和槽),则 Qt 容器可能是更好的选择。
- 如果你的项目需要高度优化的性能或者与标准库的其他部分紧密集成,则 STL 容器可能更合适。
1.4 结论
选择使用 STL 容器还是 Qt 容器取决于你的具体需求、项目类型以及你对性能和特性的需求。在许多情况下,你可以根据需要同时使用这两种类型的容器。例如,你可以使用 Qt 的 QList 来管理 GUI 中的项目列表,同时使用 STL 的 std::vector 来处理后台数据或算法计算。这样可以充分利用两种库的优势。
二、C++ STL 标准库容器介绍
2.1 简介
C++ 标准模板库(STL)提供了一系列高效的数据结构容器,用于存储和管理数据。这些容器分为顺序容器、关联容器和容器适配器。
以下将详细介绍十种常用容器:vector、list、deque、stack、queue、priority_queue、set、multiset、map 和 multimap。每个容器的介绍包括特性、时间复杂度、使用场景和代码示例。
2.2 顺序容器
2.2.1 vector(向量)
vector 是一个动态数组,支持随机访问。它在内存中是连续的,允许在尾部高效插入和删除元素。
- 特性:顺序容器,可动态调整大小。(自动翻倍申请内存)
- 时间复杂度:
- 随机访问:O(1)
- 尾部插入/删除:O(1)(平均情况)
- 中间插入/删除:O(n)
- 使用场景:需频繁随机访问、尾部增删;替代数组。
- 代码示例:
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3};
vec.push_back(4);
std::cout << vec[0] << std::endl;
return 0;
}
2.2.2 list(链表)
list 是一个双向链表,支持在任意位置高效插入和删除元素,但不支持随机访问。
- 特性:顺序容器,元素不连续存储。
- 时间复杂度:
- 插入/删除任意位置:O(1)(如果已知迭代器)
- 随机访问:O(n)
- 使用场景:需要频繁在中间插入或删除元素的场景。
- 代码示例:
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 2, 3};
auto it = lst.begin();
++it;
lst.insert(it, 4);
for (int n : lst) std::cout << n << " ";
return 0;
}
2.2.3 deque(双端队列)
deqeue 是一个双端队列,支持在头部和尾部高效插入和删除元素,同时支持随机访问。
- 特性:顺序容器,分段连续存储。
- 时间复杂度:
- 头部/尾部插入/删除:O(1)
- 随机访问:O(1)
- 使用场景:需要频繁在两端操作元素的场景,如队列和栈的组合。
- 代码示例:
#include <deque>
#include <iostream>
int main() {
std::deque<int> dq = {1, 2, 3};
dq.push_front(0);
dq.push_back(4);
std::cout << dq[1] << std::endl;
return 0;
}
2.3 关联容器
2.3.1 set(集合)
set 是一个关联容器,存储唯一元素,基于红黑树实现,元素自动排序。
- 特性:关联容器,元素唯一且有序。
- 时间复杂度:
- 插入/删除:O(log n)
- 查找:O(log n)
- 使用场景:需要唯一元素和快速查找的场景。
- 代码示例:
#include <set>
#include <iostream>
int main() {
std::set<int> s;
s.insert(3);
s.insert(1);
s.insert(2);
if (s.find(2) != s.end()) std::cout << "Found" << std::endl;
return 0;
}
2.3.2 multiset(多重集合)
multiset 类似于 set,但允许重复元素。
- 特性:关联容器,元素可重复且有序。
- 时间复杂度:
- 插入/删除:O(log n)
- 查找:O(log n)
- 使用场景:需要重复元素和排序的场景。
- 代码示例:
#include <set>
#include <iostream>
int main() {
std::multiset<int> ms;
ms.insert(1);
ms.insert(1);
std::cout << ms.count(1) << std::endl;
return 0;
}
2.3.3 map(映射)
map 是一个关联容器,存储键值对,键唯一且有序,基于红黑树实现。
- 特性:关联容器,键唯一,支持通过键访问值。
- 时间复杂度:
- 插入/删除:O(log n)
- 查找:O(log n)
- 使用场景:需要键值对映射的场景,如字典。
- 代码示例:
#include <map>
#include <iostream>
int main() {
std::map<std::string, int> m;
m["apple"] = 1;
m["banana"] = 2;
std::cout << m["apple"] << std::endl;
return 0;
}
2.3.4 multimap(多重映射)
- 特性:关联容器,键可重复且有序。
- 时间复杂度:
- 插入/删除:O(log n)
- 查找:O(log n)
- 使用场景:需要一键多值的场景。
- 代码示例:
#include <map>
#include <iostream>
int main() {
std::multimap<std::string, int> mm;
mm.insert({"fruit", 1});
mm.insert({"fruit", 2});
auto range = mm.equal_range("fruit");
for (auto it = range.first; it != range.second; ++it) {
std::cout << it->second << " ";
}
return 0;
}
2.4 容器适配器
2.4.1 stack(栈)
stack 是一个容器适配器,基于其他容器(如 deque 或 list)实现后进先出(LIFO)操作。
- 特性:容器适配器,只允许在顶部插入和删除。
- 时间复杂度:
- 插入(push):O(1)
- 删除(pop):O(1)
- 使用场景:需要 LIFO 行为的场景,如函数调用栈。
- 代码示例:
#include <stack>
#include <iostream>
int main() {
std::stack<int> st;
st.push(1);
st.push(2);
std::cout << st.top() << std::endl;
st.pop();
return 0;
}
2.4.2 queue(队列)
queue 是一个容器适配器,基于其他容器(如 deque)实现先进先出(FIFO)操作。
- 特性:容器适配器,只允许在尾部插入和头部删除。
- 时间复杂度:
- 插入(push):O(1)
- 删除(pop):O(1)
- 使用场景:需要 FIFO 行为的场景,如任务调度。
- 代码示例:
#include <queue>
#include <iostream>
int main() {
std::queue<int> q;
q.push(1);
q.push(2);
std::cout << q.front() << std::endl;
q.pop();
return 0;
}
2.4.3 priority_queue(优先队列)
priority_queue 是一个容器适配器,基于堆实现,元素按优先级排序(默认最大堆)。
- 特性:容器适配器,元素有序。
- 时间复杂度:
- 插入(push):O(log n)
- 删除(pop):O(log n)
- 访问顶部元素:O(1)
- 使用场景:需要快速访问最大或最小元素的场景,如 Dijkstra 算法。
- 代码示例:
#include <queue>
#include <iostream>
int main() {
std::priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(2);
std::cout << pq.top() << std::endl;
pq.pop();
return 0;
}
2.5 总结
- 顺序容器:vector、list、deque 支持灵活的元素访问。
- 关联容器:set、multiset、map、multimap 提供有序存储和快速查找。
- 容器适配器:stack、queue、priority_queue 基于其他容器实现特定行为。
选择合适的容器取决于具体需求,如随机访问、插入频率或元素唯一性。使用这些容器时,注意时间复杂度以优化性能。C++ STL 的完整实现可参考标准库文档。
三、Qt 容器介绍
3.1 顺序容器
3.1.1 QList(数组列表)
QList 是 Qt 中最常用的顺序容器,它提供了快速的随机访问和快速的尾部添加操作。在内部,QList 使用数组实现,因此随机访问效率高。
- 特点:最常用;支持快速随机访问(O(1));两端插入/删除高效;中间操作 O(n)。
- 适用场景:通用列表,如动态数组、需要频繁在头尾增删元素。
- 注意:内部实现为'数组列表',非严格链表。
- 使用 QList:
#include <QList>
#include <QDebug>
void testQList() {
QList<QString> list;
list.append("apple");
list.append("banana");
list.append("cherry");
list << "date" << "elderberry";
qDebug() << "第一个元素:" << list.first();
qDebug() << "最后一个元素:" << list.last();
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 (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");
}
3.1.2 QLinkedList(双向链表)
QLinkedList 是一个真正的链表实现,提供了快速的插入和删除操作,但随机访问效率较低。
- 特点:双向链表;任意位置插入/删除 O(1);不支持索引访问,必须用迭代器。
- 适用场景:频繁在中间增删元素,且无需随机访问。
- 使用 QLinkedList:
#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();
}
3.1.3 QVector (向量)
QVector 提供了连续的内存存储,类似于 C++ 的 std::vector。它在随机访问和尾部添加元素时效率很高。
- 特点:元素连续存储;随机访问极快;仅末尾插入/删除高效(O(1));中间操作需移动大量数据(O(n))。
- 适用场景:科学计算、图像处理、需与 C 风格数组兼容的场景。
- 使用 QVector:
#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.4 QStack / QQueue
- QStack:后进先出(LIFO),基于 QVector。
- QQueue:先进先出(FIFO),基于 QList。
- 适用场景:任务调度、消息队列、回溯算法等。
3.2 关联容器
3.2.1 QMap<Key, T> 映射
QMap 是一个有序的键值对容器,它基于红黑树实现,保证元素按键的升序排列。
- 特点:按键有序存储(红黑树);查找/插入/删除 O(log n);键唯一。
- 适用场景:需按键排序的字典,如配置项、字典查询。
- 使用 QMap:
#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.2 QHash<Key, T>
QHash 是一个无序的键值对容器,它基于哈希表实现,提供了更快的查找和插入操作。
- 特点:无序;平均查找/插入/删除 O(1);性能优于 QMap。
- 适用场景:高速缓存、键值查找,不关心顺序。
- 使用 QHash:
#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.2.3 QSet
- 特点:基于 QHash 实现;存储唯一元素;无序;操作 O(1)。
- 适用场景:去重、快速成员检查。
3.2.4 QMultiMap 和 QMultiHash
QMultiMap 和 QMultiHash 是允许一个键对应多个值的容器。
- 特点:允许一个键对应多个值。
- 适用场景:多对一映射,如分组数据。
- 使用 QMultiMap:
#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();
}
multiMap.remove("apple");
}
3.3 字符串专用容器
QStringList
- 本质:
typedef QList<QString>。
- 优势:提供
join()、split()、filter() 等字符串专用方法。
- 适用场景:文件路径、配置解析、CSV 处理等。
3.4 性能比较
3.4.1 顺序容器性能比较
| 操作 | QList | QLinkedList | QVector |
|---|
| 随机访问 | 极快 | 很慢 | 极快 |
| 头部插入/删除 | 快(如果预分配) | 极快 | 慢 |
| 尾部插入/删除 | 极快 | 极快 | 极快 |
| 中间插入/删除 | 慢 | 快 | 慢 |
| 内存预分配 | 支持 | 不支持 | 支持 |
3.4.2 关联容器性能比较
| 操作 | QMap | QHash |
|---|
| 插入 | 较慢(红黑树) | 极快(哈希表) |
| 查找 | 较慢(O(log n)) | 极快(平均 O(1)) |
| 删除 | 较慢(红黑树) | 极快(哈希表) |
| 有序性 | 有序 | 无序 |
| 键的类型要求 | 必须支持<运算符 | 必须支持==运算符和 qHash() 函数 |
3.4.3 Qt 容器选择建议
- QList:大多数情况下的首选顺序容器,除非需要频繁在中间插入/删除元素。
- QLinkedList:需要频繁在中间插入/删除元素时使用。
- QVector:需要连续内存存储或与 C API 交互时使用。
- QMap:需要有序存储或键的类型不支持哈希函数时使用。
- QHash:需要快速查找时使用。
- QMultiMap/QMultiHash:需要一个键对应多个值时使用。
四、对比与建议
Qt 容器和 STL 容器在功能和使用上有一些相似之处,但也存在显著差异。以下是它们的主要对比:
1. 设计理念
- Qt 容器:
- 隐式共享:Qt 容器采用隐式共享机制,多个容器可以共享同一份数据,只有在修改时才会复制数据,这提高了内存使用效率。
- 线程安全:在多线程环境中,如果所有线程都只读取容器,Qt 容器是线程安全的。
- 轻量级:Qt 容器设计得更加轻量,适合 GUI 应用和嵌入式系统。
- STL 容器:
- 标准库:STL 是 C++ 标准库的一部分,广泛用于各种 C++ 应用。
- 无隐式共享:STL 容器不提供隐式共享机制,每个容器都有自己独立的数据副本。
- 非线程安全:STL 容器本身不保证线程安全,需要用户自行管理同步。
2. 性能
- Qt 容器:
- 内存效率:由于隐式共享,Qt 容器在内存使用上通常更高效。
- 速度:在某些操作上,Qt 容器可能比 STL 容器更快,尤其是在频繁复制的场景中。
- STL 容器:
- 速度:STL 容器在单线程环境下通常性能优异,尤其是在需要频繁插入和删除的场景中(如
std::list)。
- 内存开销:STL 容器通常有更高的内存开销,因为它们不共享数据。
3. 接口和易用性
- Qt 容器:
- Java 风格迭代器:Qt 提供了 Java 风格的迭代器,对于熟悉 Java 的用户来说更易上手。
- 丰富的功能:Qt 容器提供了许多便捷函数,如
qSort、qBinaryFind 等。
- STL 容器:
- STL 风格迭代器:STL 使用更接近指针的迭代器,对于熟悉 C++ 指针操作的用户来说更直观。
- 算法支持:STL 与 STL 算法(如
std::sort、std::find)无缝集成。
4. 兼容性
- Qt 容器:
- Qt 专用:Qt 容器主要用于 Qt 框架内部,与 Qt 的其他组件(如信号槽机制)集成良好。
- 跨平台:Qt 容器在所有 Qt 支持的平台上行为一致。
- STL 容器:
- C++ 标准:STL 容器是 C++ 标准的一部分,几乎在所有 C++ 编译器中都可用。
- 跨平台:STL 容器也是跨平台的,但不同实现可能有细微差异。
5. 适用场景
- Qt 容器:
- GUI 应用:适合开发 Qt GUI 应用,尤其是需要频繁复制容器的场景。
- 嵌入式系统:由于内存效率高,适合资源受限的环境。
- STL 容器:
- 通用 C++ 开发:适合所有需要标准容器的 C++ 应用。
- 高性能计算:在需要极致性能的单线程场景中表现优异。
6. 总结
- 项目基于 Qt 框架开发;
- 需频繁复制容器(利用 COW 减少开销);
- 要与 Qt 信号槽、QVariant、QSettings 等集成;
- 使用 QString、QByteArray 等 Qt 类型(性能碾压 STL 字符串)。
- 项目为纯 C++,不依赖 Qt;
- 需要自定义内存分配器(allocator);
- 追求极致跨平台标准兼容性;
- 使用如
std::priority_queue、std::stack 等 Qt 未提供的容器。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown 转 HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
- HTML 转 Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online