Introduction
Qt庫提供了一組基於模板的一般化的容器類。這些容器可以存儲指定的類型的元素。例如,如果你需要一個可變大小的Qstring數組,可以用QVector<QString>.。
這些容器比STL容器更輕更安全更容易使用。如果你不熟悉STL或者更喜歡以Qt的方式做事,你可以用這些類取代STL類。
這些類是隱式共享的,它們都是可重入,它們進行了速度優化,用更少的內存和最小的內聯代碼擴展,生成更小的可執行文件。此外,當所有的線程僅僅以只讀的方式訪問它們時,它們是線程安全的。
為了遍歷容器的元素,你可以用 Java-style iterators 或者 STL-style iterators. 。 Java-style iterators更容易使用和提供更高級的函數。STL-style iterators 效率稍微高一些,而且可以喝Qt的或STL的通用算法一起使用。
Qt也提供關鍵字foreach 使得很容易遍歷容易內的元素。
The Container Classes
Qt提供了以下順序容器:QList, QLinkedList, QVector, QStack,和 QQueue. 。對於大多數程序而言,QList 是最好用的,即使它以數組列表實現,它提供了非常快的在前面和后面追加的函數。如果你真的需要一個連接表,可以用QLinkedList;。如果你想讓元素占用連續的內存空間,可以用 QVector. QStack and QQueue,它們提供了后進先出和先進先出。
Qt也提供了關聯式容器: QMap, QMultiMap, QHash, QMultiHash,and QSet. 。Multi容器支持多個value關聯到單一的key。Hash容器提供了通過hash函數快速查找取代二分查找。
特殊情況下, QCache 和 QContiguousCache 提供了在有限的cache 中高效的查找對象。
Class |
Summary |
QList<T> |
This is by far the most commonly used container class. It stores a list of values of a given type (T) that can be accessed by index. Internally, the QList is implemented using an array, ensuring that index-based access is very fast. Items can be added at either end of the list using QList::append() and QList::prepend(), or they can be inserted in the middle using QList::insert(). More than any other container class,QList is highly optimized to expand to as little code as possible in the executable.QStringList inherits from QList<QString>. |
QLinkedList<T> |
This is similar to QList, except that it uses iterators rather than integer indexes to access items. It also provides better performance than QList when inserting in the middle of a huge list, and it has nicer iterator semantics. (Iterators pointing to an item in a QLinkedList remain valid as long as the item exists, whereas iterators to a QList can become invalid after any insertion or removal.) |
QVector<T> |
This stores an array of values of a given type at adjacent positions in memory. Inserting at the front or in the middle of a vector can be quite slow, because it can lead to large numbers of items having to be moved by one position in memory. |
QStack<T> |
This is a convenience subclass of QVector that provides "last in, first out" (LIFO) semantics. It adds the following functions to those already present in QVector: push(), pop(), and top(). |
QQueue<T> |
This is a convenience subclass of QList that provides "first in, first out" (FIFO) semantics. It adds the following functions to those already present in QList: enqueue(), dequeue(), and head(). |
QSet<T> |
This provides a single-valued mathematical set with fast lookups. |
QMap<Key, T> |
This provides a dictionary (associative array) that maps keys of type Key to values of type T. Normally each key is associated with a single value. QMap stores its data in Key order; if order doesn't matter QHash is a faster alternative. |
QMultiMap<Key, T> |
This is a convenience subclass of QMap that provides a nice interface for multi-valued maps, i.e. maps where one key can be associated with multiple values. |
QHash<Key, T> |
This has almost the same API as QMap, but provides significantly faster lookups. QHash stores its data in an arbitrary order. |
QMultiHash<Key, T> |
This is a convenience subclass of QHash that provides a nice interface for multi-valued hashes. |
容器可以嵌套。例如,當key的類型是Qstring和value類型是Qlist<int>,完全可以用 QMap<QString, QList<int>>。缺陷就是必須在> >之間插入空格。否則C++編譯器會錯把> >,翻譯成右移位操作並報告語法錯誤。
這些容器被定義在不同的頭文件。為了方便,這些容器在文件<QtContainerFwd>.進行了前置聲明。
容器保存的類型可以是任意可賦值的數據類型。因此要用容器存儲,一個類型必須提供默認構造函數,拷貝構造函數和賦值操作。這包含了大部分可能用容器存儲的數據類型,包括基本類型如int和double,指針和Qt數據類型如Qstring,Qdate和Qtime。但是不包含QObject以及任意QObject的子類。如果你視圖實例化QList<QWidget>,編譯器將抱怨QWidget的拷貝構造函數和賦值操作是不可用的。如果你想用容器存儲這些對象,可以以指針的方式進行保存,如QList<QWidget *>.。
以下是滿足可賦值的自定義數據類型的例子:
class Employee
{
public:
Employee() {}
Employee(const Employee &other);
Employee &operator=(const Employee &other);
private:
QString myName;
QDate myDateOfBirth;
};
如果我們不提供拷貝構造函數或賦值操作,C++將提供默認的實現成員拷貝。在以上的例子,這么寫就足夠了。如果你沒有提供任何構造函數,C++將提供默認構造函數,用成員的默認構造函數進行成員初始化。即使沒有提供任何顯示的構造函數或賦值操作,下面的數據類型也可以用容器存儲。
一些容器對於要進行存儲的數據類型有額外的要求,例如,QMap<Key,T>的key類型必須提供operator<()。這樣的特殊要求在類的詳細描述中都有介紹。在一些情況下,特殊的函數有特殊的要求,這些只是每個函數的基礎描述,當要求不滿足時編譯器將報錯。
Qt容器提供operator<<() 和operator>>() 所以可以很容易用 QDataStream進行讀寫。這意味着存儲在容器中的數據類型也必須支持operator<<() 和 operator>>()。這樣的支持很簡單:
QDataStream&operator<<(QDataStream&out, const Movie &movie)
{
out << (quint32)movie.id<< movie.title
<< movie.releaseDate;
return out;
}
QDataStream&operator>>(QDataStream&in, Movie &movie)
{
quint32id;
QDatedate;
in >> id >> movie.title>> date;
movie.id = (int)id;
movie.releaseDate = date;
return in;
}
某些容器類的函數的文檔說明設計默認構造值,例如, QVector用默認構造值自動的初始化其元素。如果沒有指定key值,則QMap::value() 返回默認構造值。對於大多數值類型,這意味着簡單的用默認構造函數創建默認值。但是對於原始類型如int,double和指針,C++語言不指定任何初始化,在這種情況下,Qt容器自動的初始化為0.
The Iterator Classes
迭代器提供了訪問容器元素的統一方法。Qt容器提供兩種迭代器:Java-style iterators and STL-style iterators.。當容器中的數據被修改或者由於調用非const成員函數生成隱式共享數據副本時,這兩種迭代器都會無效。
Java-StyleIterators
Java-style iterators是在Qt4才新加入的而且Qt程序中標准的使用着。它們比STL-styleiterators用起來方便,但是效率稍微低了一點。它們的API仿照java的迭代器類。
對於美國容器類,有兩種Java-style iterator數據類型,一種是只讀,一種可讀寫:
ContainersRead-only iteratorRead-write
Containers |
Read-only iterator |
Read-write iterator |
QLinkedList<T> |
||
QSet<T> |
QSetIterator<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-style iterators 不同的是, Java-style iterators指向元素的中間而不是直接指向元素的。因此,它們指向容器的開頭,容器的末尾或者兩個元素的之間。如下圖所示:
以下是典型的遍歷 QList<QString>並輸出到控制台的循環:
list << "A" << "B" << "C" <<"D";
QListIterator<QString>i(list);
while (i.hasNext())
qDebug()<< i.next();
它的工作原理如下:QList 的迭代傳遞到 QListIterator 的構造函數。此時,迭代器指向list的第一個元素之前。然后調用 hasNext() 檢測迭代器后面是否有元素,如果有,我們調用next() 跳過該元素,next() 返回所跳過的元素。對於 QList<QString>,其元素的類型是 QString.。
下面是反向遍歷QList:
這段代碼與正向遍歷的對稱,除了開始調用toBack() 把迭代器移到最后元素的后面。
下圖說明了調用 next() 和 previous() 的效果:
下面總結了 QListIterator API:
Function |
Behavior |
Moves the iterator to the front of the list (before the first item) |
|
Moves the iterator to the back of the list (after the last item) |
|
Returns true if the iterator isn't at the back of the list |
|
Returns the next item and advances the iterator by one position |
|
Returns the next item without moving the iterator |
|
Returns true if the iterator isn't at the front of the list |
|
Returns the previous item and moves the iterator back by one position |
|
Returns the previous item without moving the iterator |
QListIterator不提供插入或刪除元素的函數。要實現插入刪除,可以用QMutableListIterator,下面例子從 QList<int> 中刪除奇書:
循環中每次調用next()都跳過后面的元素。remove() 函數從list中刪除最近跳過的元素。調用remove() 不會是迭代器失效,所以可以安全的跡象使用它。反向遍歷也一樣:
QMutableListIterator<int> i(list);
i.toBack();
while (i.hasPrevious()) {
if (i.previous() % 2 != 0)
i.remove();
}
如果想改變存在的一個元素,可以用setValue().。
和 remove()一樣, setValue()作用於最近跳過的元素。如果是正向遍歷,這個元素在迭代器的前面,如果是反向遍歷,該元素在迭代器的后面。
next() 返回一個元素的非const引用,對於簡單的操作,我們不需要setValue()::
如以上提到的,QLinkedList,, QVector',, 和 QSet的迭代器的API 跟 QList的一樣。現在來討論 QMapIterator,它遍歷(key,value)對,多少有些不同。
像 QListIterator一樣,, QMapIterator 提供toFront(), toBack(), hasNext(), next(), peekNext(), hasPrevious(), previous(),和 peekPrevious(). 。key和value通過next(),peekNext(), previous(), 或者 peekPrevious().的返回對象調用key()和value()來獲取。
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();
}
QMapIterator 也提供對於迭代器的key()和value()函數,返回最近被跳過元素的key和value。
QMapIterator<int, QWidget*> i(map);
while (i.hasNext()) {
i.next();
hash.insert(i.key(), i.value());
}
如果想遍歷有相同value的元素,可以用 findNext() 或者 findPrevious().。
STL-StyleIterators
STL-style iterators 從Qt2.0開發使用。它們與Qt的和STL的通用算法兼容,並進行了速度優化。
對於每個容器,都有兩種STL-style iterator類型:一種是只讀,一種是可讀寫的。應該進可能的使用只讀的迭代器,因為它比可讀寫的迭代器快。
Containers |
Read-only iterator |
Read-write iterator |
QList<T>::const_iterator |
QList<T>::iterator |
|
QLinkedList<T> |
QLinkedList<T>::const_iterator |
QLinkedList<T>::iterator |
QVector<T>::const_iterator |
QVector<T>::iterator |
|
QSet<T> |
QSet<T>::const_iterator |
QSet<T>::iterator |
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 iterators 的API參照數組的指針。例如,++操作將迭代器指向下一個元素,* 操作返貨迭代器指向的元素。實際上,QVector 和 QStack,的元素存儲在相鄰的內存位置, iterator 僅僅是T *,的別名, const_iterator 是 constT *.的別名。
在此討論中,我們只關注 QList 和 QMap., QLinkedList, QVector,和 QSet 的迭代器與 QList的完全一樣,同樣的, QHash 的迭代器和 QMap'的完全一樣。
list << "A" << "B" << "C" <<"D";
for (i = list.begin(); i != list.end(); ++i)
*i = (*i).toLower();
與 Java-styleiterators不同的是, STL-style iterators 直接指向元素,begin()返回指向容器的第一個元素的迭代器。end() 返回一個迭代器指向超過容器最后一個元素的虛擬的元素。end() 標記無效的位置,不能解引用,典型的用於循環終止條件。如果list是空的,begin() 和 end(),相等,循環不會被執行。
下圖的紅色箭頭顯示的是有效的迭代器位置:
用STL-style iterator 進行反向迭代在訪問元素之前必須先自減。
list << "A" << "B" << "C" <<"D";
QList<QString>::iteratori = list.end();
while (i != list.begin()) {
--i;
*i = (*i).toLower();
}
到目前為止的代碼中,我們用* 操作獲取元素的值,然后調用了QString::toLower() 。大部分的編譯器也支持 i->toLower(),,有的則不支持。
對於只讀,我們用const_iterator, constBegin(), 和 constEnd()
QList<QString>::const_iteratori;
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug()<< *i;
下表總結了STL-style iterators' API:
Expression |
Behavior |
*i |
Returns the current item |
++i |
Advances the iterator to the next item |
i += n |
Advances the iterator by n items |
--i |
Moves the iterator back by one item |
i -= n |
Moves the iterator back by n items |
i - j |
Returns the number of items between iterators i and j |
++和—操作都可以用做前綴和后綴。前綴修改迭代器並返回修改后的迭代器,后綴版本先復制迭代器,再修改,返回復制的副本。當忽略返回值的表達式中,我們建議使用前綴版本,因為它比較快一些。
對於非const迭代器類型,*操作的返回值可以用作復制操作的左值。
對於QMap 和 QHash, *操作返回元素的value部分。如果你想獲得key,可以對迭代器調用key()。對應的,迭代器也提供value()函數來獲得value。
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 的函數。如果你想用 STL 迭代器遍歷它們,應該先拷貝容器並遍歷拷貝的容器副本:
// RIGHT
const QList<int> sizes = splitter->sizes();
QList<int>::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
...
// WRONG
QList<int>::const_iterator i;
for (i = splitter->sizes().begin();
i != splitter->sizes().end();++i)
對於返回容器的const或者非const引用的函數並不會出現這種情況。
隱式共享在STL-style iterators的另外一個結果是:當容器正在使用非const迭代器時不能拷貝容器, Java-style iterators不受此限制。
The foreach Keyword
如果你只是想依次的遍歷容器的元素,可以用Qt的關鍵字foreach ,該關鍵字是Qt對C++額外添加的,用預處理器實現的。它的語法為:foreach (variable, container)
foreach 代碼比使用迭代器的同等代碼要短:
QLinkedList<QString>list;
...
QLinkedListIterator<QString>i(list);
while (i.hasNext())
qDebug()<< i.next();
如果數據類型不包含逗號,用於遍歷的變量可以聲明在foreach 之內:
和其他C++循環結構一樣,你可以在foreach 循環內用分支,可以可以用break跳出循環:
QLinkedList<QString>list;
...
foreach(const QString&str, list) {
if (str.isEmpty())
break;
qDebug()<< str;
}
對於QMap 和 QHash,,foreach 獲取的是 (key,value) 對的alue部分。如果你想遍歷key和alue,可以用迭代器或者下如下代碼:
QMap<QString,int> map;
...
foreach(const QString&str, map.keys())
qDebug()<< str << ":" << map.value(str);
For a multi-valued map:
...
foreach(const QString&str, map.uniqueKeys()) {
foreach (int i, map.values(str))
qDebug()<< str << ":" << i;
}
當進入foreach 循環時Qt自動拷貝容器。如果你在遍歷的時候改變了容器,也不會影響循環。(即使你不改變容器,也一樣會進行拷貝容器,多虧了隱式共享拷貝容器速度很快。)
由於foreach 創建了容器的副本,所以用非const變量並不能改變原始的容器,僅僅影響容器副本,這可能不是你想要的。
另外,Qt也提供了偽關鍵字forever 無限循環:
forever {
...
}
如果你擔心命名空間受污染,可以在 .pro文件中添加下面的代碼行禁止這些宏:
CONFIG += no_keywords
Other Container-LikeClasses
Qt包含了三個在某些方面類似容器的模板類,這些類不提供迭代器,而且不能用foreach 關鍵字:
· QVarLengthArray<T, Prealloc> provides a low-level variable-length array. It can beused instead of QVector in places where speed is particularly important.
· QCache<Key,T> provides a cache to store objects of a certain type T associated withkeys of type Key.
· QContiguousCache<T>provides an efficient way of caching data that is typically accessed in acontiguous way.
· QPair<T1,T2> stores a pair of elements.
另外的非模板類型為: QBitArray, QByteArray, QString,和QStringList.。
Algorithmic Complexity
算法復雜度關心當容器的元素增加時每個函數的運行有多快。例如,在QLinkedList中間插入一個元素速度是很快的,不管里面存儲了多少個元素。但是,如果在QVector 中插入一個元素,如果QVector 包含有很多元素時代價可能會很大,因為一般的元素都需要移動一個位置。
為了描述算法復雜度,我們用以下基於big Oh的術語:
· Constant time: O(1). A functionis said to run in constant time if it requires the same amount of time nomatter how many items are present in the container. One example is QLinkedList::insert().
· Logarithmic time: O(log n).A function that runs in logarithmic time is a function whose running time isproportional to the logarithm of the number of items in the container. Oneexample is qBinaryFind().
· Linear time: O(n). A functionthat runs in linear time will execute in a time directly proportional to thenumber of items stored in the container. One example is QVector::insert().
· Linear-logarithmic time: O(n log n).A function that runs in linear-logarithmic time is asymptotically slower than alinear-time function, but faster than a quadratic-time function.
· Quadratic time: O(n²). Aquadratic-time function executes in a time that is proportional to the squareof the number of items stored in the container.
下表總結了Qt順序容器類的算法復雜度:
Index lookup |
Insertion |
Prepending |
Appending |
|
QLinkedList<T> |
O(n) |
O(1) |
O(1) |
O(1) |
QList<T> |
O(1) |
O(n) |
Amort. O(1) |
Amort. O(1) |
QVector<T> |
O(1) |
O(n) |
O(n) |
Amort. O(1) |
在表中,"Amort." 代表"amortized behavior"。例如,"Amort.O(1)"意思是如果你只調用函數一次,復雜度可能是O(n) ,但是如果你多次調用函數,平均復雜度則為O(1)。
下表總結了Qt關聯容器的算法復雜度:
Key lookup |
Insertion |
|||
Average |
Worst case |
Average |
Worst case |
|
QMap<Key, T> |
O(log n) |
O(log n) |
O(log n) |
O(log n) |
QMultiMap<Key, T> |
O(log n) |
O(log n) |
O(log n) |
O(log n) |
QHash<Key, T> |
Amort. O(1) |
O(n) |
Amort. O(1) |
O(n) |
QSet<Key> |
Amort. O(1) |
O(n) |
Amort. O(1) |
O(n) |
QVector, QHash,和 QSet, ,在后面追加元素的復雜度為O(log n).,通過調用QVector::reserve(), QHash::reserve(),或者 QSet::reserve() 可以降到O(1) 。
Growth Strategies
QVector<T>, QString,和 QByteArray 的元素存儲在連續的內存中;QList<T>維護着一個指向其元素的指針數組以便快速索引元素(除非T是指針類型或者一個基本類型的指針的大小,此時值本身存在數組)。QHash<Key,T> 擁有一個hash表,hash表的大小與其中的元素的個數成比例,要避免每次在容器后面添加元素時都進行內存重新分配,這里類分配了多於實際需要的內存。
考慮下面的代碼,我們從一個QString生成另一個QString:
QStringonlyLetters(const QString&in)
{
QStringout;
for (int j = 0; j < in.size(); ++j) {
if (in[j].isLetter())
out += in[j];
}
return out;
}
我們通過每次動態的添加一個字符來創建一個字符串out。假設我們添加15000個字符到QString字符串。當QString用完內存之后進行了以下18此內存再分配:4, 8, 12, 16, 20, 52, 116, 244, 500, 1012, 2036, 4084, 6132, 8180,10228, 12276, 14324, 16372.。最后,QString字符串分配了16372個Unicode 字符的空間,其中15000個被占用。
上面的值看起來可能有點奇怪,下面是指導原則:
· QString 每次分配4個字符的空間知道其大小為20
· 從20到4080其大小每次成倍增加,.更准確的說,它增加到下一個2的冪次方減去 12.
· 從4080開始,它每次增加2048個字符。這很容易理解因為現代的操作系統當再分配內存是不拷貝整個數據,物理內存頁僅僅是被重新整理了,而且只有第一個和最后一個也需要拷貝。
QByteArray 和 QList<T> 用的算法有點像Qstring。
QVector<T> 對於可以用memcpy() 拷貝數據的也用那些算法,但是對於通過調用拷貝構造函數和析構函數拷貝的數據類型則用不同的算法。由於在那種情況下再分配內存的代價很高,當用完內存的時候QVector<T>通過成倍增長以減少分配次數。
QHash<Key, T>則是完全不一樣的情況。Qhash的內部hash表以平方次增長的,而且每次增長的元素被重定位到新的桶中,計算為 qHash(key) % QHash::capacity()。這個備注也使用與 QSet<T> 和 QCache<Key, T> 。
對於大部分程序而言,Qt默認提供的算法是起作用的。如果你需要更多的控制,QVector<T>, QHash<Key, T>, QSet<T>, QString, and QByteArray 提供了三個函數允許你檢查和指定需要用多少內存來存儲元素:
· capacity() 返回分配的可以存儲元素的數量的內存。
· reserve(size) 顯示的預先分配size個元素的內存
· squeeze() 釋放不需要存儲元素的內存。
· 如果你知道容器大概要存儲多少個元素,你可以調用reserve(),,而且當你完成填充容器,你可以調用squeeze() 釋放多余的預先分配的內存。
http://blog.csdn.net/hai200501019/article/details/9247673