C++9.4 vector容器的自增長(size、capacity、reserve)

簡介

  • vector容器,順序存儲,需要進行內存的分配,內存容量自動在增長,插入刪除麻煩

================================================================================================================================================================

一、vector容器的自增長

  • 在容器對象中insert或壓入一個元素時,該對象的大小增加1 。

  • 如果resize容器以擴充其容量,則必須在容器中添加額外的元素。標準庫處理存儲這些新元素的內存分配問題(最後面的例子)

  • 爲了支持快速的隨機訪問,vector容器的元素以連續的方式存放—-每一個元素都緊挨着前一個元素存儲。

  • 已知元素是連續存儲的,當我們在容器內添加一個元素時,如果容器中已經沒有空間容納新的元素,此時,由於元素必須連續存儲以便索引訪問,所以不能在內存中隨便找個地方存儲這個新元素。於是,vector必須重新分配存儲空間,用來存放原來的元素以及新添加的元素:存放在舊存儲空間中的元素被複制到新存儲空間裏, 接着插入新元素,最後撤銷舊的存儲空間。如果vector容器在每次添加新元素時,都要這麼分配和撤銷內存空間,其性能將會非常慢,簡直無法接受。(vector容器,順序存儲,需要進行內存的分配,插入刪除麻煩)

  • 對於不連續存儲元素的容器,不存在這樣的內存分配問題。例如,在list容器中添加一個元素,標準庫只需創建一個新元素,然後將該元素連接在已存在的鏈表中,不需要重新分配存儲空間,也不必複製任何已經存在的元素。(鏈式存儲,插入刪除方便,不需要進行內存分配)

  • 一般而言,使用list容器優於vector容器,但是,對於大部分應用,使用vector容器是最好的。原因在於,標準庫的實現者使用這樣的內存分配策略:以最小的代價連續存儲元素。訪問元素的便利彌補了其存儲的代價。

  • 爲了使vector容器實現快速的內存分配,所謂內存分配就是在剛創建一個容器時,對其進行內存分配,其實際分配的容量要比當前所需的空間多一些。vector容器預留了這些額外的存儲區,用於存放新添加的元素。於是,不必爲每個新元素重新分配容器。所分配的額外內存容量的確切數目因庫的實現不用而不同(有的是原容量加倍,有的是原容量的3/2)。比起每添加一個新元素就必須重新分配一次容器,這個分配策略效率高。 所以,比起list和deque容器,vector的增長效率更高

1.vector實現內存分配用到的——-capacity 和 reserve成員

  • vector容器處理內存分配細節是其實現的一部分,該實現部分是由vector的接口支持的。

  • vector類提供了兩個成員函數:capacity 和 reserve,使程序員可與vector容器內存分配的實現部分交互工作。

  • size 指容器當前擁有的元素個數,而capacity 則指容器在必須分配新存儲空間之前可以存儲的元素總數。

  • reserve函數的功能是設置預留多大的內存空間

vector<int> ivec;
cout<<"ivec:size:"<<ivec.size()
    <<"capacity:"<<ivec.capacity()<<endl;

for(vector<int>::size_type ix=0;ix!=24;ix++){
   ivec.push_back(ix);
}

//size 是24,capacity大於24
cout<<"ivec:size:"<<ivec.size()
    <<"capacity:"<<ivec.capacity()<<endl;
// 結果:
// 0     0
// 24    32
  • 空的vector容器的size是0,而標準庫顯然將其capacity也設置爲0.

  • 當程序員在vector中插入元素時,容器的size就是所添加元素的個數,而其capacity則必須至少等於size,但通常大於size。

  • 在上述程序中,一次添加一個元素,共添加24個元素,結果其capacity爲32 . ivec容器當前狀態如下圖所示:

//設置預留額外的存儲空間

ivec.reserve(50);//設置預留空間至少50

cout<<"ivec:size:"<<ivec.size()
    <<"capacity:"<<ivec.capacity()<<endl;
//結果: 24   50

//size大小不變
while(ivec.size()!=ivec.capacity())
  ivec.push_back(0);
cout<<"ivec:size:"<<ivec.size()
    <<"capacity:"<<ivec.capacity()<<endl; 
  //結果:
  //50   50
//因爲使用的是預留的容量,所以vector不必做任何的內存分配工作,事實上,只要還有剩餘的容量,vector就不必爲其元素重新分配存儲空間    
  • 因爲使用的是預留的容量,所以vector不必做任何的內存分配工作,事實上,只要還有剩餘的容量,vector就不必爲其元素重新分配存儲空間

  • 從上述程序可以看出,我們已經耗盡了預留的容量因爲 size與capacity值相等。此時,如果要再添加新的元素,vector必須爲自己重新分配存儲空間。一種策略是重新分配的容量的增幅爲原容量的1/2,即 等於3/2原容量。還有策略就是在原來容量基礎上加倍

ivec.push_back(42);//之前分配的所有空間已經耗盡了,此時再添加一個新的元素。
cout<<"ivec:size:"<<ivec.size()
    <<"capacity:"<<ivec.capacity()<<endl; 
//結果
// 51      100
//添加新的元素,vector必須爲自己重新分配存儲空間,重新分配的容量大小是加倍當前容量
  • 當預留空間全部使用完,如果我們還要往裏面添加新的元素時,vector會重新分配存儲空間,而且,以加倍當前容量的分配策略實現重新分配(另外的內存分配的策略)

  • vector的每種實現都可以自由地選擇自己的內存分配策略,然而,他們都必須提供reserve和capacity函數,而且必須是到必要時才分配新的內存空間。分配多少內存取決於其實現方式,不同庫採用不同的策略實現

  • 此外,每種實現都要求遵循以下原則:確保push_back操作高效得在vector中添加元素。從技術上來說,在原來爲空得vector容器上n次調用push_back函數,從而創建擁有n個元素的vector容器,其執行時間永遠不能超過n的常數倍、

例子 (很重要) 習題9.32

//下面容量自動分配時,標準庫採用採取增幅爲當前容量的1/2的分配策略(3/2 *原容量)。
//resize函數的使用
vector<string> svec;
cout<<"ivec:size:"<<svec.size()<<"capacity:"<<svec.capacity()<<endl; 

svec.reserve(1024);//分配1024個內存大小
string text_word;
while(cin>>text_word)
   svec.push_back(text_word);
cout<<"ivec:size:"<<svec.size()<<"capacity:"<<svec.capacity()<<endl; 


svec.resize(svec.size()+svec.size()/2);
cout<<"ivec:size:"<<svec.size()<<"capacity:"<<svec.capacity()<<endl; 
//解析該例子

//創建空的vector對象後,該對象的容量爲0,將其容量設置爲1024個元素,然後從標準輸入設備讀入一系列單詞,最後將該vector對象的大小調整爲所讀入單詞個數的3/2、

//如果程序讀入的單詞個數爲256或512,那麼size的大小分別是256和512.而capacity的大小都是1024。因爲他們都小於對象所分配的最大的容量空間。

//下面執行resize函數的操作,因爲resize函數的操作是調整容器內元素的個數,跟容量沒有直接的關係。讀入256或512時,resize之後分別是:384和768都是小於容器的容量1024的,所以容量不會變。如果容器的大小沒有超出已分配的內存容量,從而調整大小隻改變容器中有效的元素的個數,不會改變容量。

//如果讀入單詞個數爲1000,讀單詞時不會產生容器內存不夠情況,在使用resize函數會發生,使用resize函數後,需要1500個存儲空間,已經超過了已經分配的容量(1024),所以容器會進行內存的重新分配,重新分配的容量是(3/2*原來的容量)即1536(1024+512)。

//如果讀入的單詞個數是1048,則在讀第1025個單詞時,因爲用完已經分配的容量(1024),該容器的容量可能會增長爲1536(1024+512)。resize調整大小後,需要的大小是1572個元素的存儲空間。超過已經分配的容量1536.會再次重新分配,容量再次增長1/2*1536=768 現在的容量是1536+768=2304
//習題9.37  涉及到string類型的一些操作(9.6節)
//假設希望一次讀取一個字符並寫入string對象,而且已知需要讀入至少100個字符,考慮如何提高程序性能?

//因爲string對象中字符是連續存儲的。所以爲了提高性能,應該事先將對象的容量指定爲至少100個字符大小,
//以避免多次進行內存的重新分配,可以使用reserve函數(9.4節)
#include <iostream>
#include<string>
#include<cctype>
#include<vector>
using namespace std;
int main()
{
    string str;
    str.reserve(100);//使用reserve函數預留內存大小
    char strr;
    while(cin>>strr)
    str.push_back(strr);
    cout<<str<<endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章