【Rust基础】Rust学习笔记 - 第5天

本文作为对Rust的基础学习,建议每一位对Rust感兴趣并且想要接触Rust的同学,将本文中所有例子都自己运行一遍,不要求你手动重写,至少将例子复制下来运行查看效果。

本文基于:

rust: 1.67.0 (fc594f156 2023-01-24)

cargo: 1.67.0 (8ecd4f20a 2023-01-10)

参考文档:Rust 程序设计语言 简体中文版

  • Rust 数据结构 之 集合

    • vector

      在Rust中,vector使用采用的前三个单词缩写,例如:

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

      数据类型必须指定,因为泛型缘故,这里的Vec代表的一个32位的整型集合。

      而上述示例则是制定了一个空的vectorv的变量指向,问:变量v是存在堆中还是栈中?

      哈哈,请思考一下,文后给出答案。

      值得注意的是,这里的v是无法进行写入的因为它是let而不是let mut(不可变的变量并非常量,因为文档中详细介绍了常量和不可变变量的区别,系列文章便没提到)。

      我们可以通过直接指定v变量列表,对此Rust专门提供了一个,名为:vec!()举例:

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

      上述示例便是对vec!()宏的使用,与Rust数组数据结构类似,vector也只能存储同类型的数据值;不过它区别于数组的主要优点是:可变长,而数组则是长度固定的。

      Tips:vec!()只是简便了Vec::new()的操作,并且vec!()会自动根据列表值推断变量v的数据类型;

    • vector的存取

      正如:vector其实是一个长度可变的数组(下标从0开始),对于它的值存储,它提供了一个push方法,代表为其添加值,下面定义一个可变vec

      fn main() {
          let mut v = vec![1, 2, 3, 4];
      
          v.push(100);
      
          println!("{:?}", v)
      }
      

      上例通过v.push()将一个i32(默认)类型的100添加到v的列表中,然后输出结果:

      [1, 2, 3, 4, 100]
      

      vector的读取则提供了两个方式:

      • & 值引用
      • get函数

      举个例子:

      fn main() {
          let v = vec![1, 2, 3, 4]; //生成一个不可变的 vector
      
          let item1 = &v[0];
          println!("item1 = {:?}", item1);
      
          let item2 = v.get(0);
          match item2 {
              Some(item2) => println!("item2 = {item2}"),
              None => println!("没有该值!"),
          }
      }
      
      

      输出:

      item1 = 1
      item2 = Some(1)
      

      因为vector的下标索引和数组相同,因此它的访问基本与数组相同;

      至于get()函数返回的``Some(1),前文未提到过,这里说一嘴,它是作为enum Option`中的一个枚举字段;

      Option`中共用两个枚举字段:

      • Some:包含了某个值的枚举字段
      • None:上述值没有

      至于为什么使用Option,看看以下代码:

      fn main() {
          let v = vec![1, 2, 3, 4]; //生成一个不可变的 vector
      
          let item1 = &v[0];
          println!("item1 = {:?}", item1);
      
          let item2 = v.get(100); //修改一下
          match item2 {
              Some(item2) => println!("item2 = {item2}"),
              None => println!("没有该值!"),
          }
      }
      

      输出:

      item1 = 1
      没有该值!
      

      这样的好处不言而喻了吧,直接避免了类似JavaNullException

    • vector的删除

      对于Vector的删除,Rust提供了一个remve(index)函数和一个类似于栈弹出的pop()函数,看例子:

      fn main() {
          let mut v = vec![1, 2, 3, 4];
      
          v.remove(2);
      
          println!("{:?}", v);
      }
      

      输出:

      [1, 2, 4]
      
      fn main() {
          let mut v = vec![1, 2, 3, 4];
      
          v.pop();
      
          println!("{:?}", v);
      }
      

      输出:

      [1, 2, 3]
      
    • vector的遍历

      可以通过for进行迭代,也可以通过 while 进行下标变量,这里就举例for

      fn main() {
          let mut v = vec![1, 2, 3, 4];
      
          for ele in v {
              println!("{:?}", ele);
          }
      }
      

      输出:

      1
      2
      3
      4
      

      也可以批量更新,

      fn main() {
          let mut v = vec![1, 2, 3, 4];
      
          for ele in &mut v {
              *ele += 100;
      
              println!("{:?}", ele);
          }
      }
      

      输出:

      101
      102
      103
      104
      

      如果你学习过C语言,对于&*应该并不陌生。

    • vector中的枚举

      上文所说,vector中只允许存相同类型的值,但是对于枚举vector是将它看做为一种类型,就因为它是枚举,以下示例来自文档:

      #[derive(Debug)]
      enum SpreadsheetCell {
          Int(i32),
          Float(f64),
          Text(String),
      }
      
      fn main() {
          let row = vec![
              SpreadsheetCell::Int(3),
              SpreadsheetCell::Text(String::from("blue")),
              SpreadsheetCell::Float(10.12),
          ];
      
          println!("row={:?}", row);
      }
      

      输出:

      row=[Int(3), Text("blue"), Float(10.12)]
      

      以下是来自文档的原话:

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

      而由于Rust的安全性,vector在离开它所在的作用域之后会自定释放销毁。

      回答前面的问题:变量v是存在堆中还是栈中?

      回答:按照前文的逻辑,v作为变量是被存放在栈当中,然而它有一个地址指向,指向了堆中的一块内存区域,该内存中存放的则是变量的值。

    • String

      在Rust中字符串都是已UTF-8编码的,前文提到过字符串,下面的例子都应该不陌生了:

      fn main() {
          let s = String::from("Hello World!");
          println!("{}", s);
      }
      

      输出:

      Hello World!
      

      字符串还提供了一个new()函数,允许创建一个空的字符串,前文的猜数字就已经使用过:

      //伪代码
      fn main(){
          let mut s = String::new();
          ... //输入操作
      }
      
    • String的更新

      对于String来说,它其实与vector的数据结构类似,只需要对一个String结构增加一个mut关键字,即可对字符串进行更新修改。

      fn main() {
          let mut s = String::new();
      
          s.push_str("Hello"); //附加一个字符串切片
          s.push('!'); //附加一个字符
      
          println!("{}", s);
      }
      

      输出:

      Hello!
      

      Tips:字符串切片&str是一个引用类型,对于以下示例:

      fn main() {
      let s: &str = "Hello";
      let s1: &str = &String::from("Hello")[..];
      
      println!("{}", s);
      println!("{}", s1);
      }
      
      

      程序之初给定了一个不可变变量 s,它的类型则是 &str;而s1则是由String切片出来的。

      输出:

      Hello
      Hello
      

      另一种则是通过 +format!() 宏,举例:

      fn main() {
          let mut s = String::new();
          s += "Hello";
          println!("{}", s);
      }
      

      输出:

      Hello
      
      fn main() {
          let s1 = "Hello";
          let s2 = String::from(" World!");
          let s = format!("{s1}{s2}");
      
          println!("{}", s);
      }
      

      输出:

      Hello World!
      
    • String <–> &str

      • String&str,完整切片后即可实现:

        let s1: &str = &String::from("Hello")[..];
        
      • &strString,Rust提供了一个创建方法:to_string();

        let s: String = "Hello".to_string();
        

      值得注意的是,他们虽然能够得到相同的内容,但是在堆栈中可不一样哦

    • String的索引

      这是一个不太值得深究的事情,Rust中的String不支持索引访问,也就是:

      fn main() {
          let s = String::from("Hello World!");
      
          let c = s[0];   //不支持
          let c = &s[0];  //不支持
      
          println!("{}", c);
      }
      

      上述代码无法通过编译。

    • String切片(又谈slice)

      前面的文章说过slice,这里跟着文档再说一遍:String的切片在于它能够直接获取某个字符串的一部分(切出一部分)

      基本使用方式:

      fn main() {
          let s = String::from("Hello World!");
          let c1 = &s[..5]; //切出: Hello!
          let c2 = &s[0..5]; //切出: Hello!
          let c3 = &s[0..]; //切出: Hello World!
          let c4 = &s[..]; //切出: Hello World!
      
          println!("{}", c1);
          println!("{}", c2);
          println!("{}", c3);
          println!("{}", c4);
      }
      

      输出:

      Hello
      Hello
      Hello World!
      Hello World!
      
    • String的遍历

      String提供了一个chars()函数:

      fn main() {
          let s = String::from("Hello World!");
      
          for it in s.chars() {
              println!("{it}");
          }
      }
      

      输出:

      H
      e
      l
      l
      o
      
      W
      o
      r
      l
      d
      !
      

      或者你想要获取每个char的字节码:

      fn main() {
          let s = String::from("Hello World!");
      
          for it in s.bytes() {
              println!("{it}");
          }
      }
      

      输出:

      72
      101
      108
      108
      111
      32
      87
      111
      114
      108
      100
      33
      
    • HashMap

      终于到了HashMap了,这是一个典型的K:V集合,也称为:键值对

      在Rust中,HashMap 结构的创建与字符串类似,可以创建一个空的HashMap集合。

      use std::collections::HashMap;
      
      fn main() {
          let mut map: HashMap<String, String> = HashMap::new();
          map.insert("Key1".to_string(), "Value1".to_string());
      
          println!("{:?}", map);
      }
      

      输出:

      {"Key1": "Value1"}
      

      例子通过HashMap::new();创建了一个空的可变的map变量,通过insert()函数完成map增加。

    • HashMap更新

      因为HashMap中只允许出现一次的缘故,以下代码,再次对Key1进行设置则会将原本的Value1替换为123456

      use std::collections::HashMap;
      
      fn main() {
          let mut map: HashMap<String, String> = HashMap::new();
          map.insert("Key1".to_string(), "Value1".to_string());
      
          map.insert("Key1".to_string(), "123456".to_string());
      
          println!("{:?}", map);
      }
      

      输出:

      {"Key1": "123456"}
      
    • HashMap中的entry()函数

      查看文档之后,发现entry()非常好用,接下来介绍它的几个方法:

      use std::collections::HashMap;
      
      fn main() {
          let mut map: HashMap<String, String> = HashMap::new();
          let key1 = String::from("Key1");
          let value1 = String::from("Value1");
          map.insert(key1, value1);
      
          let e = map.entry("Key1".to_string());
      
          //e.or_insert("New Value".to_string());  //如果`Key1`不存在, 为`Key1`设置值`New Value`
          //e.or_default();  //如果`Key1`不存在,则默认给定一个空的字符串
          //e.key(); //返回当前的-key即`Key1`
      }
      
    • HashMap中的所有权

      由于Rust中变量移动(move)的特性,某个变量一旦被添加到HashMap中,所有权也会跟着移动到HashMap中,修改一下上面的例子:

      use std::collections::HashMap;
      
      fn main() {
          let mut map: HashMap<String, String> = HashMap::new();
          let key1 = String::from("Key1");
          let value1 = String::from("Value1");
          map.insert(key1, value1);
      
          println!("{}", key1); //因为所有权已经被转移, 故它无法通过编译
          println!("{}", value1); //因为所有权已经被转移, 故它无法通过编译
          println!("{:?}", map);
      }
      

      至此,本篇完!

时间:2023年2月6日 第5天


版权声明:本文为AoXue2017原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。