Rust入门笔记(三)
记录在https://course.rs 上学习Rust中的一些知识点
2.5.1 条件语句
基本语法
1
2
3
4
5
6
7
8if condition == true {
// A...
} else if {
// B...
} else {
// C..
}if语句块是个表达式,可以用来赋值。用来赋值的时候,多个分支返回的类型要一样
1
2
3
4
5
6
7
8
9
10fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
}
2.5.2 for循环
基本语法
1
2
3
4
5
6
7
8for 元素 in 集合 {
}
// 例如
for i in 1..=5 {
println!("{}", i);
}如果被循环的集合没有实现copy特征,那么for语句中应该使用它的引用,不然for语句后它会因为所有权转移,没办法被继续使用
1
2
3for item in &container {
// ...
}但如果集合是[i32:10]这种实现了copy特征的数组就无所谓
如果要修改集合中的元素,应该在集合前加mut关键字
使用方法 等价使用方式 所有权 for item in collectionfor item in IntoIterator::into_iter(collection)转移所有权 for item in &collectionfor item in collection.iter()不可变借用 for item in &mut collectionfor item in collection.iter_mut()可变借用 想省略元素的时候,可以用
for _ in 0..10这种写法对比一下两种写法
第二种更优,因为编译器无需再运行时进行边界检查。而且第一种访问期间,collection可能发生变化。第二种因为所有权限制,不会变(已经被不可变借用了,就不能再被可变借用)1
2
3
4
5
6
7
8
9
10
11// 第一种
let collection = [1, 2, 3, 4, 5];
for i in 0..collection.len() {
let item = collection[i];
// ...
}
// 第二种
for item in collection {
}continue和break语句和其他语言一样
2.5.3 while循环和loop循环
while基本语法
1
2
3
4
5while n <= 5 {
println!("{}!", n);
n = n + 1;
}loop基本语法
1
2
3loop {
println!("again!");
}loop一般配合break使用。loop本身是个表达式,可以通过break返回一个值
1
2
3
4
5
6
7
8
9
10
11
12
13fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
2.6.1 模式匹配之match和if let
模式可以是字面值,可以包含一个待匹配的命名变量,比如模式Some(y)就可以匹配所有Some枚举的值,并且把值赋给变量y
match匹配语法
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
29match target {
模式1 => 表达式1,
模式2 => {
语句1;
语句2;
表达式2
},
_ => 表达式3
}
// 例如
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
Direction::North | Direction::South => {
println!("South or North");
},
_ => println!("West"),
};
}match本身是个表达式,可以用来赋值给别人。值就是匹配到的模式对应的表达式
1
2
3
4
5
6
7
8
9fn main() {
let ip1 = IpAddr::Ipv6;
let ip_str = match ip1 {
IpAddr::Ipv4 => "127.0.0.1",
_ => "::1",
};
println!("{}", ip_str);
}利用模式匹配取到枚举里的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 一个枚举
enum Action {
ChangeColorRGB(u16, u16, u16),
}
Action::ChangeColorRGB(255,255,0)
// 模式匹配
match action {
Action::ChangeColorRGB(r, g, _) => {
println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",
r, g,
);
}
}
// 最终输出
change color into '(r:255, g:255, b:0)', 'b' has been ignoredmatch的匹配必须穷尽所有可能性,比如匹配一个枚举,就要让枚举的所有可能值,都有对应的分支能进入,不能有漏网之鱼。可以用_来匹配未列举的所有模式,用其他变量名也可以匹配剩余场景,只是变量未使用会告警。
1
2
3
4
5
6
7
8let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}if let匹配可以用来只匹配一个模式。当只关心一种场景,其余场景无所谓的时候,用if let更简洁。等号并非赋值,而是连接左侧模式和右侧表达式的符号。
1
2
3
4
5
6
7
8
9
10
11
12// 只关心v是Some(3)的场景
if let Some(3) = v {
println!("three");
}
// 用match就啰嗦了,因为必须穷尽所有场景
let v = Some(3u8);
match v {
Some(3) => println!("three"),
_ => (),
}match和if let如果想要匹配其中的变量,最好不要用同名的,不然会有变量遮蔽的问题,增加理解成本
1
2
3
4
5
6
7
8
9
10
11
12
13fn main() {
let age = Some(30);
println!("在匹配前,age是{:?}",age); // Some(30)
if let Some(age) = age {
println!("匹配出来的age是{}",age);// 30
}
match age {
Some(age) => println!("匹配出来的age是{}",age), // 30
_ => ()
}
println!("在匹配后,age是{:?}",age); // Some(30)
}matches!`可以将表达式和模式匹配,返回true或false
2.6.2 处理Option
前面枚举一节讲了特殊的枚举Option,用于处理可能值为空的场景
1 | |
可以通过模式匹配,分别处理真的有值,和为None的场景,来避开空指针错误。使用例子如下
1 | |
2.6.3 进一步理解模式匹配
本质上,解构赋值,和直接用let赋值,都是模式匹配
let赋值相当于let PATTERN = EXPRESSION;,然后将表达式里面被匹配到的值,绑定到模式里面的变量上
2.7 方法
区别于一般函数,方法可以理解为挂在对象上的一个函数。不过Rust里的方法一般是和结构体,枚举,特征一起使用。
通过impl关键字,来为结构体、枚举增加一个方法
例如
1 | |
通过impl语法,来为Circle结构体增加了一个new方法,和area方法。方法的定义和对象的定义是分开的。对于同一个对象,可以有多个impl块。
其中,new的第一个参数不是self、&self、&mut self,它是一种关联函数,通常被当做构造函数来使用。一个对象可以有多个关联函数,比如一个new是依据传入参数来创建Circle对象的,再来一个不需要传参写死值的,都可以。名字也可以随便起,只要第一个参数不是self、&self、&mut self,就是关联函数。
area就是Circle的方法了,可以通过实例+.操作符来调用,&self不需要显式传参
- 方法的第一个参数:self、&self、&mut self,实际分别是
self: Self、self: &Self、self: &mut Self的简写。三种参数对于是否转移所有权、是否可改原值有区别。直接使用self拿走所有权的场景很少 - 方法名字可以和属性名相同,如果不带括号,就是访问属性,带括号就是调用方法
- rust在调用方法的时候会自动引用和自动解引用,所以
p1.distance(&p2);和(&p1).distance(&p2);等价。一切以方法的函数签名里的类型为准,自动引用或解引用,直到和签名里的类型匹配上。 - 关联函数的调用不是通过
.,而是:: - 为枚举添加方法和为结构体是一样的