常见集合
使用 vector 存储一列值
vector 允许我们在一个单独的数据结构中储存多个值,所有值在内存中彼此相邻排列。vector 只能储存相同类型的值。
新建 vector
新建一个 vector,需要我们提供一个类型,然后调用 Vec 上的 new 方法
fn main() {
let v: Vec<i32> = Vec::new();
println!("{:#?}", v);
}
但是一般我们在创建的时候,rust 会根据我们给的值直接判断类型,所以我们不需要特意去强调泛型里的值,所以也可以像下面这样写:
fn main() {
let v = vec![1, 2, 3];
println!("{:#?}", v);
}
更新 vector
可以使用 push 函数增加元素,当 vec 离开作用域是,会自动被回收。
fn main() {
let mut v = vec![1, 2, 3];
v.push(4);
println!("{:?}", v);
}
读取 vector
我们有两种方式来读取一个 vector,get 或者 索引的方式。
索引
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("The first element is: {}", first);
}
get
fn main() {
let mut v = vec![1, 2, 3];
match v.get(3) {
Some(item) => println!("{}", item),
None => println!("None"),
}
}
由此可见,get 返回的类型实际上是一个 Option<&T>
一个注意点
fn main() {
let v = vec![1, 2, 3];
let fourth = &v[3];
println!("The fourth element is {}", fourth);
}
上面的代码在编写的时候并不会引起代码恐慌,但是在编译的时候肯定会报错。
但是注意下面的这种情况,看似你是使用了与是否添加新值没有关系的第一个元素,但是如果 vector 检测到你添加了一个元素之后,原来的内存地址需要迁移,那么第一个元素的引用就也需要改变,所以这里会引起恐慌。
fn main() {
let mut v = vec![1, 2, 3];
let fourth = &v[0];
// 这里会引起 rust panic,因为 fourth 使用了不可变引用,而 v.push(4) 会改变 v 的长度
// 这里主要是一个什么问题呢:可能内存地址会改变
v.push(4);
println!("The fourth element is {}", fourth);
}
遍历 vector
fn main() {
let mut v = vec![1, 2, 3];
for i in &v {
println!("{}", i);
}
}
修改 vector
fn main() {
let mut v = vec![1, 2, 3];
for i in &mut v {
*i = *i + 100;
println!("{}", i);
}
}
定义不同类型 vector
如果我们想在一个 vector 中定义不同的类型呢?
这时候我们就可以使用枚举,因为枚举被认为说是同一个类型。
#![allow(unused)]
fn main() {
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
}
使用字符串存储 utf8 编码的文本
什么是字符串
rust 的核心语言中只有一种字符串类型 str
。
称做 String
的类型是由标准库,提供的,没有写进核心语言部分,是一个可以增长的、可变的、有所有权的、utf8 编码的字符串类型。
新建字符串
fn main() {
// 新建一个空字符串
let mut str: String = String::new();
// 向字符串中添加内容
str.push_str("Hello, ");
str.push_str("world!");
println!("{}", str);
}
也可以在新建的时候直接加入字符串的值:
let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
更新字符串
可以使用 push_str
和 push
在字符串的结尾附加字符串,只不过 push
的参数是一个单独的字符。
索引字符串
rust 是不允许通过索引来获取字符串的!
fn main() {
let s1 = String::from("hello");
// 这样写会报错,因为 s1 是 String 类型,而不是 Vec 类型
// 也就是说,String 类型不支持索引操作
// let h = s1[0];
}
内部表现
因为存储字符时最终都是存储字符的 utf8 编码需要的字节,而有的语言中的字符每个值需要两个字节来存储,所以并不是字符串的索引对应一个有效的 unicode 标量值。
所以,请看下面的代码:
let hello = "Здравствуйте";
let answer = &hello[0];
如果可以返回的话,返回的结果就会是 208,因为第一个 Unicode 值为 3,实际上存储的字节需要两个,一个是 208,一个是 151,根据索引就会返回 0 位置上的数据,就是 208。
所以 rust 不允许通过索引获取字符串。
字符串 slice
所以根据以上的描述,如果在创建切片引用的时候,一个 unicode 包含了两个字节,那么也会创建失败。
#![allow(unused)]
fn main() {
let hello = "Здравствуйте";
// 创建成功
let s = &hello[0..4];
// 创建失败
// let s = &hello[0..1];
println!("{}", s);
}
遍历字符串
chars()
如果你需要淡出的 Unicode 标量值,可以选择 chars()
方法。
#![allow(unused)]
fn main() {
for c in "नमस्ते".chars() {
println!("{}", c);
}
}
结果:
न
म
स
这个字符没打出来
त
这个字符没打出来
bytes()
与 chars()
之间的区别就在于,bytes()
返回的结果是字节,而不是单独的 unicode 字符。
#![allow(unused)]
fn main() {
for b in "नमस्ते".bytes() {
println!("{}", b);
}
}
结果:
224
164
168
224
164
174
224
164
184
224
165
141
224
164
164
224
165
135
返回的结果就是字节。
此外
如果真的想通过索引获取字符串中的某一个值的话,可以这些写:
#![allow(unused)]
fn main() {
let s = String::from("Здравствуйте");
if let Some(third_char) = s.chars().nth(2) {
println!("Third character is: {}", third_char);
} else {
println!("String is too short.");
}
}
返回的就是第三个字符 p
。
哈希 map 存储键值对
什么是 map
hash map 是非常常用的集合类型。
HashMap<K, V>
类型存储了一个键类型 K,对应一个值类型 V 的映射。底层是通过哈希函数来实现的映射。
新建一个 map
使用 HashMap()
:
fn main() {
// 首先必须 use 标准库中的 HashMap,因为在这几个集合类型中,HashMap 是最不常用的,所以需要引入
// 所以并没有被 preclude 导入
use std::collections::HashMap;
// vscode 会提示,第一个参数的类型是字符串,第二个是整数
// 所有的键的类型必须相同,所有的值的类型也要相同
let mut scores = HashMap::new();
scores.insert(String::from("blue"), 10);
scores.insert(String::from("yellow"), 50);
}
使用 vector 中的 collect,其中的每一个元组都是一个键值对。
#![allow(unused)]
fn main() {
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
print!("{:?}", scores); // {"Blue": 10, "Yellow": 50}
}
hashmap 和 所有权
像对于 i32
这种实现了 Copy trait 的类型,它的值可以被拷贝进哈希 map。但是对于像 String
这样的拥有所有权的值,其值将被移动而哈希 map 会成为这些值的拥有者。
fn main() {
use std::collections::HashMap;
let field_name = "Favorite color";
let field_value = "Blue";
let mut map = HashMap::new();
map.insert(field_name, field_value);
// 之后 filed_name 和 field_value 不再有效
// println!("{}: {}", field_name, field_value);
println!("{:?}", map);
}
访问 map 的值
使用 get()
方法即可访问对应的值。
fn main() {
use std::collections::HashMap;
let field_name = "Favorite color";
let field_value = "Blue";
let mut map = HashMap::new();
map.insert(field_name, field_value);
// 通过引用获取值
let color = map.get(&field_name);
println!("{:?}", color);
// 通过值获取值
let color = map.get(field_name);
println!("{:?}", color);
}
遍历 map
fn main() {
use std::collections::HashMap;
let field_name = "Favorite color";
let field_value = "Blue";
let mut map = HashMap::new();
map.insert(field_name, field_value);
// 再插入一个值
map.insert("Favorite color1", "Red");
println!("{:?}", map);
// 为什么不能 map.insert(String::from("123"),123)
// 因为map的key是String类型,而不是&str类型
// 这是因为一开始第一个插入的是&str类型,所以map的key类型就是&str类型
// 如果一开始插入的是String类型,那么map的key类型就是String类型
for (key, value) in map {
println!("{}: {}", key, value);
}
}