Rust之常用集合(一):向量(vector)

开发环境

  • Windows 10
  • Rust 1.64.0

 

  •   VS Code 1.72.2 

项目工程

这里继续沿用上次工程rust-demo

常用集合

Rust的标准库包括许多非常有用的数据结构,称为集合。大多数其他数据类型表示一个特定的值,但是集合可以包含多个值。与内置数组和元组类型不同,这些集合指向的数据存储在堆上,这意味着数据量在编译时不需要知道,并且可以随着程序运行而增长或收缩。每一种收藏都有不同的功能和成本,选择一种适合你当前情况的收藏是一项你需要慢慢培养的技能。我们将讨论Rust程序中经常使用的三个集合:

  • vector允许你存储可变数量的相邻值。
  • string是字符的集合。
  • hash map允许您将一个值与一个特定的键相关联。这是一个更一般的数据结构的特殊实现,称为map

用向量存储值列表

我们要看的第一个集合类型是Vec<T >,也称为vector。向量允许您在一个数据结构中存储多个值,该数据结构将所有值在内存中相邻放置。向量只能存储相同类型的值。当您有一个项目列表时,例如文件中的文本行或购物车中的项目价格时,它们非常有用。

创建向量

为了创建一个新的空向量,我们调用Vec::new函数

fn main() {
    let v: Vec<i32> = Vec::new();       // 创建vector
}

注意,我们在这里添加了一个类型注释。因为我们没有在这个向量中插入任何值,所以Rust不知道我们打算存储什么样的元素。这是很重要的一点。向量是使用泛型实现的;现在,我们知道标准库提供的Vec<T >类型可以保存任何类型。当我们创建一个向量来保存一个特定的类型时,我们可以在尖括号中指定类型。在上例中,我们已经告诉Rust,v中的Vec<T >将保存i32类型的元素。

更常见的是,您将创建一个带有初始值的Vec < T >, Rust将推断出您想要存储的值的类型,因此您很少需要做这种类型注释。Rust方便地提供了vec!宏,它将创建一个新的向量来保存您给它的值。下例中创建了一个新的Vec<i32 >,它包含值1、2和3。整数类型是i32,因为这是默认的整数类型,

fn main() {
    let v = vec![1, 2, 3];           // 带有初始值的vector
}

因为我们已经给出了初始i32值,Rust可以推断出v的类型是Vec<i32 >,类型注释是不必要的。接下来,我们将看看如何修改向量。

更新向量

要创建一个向量,然后向其中添加元素,我们可以使用push方法,

fn main() {
    let mut v = Vec::new();         // mut关键字,创建可修改的向量vector

    v.push(5);                      // 和其他语言类似,传入数据
    v.push(6);
    v.push(7);
    v.push(8);
}

和任何变量一样,如果我们希望能够改变它的值,我们需要使用mut关键字使它可变,如第3章所讨论的。我们放入的数字都是i32类型的,Rust从数据中推断出这一点,所以我们不需要Vec<i32 >注释。

读取向量的元素

有两种方法可以引用存储在vector中的值:通过索引或者使用get方法。在下面的例子中,为了更加清晰起见,我们对这些函数返回的值的类型进行了注释。

fn main() {
    let v = vec![1, 2, 3, 4, 5];                // 定义向量

    let third: &i32 = &v[2];                     // 通过索引获取向量中的值
    println!("The third element is {}", third);

    let third: Option<&i32> = v.get(2);          // 通过get接口获取向量中的值
    match third {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element."),
    }
}

编译运行

cargo run

  结果

 请注意这里的一些细节。我们使用索引值2来获取第三个元素,因为向量是由数字索引的,从零开始。使用&和[]给出了对索引值处元素的引用。当我们使用get方法并将索引作为参数传递时,我们得到一个可以与match一起使用的Option< &T>

Rust提供这两种方法引用元素的原因是,当您试图使用现有元素范围之外的索引值时,您可以选择程序的行为方式。作为一个例子,让我们看看当我们有一个包含五个元素的向量,然后我们尝试用每种技术访问索引为100的元素时会发生什么,

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let does_not_exist = &v[100];         // 获取索引为100的值
    let does_not_exist = v.get(100);
}

 编译运行

cargo run

 当我们运行这段代码时,第一个[]方法将导致程序崩溃,因为它引用了一个不存在的元素。如果试图访问超过vector末尾的元素,希望程序崩溃时,最好使用这种方法

get方法被传递一个在vector之外的索引时,它返回None而不会死机。如果在正常情况下偶尔会访问vector范围之外的元素,那么可以使用这个方法。然后你的代码将有逻辑来处理有Some(&element)或者None,如之前章节讨论的。例如,索引可能来自输入数字的人。如果他们不小心输入了一个太大的数字,程序得到一个None值,你可以告诉用户当前向量中有多少项,然后再给他们一次输入有效值的机会。这比因为一个打字错误而导致程序崩溃更容易操作!

当程序有一个有效的引用时,借用检查器执行所有权和借用规则以确保这个引用和任何其他对vector内容的引用保持有效。回想一下在同一个作用域中不能有可变和不可变引用的规则。这条规则适用于下例,我们保存了一个对向量中第一个元素的不可变引用,并试图在末尾添加一个元素。如果我们试图在函数的后面引用这个元素,这个程序将无法运行:

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];          // 可变的向量

    let first = &v[0];

    v.push(6);                                // 改变值

    println!("The first element is: {}", first);
}

编译运行

cargo run

编译错误 

 上例中的代码看起来应该是可行的:为什么对第一个元素的引用要关心向量末尾的变化?这个错误是由vector的工作方式造成的:因为vector将值彼此相邻地放在内存中,所以在vector的末尾添加新元素可能需要分配新的内存,并将旧的元素复制到新的空间,如果没有足够的空间将所有元素彼此相邻地放在vector当前存储的位置。在这种情况下,对第一个元素的引用将指向已释放的内存。借用规则防止程序在这种情况下终止

迭代向量中的值

为了依次访问vector中的每个元素,我们将遍历所有元素,而不是使用索引一次访问一个元素。下例中显示了如何使用一个for循环来获得对i32值的向量中的每个元素的不可变引用,并打印它们。

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

编译运行

cargo run

 我们还可以迭代可变向量中每个元素的可变引用,以便对所有元素进行更改。下例中中的for循环将每个元素加50。

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;              // 每个数加 50
        println!("{}", i);
    }
}

编译运行

cargo run

 

 要改变可变引用所引用的值,我们必须在使用+=操作符之前使用* 接触引用操作符来获得I中的值。

由于借用检查器的规则,迭代一个向量,无论是不变的还是可变的,都是安全的。如果我们试图在上例中的for循环体中插入或删除项目,我们会得到一个类似于上述其中代码的编译错误。对for循环持有的向量的引用防止了对整个向量的同时修改。

使用枚举存储多种类型

向量只能存储相同类型的值。这可能不方便;肯定有需要存储不同类型的项目列表的用例。幸运的是,一个枚举的变体是在同一个枚举类型下定义的,所以当我们需要一个类型来表示不同类型的元素时,我们可以定义并使用一个枚举

例如,假设我们想从电子表格的一行中获取值,该行中的一些列包含整数、一些浮点数和一些字符串。我们可以定义一个枚举,它的变量包含不同的值类型,所有的枚举变量都被认为是相同的类型:枚举的类型。然后我们可以创建一个向量来保存这个枚举,最终保存不同的类型。

fn main() {
    enum SpreadsheetCell {          // 枚举
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec![                           // 将枚举类型存入向量
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
}

Rust需要知道在编译时vector中有哪些类型,这样它就能准确地知道在堆中需要多少内存来存储每个元素。我们还必须明确在这个向量中允许什么类型。如果Rust允许vector保存任何类型,那么有可能一个或多个类型会导致对vector元素执行的操作出错。使用枚举加match表达式意味着Rust将确保在编译时处理每一种可能的情况。

如果你不知道一个程序在运行时将在vector中存储的所有类型,enum技术就不起作用。

既然我们已经讨论了使用vectors的一些最常见的方法,可以查看Rust API文档,了解标准库在Vec<T >上定义的许多有用的方法。例如,除了push之外,pop方法还移除并返回最后一个元素。

删除向量会删除其元素

像任何其他struct一样,向量在超出范围时被释放,

fn main() {
    {
        let v = vec![1, 2, 3, 4];

        // do stuff with v
    } // <- v goes out of scope and is freed here         // 有效范围
}

vector被丢弃时,它的所有内容也被丢弃,这意味着它保存的整数将被清除。借用检查器确保仅当向量本身有效时,才使用对向量内容的任何引用。

本章重点

  • 向量vector概念及创建方法
  • 向量的使用 - 更新数据
  • 读取向量中的元素
  • 迭代向量中的值
  • 使用枚举存储多类型的数据

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值