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

Qt 中提供了丰富的容器类,用于方便地管理和操作数据。这些容器类涵盖了各种不同的用途,从简单的动态数组到复杂的映射和集合。本章我们将主要学习关联容器,主要包括QMapQSetQHash,它们提供了键值对存储和检索的功能,允许通过键来快速查找值。

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() constQMap 转换为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 中的元素是按键的升序排列的。
  • 使用场景: 适用于允许键重复,并且需要键值对有序的场景。

相同点

  1. 键值对: 都是用于存储键值对的容器。
  2. 有序性: 元素在容器中是有序的,按键的升序排列。

不同点

  1. 键唯一性:QMap 中每个键都是唯一的,而QMultiMap 允许重复的键。
  2. 使用场景: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() constQHash 转换为std::unordered_map

这些函数提供了对QHash 中键值对的插入、删除、查找和遍历等操作。根据需求选择适当的函数以满足操作要求。

1.2.3 应用案例

QHashQMap其实是一样的,如果不需要对键值对进行排序那么使用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组合

代码通过结合使用QListQMap 实现了数据的嵌套存储。具体而言,通过在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结构体排序

实现对包含结构体MyStructQList 进行排序,并输出排序后的结果。首先,定义了一个包含整数的QList,通过std::sort 函数按从大到小的顺序对该列表进行排序,并使用Display 函数输出排序后的结果。

其次,定义结构体MyStruct,其中包含两个成员变量uuiduname。创建一个存储该结构体的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 成员进行升序排序。