- 標準庫類型vector表示對象的集合,其中所有對象的類型都相同。集合中的每個對象都有一個與之對應的索引,索引用于訪問對象。因為vector"容納著”其他對象,所以它也常被稱作容器(container).第 II部將對容器進行更為詳細的介紹。 要想使用vector,必須包含適當的頭文件。在后續的例子中,都將假定做了如下using聲明:
- #include <vector>
- using std::vector;
- C++語言既有類模板(classtemplate),也有函數模板,其中vector是一個類模板。只有對C++有了相當深入的理解才能寫出模板,事實上,我們直到第16章才會學習如何自定義模板。幸運的是,即使還不會創建模板,我們也可以先試著用用它。
- 模板本身不是類或函數,相反可以將模板看作為編譯器生成類或函數編寫的一份說明。編譯器根據模板創建類或函數的過程稱為實例化(instantiation),當使用模板時,需要指出編譯器應把類或函數實例化成何種類型。
- 對于類模板來說,我們通過提供一些額外信息來指定模板到底實例化成什么樣的類,需要提供哪些信息由模板決定。提供信息的方式總是這樣:即在模板名字后面跟一對尖括號,在括號內放上信息。
- vector能容納絕大多數類型的對象作為其元素,但是因為引用不是對象(參見2.3.1節,第45頁),所以不存在包含引用的vectoro除此之外,其他大多數(非引用)內置類型和類類型都可以構成vector對象,甚至組成vector的元素也可以是vector。需要指出的是,在早期版本的C++標準中如果vector的元素還是vector(或者其他模板類型),則其定義的形式與現在的C++11新標準略有不同。過去,必須在外層vector對象的右尖括號和其元素類型之間添加一個空格,如應該寫成vector<vector<int> >而非vector<vector<int>>現在大可不必
3.3.1定義和初始化vector對象
- 和任何一種類類型一樣,vector模板控制著定義和初始化向量的方法。表3.4列出了定義vector對象的常用方法。
- 可以默認初始化vector對象(參見2.2.1節,第 40頁),從而創建一個指定類型的空 vector:
- vector<string> svec; //默認初始化,svec不含任何元素? 最好使用空的花括號進行默認初始化?vector<string> svec{};
- 看起來空vector好像沒什么用,但是很快我們就會知道程序在運行時可以很高效地往 vector對象中添加元素。事實上,最常見的方式就是先定義一個空vector,然后當運行時獲取到元素的值后再逐一添加。當然也可以在定義vector對象時指定元素的初始值。例如,允許把一個vector對 象的元素拷貝給另外一個vector對象。此時,新 vector對象的元素就是原vector 對象對應元素的副本。注意兩個vector對象的類型必須相同
- vector<int> ivec; / / 初始狀態為空
- / / 在此處給ivec添加一些值
- vector<int> ivec2 (ivec) ; // 把 ivec 的元素拷貝給 ivec2
- vector<int> ivec3 = ivec; // 把 ivec 的元素拷貝給 ivec3
- vector<string> svec (ivec2) ; // 錯誤:svec 的元素是 string 對象,不是 int
列表初始化vector對象
- C++新標準還提供了另外一種為vector對象的元素賦初值的方法,即列表初始化(參見2.2.1節,第39頁)。此時,用花括號括起來的0個或多個初始元素值被賦給vector對象:
- vector<string>articles=(”au”,"an","the'};
- 上述vector對象包含三個元素:第一個是字符串"a",第二個是字符串"an",最后一個是字符串"the".
- 之前已經講過,C++語言提供了幾種不同的初始化方式(參見2.2.1節,第39頁)。在大多數情況下這些初始化方式可以相互等價地使用,不過也并非一直如此。目前已經介紹過的兩種例外情況是:其一,使用拷貝初始化時(即使用=時)(參見3.2.1節,第76頁),只能提供一個初始值;其二,如果提供的是一個類內初始值(參見2.6.1節,第64頁),則只能使用拷貝初始化或使用花括號的形式初始化。第三種特殊的要求是,如果提供的是初始元素值的列表,則只能把初始值都放在花括號里進行列表初始化,而不能放在圓括號里:
- vector<string> vl { naM , "an”, "the”}; // 列表初始化
- vector<string> v2 ("a*1, "an”, "the"); // 錯誤
創建指定數量的元素
- 還可以用vector對象容納的元素數量和所有元素的統一初始值來初始化vector對象:
- vector<int> ivec (10, -1) ; // 10個 int類型的元素,每個都被初始化為-1
- vector<string> svec (10, "hi!”); // 10 個 string 類型的元素,每個都被初始化為"hi!”
值初始化
- 通常情況下,可以只提供Vector對象容納的元素數量而不用略去初始值。此時庫會創建一個值初始化的(value-initialized)元素初值,并把它賦給容器中的所有元素。這個初值由vector對象中元素的類型決定。如果vector對象的元素是內置類型,比如int,則元素初始值自動設為0。如果元素是某種類類型,比如string,則元素由類默認初始化:
- vector<int> ivec(10);// 10個元素,每個都初始化為0
- vector<string> svec(10);// 10個元素,每個都是空string對象
- 對這種初始化的方式有兩個特殊限制:
- 其一,有些類要求必須明確地提供初始值(參見2.2.1節,第40頁),如果vector對象中元素的類型不支持默認初始化,我們就必須提供初始的元素值。對這種類型的對象來說,只提供元素的數量而不設定初始值無法完成初始化工作。
- 其二,如果只提供了元素的數量而沒有設定初始值,只能使用直接初始化:
- vector<int> vi = 10; / / 錯誤:必須使用直接初始化的形式指定向量大小
- 這里的10是用來說明如何初始化vector對象的,我們用它的本意是想創建含有10個值 初始化了的元素的vector對象,而非把數字10 “拷貝”到 vector中。因此,此時不宜使用拷貝初始化,7.5.4節 (第 265頁)將對這一點做更詳細的介紹。
列表初始值還是元素數量?
- 在某些情況下,初始化的真實含義依賴于傳遞初始值時用的是花括號還是圓括號。例如,用一個整數來初始化vector<int>時,整數的含義可能是vector對象的容量也可能是元素的值。類似的,用兩個整數來初始化vector<int>時,這兩個整數可能一個是vector對象的容量,另一個是元素的初值,也可能它們是容量為2的vector對象中兩個元素的初值。通過使用花括號或圓括號可以區分上述這些含義:
- vector<int> vl(10);?// vl有 10個元素,每個的值都是0
- vector<int> v2(10};// v2有 1個元素,該元素的值是10
- vector<int>v3(10, 1);// v3有 10個元素,每個的值都是1
- vector<int> v4(10, 1}; // v4有 2個元素,值分別是10和 1
- 如果用的是圓括號,可以說提供的值是用來構造(construct)vector對象的。例如,vl的初始值說明了vector對象的容量;v3的兩個初始值則分別說明了vector對象的容量和元素的初值。
- 如果用的是花括號,可以表述成我們想列表初始化(listinitialize)該vector對象。也就是說,初始化過程會盡可能地把花括號內的值當成是元素初始值的列表來處理,只有在無法執行列表初始化時才會考慮其他初始化方式。在上例中,給v2和v4提供的初始值都能作為元素的值,所以它們都會執行列表初始化,vector對象v2包含一個元素而vector對象v4包含兩個元素。
- 另一方面,如果初始化時使用了花括號的形式但是提供的值又不能用來列表初始化,就要考慮用這樣的值來構造vector對象了。例如,要想列表初始化一個含有string對象的vector對象,應該提供能賦給string對象的初值。此時不難區分到底是要列表初始化vector對象的元素還是用給定的容量值來構造vector對象:
- 盡管在上面的例子中除了第二條語句之外都用了花括號,但其實只有v 5 是列表初始化。 要想列表初始化vector對象,花括號里的值必須與元素類型相同。顯然不能用int初始化string對象,所以v7 和 v8 提供的值不能作為元素的初始值。確認無法執行列表初始化后,編譯器會嘗試用默認值初始化vector對象。
3.3.2向vector對象中添加元素
- 對Vector對象來說,直接初始化的方式適用于三種情況:初始值已知且數量較少、初始值是另一個vector對象的副本、所有元素的初始值都一樣。然而更常見的情況是:創建一個vector對象時并不清楚實際所需的元素個數,元素的值也經常無法確定。還有些時候即使元素的初值已知,但如果這些值總量較大而各不相同,那么在創建vector對象的時候執行初始化操作也會顯得過于煩瑣。
- 舉個例子,如果想創建一個vector對象令其包含從0到9共10個元素,使用列表初始化的方法很容易做到這一點;但如果vector對象包含的元素是從0到99或者從0至999呢?這時通過列表初始化把所有元素都一一羅列出來就不太合適了。對于此例來說,更好的處理方法是先創建一個空vector,然后在運行時再利用vector的成員函數push_back向其中添加元素。push_back負責把一個值當成vector對象的尾元素“壓至(push)"vector對象的“尾端(back)”。例如:
- 在上例中,盡管知道vector對象最后會包含100個元素,但在一開始還是把它聲明成空vector,在每次迭代時才順序地把下一個整數作為v2的新元素添加給它。同樣的,如果直到運行時才能知道vector對象中元素的確切個數,也應該使用剛剛這種方法創建vector對象并為其賦值。例如,有時需要實時讀入數據然后將其賦予vector對象:
- 和之前的例子一樣,本例也是先創建一個空vector,之后依次讀入未知數量的值并保存至text 中。
向vector對象添加元素蘊含的編程假定
- 由于能高效便捷地向vector對象中添加元素,很多編程工作被極大簡化了。然而,這種簡便性也伴隨著一些對編寫程序更高的要求:其中一條就是必須要確保所寫的循環正確無誤,特別是在循環有可能改變vector對象容量的時候。隨著對vector的更多使用,我們還會逐漸了解到其他一些隱含的要求,其中一條是現在就要指出的:如果循環體內部包含有向vector對象添加元素的語句,則不能使用范圍for循環,具體原因將在5.4.3節(第168頁)詳細解釋。
- 范圍for語句體內不應改變其所遍歷序列的大小? ?因為vector在擴張的時候,指向最后的位置會失效,for循環找不到結束判斷條件
3.3.3其他vector操作
- 除了push_back之外,vector還提供了幾種其他操作,大多數都和string的相關操作類似,募3.5列出了其中比較重要的一些。
- 訪問vector對象中元素的方法和訪問string對象中字符的方法差不多,也是通過元素在vector對象中的位置。例如,可以使用范圍for語句處理vector對象中的所 有元素:
- 第一個循環把控制變量i定義成引用類型,這樣就能通過i給V的元素賦值,其中i的類型由auto關鍵字指定。這里用到了一種新的復合賦值運算符(參見1.4.1節,第10頁)。如我們所知,+=把左側運算對象和右側運算對象相加,結果存入左側運算對象;類似的,*=把左側運算對象和右側運算對象相乘,結果存入左側運算對象。最后,第二個循環輸出所有元素。
- vector的empty和size兩個成員與string的同名成員(參見3.2.2節,第78頁)功能完全一致:empty檢查vector對象是否包含元素然后返回一個布爾值;size則返回vector對象中元素的個數,返回值的類型是由vector定義的size_type類型。
- 要使用size_type,需首先指定它是由哪種類型定義的、vector對象的類型總是包含著元素的類型(參見3.3節,第87頁 ):
- vector<int>: : size_type // 正確
- vector: :size_type // 錯誤
- 各個相等性運算符和關系運算符也與string的相應運算符(參見3.2.2節,第79頁)功能一致。兩個vector對象相等當且僅當它們所含的元素個數相同,而且對應位置的元素值也相同。關系運算符依照字典順序進行比較:如果兩個vector對象的容量不同,但是在相同位置上的元素值都一樣,則元素較少的vector對象小于元素較多的vector對象;若元素的值有區別,則vector對象的大小關系由第一對相異的元素值的大小關系決定。只有當元素的值可比較時,vector對象才能被比較。一些類,如string等,確實定義了自己的相等性運算符和關系運算符;另外一些,如Sales_item類支持的運算已經全都羅列在1.5.1節(第17頁)中了,顯然并不支持相等性判味和關系運算等操作。因此,不能比較兩個vector<Sales_item>對象。
計算vector內對象的索引
- 使用下標運算符(參見323節,第84頁)能獲取到指定的元素。和string-樣,vector對象的下標也是從0開始計起,下標的類型是相應的size_type類型。只要vector對象不是一個常量,就能向下標運算符返回的元素賦值。此外,如3.2.3節(第85頁)所述的那樣,也能通過計算得到vector內對象的索引,然后直接獲取索引位置上的元素。
- 舉個例子,假設有一組成績的集合,其中成績的取值是從0到100。以10分為一個分數段,要求統計各個分數段各有多少個成績。顯然,從0到100總共有101種可能的成績取值,這些成績分布在11個分數段上:每10個分數構成一個分數段,這樣的分數段有10個,額外還有一個分數段表示滿分100分。這樣第一個分數段將統計成績在0到9之間的數量;第二個分數段將統計成績在10到19之間的數量,以此類推。最后一個分數段統計滿分100分的數量。
- 按照上面的描述,如果輸入的成績如下:42 65 95 100 39 67 95 76 88 76 83 92 76 93
- 則輸出的結果應該是:0 0 0 1 1 0 2 3 2 4 1
- 結果顯示:成績在30分以下的沒有、30分至39分有1個、40分至49分有1個、50分至59分沒有、60分至69分有2 個、70分至79分有3 個、80分至89分有2 個、90分至99分有4 個,還有1個是滿分。在具體實現時使用一個含有11個元素的vector對象,每個元素分別用于統計各個分數段上出現的成績個數。對于某個成績來說,將其除以10就能得到對應的分數段索引。
- 注意:兩個整數相除,結果還是整數,余數部分被自動忽略掉了。例如,42/10=4、65/10=6、100/10=10等。一旦計算得到了分數段索引,取該分數段的計數值并加1:就能用它作為vector對象的下標,進而獲取該分數段的計數值并加1:
- 在上面的程序中,首先定義了一個Vector對象存放各個分數段上成績的數量。此例中,由于初始狀態下每個元素的值都相同,所以我們為vector對象申請了11個元素,并把所有元素的初始值都設為0。while語句的條件部分負責讀入成績,在循環體內部首先檢查讀入的成績是否合法(即是否小于等于100分),如果合法,將成績對應的分數段的計數值加1。
- 執行計數值累加的那條語句很好地體現了C++程序代碼的簡潔性。
不能用下標形式添加元素
- 剛接觸C++語言的程序員也許會認為可以通過vector對象的下標形式來添加元素, 事實并非如此。下面的代碼試圖為vector對象ivec添加10個元素:
- vector<int> ivec; // 空 vector 對象
?