鍍金池/ 教程/ C/ Qt 容器類之遍歷器和隱式數(shù)據(jù)共享
Qt 容器和算法拾遺
自定義 model 之一
反走樣
Hello, world!
Qt 容器類之關(guān)聯(lián)存儲容器
QStringListModel
拖放技術(shù)之一
狀態(tài)欄
QTreeWidget
拖放技術(shù)之二
通用算法
event()
Qt 學(xué)習(xí)之路(18): Qt 標(biāo)準(zhǔn)對話框之 QInputDialog
Qt 容器類之遍歷器和隱式數(shù)據(jù)共享
QListWidget
Meta-Object 系統(tǒng)
事件接收與忽略
Qt 學(xué)習(xí)之路(tip): parent 參數(shù)
Qt 標(biāo)準(zhǔn)對話框之 QColorDialog
QPainter(續(xù))
國際化(下)
漸變填充
自定義委托
創(chuàng)建 shared library
model-view 架構(gòu)
Graphics View Framework
自定義拖放數(shù)據(jù)對象
QSortFilterProxyModel
國際化(上)
組件布局
自定義 Model 之三
事件過濾器
QDirModel
Hello, world!(續(xù))
Qt 標(biāo)準(zhǔn)對話框之 QFileDialog
自定義 model 之二
深入了解信號槽
坐標(biāo)變換
剪貼板操作
QTableWidget
QByteArray 和 QVariant
創(chuàng)建一個對話框(下)
Qt 學(xué)習(xí)之路(32): 一個簡易畫板的實(shí)現(xiàn)(Graphics View)
文本文件讀寫
自定義事件
編寫跨平臺的程序
MainWindow
初探信號槽
Qt 學(xué)習(xí)之路(17): Qt 標(biāo)準(zhǔn)對話框之 QMessageBox
繪圖設(shè)備
菜單和工具條(續(xù))
二進(jìn)制文件讀寫
QString
事件(event)
菜單和工具條
QPainter
Qt 容器類之順序存儲容器
進(jìn)程間交互
API 文檔的使用
創(chuàng)建一個對話框(上)
一個簡易畫板的實(shí)現(xiàn)(QWidget)

Qt 容器類之遍歷器和隱式數(shù)據(jù)共享

前面說過,Qt 容器類提供了兩種遍歷器:Java 風(fēng)格的和 STL 風(fēng)格的。前者比較容易使用,后者則可以用在一些通過算法中,功能比較強(qiáng)大。

對于每一個容器類,都有與之相對應(yīng)的遍歷器:只讀遍歷器和讀寫遍歷器。只讀遍歷器有QVectorIterator,QLinkedListIterator和 QListIterator三種;讀寫遍歷器同樣也有三種,只不過名字中具有一個 Mutable,即 QMutableVectorIterator,QMutableLinkedListIterator和 QMutableListIterator。這里我們只討論 QList 的遍歷器,其余遍歷器具有幾乎相同的API。

Java 風(fēng)格的遍歷器的位置如下圖所示(出自 C++ GUI Programming with Qt4, 2nd Edition):

http://wiki.jikexueyuan.com/project/learn-road-qt/images/64.png" alt="" />

可以看出,Java 風(fēng)格的遍歷器,遍歷器不指向任何元素,而是指向第一個元素之前、兩個元素之間或者是最后一個元素之后的位置。使用 Java 風(fēng)格的遍歷器進(jìn)行遍歷的典型代碼是:


QList<double> list; 
// ... 
QListIterator<double> i(list); 
while (i.hasNext()) { 
        doSomethingWith(i.next()); 
}

這個遍歷器默認(rèn)指向第一個元素,使用 hasNext()和 next()函數(shù)從前向后遍歷。你也可以使用 toBack()函數(shù)讓遍歷器指向最后一個元素的后面的位置,然后使用 hasPrevious()和 previous()函數(shù)進(jìn)行遍歷。

這是只讀遍歷器,而讀寫遍歷器則可以在遍歷的時候進(jìn)行增刪改的操作,例如:


QMutableListIterator<double> i(list); 
while (i.hasNext()) { 
        if (i.next() < 0.0) 
                i.remove(); 
}

當(dāng)然,讀寫遍歷器也是可以從后向前遍歷的,具體 API 和前面的幾乎相同,這里就不再贅述。

對應(yīng)于 Java 風(fēng)格的遍歷器,每一個順序容器類 C都有兩個 STL 風(fēng)格的遍歷器:C::iterator和 C::const_iterator。正如名字所暗示的那樣,const_iterator 不允許我們對遍歷的數(shù)據(jù)進(jìn)行修改。begin()函數(shù)返回指向第一個元素的 STL 風(fēng)格的遍歷器,例如 list[0],而 end()函數(shù)則會返回指向最后一個之后的元素的 STL 風(fēng)格的遍歷器,例如如果一個 list 長度為5,則這個遍歷器指向 list[5]。下圖所示 STL 風(fēng)格遍歷器的合法位置:

http://wiki.jikexueyuan.com/project/learn-road-qt/images/65.png" alt="" />

如果容器是空的,begin()和 end()是相同的。這也是用于檢測容器是否為空的方法之一,不過調(diào)用isEmpty()函數(shù)會更加方便。

STL 風(fēng)格遍歷器的語法類似于使用指針對數(shù)組的操作。我們可以使用++和--運(yùn)算符使遍歷器移動到下一位置,遍歷器的返回值是指向這個元素的指針。例如 QVector的 iterator 返回值是 T 類型,而 const_iterator 返回值是 const T 類型。

一個典型的使用 STL 風(fēng)格遍歷器的代碼是:


QList<double>::iterator i = list.begin(); 
while (i != list.end()) { 
        *i = qAbs(*i); 
        ++i; 
}

對于某些返回容器的函數(shù)而言,如果需要使用 STL 風(fēng)格的遍歷器,我們需要建立一個返回值的拷貝,然后再使用遍歷器進(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ù)會返回一個臨時對象,當(dāng)函數(shù)返回時,這個臨時對象就要被銷毀,因此調(diào)用臨時對象的 begin()函數(shù)是相當(dāng)不明智的做法。并且這種寫法也會有性能問題,因?yàn)?Qt 每次循環(huán)都要重建臨時對象。因此請注意,如果要使用 STL 風(fēng)格的遍歷器,并且要遍歷作為返回值的容器,就要先創(chuàng)建返回值的拷貝,然后進(jìn)行遍歷。

在使用 Java 風(fēng)格的只讀遍歷器時,我們不需要這么做,因此系統(tǒng)會自動為我們創(chuàng)建這個拷貝,所以,我們只需很簡單的按下面的代碼書寫:


QListIterator<int> i(splitter->sizes()); 
while (i.hasNext()) { 
        doSomething(i.next()); 
}

這里我們提出要建立容器的拷貝,似乎是一項(xiàng)很昂貴的操作。其實(shí)并不然。還記得我們上節(jié)說過一個隱式數(shù)據(jù)共享嗎?Qt 就是使用這個技術(shù),讓拷貝一個 Qt 容器類和拷貝一個指針那么快速。如果我們只進(jìn)行讀操作,數(shù)據(jù)是不會被復(fù)制的,只有當(dāng)這些需要復(fù)制的數(shù)據(jù)需要進(jìn)行寫操作,這些數(shù)據(jù)才會被真正的復(fù)制,而這一切都是自動進(jìn)行的,也正因?yàn)檫@個原因,隱式數(shù)據(jù)共享有時也被稱為“寫時復(fù)制”。隱式數(shù)據(jù)共享不需要我們做任何額外的操作,它是自動進(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)建一個對象,操作完畢后將其返回。但是在 C++中,很多人都會說,要避免這么寫,因?yàn)樽詈笠粋€ return 語句會進(jìn)行臨時對象的拷貝工作。如果這個對象很大,這個操作會很昂貴。所以,資深的 C++高手們都會有一個 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);

這種寫法通過傳入一個引用避免了拷貝工作。但是這種寫法就不那么自然了。而隱式數(shù)據(jù)共享的使用讓我們能夠放心的按照第一種寫法書寫,而不必?fù)?dān)心性能問題。

Qt 所有容器類以及其他一些類都使用了隱式數(shù)據(jù)共享技術(shù),這些類包括 QByteArray, QBrush, QFont, QImage, QPixmap 和 QString。這使得這些類在參數(shù)和返回值中使用傳值方式相當(dāng)高效。

不過,為了正確使用隱式數(shù)據(jù)共享,我們需要建立一個良好的編程習(xí)慣。這其中之一就是,對 list 或者 vector 使用 at()函數(shù)而不是[]操作符進(jìn)行只讀訪問。原因是[]操作符既可以是左值又可以是右值,這讓 Qt 容器很難判斷到底是左值還是右值,而 at()函數(shù)是不能作為左值的,因此可以進(jìn)行隱式數(shù)據(jù)共享。另外一點(diǎn)是,對于 begin(),end()以及其他一些非 const 容器,在數(shù)據(jù)改變時 Qt 會進(jìn)行深復(fù)制。為了避免這一點(diǎn),要盡可能使用 const_iterator, constBegin()和 constEnd().

最后,Qt 提供了一種不使用遍歷器進(jìn)行遍歷的方法:foreach 循環(huán)。這實(shí)際上是一個宏,使用代碼如下所示:


QLinkedList<Movie> list; 
Movie movie; 
... 
foreach (movie, list) { 
        if (movie.title() == "Citizen Kane") { 
                std::cout << "Found Citizen Kane" << std::endl; 
                break; 
        } 
}

很多語言,特別是動態(tài)語言,以及 Java 1.5之后,都有 foreach 的支持。Qt 中使用宏實(shí)現(xiàn)了foreach 循環(huán),有兩個參數(shù),第一個是單個的對象,成為遍歷對象,相當(dāng)于指向容器元素類型的一個指針,第二個是一個容器類。它的意思很明確:每次取出容器中的一個元素,賦值給前面的遍歷元素進(jìn)行操作。需要注意的是,在循環(huán)外面定義遍歷元素,對于定義中具有逗號的類而言,如 QPair<int, double>,是唯一的選擇。

本文出自 “豆子空間” 博客,請務(wù)必保留此出處 http://devbean.blog.51cto.com/448512/193918