枚举和模式匹配-Rust

为什么要用枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义一个名为Point的结构体
struct Point {
x: i32,
y: i32,
}
//结构体主要用于将多个不同类型的数据组合成一个有机的整体,它侧重于数据的聚合。每个结构体实例都包含所有定义的字段,就像一个包含多个属性的对象。例如,Point结构体的每个实例都有x和y坐标。

// 定义一个名为ConnectionStatus的枚举
enum ConnectionStatus {
Connected,
Disconnected,
Connecting,
}
//枚举侧重于表示一个值可以是几种不同的情况之一。它就像一个开关,可以是其中一个特定的状态。比如ConnectionStatus枚举的一个值只能是Connected、Disconnected或者Connecting中的一种情况。

定义简单枚举

枚举声明某个值是一个集合中的一员

IP 标准:IPv4(version four)和 IPv6(version six)

常规ip要么是v4要么是v6,我们可以枚举 出IP所有可能的类型,任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的,而且不能两者都是,这种特性适合用枚举存储数据

1
2
3
4
5
6
7
8
9
enum ipaddrkind {
V4,
V6,
}

fn main() {//创建ipaddrkind的两个实例
let four = ipaddrkind::V4;
let six = ipaddrkind::V6;
}

使用枚举

为枚举赋值,

每一个我们定义的枚举成员的名字变成了一个构建枚举的实例的函数,可以直接使用成员名字接受参数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[derive(Debug)]
enum ipaddrkind {
V4(String),//IpAddr::V4() 是一个获取 String 参数并返回 IpAddr 类型实例的函数调

V6(String),
}

fn main() {

let four = ipaddrkind::V4(String::from("127.0.0.1"));
let six = ipaddrkind::V6(String::from("::1"));

println!("{:?}",four);

}

IPV4是四个八位无符号数组成,我们可以通过优化实现更清晰的定义

image-20241213145744305

使用更多类型的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#[derive(Debug)]
enum Message {
Quit,
Move{x:i32,y:i32},
Write(String),
ChangeColor(i32,i32,i32),
}

impl Message {
fn call(&self){}
}

fn main() {
let m1=Message::Write(String::from("xiuxiu"));



println!("{:?}", m1.call());

}

Option枚举与空值

一个值要么有值要么没值在编程领域常常被用null,nil等关键字表示为空值,例如请求一个空的列表,就什么也不会得到,空值是一个因为某种原因目前无效或缺失的值

1
2
3
4
5
fn main() {
let mut x: &i32;
// 假设这里x是一个空引用(实际Rust不允许这样)
*x = 5;
}//这个代码会导致程序崩溃,因为x是一个没有初始化的引用,对它进行解引用操作是不安全的
1
2
3
4
enum Option<T> {//<T>泛型,代指所有可能的类型
None,
Some(T),
}

因为是枚举类型,要么是none要么是some类型

在某个函数有可能返回空值的情况下可以使用Option作为返回值,这种情况下编程者必须考虑对空值的处理,这就不会导致空引用

match控制流结构

它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。

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
//我们可以编写一个函数来获取一个未知的硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值
enum Coin {//一个硬币不能又是五美分又是一美分,这种状态适合用枚举类型
Penny,
Nickel,
Dime,
Quarter,
}

fn value(coin:Coin)->u8{

match coin {
Coin::Penny=>1,
Coin::Nickel=>5,
Coin::Dime=>10,
Coin::Quarter=>25,
}

}
fn main() {

let p1=Coin::Nickel;

println!("{}",value(p1));

}

对比if表达式必须返回一个布尔值,而match可以是任何类型的

一个分支有两个部分:一个模式和一些代码

每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 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
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}

fn value(coin:Coin)->u8{

match coin {
Coin::Penny=>{
println!("Luckly penny!");
1//不要加分号将会作为返回值,不然还要加return
},//会打印出 “Lucky penny!”,同时仍然返回代码块最后的值,1
Coin::Nickel=>5,
Coin::Dime=>10,
Coin::Quarter=>25,
}

}
fn main() {

let p1=Coin::Penny;

println!("{}",value(p1));

}

匹配Option

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn value(x:Option<i32>)->Option<i32>{

match x {
None=>println!("error value"),
Some(x)=>Some(x+1),
}

}
fn main() {

let five=Some(5);

value(five);


}

通配模式和 _ 占位符

想象我们正在玩一个游戏,如果你掷出骰子的值为 3,角色不会移动,而是会得到一顶新奇的帽

子。

如果你掷出了 7,你的角色将失去新奇的帽子。

对于其他的数值,你的角色会在棋盘上移动相应的格子,

1
2
3
4
5
6
7
8
9
10
11
12
 fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}

fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => move_player(other),//我们可以使用单元值 _ => (),表示无事发生
}
}