前面說過,Qt 容器類提供了兩種遍歷器:Java 風(fēng)格的和 STL 風(fēng)格的。前者比較容易使用,后者則可以用在一些通過算法中,功能比較強(qiáng)大。
對于每一個(gè)容器類,都有與之相對應(yīng)的遍歷器:只讀遍歷器和讀寫遍歷器。只讀遍歷器有QVectorIterator,QLinkedListIterator和 QListIterator三種;讀寫遍歷器同樣也有三種,只不過名字中具有一個(gè) Mutable,即 QMutableVectorIterator,QMutableLinkedListIterator和 QMutableListIterator。這里我們只討論 QList 的遍歷器,其余遍歷器具有幾乎相同的API。
Java 風(fēng)格的遍歷器的位置如下圖所示(出自 C++ GUI Programming with Qt4, 2nd Edition):
可以看出,Java 風(fēng)格的遍歷器,遍歷器不指向任何元素,而是指向第一個(gè)元素之前、兩個(gè)元素之間或者是最后一個(gè)元素之后的位置。使用 Java 風(fēng)格的遍歷器進(jìn)行遍歷的典型代碼是:
QList<double> list;
// ...
QListIterator<double> i(list);
while (i.hasNext()) {
doSomethingWith(i.next());
}
這個(gè)遍歷器默認(rèn)指向第一個(gè)元素,使用 hasNext()和 next()函數(shù)從前向后遍歷。你也可以使用 toBack()函數(shù)讓遍歷器指向最后一個(gè)元素的后面的位置,然后使用 hasPrevious()和 previous()函數(shù)進(jìn)行遍歷。
這是只讀遍歷器,而讀寫遍歷器則可以在遍歷的時(shí)候進(jìn)行增刪改的操作,例如:
QMutableListIterator<double> i(list);
while (i.hasNext()) {
if (i.next() < 0.0)
i.remove();
}
當(dāng)然,讀寫遍歷器也是可以從后向前遍歷的,具體 API 和前面的幾乎相同,這里就不再贅述。
對應(yīng)于 Java 風(fēng)格的遍歷器,每一個(gè)順序容器類 C都有兩個(gè) STL 風(fēng)格的遍歷器:C::iterator和 C::const_iterator。正如名字所暗示的那樣,const_iterator 不允許我們對遍歷的數(shù)據(jù)進(jìn)行修改。begin()函數(shù)返回指向第一個(gè)元素的 STL 風(fēng)格的遍歷器,例如 list[0],而 end()函數(shù)則會(huì)返回指向最后一個(gè)之后的元素的 STL 風(fēng)格的遍歷器,例如如果一個(gè) list 長度為5,則這個(gè)遍歷器指向 list[5]。下圖所示 STL 風(fēng)格遍歷器的合法位置:
如果容器是空的,begin()和 end()是相同的。這也是用于檢測容器是否為空的方法之一,不過調(diào)用isEmpty()函數(shù)會(huì)更加方便。
STL 風(fēng)格遍歷器的語法類似于使用指針對數(shù)組的操作。我們可以使用++和--運(yùn)算符使遍歷器移動(dòng)到下一位置,遍歷器的返回值是指向這個(gè)元素的指針。例如 QVector的 iterator 返回值是 T 類型,而 const_iterator 返回值是 const T 類型。
一個(gè)典型的使用 STL 風(fēng)格遍歷器的代碼是:
QList<double>::iterator i = list.begin();
while (i != list.end()) {
*i = qAbs(*i);
++i;
}
對于某些返回容器的函數(shù)而言,如果需要使用 STL 風(fēng)格的遍歷器,我們需要建立一個(gè)返回值的拷貝,然后再使用遍歷器進(jìn)行遍歷。如下面的代碼所示:
QList<int> list = splitter->sizes();
QList<int>::const_iterator i = list.begin();
while (i != list.end()) {
doSomething(*i);
++i;
}
而如果你直接使用返回值,就像下面的代碼:
// WRONG
QList<int>::const_iterator i = splitter->sizes().begin();
while (i != splitter->sizes().end()) {
doSomething(*i);
++i;
}
這種寫法一般不是你所期望的。因?yàn)?sizes()函數(shù)會(huì)返回一個(gè)臨時(shí)對象,當(dāng)函數(shù)返回時(shí),這個(gè)臨時(shí)對象就要被銷毀,因此調(diào)用臨時(shí)對象的 begin()函數(shù)是相當(dāng)不明智的做法。并且這種寫法也會(huì)有性能問題,因?yàn)?Qt 每次循環(huán)都要重建臨時(shí)對象。因此請注意,如果要使用 STL 風(fēng)格的遍歷器,并且要遍歷作為返回值的容器,就要先創(chuàng)建返回值的拷貝,然后進(jìn)行遍歷。
在使用 Java 風(fēng)格的只讀遍歷器時(shí),我們不需要這么做,因此系統(tǒng)會(huì)自動(dòng)為我們創(chuàng)建這個(gè)拷貝,所以,我們只需很簡單的按下面的代碼書寫:
QListIterator<int> i(splitter->sizes());
while (i.hasNext()) {
doSomething(i.next());
}
這里我們提出要建立容器的拷貝,似乎是一項(xiàng)很昂貴的操作。其實(shí)并不然。還記得我們上節(jié)說過一個(gè)隱式數(shù)據(jù)共享嗎?Qt 就是使用這個(gè)技術(shù),讓拷貝一個(gè) Qt 容器類和拷貝一個(gè)指針那么快速。如果我們只進(jìn)行讀操作,數(shù)據(jù)是不會(huì)被復(fù)制的,只有當(dāng)這些需要復(fù)制的數(shù)據(jù)需要進(jìn)行寫操作,這些數(shù)據(jù)才會(huì)被真正的復(fù)制,而這一切都是自動(dòng)進(jìn)行的,也正因?yàn)檫@個(gè)原因,隱式數(shù)據(jù)共享有時(shí)也被稱為“寫時(shí)復(fù)制”。隱式數(shù)據(jù)共享不需要我們做任何額外的操作,它是自動(dòng)進(jìn)行的。隱式數(shù)據(jù)共享讓我們有一種可以很方便的進(jìn)行值返回的編程風(fēng)格:
QVector<double> sineTable()
{
QVector<double> vect(360);
for (int i = 0; i < 360; ++i)
vect[i] = std::sin(i / (2 * M_PI));
return vect;
}
// call
QVector<double> v = sineTable();
Java 中我們經(jīng)常這么寫,這樣子也很自然:在函數(shù)中創(chuàng)建一個(gè)對象,操作完畢后將其返回。但是在 C++中,很多人都會(huì)說,要避免這么寫,因?yàn)樽詈笠粋€(gè) return 語句會(huì)進(jìn)行臨時(shí)對象的拷貝工作。如果這個(gè)對象很大,這個(gè)操作會(huì)很昂貴。所以,資深的 C++高手們都會(huì)有一個(gè) STL 風(fēng)格的寫法:
void sineTable(std::vector<double> &vect)
{
vect.resize(360);
for (int i = 0; i < 360; ++i)
vect[i] = std::sin(i / (2 * M_PI));
}
// call
QVector<double> v;
sineTable(v);
這種寫法通過傳入一個(gè)引用避免了拷貝工作。但是這種寫法就不那么自然了。而隱式數(shù)據(jù)共享的使用讓我們能夠放心的按照第一種寫法書寫,而不必?fù)?dān)心性能問題。
Qt 所有容器類以及其他一些類都使用了隱式數(shù)據(jù)共享技術(shù),這些類包括 QByteArray, QBrush, QFont, QImage, QPixmap 和 QString。這使得這些類在參數(shù)和返回值中使用傳值方式相當(dāng)高效。
不過,為了正確使用隱式數(shù)據(jù)共享,我們需要建立一個(gè)良好的編程習(xí)慣。這其中之一就是,對 list 或者 vector 使用 at()函數(shù)而不是[]操作符進(jìn)行只讀訪問。原因是[]操作符既可以是左值又可以是右值,這讓 Qt 容器很難判斷到底是左值還是右值,而 at()函數(shù)是不能作為左值的,因此可以進(jìn)行隱式數(shù)據(jù)共享。另外一點(diǎn)是,對于 begin(),end()以及其他一些非 const 容器,在數(shù)據(jù)改變時(shí) Qt 會(huì)進(jìn)行深復(fù)制。為了避免這一點(diǎn),要盡可能使用 const_iterator, constBegin()和 constEnd().
最后,Qt 提供了一種不使用遍歷器進(jìn)行遍歷的方法:foreach 循環(huán)。這實(shí)際上是一個(gè)宏,使用代碼如下所示:
QLinkedList<Movie> list;
Movie movie;
...
foreach (movie, list) {
if (movie.title() == "Citizen Kane") {
std::cout << "Found Citizen Kane" << std::endl;
break;
}
}
很多語言,特別是動(dòng)態(tài)語言,以及 Java 1.5之后,都有 foreach 的支持。Qt 中使用宏實(shí)現(xiàn)了foreach 循環(huán),有兩個(gè)參數(shù),第一個(gè)是單個(gè)的對象,成為遍歷對象,相當(dāng)于指向容器元素類型的一個(gè)指針,第二個(gè)是一個(gè)容器類。它的意思很明確:每次取出容器中的一個(gè)元素,賦值給前面的遍歷元素進(jìn)行操作。需要注意的是,在循環(huán)外面定義遍歷元素,對于定義中具有逗號(hào)的類而言,如 QPair<int, double>,是唯一的選擇。
本文出自 “豆子空間” 博客,請務(wù)必保留此出處 http://devbean.blog.51cto.com/448512/193918
更多建議: