枚举定义,枚举方法,match匹配
枚举(enumerations)
,也被称作 enums
。枚举允许你通过列举可能的成员(variants)
来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。然后会讲到在match
表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。
假设我们要处理 IP
地址。目前被广泛使用的两个主要 IP
标准:IPv4(version four)
和 IPv6(version six)
。这是我们的程序可能会遇到的所有可能的 IP
地址类型:所以可以 枚举
出所有可能的值,这也正是此枚举名字的由来。
任何一个 IP
地址要么是 IPv4
的要么是 IPv6
的,而且不能两者都是。IP
地址的这个特性使得枚举数据结构非常适合这个场景,因为枚举值只可能是其中一个成员。IPv4
和 IPv6
从根本上讲仍是 IP
地址,所以当代码在处理适用于任何类型的 IP
地址的场景时应该把它们当作相同的类型。
可以通过在代码中定义一个 IpAddrKind
枚举来表现这个概念并列出可能的 IP
地址类型,V4
和V6
。这被称为枚举的 成员(variants)
:
enum IpAddrKind {
V4,
V6,
}
进一步考虑一下我们的IP
地址类型,目前没有一个存储实际IP
地址 数据
的方法;只知道它是什么 类型
的。考虑到已经学习过结构体了,看示例:
// ip地址类型枚举
enum IpAddrKind {
V4,
V6,
}
// ip地址结构体
struct IpAddr {
kind: IpAddrKind,
address: String,
}
fn main() {
let i1 = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let i2 = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
}
我们创建这个结构体的两个实例。第一个,i1
,它的 kind
的值是 IpAddrKind::V4
与之相关联的地址数据是 127.0.0.1
。第二个实例,i2
,kind
的值是 IpAddrKind
的另一个成员,V6
,关联的地址是 ::1
。我们使用了一个结构体来将kind
和 address
打包在一起,现在枚举成员就与值相关联了。
我们可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。IpAddr
枚举的新定义表明了 V4
和 V6
成员都关联了 String
值:
enum IpAddr {
V4(String),
V6(String),
}
fn main() {
let i1 = IpAddr::V4(String::from("127.0.0.1"));
let i2 = IpAddr::V6(String::from("::1"));
}
我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。这里也很容易看出枚举工作的另一个细节:每一个我们定义的枚举成员的名字也变成了一个构建枚举的实例的函数。也就是说,IpAddr::V4()
是一个获取String
参数并返回 IpAddr
类型实例的函数调用。作为定义枚举的结果,这些构造函数会自动被定义。
用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。IPv4
版本的 IP
地址总是含有四个值在0
和 255
之间的数字部分。如果我们想要将 V4
地址存储为四个 u8
值而 V6
地址仍然表现为一个 String
,这就不能使用结构体了。枚举则可以轻易的处理这个情况:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let i1 = IpAddr::V4(127, 0, 0, 1);
let i2 = IpAddr::V6(String::from("::1"));
}
枚举成员中内嵌了多种多样的类型:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
这个枚举有四个含有不同类型的成员:
结构体和枚举还有另一个相似点:就像可以使用 impl
来为结构体定义方法那样,也可以在枚举上定义方法。这是一个定义于我们 Message
枚举上的叫做 call
的方法:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {}
}
fn main() {
// 调用方法一
let m = Message::Write(String::from("hello"));
m.call();
// 调用方法二
Message::Write(String::from("hello")).call();
}
方法体使用了 self
来获取调用方法的值。这个例子中,创建了一个值为 Message::Write(String::from("hello"))
的变量 m
,而且这就是当 m.call()
运行时 call
方法中的 self
的值。
Rust
有一个叫做 match
的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成,我们使用match
打印Message
枚举:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn prin(&self) {
match self {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move x = {}, y = {}", x, y),
Message::ChangeColor(a, b, c) => {
println!("ChangeColor a = {}, b = {}, c = {}", a, b, c)
}
_ => println!("Write"),
}
}
}
fn main() {
Message::Quit.prin();
Message::Move { x: 10, y: 12 }.prin();
Message::Write(String::from("write")).prin();
Message::ChangeColor(1, 2, 3).prin();
}
当 match
表达式执行时,它将结果值按顺序与每一个分支的模式相比较。如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支。match
所罗列的匹配,必须穷举出其所有可能。当然,你也可以用_
这个符号来代表其余的所有可能性情况。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!