当我们谈论编程中的数据结构时,顺序容器是不可忽视的一个重要概念。顺序容器是一种能够按照元素添加的顺序来存储和检索数据的数据结构。它们提供了简单而直观的方式来组织和管理数据,为程序员提供了灵活性和性能的平衡。
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 成员进行升序排序。