Rust 使用 Vector 儲(chǔ)存列表

2023-03-22 15:10 更新
ch08-01-vectors.md
commit e7bfb353b107cb150faab9d331c99ea2b91f3725

我們要講到的第一個(gè)類型是 Vec<T>,也被稱為 vector。vector 允許我們?cè)谝粋€(gè)單獨(dú)的數(shù)據(jù)結(jié)構(gòu)中儲(chǔ)存多于一個(gè)的值,它在內(nèi)存中彼此相鄰地排列所有的值。vector 只能儲(chǔ)存相同類型的值。它們?cè)趽碛幸幌盗许?xiàng)的場景下非常實(shí)用,例如文件中的文本行或是購物車中商品的價(jià)格。

新建 vector

為了創(chuàng)建一個(gè)新的空 vector,可以調(diào)用 Vec::new 函數(shù),如示例 8-1 所示:

    let v: Vec<i32> = Vec::new();

示例 8-1:新建一個(gè)空的 vector 來儲(chǔ)存 ?i32 ?類型的值

注意這里我們?cè)黾恿艘粋€(gè)類型注解。因?yàn)闆]有向這個(gè) vector 中插入任何值,Rust 并不知道我們想要儲(chǔ)存什么類型的元素。這是一個(gè)非常重要的點(diǎn)。vector 是用泛型實(shí)現(xiàn)的,第十章會(huì)涉及到如何對(duì)你自己的類型使用它們?,F(xiàn)在,所有你需要知道的就是 Vec<T> 是一個(gè)由標(biāo)準(zhǔn)庫提供的類型,它可以存放任何類型,而當(dāng) Vec 存放某個(gè)特定類型時(shí),那個(gè)類型位于尖括號(hào)中。在示例 8-1 中,我們告訴 Rust v 這個(gè) Vec<T> 將存放 i32 類型的元素。

通常,我們會(huì)用初始值來創(chuàng)建一個(gè) Vec<T> 而 Rust 會(huì)推斷出儲(chǔ)存值的類型,所以很少會(huì)需要這些類型注解。為了方便 Rust 提供了 vec! 宏,這個(gè)宏會(huì)根據(jù)我們提供的值來創(chuàng)建一個(gè)新的 vector。示例 8-2 新建一個(gè)擁有值 1、2 和 3 的 Vec<i32>。推斷為 i32 是因?yàn)檫@是默認(rèn)整型類型,第三章的 “數(shù)據(jù)類型” 討論過

    let v = vec![1, 2, 3];

示例 8-2:新建一個(gè)包含初值的 vector

因?yàn)槲覀兲峁┝?nbsp;i32 類型的初始值,Rust 可以推斷出 v 的類型是 Vec<i32>,因此類型注解就不是必須的。接下來讓我們看看如何修改一個(gè) vector。

更新 vector

對(duì)于新建一個(gè) 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> 注解。

丟棄 vector 時(shí)也會(huì)丟棄其所有元素

類似于任何其他的 struct,vector 在其離開作用域時(shí)會(huì)被釋放,如示例 8-4 所標(biāo)注的:

    {
        let v = vec![1, 2, 3, 4];

        // 處理變量 v
    } // <- 這里 v 離開作用域并被丟棄

示例 8-4:展示 vector 和其元素于何處被丟棄

當(dāng) vector 被丟棄時(shí),所有其內(nèi)容也會(huì)被丟棄,這意味著這里它包含的整數(shù)將被清理。這可能看起來非常直觀,不過一旦開始使用 vector 元素的引用,情況就變得有些復(fù)雜了。下面讓我們處理這種情況!

讀取 vector 的元素

現(xiàn)在你知道如何創(chuàng)建、更新和銷毀 vector 了,接下來的一步最好了解一下如何讀取它們的內(nèi)容。有兩種方法引用 vector 中儲(chǔ)存的值。為了更加清楚的說明這個(gè)例子,我們標(biāo)注這些函數(shù)返回的值的類型。

示例 8-5 展示了訪問 vector 中一個(gè)值的兩種方式,索引語法或者 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 中的項(xiàng)

這里有兩個(gè)需要注意的地方。首先,我們使用索引值 2 來獲取第三個(gè)元素,索引是從 0 開始的。其次,這兩個(gè)不同的獲取第三個(gè)元素的方式分別為:使用 & 和 [] 返回一個(gè)引用;或者使用 get 方法以索引作為參數(shù)來返回一個(gè) Option<&T>。

Rust 提供了兩種引用元素的方法的原因是當(dāng)嘗試使用現(xiàn)有元素范圍之外的索引值時(shí)可以選擇讓程序如何運(yùn)行。舉個(gè)例子,讓我們看看使用這個(gè)技術(shù),嘗試在當(dāng)有一個(gè) 5 個(gè)元素的 vector 接著訪問索引 100 位置的元素會(huì)發(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:嘗試訪問一個(gè)包含 5 個(gè)元素的 vector 的索引 100 處的元素

當(dāng)運(yùn)行這段代碼,你會(huì)發(fā)現(xiàn)對(duì)于第一個(gè) [] 方法,當(dāng)引用一個(gè)不存在的元素時(shí) Rust 會(huì)造成 panic。這個(gè)方法更適合當(dāng)程序認(rèn)為嘗試訪問超過 vector 結(jié)尾的元素是一個(gè)嚴(yán)重錯(cuò)誤的情況,這時(shí)應(yīng)該使程序崩潰。

當(dāng) get 方法被傳遞了一個(gè)數(shù)組外的索引時(shí),它不會(huì) panic 而是返回 None。當(dāng)偶爾出現(xiàn)超過 vector 范圍的訪問屬于正常情況的時(shí)候可以考慮使用它。接著你的代碼可以有處理 Some(&element) 或 None 的邏輯,如第六章討論的那樣。例如,索引可能來源于用戶輸入的數(shù)字。如果它們不慎輸入了一個(gè)過大的數(shù)字那么程序就會(huì)得到 None 值,你可以告訴用戶當(dāng)前 vector 元素的數(shù)量并再請(qǐng)求它們輸入一個(gè)有效的值。這就比因?yàn)檩斎脲e(cuò)誤而使程序崩潰要友好的多!

一旦程序獲取了一個(gè)有效的引用,借用檢查器將會(huì)執(zhí)行所有權(quán)和借用規(guī)則(第四章講到)來確保 vector 內(nèi)容的這個(gè)引用和任何其他引用保持有效?;貞浺幌虏荒茉谙嗤饔糜蛑型瑫r(shí)存在可變和不可變引用的規(guī)則。這個(gè)規(guī)則適用于示例 8-7,當(dāng)我們獲取了 vector 的第一個(gè)元素的不可變引用并嘗試在 vector 末尾增加一個(gè)元素的時(shí)候,如果嘗試在函數(shù)的后面引用這個(gè)元素是行不通的:

    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 中項(xiàng)的引用的同時(shí)向其增加一個(gè)元素

編譯會(huì)給出這個(gè)錯(cuò)誤:

$ 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)該能夠運(yùn)行:為什么第一個(gè)元素的引用會(huì)關(guān)心 vector 結(jié)尾的變化?不能這么做的原因是由于 vector 的工作方式:在 vector 的結(jié)尾增加新元素時(shí),在沒有足夠空間將所有元素依次相鄰存放的情況下,可能會(huì)要求分配新內(nèi)存并將老的元素拷貝到新的空間中。這時(shí),第一個(gè)元素的引用就指向了被釋放的內(nèi)存。借用規(guī)則阻止程序陷入這種狀況。

注意:關(guān)于 ?Vec<T>? 類型的更多實(shí)現(xiàn)細(xì)節(jié),請(qǐng)查看 “The Rustonomicon”

遍歷 vector 中的元素

如果想要依次訪問 vector 中的每一個(gè)元素,我們可以遍歷其所有的元素而無需通過索引一次一個(gè)的訪問。示例 8-8 展示了如何使用 for 循環(huán)來獲取 i32 值的 vector 中的每一個(gè)元素的不可變引用并將其打?。?br>

    let v = vec![100, 32, 57];
    for i in &v {
        println!("{}", i);
    }

示例 8-8:通過 for 循環(huán)遍歷 vector 的元素并打印

我們也可以遍歷可變 vector 的每一個(gè)元素的可變引用以便能改變他們。示例 8-9 中的 for 循環(huán)會(huì)給每一個(gè)元素加 50

    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }

示例 8-9:遍歷 vector 中元素的可變引用

為了修改可變引用所指向的值,在使用 += 運(yùn)算符之前必須使用解引用運(yùn)算符(*)獲取 i 中的值。第十五章的 “通過解引用運(yùn)算符追蹤指針的值” 部分會(huì)詳細(xì)介紹解引用運(yùn)算符。

使用枚舉來儲(chǔ)存多種類型

vector 只能儲(chǔ)存相同類型的值。這是很不方便的;絕對(duì)會(huì)有需要儲(chǔ)存一系列不同類型的值的用例。幸運(yùn)的是,枚舉的成員都被定義為相同的枚舉類型,所以當(dāng)需要在 vector 中儲(chǔ)存不同類型值時(shí),我們可以定義并使用一個(gè)枚舉!

例如,假如我們想要從電子表格的一行中獲取值,而這一行的有些列包含數(shù)字,有些包含浮點(diǎn)值,還有些是字符串。我們可以定義一個(gè)枚舉,其成員會(huì)存放這些不同類型的值,同時(shí)所有這些枚舉成員都會(huì)被當(dāng)作相同類型,那個(gè)枚舉的類型。接著可以創(chuàng)建一個(gè)儲(chǔ)存枚舉值的 vector,這樣最終就能夠儲(chǔ)存不同類型的值了。示例 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:定義一個(gè)枚舉,以便能在 vector 中存放不同類型的數(shù)據(jù)

Rust 在編譯時(shí)就必須準(zhǔn)確的知道 vector 中類型的原因在于它需要知道儲(chǔ)存每個(gè)元素到底需要多少內(nèi)存。第二個(gè)好處是可以準(zhǔn)確的知道這個(gè) vector 中允許什么類型。如果 Rust 允許 vector 存放任意類型,那么當(dāng)對(duì) vector 元素執(zhí)行操作時(shí)一個(gè)或多個(gè)類型的值就有可能會(huì)造成錯(cuò)誤。使用枚舉外加 match 意味著 Rust 能在編譯時(shí)就保證總是會(huì)處理所有可能的情況,正如第六章講到的那樣。

如果在編寫程序時(shí)不能確切無遺地知道運(yùn)行時(shí)會(huì)儲(chǔ)存進(jìn) vector 的所有類型,枚舉技術(shù)就行不通了。相反,你可以使用 trait 對(duì)象,第十七章會(huì)講到它。

現(xiàn)在我們了解了一些使用 vector 的最常見的方式,請(qǐng)一定去看看標(biāo)準(zhǔn)庫中 Vec 定義的很多其他實(shí)用方法的 API 文檔。例如,除了 push 之外還有一個(gè) pop 方法,它會(huì)移除并返回 vector 的最后一個(gè)元素。讓我們繼續(xù)下一個(gè)集合類型:String!


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)