Qt容器類之二:迭代器


一、介紹

遍歷一個容器可以使用迭代器(iterators)來完成,迭代器提供了一個統一的方法來訪問容器中的項目。Qt的容器類提供了兩種類型的迭代器:Java風格迭代器和STL風格迭代器。如果只是想按順序遍歷一個容器中的項目,那么還可以使用Qt的foreach關鍵字。



二、Java風格的迭代器

Java風格的迭代器在Qt4中加入,比STL風格的迭代器更易於使用,但是以輕微的效率作為代價,它們的API以Java的迭代器類為模型。

對於每個容器類,都有兩種Java風格的迭代器類型:一種是只讀,另一種是可讀寫。

容器 只讀迭代器 可讀寫迭代器
QList<T>, QQueue<T> QListIterator<T> QMutableListIterator<T>
QLinkedList<T> QLinkedListIterator<T> QMutableLinkedListIterator<T>
QVector<T>, QStack<T> QVectorIterator<T> QMutableVectorIterator<T>
QSet<T> QSetIterator<T> QMutableSetIterator<T>
QMap<Key, T>, QMultiMap<Key, T> QMapIterator<Key, T> QMutableMapIterator<Key, T>
QHash<Key, T>, QMultiHash<Key, T> QHashIterator<Key, T> QMutableHashIterator<Key, T>

在這里,我們只關注QList和QMap。QLinkedList、QVector和QSet與QList的迭代器有同樣的接口;QHash與QMap迭代器也有同樣的接口。

與STL風格的迭代器不同,Java風格的迭代器指向項之間的位置,而不是直接指向項。由於這個原因,它們指向第一項之前,或者最后一項之后,或者兩項之間。下面的圖展示了包含4項的list的有效的迭代器位置,用紅色箭頭表示:

img



2.1 QList示例

(1)QList正序和倒序遍歷

下面是一個典型的例子,迭代器按順序循環正序遍歷QList<QString>的所有元素,並把它們打印到控制台上:

QList<QString> list;
list << "A" << "B" << "C" << "D";
 
QListIterator<QString> i(list);
while (i.hasNext())
    qDebug() << i.next();

流程是這樣的:將要遍歷的Qlist被傳到QListIterator的構造函數,這時迭代器定位在list的第一項之前("A"之前),接下來我們調用hasNext()來檢測迭代器后面是否有一項,如果有,我們調用next()來跳過那一項,next()函數返回它跳過的那一項。

下面講解如何在QList中倒序遍歷

QListIterator<QString> i(list);

i.toBack();
while (i.hasPrevious())
    qDebug() << i.previous();

代碼和正序遍歷是對稱的,我們調用toBack()將迭代器移到最后一項后面的位置。下圖描述了在一個迭代器上調用next()和previous()函數的效果:

img


(2)QList移除

QListIterator沒有提供從list中插入或移除項的函數,想要實現插入和移除,你必須使用QMutableListIterator。下面舉例說明使用QMutableListIterator從QList<int>中移除所有奇數。

QMutableListIterator<int> i(list);

while (i.hasNext())
{
    if (i.next() % 2 != 0)
        i.remove();
}

在倒序遍歷中同樣有效:

QMutableListIterator<int> i(list);

i.toBack();
while (i.hasPrevious()) 
{
    if (i.previous() % 2 != 0)
        i.remove();
}

(3)QList修改

如果想修改某項的值,我們可以使用setValue(),下面的代碼中,我們用128來替換所以大於128的值:

QMutableListIterator<int> i(list);

while (i.hasNext())
{
    if (i.next() > 128)
        i.setValue(128);
}

下面的表概括了QListIterator的API:

函數 用途
toFront() 將迭代器移到list的最前面(在第一個項之前)
toBack() 將迭代器移到list的最后面 (最后一項之后)
hasNext() 如果迭代器沒有到list的最后則返回true
next() 返回下一項,並將迭代器向前移動一個位置
peekNext() 返回下一項,不會移動迭代器
hasPrevious() 如果迭代器沒有到list的最前面則返回true
previous() 返回上一項,並將迭代器移到上一個位置
peekPrevious() 返回上一項,不會移動迭代器


2.2 QMap示例

現在,我們來看看QMapIterator,有點不同,因為他在鍵值對上遍歷。類似於QListIterator,QMapIterator提供了toFront()、toBack()、hasNext()、next()、peekNext()、hasPrevious()、previous()以及peekPrevious()。鍵和值的部分通過調用next()、peekNext()、previous()或peekPrevious()返回的對象的key()和value()來獲得。

下面的例子中,移除所有首都名字以“City”結尾的一對(capital, country):

QMap<QString, QString> map;
map.insert("Paris", "France");
map.insert("Guatemala City", "Guatemala");
map.insert("Mexico City", "Mexico");
map.insert("Moscow", "Russia");
 
QMutableMapIterator<QString, QString> i(map);
while (i.hasNext()) 
{
    if (i.next().key().endsWith("City"))
        i.remove();
}


三、STL風格的迭代器

自從Qt2.0發布就可以使用STL風格的迭代器了,它們適用於Qt和STL的泛型算法,並且對速度作了優化。

對於每個容器類,有兩種STL風格的迭代器類型:只讀的和可讀寫的。盡可能使用只讀的迭代器,因為它們比可讀寫的迭代器要快。

容器 只讀迭代器 可讀寫的迭代器
QList<T>, QQueue<T> QList<T>::const_iterator QList<T>::iterator
QLinkedList<T> QLinkedList<T>::const_iterator QLinkedList<T>::iterator
QVector<T>, QStack<T> QVector<T>::const_iterator QVector<T>::iterator
QSet<T> QSet<T>::const_iterator QSet<T>::iterator
QMap<Key, T>, QMultiMap<Key, T> QMap<Key, T>::const_iterator QMap<Key, T>::iterator
QHash<Key, T>, QMultiHash<Key, T> QHash<Key, T>::const_iterator QHash<Key, T>::iterator

STL迭代器的API是以數組中的指針為模型的,比如++運算符將迭代器前移到下一項,*運算符返回迭代器所指的那一項。事實上,對於QVector和QStack,它們的項在內存中存儲在相鄰的位置,迭代器類型正是T *,const迭代器類型正是const T *。

在討論中,我們重點放在QList和QMap,QLinkedList、QVector和QSet的迭代器類型與QList的迭代器有相同的接口;同樣地,QHash的迭代器類型與QMap的迭代器有相同的接口。



3.1 QList示例

下面是一個典型例子,按順序循環正序遍歷QList<QString>中的所有元素,並將它們轉為小寫:

QList<QString> list;
list << "A" << "B" << "C" << "D";
 
QList<QString>::iterator i;
for (i = list.begin(); i != list.end(); ++i)
    *i = (*i).toLower();

不同於Java風格的迭代器,STL風格的迭代器直接指向每一項。begin()函數返回指向容器中第一項的迭代器。end()函數返回指向容器中最后一項后面一個位置的迭代器,end()標記着一個無效的位置,不可以被解引用,主要用在循環的break條件。如果list是空的,begin()等於end(),所以我們永遠不會執行循環。

下圖展示了一個包含4個元素的vector的所有有效迭代器位置,用紅色箭頭標出:

img

倒序遍歷需要我們在獲得項之前減少迭代器,這需要一個while循環:

QList<QString> list;
list << "A" << "B" << "C" << "D";
 
QList<QString>::iterator i = list.end();
while (i != list.begin()) 
{
    --i;
    *i = (*i).toLower();
}

如果是只讀的,你可以使用const_iterator、constBegin()和constEnd(),比如:

QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
    qDebug() << *i;


下面的表概括了STL風格迭代器的API:

表達式 用途
*i 返回當前項
++i 將迭代器指向下一項
i += n 迭代器向前移動n項
--i 將迭代器指向上一項
i -= n 將迭代器你向后移動n項
i - j 返回迭代器i和j之間項的數目


3.2 QMap示例

對於QMap和QHash,*運算符返回項的值,如果你想要獲得鍵,只需在迭代器上調用key()。為了對稱,迭代器類型還提供了value()函數來獲得值。舉個例子,下面是如何將QMap中的所有項打印到控制台上:

QMap<int, int> map;
...
QMap<int, int>::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
    qDebug() << i.key() << ":" << i.value();


四、foreach關鍵字

如果你想要按順序遍歷容器中的所有項,你可以使用Qt的foreach關鍵字。這個關鍵字是Qt特有的,與C++語言無關,並且使用了預處理器實現。

它的語法是:foreach (variable, container) statement。比如,下面是如何使用foreach遍歷QLinkedList<QString>:

QLinkedList<QString> list;
...
QString str;
foreach (str, list)
    qDebug() << str;

在QMap和QHash中,foreach可以獲得鍵值對中值的部分。如果你遍歷既想獲得鍵又想獲得值,則可以使用迭代器(這樣是最快的),或者你可以這樣寫:

QMap<QString, int> map;
...
foreach (const QString &str, map.keys())
    qDebug() << str << ":" << map.value(str);

對於一個多值的(multi-valued)map:

QMultiMap<QString, int> map;
...
foreach (const QString &str, map.uniqueKeys()) 
{
    foreach (int i, map.values(str))
        qDebug() << str << ":" << i;
}


參考:

Qt——容器類(譯)



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM