【Rust】枚举和模式匹配——Rust语言基础14

发布于:2025-03-16 ⋅ 阅读:(15) ⋅ 点赞:(0)

1. 枚举类型

枚举(enumerations),也被称作 enums。枚举允许你通过列举可能的成员(variants)来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。枚举类型可以看作是一个类型的集合。

例如下面这样定义一个枚举:

enum Lunch {
	duck,
	rabbit,
	chicken,
};

对没错,上面的例子展示了一个经典的鸡兔同笼的场景。

问题:笼子里有3只鸡,6只鸭子,5只兔子,请问为什么有37只脚?
答:因为有一只鸡在独立。

问题:笼子里有3只鸡,6只鸭子,5只兔子,请问为什么有36只脚?
答:因为有两只鸡在独立!
回答错误!!
正确回答:因为有一只鸡在睡觉没看到脚。

好了,开个玩笑,定义一个枚举变量就像上面一样简单。

如下便是使用枚举变量的示例:

	let goku = Lunck::chichen;
	let kuririn = Lunck::duck;

相信有其他编程语言基础的同学对此一定不会陌生,不过更有趣的是,在 rust 中的枚举变量的成员几乎可以指定为任何类型,就像下面这样使用。

    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    let home = IpAddr::V4(127, 0, 0, 1);

    let loopback = IpAddr::V6(String::from("::1"));

亦或者是这样。

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

这些类型的指定都是被 rust 所允许的。请告诉我你们没有忘记这些类型代表什么意义吧~,好吧忘了也没关系,再来复习一次:

  • Quit 没有关联任何数据。
  • Move 类似结构体包含命名字段。
  • Write 包含单独一个 String
  • ChangeColor 包含三个 i32

上面的枚举类型等同于多个结构体类型的定义:

struct QuitMessage; // 类单元结构体
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体

这样看枚举在一定程度上是会比结构体要方便多了,而且枚举变量同样可以通过 impl 块来定义属于该枚举的方法:

    impl Message {
        fn call(&self) {
            // 在这里定义方法体
        }
    }

    let m = Message::Write(String::from("hello"));
    m.call();

1.2. Option 枚举

Option 也是一个枚举类型,在 rust 中没有空值 Null 的概念,由于考虑到所有变量都存在一种空值和非空值的状态会为编程语言带了巨大问题甚至是会引起各种漏洞,因此 rust 摒弃了这一设计理念,但由于空值同时也具有特殊意义,又会为编程带来很多便利,取而代之则衍生出 Option 这样的枚举。

rust 标准库中是这样定义的:

enum Option<T> {
    None,
    Some(T),
}

这里的 <T> 语法是一个泛型参数,虽然目前还没学习到,但相信大家也不陌生,泛型在其它编程语言中也是很常见和重要的存在。这里的 None 就表示没有值,一定程度上等价于 Null 的作用。

2. match 控制流结构

Rust 有一个叫做 match 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;。match 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。

假设有这样一个场景,现在要上海陆家嘴举办 2025 年天下第一武道大会,邀请了:

  • 埼玉《一拳超人》
  • 五条悟《咒术回战》
  • 孙悟空《七龙珠》
  • 漩涡鸣人《火影忍者》
  • 贝吉塔《七龙珠》
  • 蒙奇·D·路飞《海贼王》

这些著名高手来参加,现在要根据这些选手的战斗能力为他们做排名。

enum Hero {
    Saitama,
    Satoru,
    Goku,
    Naruto,
    Bejita,
    Ruffy,
}

fn rank(hero: Hero) -> u8 {
	match hero {
		Hero:Saitama => 1,
		Hero:Satoru=> 2,
		Hero:Goku=> 3,
		Hero:Naruto=> 4,
		Hero:Bejita=> 5,
		Hero:Ruffy=> 6,
	}
}

[注]:这里主持人给的排名仅作为参考,并不具备实际意义,请根据大家自己内心估值来判断。也不要问为什么没有请某某某来,上海市举办方表示资金有限,请不起所有高手聚集此地。

好了,根据上面的简单例子相信各位也能够对 match 的功能了解一二,这里就有人站出来说了“这?这不就是 switch & case 语句嘛~”,欸,差不多,但是 rustmatch 要更加强大一点,请继续往下看。

2.1. match 对绑定值的匹配

正如上文提到,枚举变量可以为其成员指定几乎任何类型,对这种情况 match 该如何应对?

enum Hero {
    Saitama(i32),
    Satoru(i32),
    Goku(i32),
    Naruto(i32),
    Bejita(i32),
    Ruffy(i32),
}
// 整数表示其战斗力数值

fn power(hero: Hero) {
	match hero {
		Hero::Saitama(p) => {
			println!("埼玉: {}", p);
		},
		Hero::Satoru(p) => {
			println!("五条: {}", p);
		},
		Hero::Goku(p) => {
			println!("悟空: {}", p);
		},
		Hero::Naruto(p) => {
			println!("鸣人: {}", p);
		},
		Hero::Bejita(p) => {
			println!("王子: {}", p);
		},
		Hero::Ruffy(p) => {
			println!("草帽小子: {}", p);
		},
	}
}

fn main() {
    let goku = Hero::Goku(12000);

    power(&goku);
}

rust 中允许这样的匹配,正如上述例子中,将会匹配到 goku 属于 Hero::Goku(p) 类型,并同时将其值绑定到了 p 变量,这样就可以获取其战斗力数值了。

2.2. Option 的匹配

比如我们想要编写一个函数,它获取一个 Option<i32> ,如果其中含有一个值,将其加一。如果其中没有值,函数应该返回 None 值,而不尝试执行任何操作。

得益于 match,编写这个函数非常简单:

    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

让我们更仔细地检查 plus_one 的第一行操作。当调用 plus_one(five) 时,plus_one 函数体中的 x 将会是值 Some(5)。接着将其与每个分支比较。

            None => None,

Some(5) 并不匹配模式 None,所以继续进行下一个分支。

            Some(i) => Some(i + 1),

Some(5)Some(i) 匹配吗?当然匹配!它们是相同的成员。i 绑定了 Some 中包含的值,所以 i 的值是 5。接着匹配分支的代码被执行,所以我们将 i 的值加一并返回一个含有值 6 的新 Some

需要注意的一点是,match 匹配是穷举匹配,必须要为所有可能的结果编写对应的匹配处理过程,否则编译器将会阻止这种情况。

2.3. 通配模式以及 _ 占位符

将上面的代码做以简单改动:

enum Hero {
    Saitama(i32),
    Satoru(i32),
    Goku(i32),
    Naruto(i32),
    Bejita(i32),
    Ruffy(i32),
}
// 整数表示其战斗力数值

fn power(hero: Hero) {
	match hero {
		Hero::Saitama(p) => {
			println!("埼玉: {}", p);
		},
		Hero::Satoru(p) => {
			println!("五条: {}", p);
		},
		other => {
		// _ => {	// 与上一行代码等价
			println!("战斗能力未知!!!");
		},
	}
}

fn main() {
    let goku = Hero::Goku(12000);

    power(&goku);
}

最后一个分支则涵盖了所有其他可能的值,模式是我们命名为 other 的一个变量。

3. if let 控制流

这个语法很像 C/C++ 中的 if 语句,因此掌握起来也非常容易,还是照例给个例子看的比较直白一些:

enum Hero {
    Saitama(i32),
    Satoru(i32),
    Goku(i32),
    Naruto(i32),
    Bejita(i32),
    Ruffy(i32),
}

fn main() {
    let goku = Hero::Goku(12000);

    //power(&goku);
    //
    if let Hero::Goku(p) = goku {
        println!("悟空: {}", p);
    } else {
        println!("战斗能力未知!!!");
    }
    
}

相信这样简单的例子大家一看便能够明了,当 if let 的条件成立将会进入下方代码块,否则不会,就是这样简单的语句。

4. 小测试

这样以来,条件控制语句我们也掌握的差不多了,看完了,也懂了,让我们试着用一下吧。

接下来我们要实现一个这样的场景,接着上面天下第一武道大会结束之后,还是这些参赛选手们听闻孙悟空会一个绝技“融合”!每个人都很感兴趣,都希望试试自己与另外一个人融合之后会产生什么样的现象。

假设融合对象只能是如下:

  • 五条悟 & 漩涡鸣人
  • 埼玉 & 路飞
  • 孙悟空 & 贝吉塔
  • 其它情况则融合失败。
impl Hero {
    fn fusion(&self, other: &Hero) -> Hero {
        match self {
            Hero::Saitama(p) | Hero::Ruffy(p) => {
                Hero::Saiffy(self.get() + other.get(), String::from("saitama&ruffy"))
            },
            Hero::Satoru(p) | Hero::Naruto(p) => {
                Hero::Natoru(self.get() + other.get(), String::from("satoru&naruto"))
            },
            Hero::Goku(p) | Hero::Bejita(p) => {
                Hero::Gojita(self.get() + other.get(), String::from("goku&bejita"))
            },
            other => {
            	println!("融合失败!!!");
                Hero::NULL
            },
        }
    }

    fn get(&self) -> &i32 {
        match self {
            Hero::Saitama(p) => p,
            Hero::Satoru(p) => p,
            Hero::Goku(p) => p,
            Hero::Naruto(p) => p,
            Hero::Bejita(p) => p,
            Hero::Ruffy(p) => p,
            other => &0,
        }
    }
}

#[derive(Debug)]
enum Hero {
    Saitama(i32),
    Satoru(i32),
    Goku(i32),
    Naruto(i32),
    Bejita(i32),
    Ruffy(i32),
    NULL,

    Gojita(i32, String),
    Natoru(i32, String),
    Saiffy(i32, String),
}

fn main() {

    let goku = Hero::Goku(12000);

    let bejita = Hero::Bejita(16000);

    let gojita = Hero::fusion(&goku, &bejita);

    println!("goku+bejita = {:?}", gojita);
}

[注]:上面代码仅供读者参考,希望大家能够实现出更加有趣的代码~

下一篇《Rust语言基础15》


觉得这篇文章对你有帮助的话,就留下一个赞吧v*
请尊重作者,转载还请注明出处!感谢配合~
[作者]: Imagine Miracle
[版权]: 本作品采用知识共享署名-非商业性-相同方式共享 4.0 国际许可协议进行许可。
[本文链接]: https://blog.csdn.net/qq_36393978/article/details/146249359