《Qt MOOC系列教程》第一章第三節:容器

Y03977211367Y發表於2020-11-22

1. Qt容器

Qt庫提供了一組通用的基於模板的容器類。這些類可用於儲存指定型別的項。例如,如果您需要一個可調整大小的QString陣列可以使用QVector<QString>

這些容器類的設計比STL容器更輕,更安全,更易於使用。如果您不熟悉STL,或者更喜歡以“Qt方式”進行操作,則可以使用這些類而不是STL類。不過也可以使用標準容器。

Qt容器類是隱式共享的,它們是可重入的,並且它們針對速度,低記憶體消耗和最小的內聯程式碼擴充套件進行了優化,從而可以生成較小的可執行檔案。此外,在所有用於訪問它們的執行緒將它們用作只讀容器的情況下,它們是執行緒安全的。

要遍歷儲存在容器中的專案,可以使用兩種型別的迭代器:Java樣式的迭代器和STL樣式的迭代器。Java樣式的迭代器更易於使用並提供高階功能,而STL樣式的迭代器效率更高,可以與Qt和STL的通用演算法一起使用。

Qt還提供了一個foreach關鍵字,使訪問容器中儲存的所有專案變得非常容易。

2. 容器類

Qt提供了以下順序容器:QListQLinkedListQVectorQStackQQueue。對於大多數應用程式,QVector是最佳的使用型別,除非您對其他型別有特定的需求。

Qt還提供了一些關聯容器:QMapQMultiMapQHashQMultiHashQSet。"Multi"容器可以很方便地支援單個鍵關聯多個值。"Hash"容器通過使用雜湊函式而不是對排序集進行二進位制搜尋來提供更快的查詢。

在特殊情況下,QCacheQContiguousCache類提供了對有限快取儲存中的物件的高效雜湊查詢。

容器可以巢狀。例如,完全可以使用QMap<QString, QList<int>>,其中鍵型別是QString,值型別是QList<int>

容器被定義在與容器同名的標頭檔案中(例如<QLinkedList>)。為了方便起見,在<QtContainerFwd>中前置宣告容器。

儲存在各種容器中的值可以是任何可賦值的資料型別。為了具有此資格,型別必須提供預設建構函式、複製建構函式和賦值操作符。這涵蓋了您可能想要儲存在容器中的大多數資料型別,包括基本型別,如int和double、指標型別和Qt資料型別,如QStringQDateQTime,但不包括QObjectQObject子類(QWidgetQDialogQTimer等)。如果嘗試例項化QList<QWidget>,編譯器將會抱怨QWidget的複製建構函式和賦值操作符被禁用。如果要將此類物件儲存在容器中,請將它們儲存為指標,例如作為QList<QWidget *>

下面是一個滿足可賦值的資料型別要求的自定義資料型別示例:

class Employee
{
public:
     Employee() {}
     Employee(const Employee &other);

     Employee &operator=(const Employee &other);

 private:
      QString myName;
      QDate myDateOfBirth;
};

如果我們不提供複製建構函式或賦值操作符,C++會提供一個預設實現,執行成員的逐個複製。在上面的例子中,這就足夠了。另外,如果您沒有提供任何建構函式,C++提供了一個預設建構函式,它使用預設建構函式初始化它的成員。雖然它沒有提供任何顯式建構函式或賦值操作符,但以下資料型別可以儲存在容器中:

struct Movie
{
    int id;
    QString title;
    QDate releaseDate;
};

有些容器對它們可以儲存的資料型別有額外的要求。例如,QMap<Key, T>Key型別必須提供operator<()。這些特殊要求記錄在類的詳細描述中。在某些情況下,特定的功能有特殊的要求,這些是按每個函式描述的。如果沒有滿足要求,編譯器會報錯。

Qt的容器提供了operator<<()operator>>()操作符,因此可以使用QDataStream輕鬆地對它們進行讀寫。這意味著儲存在容器中的資料型別還必須支援operator<<()operator>>()。提供這種支援很簡單,下面我們如何對上面的Movie結構執行此操作:

QDataStream &operator<<(QDataStream &out, const Movie &movie)
{
    out << (quint32)movie.id << movie.title
        << movie.releaseDate;
    return out;
}

QDataStream &operator>>(QDataStream &in, Movie &movie)
{
    quint32 id;
    QDate date;

    in >> id >> movie.title >> date;
    movie.id = (int)id;
    movie.releaseDate = date;
    return in;
}

某些容器類函式的文件引用預設構造的值,例如QVector自動用預設構造的值初始化它的項,如果指定的key不在map中,QMap::value()返回一個預設構造的值。對於大多數值型別,這僅意味著使用預設建構函式建立一個值(例如的空字串QString)。但是對於如intdouble這些原始型別以及指標型別,C++語言未指定任何初始化,在這種情況下,Qt的容器會自動將該值初始化為0。

3. 正確且有效的使用迭代器

迭代器提供了一種統一的方法來訪問容器中的專案。Qt的容器類提供了兩種型別的迭代器:Java樣式的迭代器和STL樣式的迭代器。

對於不可變數的迭代可以通過一個簡單的範圍迴圈來完成for (const &noteConstRefToMyItem : container)

Qt4中引入了Java風格的迭代器。在某些方面,它們比使用STL風格的迭代器更方便,但效率稍低。它們的API模仿Java的迭代器類。

一般來說,我們不建議使用普通Java風格的迭代器,但如果您願意,也可以這樣做。對於可變迭代,Java風格的可變迭代器可以說是最容易使用的:

QMutableListIterator的示例如下:

QMutableListIterator<int> i(list);
while (i.hasNext()) {
    if (i.next() % 2 != 0)
        i.remove();
}

該程式碼從列表中刪除所有奇數。

next()函式會在每次迴圈中呼叫。它會跳到列表中的下一項。該remove()函式刪除了我們從列表中跳到的最後一項。呼叫remove()不會使迭代器無效,因此可以安全地繼續使用它。

如果我們只想修改現有專案的值,則可以使用setValue()。在下面的程式碼中,我們將大於128的值都替換為128:

QMutableListIterator<int> i(list);
while (i.hasNext()) {
    if (i.next() > 128)
        i.setValue(128);
}

關聯容器的迭代器的工作方式略有不同,但思想是相同的。官方文件深入介紹了不同的迭代器,還包括多個示例。

在本練習中,您將熟悉QVectorQMap,練習說明可以在containers.cpp中找到。

4. 容器操作中的演算法

如果您有興趣瞭解Qt容器和資料型別的演算法複雜性,請在此處檢視文件。在很多方面,理解為什麼使用某些容器比其他容器更合適是很有用的,在某些應用中甚至關注最有效的增長策略,但在這裡討論這些已經超出了本課程的目標。

Source

獲取更多資訊,請關注作者公眾號:程式設計師練兵場
在這裡插入圖片描述

相關文章