程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Qt學習之路(36): Qt容器類之遍歷器和隱式數據共享

Qt學習之路(36): Qt容器類之遍歷器和隱式數據共享

編輯:關於C語言

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved