之前用到的 String 类型,并没有那么简单,接下来会聊一下关于 String 更多的一些应用。在说 String 前,需要先学习一下 Vec 这种数据类型,类似于一个动态的数组。然后是 HashMap,一个键对值的数据类型,与其他编程语言中的字典很类似。

Vec

Vec<T> 和数组一样,用于存储一系列相同类型的值。但是 Vec 可以动态地插入,删除。首先,是创建一个 Vec,可以使用 Vec::new(),或者使用宏 vec!。要注意的是,只有使用 mut,才能使 Vec 可变,也就是可以插入和删除值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
// 在定义时就标明数据类型为 i32 的 Vec
let v1: Vec<i32> = Vec::new();

// 在定义时不标明类型,而在首次插入值时,由Rust自动推断
let mut v2 = Vec::new();

// 这里插入了一个 i32 的值,所以Rust推断 v2 为存放 i32 的 Vec
v2.push(2);

let v3 = vec![1,2,3,4,5];

let v4 = vec!["hello", "rust"];

let mut v5: Vec<String> = vec!["hello".to_string(), "rust".to_string()];
}

向 Vec 有两个操作函数,一个是 push,往里插入值,一个是 pop,往外弹出值,Pop返回的是最后插入的值。看下面的代码

1
2
3
4
5
6
7
8
9
fn main() {
let mut v1 = Vec::new();
v1.push(2);
v1.push(3);
v1.push(4);
let x = v1.pop();

println!("{:?}", x); // Some(4);
}

为什么是 Some(4) 而不是 4呢,因为 pop 返回的是 Option<T> 类型的。我们也可以像访问数组一样,使用 [索引] 的形式来访问 Vec 中的值

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let mut v1 = Vec::new();
v1.push(2);
v1.push(3);
v1.push(4);
let x = v1[1];

println!("{:?}", x); // 3

v1[0] = 100;
println!("{:?}", v1); // [100, 3, 4]
}

可以看出,使用索引的形式访问 Vec,返回的就是里面的值,而不是 Option<T> 类型。但是,使用索引访问时,如果索引越界,将导致 panic。如果要更新直接 Vec 中的值,也可以直接使用索引的形式。

接下来就是遍历,看下面的代码

1
2
3
4
5
6
7
8
9
10
fn main() {
let mut v1 = Vec::new();
v1.push(2);
v1.push(3);
v1.push(4);

for x in v1.iter() {
println!("{}", x);
}
}

遍历我们使用 iter() 这个函数,代码中的 x 是对 v1中每一个值的引用,&i32 类型。以上就是 Vec 的常用内容,更详细的方法直接看官方文档

String

对于 String,在之前我们已经使用过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
// 下面两种形式创建一个 String
let mut s1 = String::from("hello");
let mut s2 = "hello".to_string();

// push_str 可以拼接一个字符串
s1.push_str(" Rust");
println!("s1: {}", s1);

// push 可以拼接一个字符
s2.push(' ');
s2.push('A');

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

我们也可以使用下面的方式来拼接字符串

1
2
3
4
5
6
7
fn main() {
let s1 = String::from("Hello ");
let s2 = s1 + "Rust";

println!("s2: {}", s2);
// println!("s1: {}", s1); // 这里不能再使用 s1,因为 s1 的所有权已经移动
}

String 类型可以使用 + 操作,注意第一个参数,一定是一个 String,而后面的参数,都是 &str,感觉内部对于 + 的实现,就是调用了,push_str。除此之外,还可以使用一个宏来拼接字符串

1
2
3
4
5
6
7
8
fn main() {
let s1 = String::from("Hello");
let s2 = String::from("Rust");
let s3 = format!("{} {}", s1, s2);
println!("s1: {}", s1);
println!("s2: {}", s2);
println!("s3: {}", s3);
}

format! 这个宏并没有获取 s1,s2 的所有权。接下来我们看一个和其他编程语言中的字符串不一样的地方。

1
2
3
4
5
6
7
fn main() {
let s1 = String::from("Hello");
println!("{}", s1.len()); // 5

let s2 = String::from("你好");
println!("{}", s2.len()); // 6
}

上面的代码中,s2 的长度是 6,而不是2,这是为什么呢。这里因为,Rust 中的 String 是 UTF-8 编码的,它可以包含任何可以正确编码的数据,例如下面的代码

1
2
3
4
5
6
7
8
9
10
11
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");

第一段代码中,”Hello” 是五个字母,每一个字母的 UTF-8 编码都占用一个字节,所以s1的长度是 5,对于 Rust 来说,len() 函数取的不是字符的长度,而是字节的长度,这一点与其他编程语言不太一样。而 “你好”,显然,每一个字被编码为三个 UTF-8 字节,所以长度为 6。

对于 Rust 来说,关于字符串,有三个概念,字节标量值字形簇。例如 “नमस्ते”,从节字的角度来说,它是 [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]。而从标量值(char)的角度来说,它是 [‘न’, ‘म’, ‘स’, ‘्’, ‘त’, ‘े’]。最后,如果从字形簇的角度来说,它就是原文中的样子 [“न”, “म”, “स्”, “ते”]。字形簇是最接近我们常用的字母的概念。

字符串是不允许使用 [索引] 的形式访问单个字符的,也是因为 String 存储的是 UTF-8 编码的数据这种特性,因为如果允许使用索引,可能导致访问到字符一个字符的中间,就造成了无效索引。

字符串的遍历,可以使用 chars() 这个函数,它会返回这个字符串的 Unicode 标题值。

1
2
3
4
5
6
fn main() {
let s1 = String::from("नमस्ते");
for x in s1.chars(){
println!("x: {}", x);
}
}

如果要访问每一个 UTF-8 值,也可以使用 bytes() 方法

1
2
3
4
5
6
fn main() {
let s1 = String::from("नमस्ते");
for x in s1.bytes(){
println!("x: {}", x);
}
}

HashMap

HashMap 是一个键对值的存储结构,键保持唯一,对,它就像其他编程语言中的字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use std::collections::HashMap;

fn main() {
// 创建一个HashMap
let mut map: HashMap<i32, i32> = HashMap::new();

// 向HashMap中插入值
map.insert(0, 0);
map.insert(1, 1);
println!("{:?}", map); // {0: 0, 1: 1}

// 如果插入重复Key的值,原值将被覆盖 (更新)
map.insert(0, 10);
println!("{:?}", map); // {0: 10, 1: 1}

// 判断是否包指定的 Key,这里传的是引用
if map.contains_key(&0) {
println!("Has Key 0");
} else {
println!("No key 0");
}

// 移除一指定 key 的键和值
map.remove(&0);
println!("{:?}", map); // {1: 1}

// 如果 map 中不存在指定的 key,则插入,否则不插入
map.entry(0).or_insert(100);
println!("{:?}", map); // {0: 100, 1: 1}

// 从 map 中获取一个值,注意返回的类型是 Option<T> 类型
let first = map.get(&0);
println!("{:?}", first); // Some(100);
}