当我们谈论编程中的数据结构时,顺序容器是不可忽视的一个重要概念。顺序容器是一种能够按照元素添加的顺序来存储和检索数据的数据结构。它们提供了简单而直观的方式来组织和管理数据,为程序员提供了灵活性和性能的平衡。
Qt 中提供了丰富的容器类,用于方便地管理和操作数据。这些容器类涵盖了各种不同的用途,从简单的动态数组到复杂的映射和集合。本章我们将主要学习关联容器,主要包括 QMap
,QSet
和 QHash
,它们提供了键值对存储和检索的功能,允许通过键来快速查找值。
1.1 QMap
QMap
是 Qt 中的有序关联容器,用于存储键值对,并按键的升序进行排序。以下是关于 QMap
的概述:
1.1.1 特点和用途
- 有序性:
QMap
中的元素是有序的,按照键的升序进行排列。 - 唯一键: 每个键在
QMap
中是唯一的,不允许重复键。 - 键值对存储: 存储键值对,每个键关联一个值。
- 性能: 插入和查找操作的平均复杂度是 O(log n),适用于需要按键排序并进行频繁查找的场景。
1.1.2 函数和功能
以下是关于 QMap
常用函数及其功能的总结:
函数 | 功能 |
---|---|
insert(const Key &key, const T &value) | 向 QMap 中插入键值对。 |
insertMulti(const Key &key, const T &value) | 向 QMap 中插入允许相同键的多个值。 |
remove(const Key &key) | 移除指定键的元素。 |
value(const Key &key) const | 返回指定键的值。 |
contains(const Key &key) const | 判断是否包含指定键。 |
isEmpty() const | 判断 QMap 是否为空。 |
size() const | 返回 QMap 中键值对的数量。 |
clear() | 清空 QMap 中的所有元素。 |
keys() const | 返回 QMap 中所有键的列表。 |
values() const | 返回 QMap 中所有值的列表。 |
begin() | 返回指向 QMap 开始位置的迭代器。 |
end() | 返回指向 QMap 结束位置的迭代器。 |
constBegin() const | 返回指向 QMap 开始位置的常量迭代器。 |
constEnd() const | 返回指向 QMap 结束位置的常量迭代器。 |
find(const Key &key) const | 返回指向 QMap 中指定键的迭代器。 |
lowerBound(const Key &key) const | 返回指向 QMap 中不小于指定键的第一个元素的迭代器。 |
upperBound(const Key &key) const | 返回指向 QMap 中大于指定键的第一个元素的迭代器。 |
count(const Key &key) const | 返回指定键的数量。 |
toStdMap() const | 将 QMap 转换为 std::map。 |
这些函数提供了对 QMap
中键值对的插入、删除、查找和遍历等操作。根据需求选择适当的函数以满足操作要求。
1.1.3 应用案例
正如如下代码所示,我们提供了QMap<QString,QString>
字典类型的关联数组,该数组中一个键映射对应一个值,QMap容器是按照顺序存储的,如果项目中不在意顺序可以使用QHash
容器,使用QHash
效率更高些。
#include <QCoreApplication>
#include <iostream>
#include <QString>
#include <QtGlobal>
#include <QMap>
#include <QMapIterator>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QMap<QString,QString> map;
map["1001"] = "admin";
map["1002"] = "guest";
map.insert("1003","lyshark");
map.insert("1004","lucy");
// map.remove("1002");
// 根据键值对查询属性
std::cout << map["1002"].toStdString().data() << std::endl;
std::cout << map.value("1003").toStdString().data() << std::endl;
std::cout << map.key("admin").toStdString().data() << std::endl;
// 使用STL语法迭代枚举Map键值对
QMap<QString,QString>::const_iterator x;
for(x=map.constBegin();x != map.constEnd(); x)
{
std::cout << x.key().toStdString().data() << " : ";
std::cout << x.value().toStdString().data() << std::endl;
}
// 使用STL语法实现修改键值对
QMap<QString,QString>::iterator write_x;
write_x = map.find("1003");
if(write_x !=map.end())
write_x.value()= "you ary in";
// 使用QTglobal中自带的foreach遍历键值对
QString each;
// --> 单循环遍历
foreach(const QString &each,map.keys())
{
std::cout << map.value(each).toStdString().data() << std::endl;
}
// --> 多循环遍历
foreach(const QString &each,map.uniqueKeys())
{
foreach(QString x,map.value(each))
{
std::cout << each.toStdString().data() << " : ";
std::cout << x.toStdString().data() << std::endl;
}
}
return a.exec();
}
上述代码是如何使用QMap
容器,其实还有一个QMultiMap
容器,该容器其实是QMap
的一个子集,用于处理多值映射的类,也就是说传统QMap
只能是一对一的关系,而QMultiMap
则可以实现一个Key
对应多个Value
或者是反过来亦可,实现一对多的关系。
如果总结起来可以发现两者的异同点;
QMap
- 唯一键:
QMap
中每个键都是唯一的,不允许重复键。 - 键排序:
QMap
中的元素是按键的升序排列的。 - 使用场景: 适用于需要键值对有序且键唯一的场景。
QMultiMap
- 允许重复键:
QMultiMap
中可以包含重复的键,即多个键可以映射到相同的值。 - 键排序:
QMultiMap
中的元素是按键的升序排列的。 - 使用场景: 适用于允许键重复,并且需要键值对有序的场景。
相同点
- 键值对: 都是用于存储键值对的容器。
- 有序性: 元素在容器中是有序的,按键的升序排列。
不同点
- 键唯一性:
QMap
中每个键都是唯一的,而QMultiMap
允许重复的键。 - 使用场景:
QMap
适用于需要键唯一的情况,而QMultiMap
适用于允许键重复的情况。
如下所示,展示了如何使用QMultiMap
实现一对多的映射关系;
#include <QCoreApplication>
#include <iostream>
#include <QString>
#include <QList>
#include <QMultiMap>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QMultiMap<QString,QString> mapA,mapB,mapC,mapD;
mapA.insert("lyshark","1000");
mapA.insert("lyshark","2000");
mapB.insert("admin","3000");
mapB.insert("admin","4000");
mapC.insert("admin","5000");
// 获取到里面的所有key=lyshark的值
QList<QString> ref;
ref = mapA.values("lyshark");
for(int x=0;x<ref.size(); x)
{
std::cout << ref.at(x).toStdString().data() << std::endl;
}
// 两个key相同可相加后输出
mapD = mapB mapC;
ref = mapD.values("admin");
for(int x=0;x<ref.size();x )
{
std::cout << ref.at(x).toStdString().data() << std::endl;
}
return a.exec();
}
1.2 QHash
QHash
是一个无序的关联容器,它存储键值对,但与 QMap
不同,QHash
不会对键进行排序。
1.2.1 特点和用途
- 键值对存储:
QHash
中的元素以键值对的形式存储,但与QMap
不同,QHash
中的元素是无序的。 - 无序性:
QHash
中的元素是无序的,没有特定的排列顺序。 - 唯一键: 每个键在
QHash
中是唯一的,不允许重复键。 - 性能: 插入和查找操作的平均复杂度是 O(1),适用于需要快速插入和查找的场景。
1.2.2 函数和功能
以下是关于 QHash
常用函数及其功能的总结:
函数 | 功能 |
---|---|
insert(const Key &key, const T &value) | 向 QHash 中插入键值对。 |
insertMulti(const Key &key, const T &value) | 向 QHash 中插入允许相同键的多个值。 |
remove(const Key &key) | 移除指定键的元素。 |
value(const Key &key) const | 返回指定键的值。 |
contains(const Key &key) const | 判断是否包含指定键。 |
isEmpty() const | 判断 QHash 是否为空。 |
size() const | 返回 QHash 中键值对的数量。 |
clear() | 清空 QHash 中的所有元素。 |
keys() const | 返回 QHash 中所有键的列表。 |
values() const | 返回 QHash 中所有值的列表。 |
begin() | 返回指向 QHash 开始位置的迭代器。 |
end() | 返回指向 QHash 结束位置的迭代器。 |
constBegin() const | 返回指向 QHash 开始位置的常量迭代器。 |
constEnd() const | 返回指向 QHash 结束位置的常量迭代器。 |
find(const Key &key) const | 返回指向 QHash 中指定键的迭代器。 |
count(const Key &key) const | 返回指定键的数量。 |
unite(const QHash &other) | 合并两个 QHash,将 other 中的元素合并到当前 QHash。 |
intersect(const QHash &other) | 保留两个 QHash 中共有的元素,删除其他元素。 |
subtract(const QHash &other) | 从当前 QHash 中移除与 other 共有的元素。 |
toStdHash() const | 将 QHash 转换为 std::unordered_map。 |
这些函数提供了对 QHash
中键值对的插入、删除、查找和遍历等操作。根据需求选择适当的函数以满足操作要求。
1.2.3 应用案例
QHash
与QMap
其实是一样的,如果不需要对键值对进行排序那么使用QHash
将会得到更高的效率,正是因为Hash
的无序,才让其具备了更加高效的处理能力。
#include <QCoreApplication>
#include <iostream>
#include <QString>
#include <QHash>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QHash<QString, QString> hash;
hash["1001"] = "admin";
hash["1002"] = "guest";
hash.insert("1003", "lyshark");
hash.insert("1004", "lucy");
// hash.remove("1002");
// 根据键值对查询属性
std::cout << hash["1002"].toStdString().data() << std::endl;
std::cout << hash.value("1003").toStdString().data() << std::endl;
std::cout << hash.key("admin").toStdString().data() << std::endl;
// 使用STL语法迭代枚举Hash键值对
QHash<QString, QString>::const_iterator x;
for (x = hash.constBegin(); x != hash.constEnd(); x)
{
std::cout << x.key().toStdString().data() << " : ";
std::cout << x.value().toStdString().data() << std::endl;
}
// 使用STL语法实现修改键值对
QHash<QString, QString>::iterator write_x;
write_x = hash.find("1003");
if (write_x != hash.end())
write_x.value() = "you are in";
// 使用Qt中自带的foreach遍历键值对
QString each;
// --> 单循环遍历
foreach (const QString &each, hash.keys())
{
std::cout << hash.value(each).toStdString().data() << std::endl;
}
// --> 多循环遍历
foreach (const QString &each, hash.uniqueKeys())
{
foreach (QString x, hash.values(each))
{
std::cout << each.toStdString().data() << " : ";
std::cout << x.toStdString().data() << std::endl;
}
}
return a.exec();
}
这里需要说明一点,与QMap
一样,QHash
也能够使用QMultiHash
其操作上与QMultiMap
保持一致,此处读者可自行尝试。
1.3 QSet
QSet
是 Qt 中的无序关联容器,类似于 C 标准库的 std::unordered_set
。它主要用于存储唯一值,而不关心元素的顺序。以下是关于 QSet
的概述:
1.3.1 特点和用途
- 无序性:
QSet
中的元素是无序的,没有特定的排列顺序。 - 唯一值: 每个值在
QSet
中是唯一的,不允许重复值。 - 性能: 适用于需要快速查找和检索唯一值的场景,性能比有序容器(如
QMap
)更高。 - 底层实现: 使用哈希表实现,因此插入和查找操作的平均复杂度是 O(1)。
1.3.2 函数和功能
以下是关于 QSet
常用函数及其功能的总结:
函数 | 功能 |
---|---|
insert(const T &value) | 向 QSet 中插入元素。 |
contains(const T &value) const | 判断是否包含指定元素。 |
remove(const T &value) | 移除指定元素。 |
isEmpty() const | 判断 QSet 是否为空。 |
size() const | 返回 QSet 中元素的数量。 |
clear() | 清空 QSet 中的所有元素。 |
unite(const QSet &other) | 合并两个 QSet,将 other 中的元素合并到当前 QSet。 |
intersect(const QSet &other) | 保留两个 QSet 中共有的元素,删除其他元素。 |
subtract(const QSet &other) | 从当前 QSet 中移除与 other 共有的元素。 |
begin() | 返回指向 QSet 开始位置的迭代器。 |
end() | 返回指向 QSet 结束位置的迭代器。 |
constBegin() const | 返回指向 QSet 开始位置的常量迭代器。 |
constEnd() const | 返回指向 QSet 结束位置的常量迭代器。 |
这些函数提供了对 QSet
中元素的插入、删除、查找和遍历等操作。QSet
是一个无序容器,用于存储唯一的元素。根据需求选择适当的函数以满足操作要求。
1.3.3 应用案例
QSet 集合容器,是基于散列表(哈希表)的集合模板,存储顺序同样不定,查找速度最快,其内部使用QHash
实现。
#include <QCoreApplication>
#include <iostream>
#include <QString>
#include <QSet>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QSet<QString> set;
set << "dog" << "cat" << "tiger";
// 测试某值是否包含于集合
if(set.contains("cat"))
{
std::cout << "include" << std::endl;
}
return a.exec();
}
1.4 嵌套案例总结
1.4.1 QList与QMap组合
代码通过结合使用 QList
和 QMap
实现了数据的嵌套存储。具体而言,通过在 QMap
中存储键值对,其中键是时间字符串,而值是包含浮点数数据的 QList
。这种结构使得可以方便地按时间检索相关联的数据集。
#include <QCoreApplication>
#include <iostream>
#include <QString>
#include <QtGlobal>
#include <QList>
#include <QMap>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QMap<QString,QList<float>> map;
QList<float> ptr;
// 指定第一组数据
ptr.append(10.1);
ptr.append(12.5);
ptr.append(22.3);
map["10:10"] = ptr;
// 指定第二组数据
ptr.clear();
ptr.append(102.2);
ptr.append(203.2);
ptr.append(102.1);
map["11:20"] = ptr;
// 输出所有的数据
QList<float> tmp;
foreach(QString each,map.uniqueKeys())
{
tmp = map.value(each);
std::cout << "Time: " << each.toStdString().data() << std::endl;
for(qint32 x=0;x<tmp.count();x )
{
std::cout << tmp[x]<< std::endl;
}
}
return a.exec();
}
在示例中,两组数据分别对应不同的时间键,每组数据存储在相应的 QList
中。最后,通过迭代输出了所有数据,以时间为键检索相应的数据集,并将每个数据集中的浮点数逐个输出。整体而言,这种数据结构的嵌套使用有助于组织和检索多维度的数据。
1.4.2 QList合并为QMap
通过使用 QList
存储头部信息(Header)和相应的数值信息(Values),然后通过循环迭代将两个列表合并为一个 QMap
。在这个 QMap
中,头部信息作为键,而数值作为相应的值,形成了一个键值对应的字典结构。最后,通过 QMap
的键值对操作,输出了特定字典中的数据。
#include <QCoreApplication>
#include <iostream>
#include <QString>
#include <QtGlobal>
#include <QList>
#include <QMap>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<QString> Header = {"MemTotal","MemFree","Cached","SwapTotal","SwapFree"};
QList<float> Values = {12.5,46.8,68,100.3,55.9};
QMap<QString,float> map;
// 将列表合并为一个字典
for(int x=0;x<Header.count();x )
{
QString head = Header[x].toStdString().data();
float val = Values[x];
map[head] = val;
}
// 输出特定字典中的数据
std::cout << map.key(100.3).toStdString().data() << std::endl;
std::cout << map.value("SwapTotal") << std::endl;
return a.exec();
}
整体而言,这样的数据结构使得能够更方便地按照特定的头部信息检索相应的数值。
1.4.3 QMap拆分为QList
这段代码演示了如何使用 QMap
存储键值对,并分别将键和值存储到两个 QList
中。首先,通过 Display
函数输出了 QMap
中的键值对。
接着,通过 map.keys()
和 map.values()
分别获取 QMap
中的所有键和值,将它们存储到两个 QList
中,并使用循环分别输出了这两个列表的内容。
#include <QCoreApplication>
#include <iostream>
#include <QString>
#include <QtGlobal>
#include <QList>
#include <QMap>
void Display(QMap<QString,float> map)
{
foreach(const QString &each,map.uniqueKeys())
{
std::cout << each.toStdString().data() << std::endl;
std::cout << map.value(each) << std::endl;
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QMap<QString,float> map;
map["MemTotal"] = 12.5;
map["MemFree"] = 32.1;
map["Cached"] = 19.2;
Display(map);
QList<QString> map_key;
QList<float> map_value;
// 分别存储起来
map_key = map.keys();
map_value = map.values();
// 输出所有的key值
for(int x=0;x<map_key.count();x )
{
std::cout << map_key[x].toStdString().data() << std::endl;
}
// 输出所有的value值
for(int x=0;x<map_value.count();x )
{
std::cout << map_value[x] << std::endl;
}
return a.exec();
}
1.4.4 QList结构体排序
实现对包含结构体 MyStruct
的 QList
进行排序,并输出排序后的结果。首先,定义了一个包含整数的 QList
,通过 std::sort
函数按从大到小的顺序对该列表进行排序,并使用 Display
函数输出排序后的结果。
其次,定义结构体 MyStruct
,其中包含两个成员变量 uuid
和 uname
。创建一个存储该结构体的 QList
,并添加了几个结构体对象。通过 devListSort
函数,以结构体的 uuid
成员进行排序,并使用循环输出排序后的结果。
#include <QCoreApplication>
#include <iostream>
#include <QString>
#include <QtGlobal>
#include <QList>
struct MyStruct
{
int uuid;
QString uname;
};
void Display(QList<int> ptr)
{
foreach(const int &each,ptr)
std::cout << each << " ";
std::cout << std::endl;
}
// 由大到小排列
int compare(const int &infoA,const int &infoB)
{
return infoA > infoB;
}
// 针对结构体的排序方法
void devListSort(QList<MyStruct> *list)
{
std::sort(list->begin(),list->end(),[](const MyStruct &infoA,const MyStruct &infoB)
{
return infoA.uuid < infoB.uuid;
});
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 定义并对单一数组排序
QList<int> list = {56,88,34,61,79,82,34,67,88,1};
std::sort(list.begin(),list.end(),compare);
Display(list);
// 定义并对结构体排序
QList<MyStruct> list_struct;
MyStruct ptr;
ptr.uuid=1005;
ptr.uname="admin";
list_struct.append(ptr);
ptr.uuid=1002;
ptr.uname = "guest";
list_struct.append(ptr);
ptr.uuid = 1000;
ptr.uname = "lyshark";
list_struct.append(ptr);
devListSort(&list_struct);
for(int x=0;x< list_struct.count();x )
{
std::cout << list_struct[x].uuid << " ---> ";
std::cout << list_struct[x].uname.toStdString().data() << std::endl;
}
return a.exec();
}
上述这段代码演示了如何对一个包含整数的列表和一个包含结构体的列表进行排序,并输出排序后的结果。在结构体排序的情况下,使用了自定义的排序方法 devListSort
,该方法按照结构体的 uuid
成员进行升序排序。