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)。一個函數以二次方時間復雜度運行,是說它的運行時間和容器中元素的個數的二次方成正相關。
下面這個表格,總結了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. 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時並不會拷貝所有的數據;只是物理內存頁的簡單排序,只有位於第一頁和最后一頁的數據需要拷貝。
QByteArray和QList使用的分配算法和QString類似。
至於QVector,當其中存儲的數據類型是可以使用memcpy()函數在內存移動時(包括C++基本類型,指針類型,Qt的共享類),也使用和QString類似的分配算法。但當其中存儲的類型只有通過調用構造函數和析構函數才能在內存中移動時,會使用不同的分配算法。因為在這種情況下,內存重分配的代價很高,QVector會在內存用盡后總是增長一倍的內存,從而減少內存重分配的次數。
QHash<Ket, T>是完全不同的情況。QHash內部的哈希表已2的次方增長,並且每次增長時,其中的元素會被重新分配到一個新的桶中,計算方式為qHash(key) % QHash::capacity()(桶的數量)。這個方式也應用於QSet和QCache。

對大部分應用程序來說,Qt提供的默認生長算法就足夠了。如果你需要更多的控制,QVector,QHash,QSet,QString和QByteArray提供了三個函數,允許你去檢查和指定你需要多少內存去存儲元素:
capacity():基於已分配的內存,返回元素的個數(對QHash和QSet來說,就是哈希表中桶的數量)
reserve(size):顯式的預分配size個元素的內存
squeeze():釋放為存儲元素的多余內存。
如果你知道大約將在容器中存儲多少個元素,你可以在開始插入元素前調用reserve(),預分配好需要的空間;然后,在完全插入元素后,可以調用squeeze()來釋放多余的內存。

---------------------
作者:求道玉
來源:CSDN
原文:https://blog.csdn.net/Amnes1a/article/details/66478376
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM