Qt中的常用容器類
在Qt庫中為我們提供了一系列的基於模板的容器類。這些類可以被用來儲存特定型別的項。例如,如果你需要一個大小可以變得QString陣列,那麼可以使用QVector<QString>。
這些容器類都是隱式共享的,可重入的,並且在速度上進行了優化,記憶體佔用少,內聯程式碼擴充套件少,從而可以產生更小的可執行檔案。此外,當他們被用作只讀容器時,還是執行緒安全的。對於遍歷這些容器來說,可以使用兩種型別的迭代器:Java風格的迭代器和STL風格的迭代器。其中,Java風格的迭代器更容易使用,特別是對於Java工作人員來說,它提供了高層次的函式;然而,STL風格的迭代器會更高效,並且可以和Qt和STL的通用演算法結合使用。另外,Qt還提供了一個foreach關鍵字,使遍歷容器中的每一項更容易了。
Qt中的容器和STL中的類似,也分為序列式容器和關聯式容器。其中,序列式容器有:QList,QLinkedList,QVector,QStack,QQueue。對大部分應用程式來說,QList都是一個很好的選擇。儘管它在底層被實現為一個array-list,但它為我們提供了非常快速的新增操作,包括在頭部新增和在尾部新增。如果你確實需要一個linked-list,可以使用QLinkedList;如果你想確保你的元素佔用連續的記憶體空間,可以使用QVector。而QStack和QQueue是兩個提供了LIFO和FIFO語義的方便類。
除了序列式容器,Qt中還提供了關聯式容器:QMap,QMultiMap,QHash,QMultiHash,QSet。這些容器中儲存的都是key-value對。其中,"Multi"容器又支援一個key可以關聯多個value。"Hash"容器通過使用一個hash函式而不是二分搜尋提供了更快速的查詢操作。
我們將這些容器類的總結在下表中:
QList<T> | 這是最通用的一個容器類。它裡面儲存了給定型別T的一個列表,這個列表可以使用下標來訪問。其實,在底層QList被實現為一個數組, 確保基於下標的訪問非常快速。可以使用QList::append()和QList::prepend()向連結串列的兩端新增元素,或者使用QList::insert()在連結串列的中間插入元素。 並且,和其他容器相比,更重要的是,QList在可執行檔案中展開的程式碼量是非常少的,是經過高度優化的。QStringList就繼承自QList<QString>。 |
QLinkedList<T> | 這個容器類類似於QList,只不過它是使用迭代器來訪問,而不是下標。當從中間插入時,它的效率比QList還要高。並且,它有更好的迭代器語義。 即指向QLinkedList中某個元素的迭代器,只有該元素存在就會一直保持有效,而指向QList中某元素的迭代器,在向QList進行任意插入或刪除時都會導致 該迭代器失效。 |
QVector<T> | 這個容器類會在一塊相鄰的記憶體中儲存一個給定型別的值的陣列。在一個vector的前端或中間插入是非常慢的,因為這會導致大量現存的元素移動以為新的 元素騰出位置。 |
QStack<T> | 這個容器類繼承自QVector,提供了“先入後出”的語義。 |
QQueue<T> | 這個容器類繼承自QList,提供了“先入先出”的語義。 |
QSet<T> | 這個容器類提供了不允許有重複值的集合,提供快速的查詢效率。 |
QMap<Key, T> | 這個容器類提供了一個字典形式的容器,它會將Key型別的值對映到T型別的value上。通常情況下,每一個key只關聯一個值。並且,QMap會按Key的順序儲存 相應的值;所以,如果不關心元素的儲存順序,QHash是一個更好的選擇。 |
QMaultiMap<Key, T> | 這個容器類繼承自QMap,提供了多值的字典,也就是說,該容器中的一個key可以關聯多個值。 |
QHash<Key, T> | 這個容器類的API和QMap幾乎一樣,但它提供了更快速的查詢操作。並且,該類會按任意的順序儲存值。 |
QMultiHash<Key, T> | 這個容器類繼承自QHash,提供了多值hash表。 |
容器是可以巢狀使用的。例如,可以使用QMap<QString, QList<int>>這種型別,其key的型別是QString,值型別是QList<int>。
上面提到的這些容器分別被定義在各自的、名稱和容器名一樣的標頭檔案中。例如,<QLinkedList>。
這些容器中儲存的值可以是任何能被賦值的資料型別,即該型別必須提供一個預設的建構函式、一個拷貝建構函式、一個賦值運算子。這樣的資料型別涵蓋了大部分你可以儲存的型別,包括基本型別入int和double,指標型別,Qt的資料型別QString,QDate,QTime,但不包括QObject或其子類(QWidget,QDialog,QTimer等等)。如果你嘗試構建一個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++為我們提供的預設建構函式會使用成員變數所對應資料型別的預設值進行各個成員的初始化。所以,下面這種自定義資料型別雖然沒有提供顯式的建構函式或賦值運算子,它也可以被儲存到容器中:
struct Movie
{
int id;
QString title;
QDate releaseDate;
};
而對於其他的容器可能還有特殊的要求。例如,QMap<Key, T>的Key型別必須提供operator<()。這些特定的要求在容器類的文件中都有詳細說明。一般,如果某個要求沒被滿足,編譯器就會報錯。
Qt的容器還提供了operator<<() 和 operator>>() 以使它們可以方便的使用QDataStream類讀取或寫入資料。這也意味著儲存在容器中的資料型別必須支援這兩種操作。提供這種支援是很簡單的。比如,下面的例子,是我們為上面宣告的Movie結構體提供的 << 和 >>運算子:
QDataStream &operator<<(QDataStream &out, const Movie &movie)
{
out << (quint32)movie.id << movie.title
<< movie.releaseDate;
return out;
}
QDataStream &operator>>(QDataStream &in, Movie &movie)
{
quint32 id;
QDate date;
in >> id >> movie.title >> date;
movie.id = (int)id;
movie.releaseDate = date;
return in;
}
大部分容器類的說明文件中都提到了“預設值”。例如,QVector會自動的使用預設建構函式的值初始化它的元素,QMap::value()方法在指定的key不存在的情況下會返回一個預設建構函式產生的值。對大部分資料型別來說,這只是意味著預設建構函式建立了一個值,例如,QString的預設建構函式會創建出一個空字串。但是,對於int和double這類的基本型別,和指標型別,C++語言並不會指定任何初始化。在這種情況下,Qt的容器會自動的用0對它們進行初始化。至於對容器的操作,和stl一樣,通常是使用迭代器。迭代器為訪問容器中的元素提供了一個統一的方式。Qt中的容器類提供了兩類迭代器:Java風格的迭代器和STL風格的迭代器。但是,當容器中的資料被修改後或由於呼叫了non-const成員函式導致其脫離了隱式共享,那麼這兩種迭代器都會失效。
Java風格的迭代器:
Java風格的迭代器在Qt4中被引入,成功Qt應用程式的標準組件。它們比STL風格的迭代器更好用,帶代價是效率更低。這些迭代器都是特定的類,所以其具體使用方法在每個類的文件中有詳細說明。另外,每一個容器類都又提供了兩種型別的Java風格的迭代器:一種是隻讀迭代器,一種是讀寫迭代器。詳細型別說明如下表:
Containers | Read-only iterator | Read-write iterator |
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<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風格的迭代器,Java風格的迭代器指向兩個元素之間,而不是直接指向某個具體的元素。由於這個原因,這些迭代器要麼指向第一個元素前面,要麼指向最後一個元素後面,要麼在某兩個元素之間。下面的圖示顯示了對於一個連結串列來說,有效的迭代器指向:
下面的程式碼展示了,使用Java風格的迭代器遍歷一個QList的典型做法:
QList<QString> list;
list << "A" << "B" << "C" << "D";
QListIterator<QString> i(list);
while (i.hasNext())
qDebug() << i.next();
該程式碼的執行原理是:將要遍歷的QList傳給QListIterator的建構函式。此時,迭代器就指向了連結串列中第一個元素的前面,即"A"的前面。接著,我們呼叫hasNext()方法來判斷在當前迭代器後面是否有一個元素。如果有,我們就呼叫next()函式來跳過那個元素。並且,next()函式會返回它跳過的那個元素。在這個例子中就是返回一個QString字串。
下面的程式碼展示了怎麼從後向前遍歷一個QList:
QListIterator<QString> i(list);
i.toBack();
while (i.hasPrevious())
qDebug() << i.previous();
該程式碼和向前遍歷的程式碼類似,除了我們在一開始呼叫了toBack()函式將迭代器移到最後一個元素的後面。
下面的圖示說明了電影next() 和 previous()的作用:
下表中列出了QListIterator類的API及其作用:
toFront() | 移動迭代器到第一個元素之前 |
toBack() | 移動迭代器到最後一個元素之後 |
hasNext() | 如果迭代器還未遍歷到列表的最後,返回true |
next() | 返回下一個元素,並將迭代器向前移動一個位置。 |
peekNext() | 返回下一個元素,不移動迭代器。 |
hasPrevious() | 如果迭代器還未遍歷到列表的前端,返回true。 |
previous() | 返回前一個元素,並將迭代器向後移動一個位置。 |
peekPrevious() | 返回前一個元素,不移動迭代器。 |
另外,上面我們就說過,QListIterator是隻讀迭代器,所以,我們無法使用該迭代器在遍歷的過程中進行插入或刪除操作。要使用這種功能,必須使用QMutableListIterator。下面的例子展示了使用QMutableListIterator來刪除QList中所有的奇數:
QMutableListIterator<int> i(list);
while (i.hasNext()) {
if (i.next() % 2 != 0)
i.remove();
}
在上面的迴圈中,每次迴圈都呼叫了next()函式。這會跳過列表中的下一個元素。remove()函式會從連結串列中刪除我們之前跳過的那個元素。並且,remove()函式不會使迭代器失效,我們可以安全的繼續使用它。對於,從後向前遍歷也是一樣,如下程式碼所示:
QMutableListIterator<int> i(list);
i.toBack();
while (i.hasPrevious()) {
if (i.previous() % 2 != 0)
i.remove();
}
如果我們只是想修改一個現存的元素,我們可以使用setValue()函式。如下面的程式碼,我們用128替換容器中大於128的元素:
QMutableListIterator<int> i(list);
while (i.hasNext()) {
if (i.next() > 128)
i.setValue(128);
}
類似於remove()函式,setValue()也是工作在我們剛跳過的元素上。如果我們是向前遍歷,該元素就是當前迭代器之前的那個元素;如果我們是向後遍歷,該元素就是當前迭代器之後的那個元素。
其實,next()函式會返回一個元素的非常量引用。所以,對應簡單的操作,我們不需要呼叫setValue()函式,而是直接進行相應修改即可。如下程式碼:
QMutableListIterator<int> i(list);
while (i.hasNext())
i.next() *= 2;
我們上面提到過,QLinkedList,QVector,QSet的迭代器操作和QList完全一下。那麼,下面我們就來看一下QMapIterator,因為該迭代器是工作在key-value對上,所以和上面講的有點不同。
類似於QListIterator,QMapIterator也提供了toFront(),toBack(),hasNext(),next(),peekNext(),hasPrevious(),peekPrevious()。至於具體的key和value,我們可以呼叫key() 和 value() 函式,從next(),peekNext(),previous()或者peekPrevious()返回的物件中提取。
下面的例子中,我們從map中刪除所有capital以"City" 結尾的(capital,country)對:
QMap<QString, QString> map;
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() 函式,可以直接作用於迭代器本身,返回上次跳過的元素的鍵和值。例如,下面的程式碼將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());
}
如果你想迭代所有具有特定值的元素,可以使用findNext()或findPrevious()。在下面的例子中,我們從容器中刪除具有特定值的所有項:
QMutableMapIterator<int, QWidget *> i(map);
while (i.findNext(widget))
i.remove();
STL風格的迭代器:
STL風格的迭代器,在Qt 2 中就存在了。它們兼容於Qt和STL的通用演算法,並且在訪問速度上進行了優化。
同樣,每一種容器也都提供了兩種型別的STL風格迭代器:只讀迭代器和讀寫迭代器。我們應儘量使用只讀迭代器,因為它們更快。
我們同樣用一張表來列舉每一種STL風格的迭代器:
Containers | Read-only iterator | Read-write iterator |
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迭代器的API在每一個類中也都有詳細的說明。比如,++運算子會將迭代器前進到下一個元素,*運算子返回迭代器所指向的元素。事實上,對QVector和QStack來說,由於它們的元素都是儲存在連續的記憶體中,所以它們的迭代器型別就是T*,它們的只讀迭代器型別就是const T*。下面,我們還是以QList和QMap為例還說明stl風格的迭代器的使用方法。
下面的例子程式碼,是使用STL風格的迭代器遍歷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風格的迭代器來說,有效的迭代器位置:
同樣,使用STL風格的迭代器做反向遍歷的程式碼如下:
QList<QString> list;
list << "A" << "B" << "C" << "D";
QList<QString>::reverse_iterator i;
for (i = list.rbegin(); i != list.rend(); ++i)
*i = i->toLower();
}
到目前為止,我們在程式碼中都是使用一元運算子*來提取某個迭代器位置的元素內容,然後呼叫QString;:toLower()。其實,大部分c++編譯器還允許我們使用i->toLower()的形式。對於只讀迭代器,可以使用const_iterator,例如:
QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug() << *i;
接下來,我們也用一張表來總結一下stl風格的迭代器的相關操作:
*i | 返回當前元素 |
++i | 步進迭代器到下一個元素位置 |
i += n | 將迭代器向前步進n個元素 |
--i | 步進迭代器到前一個位置 |
i -= n | 將迭代器向前步進n個位置 |
i - j | 返回 迭代器 i 和 j之間的元素個數 |
對於++和--操作,既支援前++,也支援後++,--也一樣。至於這兩者的區別,我相信大家都能理解,在此就不解釋了。
對於非const迭代器型別,*運算子返回的值可以被當做左值來使用。
對於QMap和QHash來說,*運算子返回一個元素的value部分。如果你想獲得key,可以在迭代器上呼叫key() 方法。而處於對稱性,迭代器還提供了value() 方法來獲得value()值。例如,下面的程式碼說明了怎麼打印出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風格的迭代器來迭代這些容器,你應該先拿到該容器的一份拷貝,然後遍歷這份拷貝。例如:
// 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)
...
foreach 關鍵字
如果你只是想順序的變數容器中的所以元素,可以使用Qt的foreach關鍵字。這個關鍵字是Qt特定的,是使用前處理器實現的。
它的語法是:foreach(variable, container) statement。例如,下面的程式碼說明了怎麼使用foreach來迭代QLinkedList<QString>:
QLinkedList<QString> list;
...
QString str;
foreach (str, list)
qDebug() << str;
使用foreach的程式碼,通常都會比使用迭代器寫出的程式碼更短:
QLinkedList<QString> list;
...
QLinkedListIterator<QString> i(list);
while (i.hasNext())
qDebug() << i.next();
如果容器中的資料型別不包括逗號,那麼,我們還可以將遍歷容器所用的變數定義在foreach內部。如下所示:
QLinkedList<QString> list;
...
foreach (const QString &str, list)
qDebug() << str;
同樣,類似於c++的for迴圈,當有多條語句時,也可以使用花括號和break關鍵字:
QLinkedList<QString> list;
...
foreach (const QString &str, list) {
if (str.isEmpty())
break;
qDebug() << str;
}
而對於QMap和QHash來說,foreach會訪問其中儲存的key-value對的value部分。如果你想同時獲得key和value,可以使用迭代器,或者先獲得其中的key,在通過key取到對應的值。如下程式碼所示:
QMap<QString, int> map;
...
foreach (const QString &str, map.keys())
qDebug() << str << ':' << map.value(str);
而對於多值關聯的map來說,可以使用兩個foreach,如下方式訪問:
QMultiMap<QString, int> map;
...
foreach (const QString &str, map.uniqueKeys()) {
foreach (int i, map.values(str))
qDebug() << str << ':' << i;
}
在進入一個foreach迴圈時,Qt會自動拿到容器的一個拷貝。所以,如果你在foreach的過程中,修改了容器,並不會影響這個迴圈。而因為foreach會創建出一份容器的拷貝,所以使用一個非常量引用並不會使你能夠修改原始容器。而僅僅會影響到拷貝,這可能不是你想要的結果。
所以,相對於Qt的foreach,一個可選的方案是C++11中的基於範圍的for迴圈。但是,要注意的是,基於範圍的for迴圈可以會強制一個Qt容器脫離隱式共享,而foreach不會。但是,使用foreach總是會拷貝容器,這個代價對STL的容器來說,通常是昂貴的。所以,一般,我們可以對Qt容器使用foreach關鍵字,而對於STL的容器使用基於範圍的for迴圈。而除了上面的foreach之外,Qt還為無限迴圈提供了一個偽關鍵字:forever。使用如下:
forever {
//一直執行的程式碼
}
當然,如果你擔心這些Qt特定的關鍵字會導致名稱空間的汙染,也可以禁用跌這些巨集。只需在.pro檔案中新增如下一句即可:
CONFIG += no_keywords
其他的類容器類
Qt中包含三個模板類,其在某些方面類似於容器。但這些類不提供迭代器,也不能用於foreach關鍵字。
- QVarLengthArray<T, Prealloc>:該類提供了一個低階的變長陣列。在某些非常看重訪問速度的情況下,可以使用該類替代QVector。
- QCache<Key, T>:該類提供了一個儲存key-value對的快取
- QContiguousCache<T>:該類提供了一種高效的快取資料的方式,其是使用連續的記憶體進行訪問。
- QPair<T1, T2>:用來儲存元素對。
演算法複雜度是當容器中的元素增多時每一個函式的執行速度是多塊或多慢。例如,在QLinkedList的中間插入一個元素是一個非常快速的操作,而不管當前連結串列中有多少元素。另一方面,在QVector的中間插入一個元素效率就是非常低下的,特別是當QVector中已經有了大量的元素,因為,這個操作會導致QVector中一半的元素都要在記憶體中移動一個位置。
為了描述演算法複雜度,我們使用以下幾個術語,基於"big O":
- 常量時間:O(1)。若無論容器中有多少元素,一個函式總能在相同的時間內執行完,那麼這個函式就是常量時間複雜度的。例如,QLinkedList::insert()。
- 對數時間:O(log n)。一個函式以對數時間執行,是說它的執行時間是和容器中的元素的個數成對數相關的。例如,二分查詢qBinaryFind()。
- 線性時間:O(n)。一個函式以線性時間執行,是說它的執行時間和容器中儲存的元素個數成正相關。例如QVector::insert()。
- 線性對數時間:O(nlog n)。一個函式以線性對數時間執行,是說它的執行會隨著容器中元素個數的增多逐漸慢於線性時間函式,但快於二次方複雜度的函式。
- 二次方時間:O(n2)。一個函式以二次方時間複雜度執行,是說它的執行時間和容器中元素的個數的二次方成正相關。
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. O(1)"意思是如果你只調用函式一次,複雜度可能是O(n) ,但是如果你多次呼叫函式,平均複雜度則為O(1)。
下面的表格總結了Qt中的關聯容器的演算法複雜度:
對於QVector,QHash,和QSet,追加一個元素的效能平均是O(n)。不過,我們可以在真正插入元素之前,使用將要儲存的元素數目來呼叫QVector::reserve(),QHash::reserve()或者QSet::reserve()把這個複雜度降低到O(1)。
生長策略
QVector<T>,QString,和QByteArray會將它們的內容儲存在連續的記憶體區中。QList<T>維護一個指向所儲存元素的指標陣列,以此提供快速的基於下標的訪問(除非T是一個 指標型別或一個大小等於指標大小的基本型別,在這種情況下,元素本身會被儲存在數組裡面。);QHash<Key, T>儲存一個hash表,其大小和元素的個數有關。同時,為了避免每次新增元素都導致記憶體的重新分配,這個容器類通常會分配比實際情況更多的記憶體。
考慮下面的程式碼段,其從一個QString構建了一個QString:
QString onlyLetters(const QString &in)
{
QString out;
for (int j = 0; j < in.size(); ++j) {
if (in[j].isLetter())
out += in[j];
}
return out;
}
我們動態的構建了一個QString out物件,使用一次追加一個字元的方式。我們先假定要向QString追加15000個字元。在此過程中會進行18次記憶體的重新分配(而不是15000次),分別是:4,8,16,20,52,116,244,500,1012,2036,4084,6132,8180,10288,12276,14324,16372。到最後,QString物件有16372個Unicode字元空間被分配,其中的15000個被佔用。
上面的這些記憶體數值可能很奇怪,但下面有一些指導原則:
- QString會一次分配4個字元,直到其大小達到20。
- 從20到4084,按每次擴大一倍的方式增長。更準確的說,它增長到下一個2的n次方,在減去12。20=2^5-12,52=2^6-12,等等。(減去12是因為有些記憶體分配器需要 使用一些位元組為每個記憶體塊做簿記。)
- 從4048開始,每次增加2048個字元(4096個位元組)。這是有意義的,因為現代的作業系統在重新分配一個buffer時並不會拷貝所有的資料;只是實體記憶體頁的簡單排序,只有位於第一頁和最後一頁的資料需要拷貝。
- capacity():基於已分配的記憶體,返回元素的個數(對QHash和QSet來說,就是雜湊表中桶的數量)
- reserve(size):顯式的預分配size個元素的記憶體
- squeeze():釋放為儲存元素的多餘記憶體。