C++ Qt開發:使用關聯容器類

2023-12-10 18:01:39

當我們談論程式設計中的資料結構時,順序容器是不可忽視的一個重要概念。順序容器是一種能夠按照元素新增的順序來儲存和檢索資料的資料結構。它們提供了簡單而直觀的方式來組織和管理資料,為程式設計師提供了靈活性和效能的平衡。

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() 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 中的元素是按鍵的升序排列的。
  • 使用場景: 適用於允許鍵重複,並且需要鍵值對有序的場景。

相同點

  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() const QHash 轉換為 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 成員進行升序排序。