Rust 常见集合
Rust标准库中包含一系列标量的数据结构被称为集合,大部分其他的数据结构类型都代表一个特定的值,不过集合可以包含多个值,不同于内建的数组和元组类型,这些集合指向的数据是存储在堆上的,这意味着数据的数量不必在编译时已知,并且还可以随着程序的运行增长或缩小,每种集合都有着不同的功能和成本,而根据当前情况选择合适的集合,这是一种本事。
下面我们要介绍的广泛使用的集合:
1、vector:允许我们一个挨着一个的存储一系列数量可变的值
2、string:字符串是字符的集合
3、哈希map:允许我们将值与一个特定的键相关联,文档:https://doc.rust-lang.org/stable/std/collections/
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 的最后一个元素。
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 必须从开头到索引位置遍历来确定有多少有效的字符。