基础篇-Vector

  • 木头
  • 更新于 2023-02-27 11:53
  • 阅读 1494

Vec新建,更新,读取

Vec<T>,也被称为 vectorvector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。

新建 vector

创建一个新的空 vector,可以调用 Vec::new 函数:

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

注意这里我们增加了一个类型注解。因为没有向这个vector 中插入任何值,Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是用泛型实现的。现在,所有你需要知道的就是 Vec<T>是一个由标准库提供的类型,它可以存放任何类型,而当 Vec 存放某个特定类型时,那个类型位于尖括号中。我们告诉 Rust v 这个 Vec<T> 将存放 i32 类型的元素。

通常,我们会用初始值来创建一个 Vec<T>Rust 会推断出储存值的类型,所以很少会需要这些类型注解。为了方便 Rust 提供了 vec! 宏,这个宏会根据我们提供的值来创建一个新的 vector

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

Rust推断为 i32 是因为这是默认整型类型,因为我们提供了 i32 类型的初始值,Rust 可以推断出 v 的类型是 Vec<i32>,因此类型注解就不是必须的。

更新 vector

新建一个vector 并向其增加元素,可以使用 push 方法:

fn main() {
    let mut v: Vec<i32> = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
}

注意如果想要能够改变它的值,必须使用mut 关键字使其可变。放入其中的所有值都是 i32类型的,而且 Rust 也根据数据做出如此判断,所以不需要 Vec<i32>注解。

读取 vector

有两种方法引用 vector 中储存的值:通过索引或使用 get 方法:

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

    let t = &v[2];
    println!("索引取第3个值:{}", t);

    if let Some(t) = v.get(2) {
        println!("get取第3个值:{}", t);
    } else {
        println!("超出索引");
    }
}

这里有几个细节需要注意。我们使用索引值 2 来获取第三个元素,因为索引是从数字 0 开始的。使用 &[]会得到一个索引位置元素的引用。当使用索引作为参数调用get 方法时,会得到一个可以用于 matchOption<&T>,使用get读取值的时候返回Option<&T>的好处在于可以很好判断是否超出索引范围。

一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则来确保 vector内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面引用这个元素是行不通的:

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

    let f = &[2];

    v.push(7);

    println!("{}", f);
}

编译会给出这个错误:

$ cargo run                                                                                            
   Compiling variables v0.1.0 (/projects/variables)
error[E0277]: `[{integer}; 1]` doesn't implement `std::fmt::Display`
 --> src/main.rs:8:20
  |
8 |     println!("{}", f);
  |                    ^ `[{integer}; 1]` cannot be formatted with the default formatter
  |
  = help: the trait `std::fmt::Display` is not implemented for `[{integer}; 1]`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain 

为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector的工作方式:在vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。

遍历 vector 中的元素

如果想要依次访问 vector 中的每一个元素,我们可以遍历其所有的元素而无需通过索引一次一个的访问。如何使用 for 循环来获取 i32 值的 vector 中的每一个元素的不可变引用并将其打印:

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

    for val in &v {
        println!("{}", val);
    }
}

我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变他们:

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

    for val in &mut v {
        *val += 1;
        println!("{}", val);
    }
}

为了修改可变引用所指向的值,在使用 += 运算符之前必须使用解引用运算符(*)获取 i中的值。

使用枚举来储存多种类型

vector只能储存相同类型的值。这是很不方便的;绝对会有需要储存一系列不同类型的值的用例。枚举的成员都被定义为不相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举:

#[derive(Debug)]
enum SpreadsheetCell {
    Int(i32),
    Float(f32),
    Text(String),
}

fn main() {
    let v = vec![
        SpreadsheetCell::Int(1),
        SpreadsheetCell::Float(1.1),
        SpreadsheetCell::Text(String::from("hello world")),
    ];

    println!("{:#?}", v);
}

Rust在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust允许 vector存放任意类型,那么当对vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加match意味着 Rust 能在编译时就保证总是会处理所有可能的情况。

丢弃 vector

类似于任何其他的 structvector在其离开作用域时会被释放:

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

        // v 可以继续使用
    } // <- v 超出范围并在此处释放
}

vector被丢弃时,所有其内容也会被丢弃,这意味着这里它包含的整数将被清理。

标准库 VecAPI 文档。

  • 原创
  • 学分: 2
  • 分类: Rust
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
121 订阅 31 篇文章

0 条评论

请先 登录 后评论
木头
木头
0xC020...10cf
江湖只有他的大名,没有他的介绍。