- 常见容器
- 向量`Vec`
- 创建向量
- 添加元素
- 读取元素
- 向量元素的引用和向量的引用
- 删除向量
- 使用迭代器访问元素
- 字符串`String`
- 创建字符串
- 更新字符串
- 读取字符串中的元素
- 字符串切片
- 哈希表`HashMap
向量和数组的最大区别在于,数组的长度必须在编译时确定,而且之后长度不能更改,所以数组使用的是栈内存,而向量的长度是动态的,可以在运行时增加或减少,因此使用的是堆内存。
创建向量fn main() { let vec: Vec= Vec::new(); }
这种创建方式必须指定向量类型。
fn main() { let vec = vec![1, 2, 3]; let vec = vec![0; 5]; // [0, 0, 0, 0, 0] }
这种创建方式可以根据数据自动推断出向量类型。
添加元素fn main() { let vec: Vec读取元素= Vec::new(); vec.push(0); }
直接利用索引读取,如下:
fn main() { let vec = vec![1, 2, 3, 4]; println!("{}", vec[3]); }
当访问越界时,比如vec[4],在运行时会panic。也可以使用get方法读取,它返回的是Option
fn main() { let vec = vec![1, 2, 3, 4]; let index = 4; match vec.get(index) { Some(v) => println!("{}", v), None => println!("None"), } }向量元素的引用和向量的引用
Vec
fn main() { let mut vec = vec![1, 2, 3, 4]; let ele = &vec[3]; vec.push(5); } // 报错信息 // 3 | let ele = &vec[3]; // | --- immutable borrow occurs here // 4 | vec.push(5); // | ^^^^^^^^^^^ mutable borrow occurs here // 5 | println!("{}", ele); // | --- immutable borrow later used here
如果向量中的元素没有实现Copy特性,比如Vec
fn main() { let vec = vec![String::from("hello"), String::from("world")]; let x = vec[0]; } // 报错信息 // 3 | let x = vec[0]; // | ^^^^^^ // | | // | move occurs because value has type `String`, which does not implement the `Copy` trait // | help: consider borrowing here: `&vec[0]`
如提示,可以通过引用访问元素,let x = &vec[0];,也可以通过get方法访问元素,这种情况下,返回的是引用,如下:
fn main() { let vec = vec![String::from("hello"), String::from("world")]; let x = vec.get(0); // x是&String类型 }删除向量
当向量离开作用域后,会调用drop方法释放向量占用的资源,同时,向量中的元素也会调用drop方法释放资源。
使用迭代器访问元素如果不改变向量中的元素,如下:
fn main() { let vec = vec![1, 2, 3, 4]; for e in vec { print!("{}, ", e); } println!(""); } // 输出 // 1, 2, 3, 4,
如果改变向量中的元素,如下:
fn main() { let vec = vec![1, 2, 3, 4]; for mut e in vec { e += 1; print!("{}, ", e); } println!(""); } // 输出 // 2, 3, 4, 5,
当执行for e in vec {}时,会自动调用vec的iter方法,返回一个迭代器,迭代器调用next方法获取下一个元素,返回Some或None,当返回Some时,会将数据提取出来赋给e,如果返回None,则遍历完成,退出。类似地,当执行执行for mut e in vec {}时,会自动调用vec的into_iter方法,into_iter和iter的区别见博客。而iter和into_iter方法,第一个参数都是self,也就是说执行方法后会发生所有权的转移,在执行for e in vec {}之后,vec就不能使用了,示例如下:
fn main() { let vec = vec![String::from("hello"), String::from("world")]; for s in vec { println!("{}", s); } println!("{:?}", vec); } // 报错信息 // 2 | let vec = vec![String::from("hello"), String::from("world")]; // | --- move occurs because `vec` has type `Vec`, which does not implement the `Copy` trait // 3 | for s in vec { // | --- // | | // | `vec` moved due to this implicit call to `.into_iter()` // | help: consider borrowing to avoid moving into the for loop: `&vec` // ... // 6 | println!("{:?}", vec); // | ^^^ value borrowed here after move
还需要注意的是,vec中元素的所有权,也在每一次迭代中移动到了变量e上。
如果想要在使用迭代器访问vec元素后,依然可以使用vec,代码如下:
fn main() { let vec = vec![1, 2, 3, 4]; for r in &vec { print!("{}, ", r); } println!(""); } // 输出 // 1, 2, 3, 4,
并且想改变vec中元素的值,代码如下:
fn main() { let mut vec = vec![1, 2, 3, 4]; for r in &mut vec { *r += 1; print!("{}, ", r); } println!(""); println!("{:?}", vec) } // 输出 // 2, 3, 4, 5, // [2, 3, 4, 5]
for r in &vec {}编译时,发现&Vec
综上,for mut e in vec {}中mut在e前面,是因为e获取了vec中元素的所有权,因此想要e可变,需要用mut关键字修饰它,而for r in &mut vec {}中,r获取的是vec中元素的可变引用,由于我们只需要改变r指向的对象,而不需要改变r本身,所用不需要用mut修饰r。
字符串StringString、str和&str这三者的联系和区别,可见博客。
简单来说,String类型包含一个指向堆的指针,以及长度和容量信息,&str包含一个指向堆的指针和长度信息,str类型是被&str指向的数据,实际中,基本不会用到str,常用的是&str。
创建字符串有以下几种方式:
- 使用String::new()创建一个空的字符串。
- 使用String::from("...")创建一个字符串。
- 在实现了Display特性的对象上调用to_string方法得到一个字符串。
fn main() { let s = String::new(); let s = String::from("hello, world"); let n = 3; let s = n.to_string(); }更新字符串
- push方法,其参数是char类型。
- push_str方法,其参数是&str类型。
- let s3 = s1 + &s2;,调用字符串的add方法,其签名为fn add(self, s: &str) -> String,由于第一个参数是self,所以s1的所有权会发生转移。
- 使用format!宏,比如let s3 = format!("{}{}", s1, s2);,其用法和println!类似,区别在于后者将字符串显示在屏幕上,而前者返回字符串。
Rust中不能通过索引来获取字符串中的单个字符,比如下面的代码就会报错,
fn main() { let s = String::from("hello"); println!("{}", s[0]); } // 报错信息 // 3 | println!("{}", s[0]); // | ^^^^ `String` cannot be indexed by `{integer}` // | // = help: the trait `Index<{integer}>` is not implemented for `String`
其中原因要从Rust对字符串的表示说起。Rust中的字符串是UTF-8编码的,这是一个变长编码,不同字符的编码长度不一样,比如字符a需要一个字节进行编码,而字符你需要三个字节。 Rust中的String实质上是对Vec
字符串的len方法返回的也是存储字符串所需的字节数量,如下:
fn main() { let s = String::from("你好"); println!("{}", s.len()); } // 输出 // 6
如果需要访问字符串中的字符,可以调用chars方法,chars方法返回是一个迭代器,通过迭代器可以依次访问字符串中的字符,如果需要依次访问字节,可以使用bytes方法,如下:
fn main() { let s = String::from("你好"); for c in s.chars() { print!("{}", c); } println!(""); for b in s.bytes() { print!("{}, ", b); } println!(""); } // 输出 // 你好 // 228, 189, 160, 229, 165, 189,字符串切片
获取字符串的子串,可以利用字符串切片,但需要注意的是,&x[start..end]中的起始和结束位置,是指字节而不是字符,如果获取的子串不能被正确解码,就会在运行时panic,如下:
fn main() { let s = String::from("你好"); println!("{}", &s[0..1]); } // 报错信息 // thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside '你' (bytes 0..3) of `你好`'哈希表HashMap
哈希表HashMap
常见的方法是使用new创建一个空哈希表,然后插入元素,如下:
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(String::from("hello"), 1); map.insert(String::from("world"), 2); }
也可以使用迭代器的collect方法创建哈希表,但要求迭代器中的每个元素是元组,元组的第一个值作为键,第二个值作为键对应的值,如下:
use std::collections::HashMap; fn main() { let strings = vec![String::from("hello"), String::from("world")]; let numbers = vec![1, 2]; let mut map: HashMap<_, _> = strings.into_iter().zip(numbers.into_iter()).collect(); }
这里需要指定变量的类型是HashMap<_, _>,因为collect方法可以将迭代器转换为不同的类型,比如这里是将迭代器转换为哈希表,但也可以转换成向量,如下:
fn main() { let strings = vec![String::from("hello"), String::from("world")]; let numbers = vec![1, 2]; let vec: Vec<_> = strings.into_iter().zip(numbers.into_iter()).collect(); println!("{:?}", vec); } // 输出 // [("hello", 1), ("world", 2)]
因此必须显式地表明collect转换后的类型,这里不用表明键和值的类型,因为编译器可以自动推断。
哈希表和所有权将没有实现Copy特性的对象,比如字符串,插入到哈希表中(作为键或者值),所有权会发生转移,如下:
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); let x = String::from("hello"); map.insert(x, 1); println!("{}", x); } // 报错 // 5 | let x = String::from("hello"); // | - move occurs because `x` has type `String`, which does not implement the `Copy` trait // 6 | map.insert(x, 1); // | - value moved here // 7 | println!("{}", x); // | ^ value borrowed here after move访问元素
同向量类似,哈希表也使用get方法访问元素,返回的是Option<&V>类型,如果查询的键不存在,返回None,否则返回Some,注意这里返回的是引用,所以不获取所有权,而且这里是不可变引用,也不能对其解引用后修改,例子如下:
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(String::from("hello"), 1); map.insert(String::from("world"), 2); let key = String::from("hello"); let value = map.get(&key); match value { Some(v) => *v += 1, None => println!("None"), } } // 报错信息 // 11 | Some(v) => *v += 1, // | - ^^^^^^^ `v` is a `&` reference, so the data it refers to cannot be written // | | // | help: consider changing this to be a mutable reference: `&mut i32`
此外,哈希表的get方法,传递的参数类型为&T,这里因为哈希表的键可能是字符串这种没有实现Copy特性的类型,而我们不希望在传参时发生所有权的转移。
类似于向量,哈希表也可以通过迭代器访问,比如for (k, v) in &hash_map {}。
更新哈希表可以直接调用insert方法向哈希表中插入元素,问题在于如果键存在,则会覆盖原来的键值。
考虑场景:当需要插入的键已经存在于哈希表中,则不插入,否则插入。
这种情况可以使用哈希表的entry方法来处理,将想要插入的键作为参数传递给entry,entry会返回Entry枚举类型,该枚举类型有or_insert方法,将想要插入的值传递给or_insert方法,如果键存在于哈希表中,就不会插入值,否则插入,代码如下:
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(String::from("hello"), 1); map.insert(String::from("world"), 2); map.entry(String::from("hello")).or_insert(3); println!("{:?}", map); map.entry(String::from("worl")).or_insert(4); println!("{:?}", map); } // 输出 // {"world": 2, "hello": 1} // {"worl": 4, "world": 2, "hello": 1}
值得注意的是,entry和or_insert方法的参数不是引用类型,因此会发生所有权的转移。
考虑场景:对哈希表中某键对应的值进行更新,比如计数器。
这种情况也可以使用or_insert方法来处理,or_insert返回的是值的可变引用,因此可以解引用后再进行修改,如下:
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(String::from("hello"), 1); map.insert(String::from("world"), 2); let v = map.entry(String::from("hello")).or_insert(3); *v += 100; println!("{:?}", map); } // 输出 // {"world": 2, "hello": 101}
这里or_insert方法之所以返回&mut T,而不是Option<&mut T>,是因为当键对应的值不存在时,or_insert方法首先会插入值,再返回可变引用,当键对应的值存在时,直接返回可变引用,所以,or_insert始终可以返回可变引用,就没有必要使用Option枚举类型。
哈希函数Rust默认使用是哈希函数是SipHash,也可以设置使用其它的哈希函数。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)