上一節咱們大體瞭解了有關存儲容器的相關內容。對於全部的容器,最經常使用的操做就是遍歷。本章咱們將詳細瞭解有關遍歷器的內容。算法
儘管這個問題不是本章須要考慮的,可是咱們仍是須要來解釋下,爲何要有遍歷器。沒有遍歷器時,若是咱們須要向外界提供一個列表,咱們一般會將其返回:數組
QList<int> intlist() const { return list; }
這麼作的問題是:向用戶暴露了集合的內部實現。用戶知道,原來你用的就是一個QList
啊~那我就能夠向裏面增長東西了,或者修改其中的內容。有時這不是咱們所指望的。不少時候,咱們只是想提供用戶一個集合,只容許用戶知道這個集合中有什麼,而不是對它進行修改。爲此,咱們但願有這麼一種對象:經過它就可以提供一種通用的訪問集合元素的方法,無論底層的集合是鏈表仍是散列,均可以經過這種對象實現。這就是遍歷器。函數
Qt 的容器類提供了兩種風格的遍歷器:Java 風格和 STL 風格。這兩種風格的遍歷器在經過非 const 函數對集合進行修改時都是不可用的。優化
Java 風格的遍歷器是在 Qt4 首先引入的,是 Qt 應用程序首先推薦使用的形式。這種風格比起 STL 風格的遍歷器更方便。方便的代價就是不如後者高效。它們的 API 很是相似於 Java 的遍歷器類,故名。spa
每一種容器都有兩種 Java 風格的遍歷器:一種提供只讀訪問,一種提供讀寫訪問:.net
容器 | 只讀遍歷器 | 讀寫遍歷器 |
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<T> |
QMutableMapIterator<T> |
QHash<Key, T> ,QMultiHash<Key, T> |
QHashIterator<T> |
QMutableHashIterator<T> |
這裏咱們只討論QList
和QMap
的遍歷器。QLinkedList
、QVector
和QSet
的遍歷器接口與QList
的是同樣的;QHash
遍歷器的接口則同QMap
是同樣的。指針
不一樣於下面咱們將要介紹的 STL 風格的遍歷器,Java 風格的遍歷器指向的是兩個元素之間的位置,而不是指向元素自己。所以,它們可能會指向集合第一個元素以前的位置,也可能指向集合的最後一個元素以後的位置,以下圖所示:code
咱們經過下面的代碼看看如何使用這種遍歷器:對象
QList<QString> list; list << "A" << "B" << "C" << "D"; QListIterator<QString> i(list); while (i.hasNext()) { qDebug() << i.next(); }
首先,咱們使用 list 對象建立一個遍歷器。剛剛建立完成時,該遍歷器位於第一個元素以前(也就是 A 以前)。咱們經過調用hasNext()
函數判斷遍歷器以後的位置上有無元素。若是有,調用next()
函數將遍歷器跳過其後的元素。next()
函數返回剛剛跳過的元素。固然,咱們也可使用hasPrevious()
和previous()
函數來從尾部開始遍歷,詳細內容能夠參考 API 文檔。接口
QListIterator
是隻讀遍歷器,不能插入或者刪除數據。若是須要這些操做,咱們可使用QMutableListIterator
。來看下面的代碼:
QMutableListIterator<int> i(list); while (i.hasNext()) { if (i.next() % 2 != 0) { i.remove(); } }
這段代碼使用QMutableListIterator
遍歷集合,若是其值是奇數則將其刪除。在每次循環中都要調用next()
函數。正如前面所說,它會跳過其後的一個元素。remove()
函數會刪除咱們剛剛跳過的元素。調用remove()
函數並不會將遍歷器置位不可用,所以咱們能夠連續調用這個函數。向前遍歷也是相似的,這裏再也不贅述。
若是咱們須要修改已經存在的元素,使用setValue()
函數。例如:
QMutableListIterator<int> i(list); while (i.hasNext()) { if (i.next() > 128) { i.setValue(128); } }
如同remove()
函數,setValue()
也是對剛剛跳過的元素進行操做。實際上,next()
函數返回的是集合元素的非 const 引用,所以咱們根本不須要調用setValue()
函數:
QMutableListIterator<int> i(list); while (i.hasNext()) { i.next() *= 2; }
QMapItrator
也是相似的。例如,使用QMapItrator
咱們能夠將數據從QMap
複製到QHash
:
QMap<int, QWidget *> map; QHash<int, QWidget *> hash; QMapIterator<int, QWidget *> i(map); while (i.hasNext()) { i.next(); hash.insert(i.key(), i.value()); }
STL 風格的遍歷器從 Qt 2.0 就開始提供。這種遍歷器可以兼容 Qt 和 STL 的通用算法,而且爲速度進行了優化。同 Java 風格遍歷器相似,Qt 也提供了兩種 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 風格的遍歷器具備相似數組指針的行爲。例如,咱們可使用 ++ 運算符讓遍歷器移動到下一個元素,使用 * 運算符獲取遍歷器所指的元素。對於QVector
和QStack
,雖然它們是在連續內存區存儲元素,遍歷器類型是typedef T *
,const_iterator
類型則是typedef const T *
。
咱們仍是以QList
和QMap
爲例,理由如上。下面是有關QList
的相關代碼:
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()
實際是一個非法位置,永遠不可達。這是爲跳出循環作的一個虛元素。若是集合是空的,begin()
等於end()
,咱們就不能執行循環。
下圖是 STL 風格遍歷器的示意圖:
咱們使用const_iterator
進行只讀訪問,例如:
QList<QString>::const_iterator i; for (i = list.constBegin(); i != list.constEnd(); ++i) { qDebug() << *i; }
QMap
和QHash
的遍歷器,* 運算符返回集合鍵值對。下面的代碼,咱們打印出QMap
的全部元素:
QMap<int, int> map; QMap<int, int>::const_iterator i; for (i = map.constBegin(); i != map.constEnd(); ++i) { qDebug() << i.key() << ":" << i.value(); }
因爲有隱式數據共享(咱們會在後面的章節介紹該部份內容),即便一個函數返回集合中元素的值也不會有很大的代價。Qt API 包含了不少以值的形式返回QList
或QStringList
的函數(例如QSplitter::sizes()
)。若是你但願使用 STL 風格的遍歷器遍歷這樣的元素,應該使用遍歷器遍歷容器的拷貝,例如:
// 正確的方式 const QList<QString> sizes = splitter->sizes(); QList<QString>::const_iterator i; for (i = sizes.begin(); i != sizes.end(); ++i) ... // 錯誤的方式 QList<QString>::const_iterator i; for (i = splitter->sizes().begin(); i != splitter->sizes().end(); ++i) ...
對於那些返回集合的 const 或非 const 引用的函數,就不存在這個問題。
另外,隱式數據共享對 STL 風格遍歷器形成的另外一個影響是,當一個容器正在被一個遍歷器遍歷的時候,不能對這個容器進行拷貝。若是你必須對其進行拷貝,那麼就得萬分當心。例如,
QVector<int> a, b; a.resize(100000); // 使用 0 填充一個很是大的 vector QVector<int>::iterator i = a.begin(); // 使用遍歷器 i 的錯誤方式(注意,此時,a 上面已經有一個正在遍歷的遍歷器): b = a; /* 如今,咱們的萬分當心遍歷器 i。由於它指向了共享的數據。 若是咱們執行語句 *i = 4,咱們就會改變了共享的數據實例(兩個 vector 都會被改變)。 這裏的行爲與 STL 容器不一樣,所以這種問題僅出如今 Qt 中;使用 STL 標準容器不存在這個問題。 */ a[0] = 5; /* 如今,容器 a 被修改了,其實際數據已經與共享數據不一樣, 即便 i 就是從容器 a 建立的遍歷器,可是它指向的數據與 a 並不一致,其表現就像是 b 的遍歷器。 這裏的情形是:(*i) == 0. */ b.clear(); // 如今咱們清空 b,此時,遍歷器 i 已經不可用了。 int j = *i; // 無定義行爲! /* 來自 b 的數據(也就是 i 指向的那些數據)已經被銷燬了。 這種行爲在 STL 容器中是徹底可行的(在 STL 容器中,(*i) == 5), 可是使用 QVector 則頗有可能出現崩潰。 */
雖然這個例子只演示了QVector
,但實際上,這個問題適用於全部隱式數據共享的容器類。
foreach
關鍵字若是咱們僅僅想要遍歷集合全部元素,咱們可使用 Qt 的foreach
關鍵字。這個關鍵字是 Qt 特有的,經過預處理器進行處理。C++ 11 也提供了本身的foreach
關鍵字,不過與此仍是有區別的。
foreach
的語法是foreach (variable, container)
。例如,咱們使用foreach
對QLinkedList
進行遍歷:
QLinkedList<QString> list; ... QString str; foreach (str, list) { qDebug() << str; }
這段代碼與下面是等價的:
QLinkedList<QString> list; ... QLinkedListIterator<QString> i(list); while (i.hasNext()) { qDebug() << i.next(); }
若是類型名中帶有逗號,好比QPair<int, int>
,咱們只能像上面同樣,先建立一個對象,而後使用foreach
關鍵字。若是沒有逗號,則能夠直接在foreach
關鍵字中使用新的對象,例如:
QLinkedList<QString> list; ... foreach (const QString &str, list) { qDebug() << str; }
Qt 會在foreach
循環時自動拷貝容器。這意味着,若是在遍歷時修改集合,對於正在進行的遍歷是沒有影響的。即便不修改容器,拷貝也是會發生的。可是因爲存在隱式數據共享,這種拷貝仍是很是迅速的。
由於foreach
建立了集合的拷貝,使用集合的非 const 引用也不能實際修改原始集合,所修改的只是這個拷貝。