[数据结构] [ Qt ] Qt 关联容器类 QMap、QSet

一、介绍

Qt库提供了一套通用的基于模板的容器类,可以用这些类存储指定类型的项。比如,你需要一个大小可变的QString的数组,则使用QVector<QString>。

这些容器类比STL(C++标准模板库)容器设计得更轻量、更安全并且更易于使用。如果对STL不熟悉,或者倾向于用“Qt的方式”,那么你可以使用这些类,而不去使用STL的类。

你可以用两种方式遍历容器内存储的项:Java风格的迭代器和STL风格的迭代器。Java风格的迭代器更易于使用,并且提供了更高级的功能;STL风格的迭代器更高效,并且可以和Qt与STL的泛型算法一起使用。

Qt还提供了foreach关键字使我们方便地遍历容器中的项。


二、容器类

Qt提供了一些顺序容器:QList、QLinkedList、QVector、QStack和QQueue。因为这些容器中的数据都是一个接一个线性存储的,所以称为顺序容器。大多数时候,QList是最好的选择,虽然是用数组实现的,但在它的首尾添加元素都非常快。如果你需要一个链表(linked-list)就用QLinkedList;想要你的项在内存中连续存储,就使用QVector。QStack和QQueue(栈和队列)分别提供了后进先出(LIFO)和先进先出(FIFO)的机制。

Qt还提供了一些关联容器:QMap、QMultiMap、QHash、QMultiHash、QSet。因为这些容器存储的是<键,值>对,比如QMap<Key,T>,所以称为关联容器。其中“Multi”容器支持一个键对应多个值。“Hash”容器在有序集合上使用hash函数进行快速的查找,而没有用二叉搜索。

下表对常用的容器类进行了介绍。

概述
QList<T>这是目前使用最频繁的容器类,它存储了指定类型(T)的一串值,可以通过索引来获得。本质上QList是用数组实现的,从而保证基于索引的访问非常快。
QLinkedList<T>类似于QList,但它使用迭代器而不是整数索引来获得项。当在一个很大的list中间插入项时,它提供了更好的性能,并且它有更好的迭代器机制。
QVector<T>在内存中相邻的位置存储一组值,在开头或中间插入会非常慢,因为它会导致内存中很多项移动一个位置。
QStack<T>QVector的一个子类,提供后进先出的机制。在当前的QVector中增加了几个方法:push()、pos()、top()。
QQueue<T>QList的一个子类,提供了先进先出的机制,在当前的QList中增加了几个方法:enqueue()、dequeue()、head()。
QSet<T>单值的数学集合,能够快速查找。
QMap<Key, T>提供了字典(关联数组)将类型Key的键对应类型T的值。通常一个键对应一个值,QMap以Key的顺序存储数据,如果顺序不重要,QHash是一个更快的选择。
QMultiMap<Key, T>QMap的子类,提供了多值的接口,一个键对应多个值。
QHash<Key, T>和QMap几乎有着相同的接口,但查找起来更快。QHash存储数据没有什么顺序。
QMultiHash<Key, T>QHash的子类,提供了多值的接口。

容器也可以嵌套使用,例如QMap<QString,QList<int> >,这里键的类型是QString,而值的类型是QList<int>,需要注意,在后面的“> >”符号之间要有一个 空格,不然编译器会将它当作“>>”操作符对待。

在各种容器中存储的值类型可以是任何的可赋值的数据类型,像基本的类型double、指针类型、Qt的数据类型(如QString、 QDate、QTime)等。但是QObject以及QObject的子类都不能存储在容器中,不过,可以存储这些类的指针,例如QList<QWidget * >。


三、QMap的示例程序

下面分别对最为常用的QMap进行介绍,而对于其他几个容器,可以参照着进行操作,因为它们的接口函数是很相似的,当然也可以参考帮助手册。

QMap的示例程序

新建Qt5控制台应用,项目名称为myMap。这里只是为了演示QMap容器类的使用,所以没有使用图形界面,这样只需要建立控制台程序就可以了。将main.cpp文件更改如下:

#include <QCoreApplication>
#include <QMap>
#include <QMultiMap>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //创建QMap
    QMap<QString, int> map;
    map["one"] = 1;          //向map中插入("one",1)
    map["three"] = 3;

    //使用insert()函数进行插入
    map.insert("seven", 7);

    //获取键的值,使用“[ ]”操作符时如果map中没有该键,那么会自动插入
    int value1 = map["six"]; //如果map中没有该键,则返回0
    qDebug() << "value1:" << value1;
    qDebug() << "contains 'six' ?" << map.contains("six") << endl;

    //使用value()函数获取键的值,这样当键不存在时不会自动插入
    int value2 = map.value("five");
    qDebug() << "value2:" << value2;
    qDebug() << "contains 'five' ?" << map.contains("five") << endl;

    //当键不存在时,value()默认返回0,这里可以设定该值,比如这里设置为9
    int value3 = map.value("nine", 9);
    qDebug() << "value3:" << value3 << endl;

    //map默认是一个键对应一个值,如果重新给该键设置了值,那么以前的会被擦除
    map.insert("ten", 10);
    map.insert("ten", 100);
    qDebug() << "ten: " << map.value("ten") << endl;

    //可以使用insertMulti()函数来实现一键多值,然后使用values()函数来获取值的列表
    map.insertMulti("two", 2);
    map.insertMulti("two", 4);
    QList<int> values = map.values("two");
    qDebug() << "two: " << values << endl;

    //------------------------------------------------------------------

    //也可以使用QMultiMap类来实现一键多值
    QMultiMap<QString, int> map1, map2, map3;
    map1.insert("values", 1);
    map1.insert("values", 2);
    map2.insert("values", 3);
    //可以进行相加,这样map3的“values”键将包含2,1,3三个值
    map3 = map2 + map1;
    QList<int> myValues = map3.values("values");
    qDebug() << "the values are: ";
    for (int i=0; i<myValues.size(); ++i)
    {
        qDebug() << myValues.at(i);
    }

    return a.exec();
}

运行程序,Qt的“应用程序输出”窗口输出如下:

value1: 0
contains 'six' ? true

value2: 0
contains 'five' ? false

value3: 9

ten:  100

two:  (4, 2)

the values are:
2
1
3

QMap类是一个容器类,提供了一个基于跳跃列表的字典(a skip-list-based dictionary)。QMap<Key,T>是Qt的通用容器类之一,它存储(键,值)对并提供了与键相关的值的快速查找。

QMap中提供了很多方便的接口函数,例如插人操作inSert()、获取值value()、是否包含一个键contains()、删除一个键remove()、删除一个键并获取该键对应的值take()、插入一键多值insertMulti()等。

可以使用 “[]”操作符插入一个键值对或者获取一个键的值,不过当使用该操作符获取一个不存在的键的值时,会默认向map中插人该键;为了避免这个情况,可以使用value()函数来获取键的值。当使用value()函数时,如果指定的键不存在,那么默认会返回0,可以在使用该函数时提供参数来更改这个默认返回的值。QMap默认是一个键对应一个值的,但是也可以使用insertMulti()进行一键多值的插入,对于一键多值的情况,更方便的是使用QMap的子类QMultiMap。

3、QMap的使用(插入、取值、删除、遍历)

一、简介

QMap 提供了一个从类项为 key 的键到类项为T的直的映射,通常所存储的数据类型是一个键对应一个直,并且按照Key的次序存储数据,这个类也支持一键多值的情况,用类 QMultiMap。

QMap 在 Qt 项目实际开发中经常用到,下面讲解一些其的常用方法。

二、常用方法

1. 实例化QMap对象
/* 创建QMap实例, 第一个参数为QString类型的键,第二个参数为int类型的值 */
QMap<QString, int> map;

2. 插入数据
// 插入数据 两种方式
map["math"] = 100;
map.insert("English", 99);

QMap 只允许每个键有一个值。如果使用 QMap 中已存在的键调用Insert(),则先前的值将被擦除。

3. 移除数据
// 移除数据
map.remove("math");

成功返回 1,但如果键不在映射中,则返回 0。

4. 遍历数据
/* 遍历数据 (先随便插入几个)*/
map.insert("Math", 100);
map.insert("Chinese", 98);
map.insert("physical", 97);
map.insert("chemical", 96);
map.insert("biological", 95);

/* QT提供了多种方式的迭代 */
// 第一种是STL类型的迭代
QMap<QString, int>::const_iterator iterator_1 = map.constBegin();
while (iterator_1 != map.constEnd()) {
    qDebug() << iterator_1.key() << ":" << iterator_1.value();
    ++iterator_1;
}

// 第二种是for循环遍历
QList<QString> keyList = map.keys(); // 存放的就是QMap的key值
for(int i=0;i<keyList.size();i++)
{
    // 这里知道了key不管是删除还是查找还是啥啥啥都很方便
}

5. 由键查找对应键值
map.value("Math");

如果指定的键不在映射中,会返回默认值,比如QString-int的则返回0,QString-QByteArray的则返回空值。

6. 由键值查找键
map.key(100);

7. 修改键值
// 通常一个键只对应一个值,如果再次调用insert()方法,会覆盖以前的值
map.insert("Math", 120);
qDebug() << map.value("Math");

8. 查找是否包含某个键
bool isok = map.contains("Math");
qDebug() << isok;

9. 获取所有的键和键值
QList<QString> allKeys = map.keys();
qDebug() << allKeys;
QList<int> allValues = map.values();
qDebug() << allValues;

10. 一个键对应多个值
// 使用QMultiMap类来实例化一个QMap对象
QMultiMap<QString, QString> multiMap;
multiMap.insert("People", "Name");
multiMap.insert("People", "Gender");
multiMap.insert("People", "Age");
multiMap.insert("People", "Height");
multiMap.insert("People", "Weight");
qDebug() << multiMap;
// 从打印结果可以看出multiMap仍为一个QMap对象

三、自定义QMap类

QMap 仅有键和键值,作为一个容器,它只能使两个数据产生一一对应关系,但是目前我有三个数据需要关联起来,一开始我是这样做的:

QMap<QString, int> mapOfId; 
QMap<QString, QDateTime>mapOfTime;

使用两个 Qmap 就能达到要求,后面发觉还是有点麻烦,索性用 QList 自定义了一个能存储三个值的容器,美其名曰 CMAP。

  • 新建->Library–>C++ Library–>自定义库名称
  • cmap.h 函数声明
  • cmap.cpp 函数定义
  • 点击运行,生成静态链接库

cmap.h 文件实现:

#ifndef CMAP_H
#define CMAP_H
 
#include "CMAP_global.h"
#include <QStringList>
#include <QDebug>
#include <QList>
 
class CMAP_EXPORT CMAP
{
public:
    CMAP();
 
    void insert(int key, QString value1, int value2); /* 插入一行数据 */
    void insert(QList<int> keys, QStringList value1s, QList<int> value2s); /* 插入多行数据 */
    QList<int> keys() const; /* 获取所有键 */
    QStringList value1s() const; /* 获取所有值1 */
    QList<int> value2s() const; /* 获取所有值2 */
    QString value1(int key) const; /* 由键值得到对应值1 */
    int value2(int key) const; /* 由键值得到对应值2 */
    int key(QString value1) const; /* 由值1获取键值 */
    bool contains(int key) const; /* 判断是否包含键 */
    bool contains(QString value1) const; /* 判断是否包含值1 */
    bool remove(int key);  /* 通过键删除一行数据 */
    bool remove(QString value1); /* 通过值1删除一行数据 */
    void clear(); /* 清除map */
    int size() const; /* 返回map长度 */
    void print() const; /* 打印所有<键,值1,值2> */
 
private:
 
    QList<int> key_list;
    QStringList value1_list;
    QList<int> value2_list;
 
 
};
 
#endif // CMAP_H

cmap.cpp 文件实现:

#include "cmap.h"
 
CMAP::CMAP()
{
    clear();
}
 
void CMAP::insert(int key, QString value1, int value2)
{
    key_list << key;
    value1_list << value1;
    value2_list << value2;
}
 
void CMAP::insert(QList<int> keys, QStringList value1s, QList<int> value2s)
{
    Q_ASSERT(keys.size() == value1s.size());
    Q_ASSERT(keys.size() == value2s.size());
    key_list << keys;
    value1_list << value1s;
    value2_list << value2s;
}
 
QList<int> CMAP::keys() const
{
    return key_list;
}
 
QStringList CMAP::value1s() const
{
    return value1_list;
}
 
QList<int> CMAP::value2s() const
{
    return value2_list;
}
 
 
bool CMAP::contains(int key) const
{
    if(key_list.contains(key)) return true;
    else return false;
}
 
bool CMAP::contains(QString value1) const
{
    if(value1_list.contains(value1)) return true;
    else return false;
}
 
bool CMAP::remove(int key)
{
    if(!this->contains(key)) return false;
 
    int i = key_list.indexOf(key);
    key_list.removeAt(i);
    value1_list.removeAt(i);
    value2_list.removeAt(i);
    return true;
}
 
bool CMAP::remove(QString value1)
{
    if(!this->contains(value1)) return false;
 
    int i = value1_list.indexOf(value1);
    key_list.removeAt(i);
    value1_list.removeAt(i);
    value2_list.removeAt(i);
    return true;
}
 
void CMAP::clear()
{
    key_list.clear();
    value1_list.clear();
    value2_list.clear();
}
 
int CMAP::size() const
{
    return key_list.size();
}
 
void CMAP::print() const
{
    for(int i = 0; i < size(); i++) {
        qDebug() << QString("key:%1 value1:%2 value2:%3").
                    arg(key_list.at(i)).
                    arg(value1_list.at(i)).
                    arg(value2_list.at(i));
    }
}
 
 
QString CMAP::value1(int key) const
{
    if(!this->contains(key)) return "";
 
    int i = key_list.indexOf(key);
    return value1_list.at(i);
}
 
int CMAP::value2(int key) const
{
    if(!this->contains(key)) return -1;
 
    int i = key_list.indexOf(key);
    return value2_list.at(i);
}
 
int CMAP::key(QString value1) const
{
    if(!this->contains(value1)) return -1;
    int i = value1_list.indexOf(value1);
    return key_list.at(i);
}

CMAP_global.h 文件不变即可,这个生成的文件可以直接使用。

QSet理论总结

一、概述


QSet是Qt的通用容器类之一。俗称一个集合。QSet会按未指定的顺序存储值,也就是随机存值的方式,并提供非常快速的值查找。在内部,QSet实现为QHash。
在集合内部。值是唯一的哈。就行下面这个例子,把QStringList转成 Set 的方式就可以去除重复的元素。

  QStringList list;
  list << "Julia" << "Mike" << "Mike" << "Julia" << "Julia";

  QSet<QString> set = QSet<QString>::fromList(list);
  set.contains("Julia");  // returns true
  set.contains("Mike");   // returns true
  set.size();             // returns 2


二、使用


1. 声明


下面是一个具有QString值的QSet示例:

QSet<QString> set;



2. 插入元素


要向集合中插入一个值,可以使用insert():

set.insert("one");
set.insert("three");
set.insert("seven");

另一种向集合中插入元素的方法是使用<<()运算符:

set << "twelve" << "fifteen" << "nineteen";

要测试一个元素是否属于集合,可以使用contains():

if (!set.contains("ninety-nine"))
      ...


3. 遍历元素


如果想遍历存储在QSet中的所有值,可以使用迭代器。QSet支持java风格的迭代器(QSetIterator和QMutableSetIterator)和stl风格的迭代器(QSet::iterator和QSet::const_iterator)。

下面是如何使用java风格的迭代器迭代QSet<QWidget *>:

QSetIterator<QWidget *> i(set);
while (i.hasNext())
      qDebug() << i.next();

下面是相同的代码,但使用了stl风格的迭代器:

QSet<QWidget *>::const_iterator i = set.constBegin();
while (i != set.constEnd()) {
      qDebug() << *i;
      ++i;
}

QSet是无序的,因此不能假定迭代器的序列是可预测的。如果需要按键排序,则使用QMap。
要浏览QSet,你还可以使用foreach:

QSet<QString> set;
  ...
foreach (const QString &value, set)
      qDebug() << value;


4. 删除元素


可以使用remove()方法从集合中删除元素。还有一个clear()函数可以删除所有元素。

5. 集合的运算


我们知道数学上的集合有很多运算


QSet 也提供了这些功能

QSet & intersect(const QSet &other):返回两个集合的交集
QSet & unite(const QSet &other):返回两个集合的并集
QSet & subtract(const QSet &other) :返回两个集合的差集


6. 其他


QSet的值数据类型必须是可赋值的数据类型。例如,您不能将QWidget存储为值;相反,存储一个QWidget *。此外,类型必须提供==()运算符,还必须有一个全局的qHash()函数,返回键类型参数的散列值。有关QHash()支持的类型列表,请参阅QHash文档。

在内部,QSet使用散列表来执行查找。散列表会自动增长和收缩,以提供快速查找,而不会浪费内存。如果你已经知道QSet大约包含多少个元素,那么仍然可以通过调用reserve()来控制散列表的大小,但这不是获得良好性能所必需的。你也可以调用capacity()来取得散列表的大小。

QSet 可以和QList相互转换

QList toList() :把QSet转成QList,但是是无序的。
fromList(const QList &list):把QList转为QSet,也是无序的而且是去重复的。

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高亚奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值