Rust 常见集合

  Rust标准库中包含一系列标量的数据结构被称为集合,大部分其他的数据结构类型都代表一个特定的值,不过集合可以包含多个值,不同于内建的数组和元组类型,这些集合指向的数据是存储在堆上的,这意味着数据的数量不必在编译时已知,并且还可以随着程序的运行增长或缩小,每种集合都有着不同的功能和成本,而根据当前情况选择合适的集合,这是一种本事。

  下面我们要介绍的广泛使用的集合:

    1、vector:允许我们一个挨着一个的存储一系列数量可变的值

    2、string:字符串是字符的集合

    3、哈希map:允许我们将值与一个特定的键相关联,文档:https://doc.rust-lang.org/stable/std/collections/

 

vector 用来储存一系列的值
  我们要讲到的第一个类型是 Vec<T> ,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
fn main() {
    // 原始的创建vector方式
    let v: Vec<i32> = Vec::new(); // 这里使用了<T> 来进行类型注释,来约束v要存储的类型
    
    // 自带类型推导,且使用Rust提供的宏vec!来取代Vec::new()
    let v1 = vec![1,2,3];

    println!("v:{:?},v1:{:?}",v,v1);  //v:[],v1[1, 2, 3]

    // 更新vector
    let mut v = vec![];   // vec! 相当于 Vec::new(), Rust后面会自动判断类型
    v.push(1);
    // v.push("we");  // 报错,类型不一致
    v.push(4);  // 使用 push 方法向 vector 增加值

    println!("v:{:?}",v);  // v:[1, 4]

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

  这里 v 离开作用域并被丢弃,当 vector 被丢弃时,所有其内容也会被丢弃,这意味着这里它包含的整数将被清理。这可能看起来非常直观,不过一旦开始使用 vector 元素的引用,情况就变得有些复杂了。

 

读取vector的元素

  有两种方法引用 vector 中储存的值。为了更加清楚的说明这个例子,我们标注这些函数返回的值的类型。

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

   let third: &i32 = &v[2];  // 使用 & 和 [] 返回一个引用
   println!("The third element is {}",third);

   match v.get(2) {  // get方式获取会返回一个Option<&T>
       Some(third) => println!("the third element is {}",third),
       None => println!("there is no third element"),
   }

   println!("索引获取: {}",&v[3]);  // 索引获取: 4,这种方式如果越界会panic
   println!("get获取: {:?}",v.get(3)); // get获取: Some(4),配合match更加安全,因为有None判断
}

  注意,一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则:

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

   v.push(6);  // 报错,因为此时v有一个引用&v[0],但是这里进行了push动作,
   // 要求v的内存要重新分配,但是又存在引用,所以不行
   println!("the first element is:{}",first);
}

  在 vector 的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。

 

遍历vector元素

  无需通过索引一次一个的访问,可以使用for循环来获取i32的vecotr的每个元素的不可变引用并打印:

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

   // 遍历不可变引用来读取
   for i in &v {
       println!{"{}",i};
   }

   // 遍历可变引用来修改
   let mut v = vec![4,4,2,2,9];  // 首先元素是可变的mut
   for i in &mut v {   // 其次引用是可变的
       *i += 50;  // 最后作用的是需要使用*
   }
   println!("change v: {:?}",v);
}

  

使用枚举来存储多种类型

  我们提到 vector 只能储存相同类型的值,这是很不方便的,因为我们需要存储一系列不同类型的值的用例,幸运的是枚举的成员都被定义为相同的枚举类型,所以当需要在vector中存储不同类型的值时,我们可以给其中的值套成外壳(枚举)。

  假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型,那个枚举的类型。接着可以创建一个储存枚举值的vector,这样最终就能够储存不同类型的值了。

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

fn main() {
    let row = vec![
        Spreadsheet::Int(3),
        Spreadsheet::Text(String::from("wanglx")),
        Spreadsheet::Float(1.12),
    ];
    println!("{:?}",row);  // [Int(3), Text("wanglx"), Float(1.12)]
}

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

  请一定去看看标准库中 Vec 定义的很多其他实用方法的 API 文档。例如,除了 push 之外还有一个 pop 方法,它会移除并返回vector 的最后一个元素。

 
字符串
  Rust 的核心语言中只有一种字符串类型: str ,字符串 slice,它通常以被借用的形式出现, &str 。第四章讲到了 字符串 slice:它们是一些储存在别处的 UTF-8 编码字符串数据的引用。比如字符串字面值被储存在程序的二进制输出中,字符串 slice 也是如此。称作 String 的类型是由标准库提供的,而没有写进核心语言部分,它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 String 和字符串 slice &str 类型,而不仅仅是其中之一。虽然本部分内容大多是关于 String 的,不过这两个类型在 Rust 标准库中都被广泛使用, String 和字符串slice 都是 UTF-8 编码的。
fn main() {
    // 新建一个字符串
    let mut s = String::new();

    s = "initial contents".to_string();  // 使用to_string()将&str转换为String

    let s = "initial contents".to_string();

    let s = String::from("initial contents");  // 等价于上面,使用String::From

    println!("s:{}",s);

    // 字符串的更新
    let mut s = String::from("foo");
    s.push_str("bar");  //  push_str 方法采用字符串 slice,因为我们并 不需要获取参数的所有权
    println!("push_str:{}",s);  // foobar

    let mut s1 = String::from("foo ");
    let s2 = "bar";
    s1.push_str(s2); // 这说明只是引用没有获取所有权

    println!("s2:{},s1:{}",s2,s1); // s2:bar,s1:foo bar

    let mut s4 = String::from("Foo ");
    s4.push('x');  // push 可以添加一个字符
    println!("s4:{}",s4);

    // + 和 format
    let s5 = String::from("Hello ");
    let s6 = String::from("Everyone");
    let s7 = s5 + &s6;  //  // 注意 s5 被移动了,不能继续使用, 调用fn add(self, s: &str) -> String
    println!("s7:{}",s7);  // s7:Hello Everyone

    let s8 = s7 + "xxx";
    println!("s8:{}",s8); // Hello Everyonexxx, 这表示可以拼接&str

    // format格式化
    let s1 = String::from("tic"); 
    let s2 = String::from("tac"); 
    let s3 = String::from("toe"); 
    let s = format!("{}-{}-{}", s1, s2, s3);
    println!("s:{}",s); // s:tic-tac-toe

    // 索引字符串
    // let h = s3[1];  // 报错,提示不支持索引字符串
    // 原因: String 是一个 Vec<u8> 的封装

    println!("{}",String::from("Hola").len()); // 4, 因为使用utf-8就能表示,所以一个字符占一个字节
    println!("{}",String::from("Здравствуйте").len()); // 24,使用的是Unicode,所以每个 Unicode 标量值需要两个字节存储
    let hello = "Здравствуйте"; 
    // let answer = &hello[0];  // 报错
}

  Rust 不允许使用索引获取 String 字符的原因是,索引操作预期总是需要常数时间(O(1))。但是对于 String 不可能保证这样的性能,因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符。

 
 
 
posted @ 2020-06-28 18:11  独角兕大王  阅读(716)  评论(0编辑  收藏  举报