Rust入门笔记(二)
记录在https://course.rs 上学习Rust中的一些知识点
2.4.1 字符串与切片
Rust的字符串和切片概念也是比较难的一点。感觉我之前几次都是看到这里,然后卡住了很久。后面就忙了没空继续看了,下一次又从头看起(捂脸)。所以这部分对我也是一座大山。
String是字符串类型,内容是字节数组。值的内容在堆上,而本身是一个胖指针,包含ptr,len(已使用长度),capacity(最大容量)。
len和capacity全是按字节数计算的,本身内容是utf-8编码(1~4个字节不定长),区别于char类型的Unicode固定4字节。所以不可以按索引遍历,避免遍历到某个字符中间的某个字节上切片:引用集合(详细定义在后面几节)中部分连续的元素序列。字符串切片(&str类型),即引用字符串中的一部分, 索引是左闭右开。
1
2
3
4let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];特殊索引:省略左边便是从0开始,省略右边表示直到最后。两侧全省略,即
..表示全部由于第1点提过字符串索引是按字节的,所以通过所以去构造字符串切片需要谨慎。
&str和String共享堆上的同一块内存,所有权在String, &str本身是引用,即借用

字符串字面量也是&str,例如
let s: &str = "Hello, world!";&str转成String:
- String::from(“hello,world!”);`
"hello,world".to_string()
String转成&str:
- &s(&String可以被隐式转换成&str)
- &s[..](标准字符串切片语法)
- s.as_str()
遍历String:
按Unicode字符遍历
1
2
3for c in "中国人".chars() {
println!("{}", c);
}按字节遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14for b in "中国人".bytes() {
println!("{}", b);
}
//输出
228
184
173
229
155
189
228
186
186
如果想截取子串,需要借助三方库,例如utf8_slice
字符串操作:
push、push_str操作原字符串
insert操作原字符串
replace、replacen不操作原字符串,replace_range操作原字符串
pop、remove、truncate(删除指定位置到结尾的全部字符)、clear都是操作原字符串
+、+=不操作原字符串,但是要求第二个参数一定是&str,不能是String(&String可以,会被强制转换)
2.4.2 元组+2.4.3结构体
元组是用()包起来的,可以使不同类型。可以通过“.+索引”来访问元组中指定的元素
结构体使用例如
1
2
3
4
5
6
7
8
9
10
11
12
13struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};结构体被声明为可变时,才可修改里面字段的值
当结构体去某些字段的所有权时,结构体不可被访问。但是可访问里面仍有所有权的字段。
例如失去了email的所有权,那么访问user和user.email都会报错,但是访问user.username是可以的基于已有结构体创建新结构体,例如
1
2
3
4
5let user2 = User {
email: String::from("another@example.com"),
..user1
};
与ts用
...来展开老对象不同的是,rust用..,且要把改变的值写在前面,..user1必须写在最后,这与ts是相反的如果结构体里面的字段是引用类型(表明所有权借用自别处),则需要生命周期
想打印结构体,需要用
#[derive(Debug)]标记结构体,然后用{:?}或{:#?}
2.4.4 枚举
Rust的枚举也和其他语言很不一样,不只是几个固定的值,功能更加丰富。更像是一种分类,每个分类下还可以有更具体的值
最简单(仅有分类)的例如
1
2
3
4
5
6
7
8
9
10// 定义
enum PokerSuit {
Clubs,
Spades,
Diamonds,
Hearts,
}
// 使用
PokerSuit::Clubs每个分类下,带有不同的值
1
2
3
4
5
6
7
8
9
10
11
12// 定义
enum Message {
Quit, // 不带值
Write(String), // 带 1 个值
Move(i32, i32), // 带多个值(元组)
ChangeColor { r: u8, g: u8, b: u8 }, // 带命名字段(像 struct)
}
// 使用
Message::Write(String::from("hello"));
Message::Move(2,10);
Message::ChangeColor{r: 10, g: 55, b: 38};特殊的枚举Option,标准库内置的枚举类型,处理空值。有点类似ts中,将类型声明为T|undefined
1
2
3
4
5
6
7
8
9
10enum Option<T> {
Some(T),
None,
}
// 使用
let some_number = Some(5);
let some_string = Some("a string");
// 使用None值的时候,需要指定T的类型
let absent_number: Option<i32> = None;
2.4.5 数组
Rust的数组(array)是定长的,和ts不一样,和c++很像。变长的是Vector
声明数组,类型声明中,元素类型和数量用分号分隔。元素类型必须一致
1
let a: [i32; 5] = [1, 2, 3, 4, 5];快捷声明重复值
1
let a = [3; 5]; //[3, 3, 3, 3, 3]通过索引访问数组元素
a[0],如果索引越界会panic如果数组元素不是基础类型(没有实现Copy特征),就不能使用上面的快捷创建重复值的数组写法
因为这种写法是通过Copy来赋值的。可以用下面的std::array::from_fn方法,_i是未使用到的变量(类似匿名函数),值是当前的索引1
2
3let arr = [String::from("rust is good!"); 8]; // ❌️
let arr: [String; 8] = std::array::from_fn(|_i| String::from("rust is good!")) // ✅️数组切片
我们常说的数组切片,其实指的是数组切片的引用。严格来讲,数组类型是[T; n],切片类型是[T],切片引用类型是&[T]。
切片类型的长度是不固定的,因为需要真实包含被切的元素,但是具体几个又无法在编译期得知。
但是切片引用类型的长度是固定的,它只需要存原本数组中,第一个切的元素的地址,和一共要包含多少个元素1
2
3
4
5let a: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];
assert_eq!(slice, &[2, 3]);