Vector用法(C++ Primer中文版)
vector 是同一種類型的對象的集合,每個對象都有一個對應的整數索引值。和 string 對象一樣,標準庫負責管理存儲元素的相關內存。我們把 vector 稱為 容器 ,是因為它可以包含其他對象。一個容器中的所有對象都必須是同一種類型的。我們將在第 9 章更詳細地介紹容器。
使用 vector 之前,必須包含相應的頭文件。本書給出的例子,都是假設已作了相應的 using 聲明:
#include <vector>
using std::vector;
vector 是一個 類模板 ( class template )。模板允許程序員編寫單個類或函數定義,這個類和函數定義可用于不同的數據類型上。因此,我們可以定義保存 string 對象的 vector ,或保存 int 值的 vector ,又或是保存自定義的類類型對象(如 Sales_item 對象)的 vector 。將在第 16 章介紹如何定義程序員自己的類模板。幸運的是,使用類模板時只需要簡單了解類模板是如何定義的就可以了。
聲明從類模板產生的某種類型的對象,需要提供附加信息,信息的種類取決于模板。以 vector 為例,必須說明 vector 保存何種對象的類型,通過將類型放在類模板名稱后面的尖括號中來指定類型:
vector<int> ivec;?????????????? ? // ivec holds objects of type int
vector<Sales_item> Sales_vec;? // holds Sales_items
和其他變量定義一樣,定義 vector 對象要指定類型和一個變量的列表。上面的第一個定義,類型是 vector<int> ,該類型即是含有若干 int 類型對象的 vector ,變量名為 ivec 。第二個定義的變量名是 Sales_vec ,它所保存的元素是 Sales_item 類型的對象。
vector 不是一種數據類型,而只是一個類模板,可用來定義任意多種數據類型。 vector 類型的每一種都指定了其保存元素的類型。因此, vector<int> 和 vector <string> 都是數據類型。
3.3.1? vector 對象的定義和初始化
vector 類定義了好幾種構 造函數( 2. 3. 3 節) ,用來定義和初始化 vector 對象。表 3 - 4 列出了這些構造函數:
表 3-4? 幾種初始化 vector 對象的方式
| vector <T >? v1 ; | vector 保存類型為 T 的對象。默認構造函數 v1 為空。 |
| vector < T > v2 ( v1 ); | v2 是 v1 的一個副本。 |
| vector < T > v3 ( n , i ); | v3 包含 n 個值為 i 的元素。 |
| vector < T > v4 ( n ); | v4 含有值初始化的元素的 n 個副本。 |
1. 創建確定個數的元素
若要創建非空的 vector 對象,必須給出初始化元素的值。當把一個 vector 對象復制到另一個 vector 對象時,新復制的 vector 中每一個元素都初始化為原 vector 中相應元素的副本。但這兩個 vector 對象必須保存同一種元素類型 :
vector<int> ivec1;?????????? ???? // ivec1 holds objects of type int
vector<int> ivec2(ivec1);?? ??? // ok: copy elements of ivec1 into ivec2
vector<string> svec(ivec1);??? // error: svec holds strings, not ints
可以用元素個數和元素值對 vector 對象進行初始化。構造函數用元素個數來決定 vector 對象保存元素的個數,元素值指定每個元素的初始值:
vector<int> ivec4(10, -1);???? // 10 elements, each initialized to -1
vector<string> svec(10, "hi!"); // 10 strings, each initialized to "hi!"
關鍵概念: vector 對象動態增長 ???????????????????????????????????????????????????????
vector 對象(以及其他標準庫容器對象)的重要屬性就在于可以在運行時高效地添加元素。因為 vector 增長的效率高,在元素值已知的情況下,最好是動態地添加元素。
正如第 4 章將介紹的,這種增長方式不同于 C 語言 中的內置數據類型,也不同于大多數其他編程語言的數據類型。特別地,如果讀者習慣了 C 或 Java 的風格,由于 vector 元素連續存儲,可能希望最好是預先分配合適的空間。但事實上,為了達到連續性, C ++ 的做法恰好相反,具體原因將在第 9 章探討。
雖然可以對給定元素個數的 vector 對象預先分配內存,但更有效的方法是先初始化一個空 vector 對象,然后再動態地增加元素(我們隨后將學習如何進行這樣的操作)。
?
2. 值初始化
如果沒有給出元素的初始化式,那么標準庫將提供一個 值初始化的 ( value initialized )元素初始化式。這個由庫生成的初始值用于初始化容器中的每個元素。而元素初始化式的值取決于存儲在 vector 中元素的數據類型。
如果 vector 保存內置類型(如 int 類型) 的元素,那么標準庫將用 0 值創建元素初始 化值:
vector<string> fvec(10); // 10 elements, each initialized to 0
如果向量保存類類型(如 string )的元素,標準庫將用該類型的默認構造函數 創建 元素初始值:
vector<string> svec(10); // 10 elements, each an empty string
第 12 章將介紹一些有自定義構造函數但沒有默認構造函數的類,在初始化這種類型的 Vector 對象時,程序員就不能僅提供元素個數,還需要提供元素初始值。
還有第三種可能性:元素類型可能是沒有定義任何構造函數的類類型。這種情況下,標準庫仍產生一個帶初始值的對象,這個對象的每個成員進行了值初始化。
習題 ???????? ??????????????????????????????????????????????????
習題 3.11 ? 下面哪些 vector 定義不正確?
?? ( a ) vector < vector < int > > ivec ;
?? ( b ) vector < string > svec = ivec ;
?? ( c ) vector < string > svec ( 10 ,” null ”);
習題 3.12 ? 下列每個 vector 對象中元素個數是多少?各元素的值是什么?
?? (a) vector<int> ivec1;
?? (b) vector<int> ivec2(10);
?? (c) vector<int> ivec3(10,42);
?? (d) vector<string> svec1;
?? (e) vector<string> svec2(10);
?? (f) vector<string> svec3(10,”hello”);
3.3.2 ? vector 的操作
vector 標準庫提供許多類似于 string 對象的操作,表 3 - 5 列出了幾種最重要的 vector 操作。
表 3-5? vector 操作
| v. empty() | 如果 v 為空,則返回 true, 否則返回 false 。 |
| v . size () | 返回 v 中元素的個數。 |
| v . push _ back ( t ) | 在 v 的末尾增加一個值為 t 的元素。 |
| v [ n ] | 返回 v 中位置為 n 的元素。 |
| v1 = v2 | 把 v1 的元素替換為 v2 中元素的副本。 |
| v1 == v2 | 如果 v1 與 v2 相等,則返回 true 。 |
| !=, <, <=, >, >= | 保持這些操作符慣有的含義。 |
1. vector 對象的 size
empty 和 size 操作類似于 string 類型的相關操作( 3 . 2 . 3 節)。成員函數 size 返回相應 vector 類定義的 size_type 的值。
使用 size_type 類型時,必須指出該類型是在哪里定義的。 vector 類型總是 包括 vector 的元素類型:
vector<int>::size_type??? ??? // ok
vector::size_type????????? // error
2. 向 vector 添加元素
push_back() 操作接受一個元素值,并將它作為一個新的元素添加到 vector 對象的后面,也就是“ 插入 ( push )” 到 vector 對象的 “ 后面 ( back ) ” :
// read words from the standard input and store them as elements in a vector
string word;
vector<string> text;??? ??? // empty vector
while (cin >> word) {
??? text.push_back(word);? // append word to text
}
該循環 從標準輸入讀取一系列 string 對象,逐一追加到 vector 對象的后面。首先定義一個空的 vector 對象 text 。每循環一次就添加一個新元素到 vector 對象,并將從 輸入 讀取的 word 值賦予該元素。當循環結束時, text 就包含了所有讀入的元素。
3. vector 的下標操作
vector 中的對象是沒有命名的,可以按 vector 中對象的位置來訪問它們。通常使用下標操作符來獲取元素。 vector 的下標操作類似于 string 類型的下標操作 ( 3 .2 .3 節 ) 。
vector 的下標操作符接受一個值,并返回 vector 中該對應位置的元素。 vector 元素的位置從 0 開始。下例使用 for 循環把 vector 中的每個元素值都重置為 0 :
// reset the elements in the vector to zero
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
??? ivec[ix] = 0;
和 string 類型的下標操作符一樣, vector 下標操作的結果為左值,因此可以像循環體中所做的那樣實現寫入。另外,和 string 對象的下標操作類似,這里用 size_type 類型作為 vector 下標的類型。
在上例中,即使 ivec 為空, for 循環也會正確執行。 ivec 為空則調用 size 返回 0 ,并且 for 中的測試比較 ix 和 0 。第一次循環時,由于 ix 本身就是 0 ,則條件測試失敗, for 循環體一次也不執行。
關鍵概念:安全的泛型編程 ???????????????????????????????????????????????????????
習慣于 C 或 Java 編程的 C ++ 程序員可能會覺得難以理解, for 循環的判斷條件用 != 而不是用 < 來測試 vector 下標值是否越界。 C 程序員難以理解的還有,上例中沒有在 for 循環之前就調用 size 成員函數并保存其返回的值,而是在 for 語句頭中調用 size 成員函數。
C ++ 程序員習慣于優先選用 != 而不是 < 來編寫循環判斷條件。在上例中,選用或不用某種操作符并沒有特別的取舍理由。學習完本書第二部分的泛型編程后,你將會明白這個習慣的合理性。
調用 size 成員函數而不保存它返回的值,在這個例子中同樣不是必需的,但這反映了一個良好的編程習慣。在 C ++ 中,有些數據結構(如 vector )可以動態增長。上例中循環僅需要讀取元素,而不需要增加新的元素。但是,循環可以容易地增加新元素,如果確實增加了新元素的話,那么測試已保存的 size 值作為循環的結束條件就會有問題,因為沒有將新加入的元素計算在內。所以我們傾向于在每次循環中測試 size 的當前值,而不是在進入循環時,存儲 size 值的副本。
我們將在第 7 章學習到, C ++ 中有些函數可以聲明為內聯( inline )函數。編譯器遇到內聯函數時就會直接擴展相應代碼,而不是進行實際的函數調用。像 size 這樣的小庫函數幾乎都定義為內聯函數,所以每次循環過程中調用它的運行時代價是比較小的。
?
4. 下標操作不添加元素
初學 C ++ 的程序員可能會認為 vector 的下標操作可以添加元素,其實不然:
vector<int> ivec;?? // empty vector
for (vector<int>::size_type ix = 0; ix != 10; ++ix)
???? ivec[ix] = ix; // disaster: ivec has no elements
上述程序試圖在 ivec 中插入 10 個新元素,元素值依次為 0 到 9 的整數。但是,這里 ivec 是空的 vector 對象,而且下標只能用于獲取已存在的元素。
這個循環的正確寫法應該是:
for (vector<int>::size_type ix = 0; ix != 10; ++ix)
???? ivec.push_back(ix);? // ok: adds new element with value ix
?
?
| ? | |
| ? |
?
必須是已存在的元素才能用下標操作符進行索引。通過下標操作進行賦值時,不會添加任何元素。
警告:僅能對確知已存在的元素進行下標操作 ???????????????????????????????????????????
對于下標操作符 ( [] 操作符 ) 的使用有一點非常重要,就是僅能提取確實已存在的元素,例如:
vector<int> ivec;????? // empty vector
cout << ivec[0];? ????? // Error: ivec has no elements!
?
vector<int> ivec2(10); // vector with 10 elements
cout << ivec[10];????? // Error: ivec has elements 0...9
試圖獲取不存在的元素必然產生運行時錯誤。和大多數同類錯誤一樣,不能確保執行過程可以捕捉到這類錯誤,運行程序的結果是不確定的。由于取不存在的元素的結果是未定義的,因而不同的實現會導致不同的結果,但程序運行時幾乎肯定會以某種有趣的方式失敗。
本警告適用于任何使用下標操作的時候,如 string 類型的下標操作,以及將要簡要介紹的內置數組的下標操作。
不幸的是,試圖對不存在的元素進行下標操作是程序設計過程中經常會犯的嚴重錯誤。所謂的“緩沖區溢出”錯誤就是對不存在的元素進行下標操作的結果。這樣的缺陷往往導致 PC 機和其他應用中最常見的安全問題。
習題 ??????????????????????????????????????????????????????????
習題 3.13? 讀一組整數到 vector 對象,計算并輸出每對相鄰元素的和。如果讀入元素個數為奇數,則提示用戶最后一個元素沒有求和,并輸出其值。然后修改程序:頭尾元素兩兩配對(第一個和最后一個,第二個和倒數第二個,以此類推),計算每對元素的和,并輸出。
習題 3.14? 讀入一段文本到 vector 對象,每個單詞存儲為 vector 中的一個元素。把 vector 對象中每個單詞轉化為大寫字母。輸出 vector 對象中轉化后的元素,每八個單詞為一行輸出。
習題 3.15? 下面程序合法嗎?如果不合法,如何更正?
?? vector <int > ivec ;
?? ivec [0 ] = 42 ;
習題 3.16 ? 列出三種定義 vector 對象的方法,給定 10 個元素,每個元素值為 42 。指出是否還有更好的實現方法,并說明為什么。
總結
以上是生活随笔為你收集整理的Vector用法(C++ Primer中文版)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么制作u盘启动电脑开机密码 制作U盘启
- 下一篇: spool用法小结