ch08-01-vectors.md
commit e7bfb353b107cb150faab9d331c99ea2b91f3725
我們要講到的第一個類型是 Vec<T>
,也被稱為 vector。vector 允許我們在一個單獨的數(shù)據(jù)結(jié)構(gòu)中儲存多于一個的值,它在內(nèi)存中彼此相鄰地排列所有的值。vector 只能儲存相同類型的值。它們在擁有一系列項的場景下非常實用,例如文件中的文本行或是購物車中商品的價格。
為了創(chuàng)建一個新的空 vector,可以調(diào)用 Vec::new
函數(shù),如示例 8-1 所示:
let v: Vec<i32> = Vec::new();
示例 8-1:新建一個空的 vector 來儲存 ?i32
?類型的值
注意這里我們增加了一個類型注解。因為沒有向這個 vector 中插入任何值,Rust 并不知道我們想要儲存什么類型的元素。這是一個非常重要的點。vector 是用泛型實現(xiàn)的,第十章會涉及到如何對你自己的類型使用它們。現(xiàn)在,所有你需要知道的就是 Vec<T>
是一個由標(biāo)準(zhǔn)庫提供的類型,它可以存放任何類型,而當(dāng) Vec
存放某個特定類型時,那個類型位于尖括號中。在示例 8-1 中,我們告訴 Rust v
這個 Vec<T>
將存放 i32
類型的元素。
通常,我們會用初始值來創(chuàng)建一個 Vec<T>
而 Rust 會推斷出儲存值的類型,所以很少會需要這些類型注解。為了方便 Rust 提供了 vec!
宏,這個宏會根據(jù)我們提供的值來創(chuàng)建一個新的 vector。示例 8-2 新建一個擁有值 1
、2
和 3
的 Vec<i32>
。推斷為 i32
是因為這是默認(rèn)整型類型,第三章的 “數(shù)據(jù)類型” 討論過
let v = vec![1, 2, 3];
示例 8-2:新建一個包含初值的 vector
因為我們提供了 i32
類型的初始值,Rust 可以推斷出 v
的類型是 Vec<i32>
,因此類型注解就不是必須的。接下來讓我們看看如何修改一個 vector。
對于新建一個 vector 并向其增加元素,可以使用 push
方法,如示例 8-3 所示:
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
示例 8-3:使用 push
方法向 vector 增加值
如第三章中討論的任何變量一樣,如果想要能夠改變它的值,必須使用 mut
關(guān)鍵字使其可變。放入其中的所有值都是 i32
類型的,而且 Rust 也根據(jù)數(shù)據(jù)做出如此判斷,所以不需要 Vec<i32>
注解。
類似于任何其他的 struct
,vector 在其離開作用域時會被釋放,如示例 8-4 所標(biāo)注的:
{
let v = vec![1, 2, 3, 4];
// 處理變量 v
} // <- 這里 v 離開作用域并被丟棄
示例 8-4:展示 vector 和其元素于何處被丟棄
當(dāng) vector 被丟棄時,所有其內(nèi)容也會被丟棄,這意味著這里它包含的整數(shù)將被清理。這可能看起來非常直觀,不過一旦開始使用 vector 元素的引用,情況就變得有些復(fù)雜了。下面讓我們處理這種情況!
現(xiàn)在你知道如何創(chuàng)建、更新和銷毀 vector 了,接下來的一步最好了解一下如何讀取它們的內(nèi)容。有兩種方法引用 vector 中儲存的值。為了更加清楚的說明這個例子,我們標(biāo)注這些函數(shù)返回的值的類型。
示例 8-5 展示了訪問 vector 中一個值的兩種方式,索引語法或者 get
方法:
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
列表 8-5:使用索引語法或 get
方法來訪問 vector 中的項
這里有兩個需要注意的地方。首先,我們使用索引值 2
來獲取第三個元素,索引是從 0 開始的。其次,這兩個不同的獲取第三個元素的方式分別為:使用 &
和 []
返回一個引用;或者使用 get
方法以索引作為參數(shù)來返回一個 Option<&T>
。
Rust 提供了兩種引用元素的方法的原因是當(dāng)嘗試使用現(xiàn)有元素范圍之外的索引值時可以選擇讓程序如何運行。舉個例子,讓我們看看使用這個技術(shù),嘗試在當(dāng)有一個 5 個元素的 vector 接著訪問索引 100 位置的元素會發(fā)生什么,如示例 8-6 所示:
let v = vec![1, 2, 3, 4, 5];
let does_not_exist = &v[100];
let does_not_exist = v.get(100);
示例 8-6:嘗試訪問一個包含 5 個元素的 vector 的索引 100 處的元素
當(dāng)運行這段代碼,你會發(fā)現(xiàn)對于第一個 []
方法,當(dāng)引用一個不存在的元素時 Rust 會造成 panic。這個方法更適合當(dāng)程序認(rèn)為嘗試訪問超過 vector 結(jié)尾的元素是一個嚴(yán)重錯誤的情況,這時應(yīng)該使程序崩潰。
當(dāng) get
方法被傳遞了一個數(shù)組外的索引時,它不會 panic 而是返回 None
。當(dāng)偶爾出現(xiàn)超過 vector 范圍的訪問屬于正常情況的時候可以考慮使用它。接著你的代碼可以有處理 Some(&element)
或 None
的邏輯,如第六章討論的那樣。例如,索引可能來源于用戶輸入的數(shù)字。如果它們不慎輸入了一個過大的數(shù)字那么程序就會得到 None
值,你可以告訴用戶當(dāng)前 vector 元素的數(shù)量并再請求它們輸入一個有效的值。這就比因為輸入錯誤而使程序崩潰要友好的多!
一旦程序獲取了一個有效的引用,借用檢查器將會執(zhí)行所有權(quán)和借用規(guī)則(第四章講到)來確保 vector 內(nèi)容的這個引用和任何其他引用保持有效?;貞浺幌虏荒茉谙嗤饔糜蛑型瑫r存在可變和不可變引用的規(guī)則。這個規(guī)則適用于示例 8-7,當(dāng)我們獲取了 vector 的第一個元素的不可變引用并嘗試在 vector 末尾增加一個元素的時候,如果嘗試在函數(shù)的后面引用這個元素是行不通的:
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {}", first);
示例 8-7:在擁有 vector 中項的引用的同時向其增加一個元素
編譯會給出這個錯誤:
$ cargo run
Compiling collections v0.1.0 (file:///projects/collections)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:6:5
|
4 | let first = &v[0];
| - immutable borrow occurs here
5 |
6 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
7 |
8 | println!("The first element is: {}", first);
| ----- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `collections` due to previous error
示例 8-7 中的代碼看起來應(yīng)該能夠運行:為什么第一個元素的引用會關(guān)心 vector 結(jié)尾的變化?不能這么做的原因是由于 vector 的工作方式:在 vector 的結(jié)尾增加新元素時,在沒有足夠空間將所有元素依次相鄰存放的情況下,可能會要求分配新內(nèi)存并將老的元素拷貝到新的空間中。這時,第一個元素的引用就指向了被釋放的內(nèi)存。借用規(guī)則阻止程序陷入這種狀況。
注意:關(guān)于 ?
Vec<T>
? 類型的更多實現(xiàn)細節(jié),請查看 “The Rustonomicon”
如果想要依次訪問 vector 中的每一個元素,我們可以遍歷其所有的元素而無需通過索引一次一個的訪問。示例 8-8 展示了如何使用 for
循環(huán)來獲取 i32
值的 vector 中的每一個元素的不可變引用并將其打?。?br>
let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
示例 8-8:通過 for
循環(huán)遍歷 vector 的元素并打印
我們也可以遍歷可變 vector 的每一個元素的可變引用以便能改變他們。示例 8-9 中的 for
循環(huán)會給每一個元素加 50
:
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
示例 8-9:遍歷 vector 中元素的可變引用
為了修改可變引用所指向的值,在使用 +=
運算符之前必須使用解引用運算符(*
)獲取 i
中的值。第十五章的 “通過解引用運算符追蹤指針的值” 部分會詳細介紹解引用運算符。
vector 只能儲存相同類型的值。這是很不方便的;絕對會有需要儲存一系列不同類型的值的用例。幸運的是,枚舉的成員都被定義為相同的枚舉類型,所以當(dāng)需要在 vector 中儲存不同類型值時,我們可以定義并使用一個枚舉!
例如,假如我們想要從電子表格的一行中獲取值,而這一行的有些列包含數(shù)字,有些包含浮點值,還有些是字符串。我們可以定義一個枚舉,其成員會存放這些不同類型的值,同時所有這些枚舉成員都會被當(dāng)作相同類型,那個枚舉的類型。接著可以創(chuàng)建一個儲存枚舉值的 vector,這樣最終就能夠儲存不同類型的值了。示例 8-10 展示了其用例:
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
示例 8-10:定義一個枚舉,以便能在 vector 中存放不同類型的數(shù)據(jù)
Rust 在編譯時就必須準(zhǔn)確的知道 vector 中類型的原因在于它需要知道儲存每個元素到底需要多少內(nèi)存。第二個好處是可以準(zhǔn)確的知道這個 vector 中允許什么類型。如果 Rust 允許 vector 存放任意類型,那么當(dāng)對 vector 元素執(zhí)行操作時一個或多個類型的值就有可能會造成錯誤。使用枚舉外加 match
意味著 Rust 能在編譯時就保證總是會處理所有可能的情況,正如第六章講到的那樣。
如果在編寫程序時不能確切無遺地知道運行時會儲存進 vector 的所有類型,枚舉技術(shù)就行不通了。相反,你可以使用 trait 對象,第十七章會講到它。
現(xiàn)在我們了解了一些使用 vector 的最常見的方式,請一定去看看標(biāo)準(zhǔn)庫中 Vec
定義的很多其他實用方法的 API 文檔。例如,除了 push
之外還有一個 pop
方法,它會移除并返回 vector 的最后一個元素。讓我們繼續(xù)下一個集合類型:String
!
更多建議: