【Rust】——所有的模式语法

💻博主现有专栏:

                C51单片机(STC89C516),c语言,c++,离散数学,算法设计与分析,数据结构,Python,Java基础,MySQL,linux,基于HTML5的网页设计及应用,Rust(官方文档重点总结),jQuery,前端vue.js,Javaweb开发,Python机器学习等
🥏主页链接:

                Y小夜-CSDN博客

目录

🎯匹配字面值

🎯匹配命名变量

🎯多个模式

🎯通过..-=匹配值的范围

🎯解构并分解值

🎃解构结构体

🎃解构枚举

🎃解构嵌套的结构体和枚举

🎃解构结构体和元组

🎯忽略模式中的值

🎃使用_使用忽略整个值

🎃使用嵌套的_忽略部分值

🎃通过在名字前以一个_开头来忽略未使用的变量

🎃用..忽略剩余值

🎯匹配守卫提供的额外条件

🎯@绑定


🎯匹配字面值

可以直接匹配字面值模式。

    let x = 1;match x {1 => println!("one"),2 => println!("two"),3 => println!("three"),_ => println!("anything"),}

这段代码会打印 one 因为 x 的值是 1。如果希望代码获得特定的具体值,则该语法很有用。

🎯匹配命名变量

        命名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。然而当其用于 match 表达式时情况会有些复杂。因为 match 会开始一个新作用域,match 表达式中作为模式的一部分声明的变量会覆盖 match 结构之外的同名变量,与所有变量一样。

    let x = Some(5);let y = 10;match x {Some(50) => println!("Got 50"),Some(y) => println!("Matched, y = {y}"),_ => println!("Default case, x = {:?}", x),}println!("at the end: x = {:?}, y = {y}", x);

        让我们看看当 match 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 x 中定义的值,所以代码继续执行。

        第二个匹配分支中的模式引入了一个新变量 y,它会匹配任何 Some 中的值。因为我们在 match 表达式的新作用域中,这是一个新变量,而不是开头声明为值 10 的那个 y。这个新的 y 绑定会匹配任何 Some 中的值,在这里是 x 中的值。因此这个 y 绑定了 x 中 Some 内部的值。这个值是 5,所以这个分支的表达式将会执行并打印出 Matched, y = 5

        如果 x 的值是 None 而不是 Some(5),头两个分支的模式不会匹配,所以会匹配下划线。这个分支的模式中没有引入变量 x,所以此时表达式中的 x 会是外部没有被覆盖的 x。在这个假想的例子中,match 将会打印 Default case, x = None

        一旦 match 表达式执行完毕,其作用域也就结束了,同理内部 y 的作用域也结束了。最后的 println! 会打印 at the end: x = Some(5), y = 10

🎯多个模式

        在 match 表达式中,可以使用 | 语法匹配多个模式,它代表 or)运算符模式。例如,如下代码将 x 的值与匹配分支相比较,第一个分支有  选项,意味着如果 x 的值匹配此分支的任一个值,它就会运行:

    let x = 1;match x {1 | 2 => println!("one or two"),3 => println!("three"),_ => println!("anything"),}

上面的代码会打印 one or two

🎯通过..-=匹配值的范围

  ..= 语法允许你匹配一个闭区间范围内的值。在如下代码中,当模式匹配任何在给定范围内的值时,该分支会执行:

    let x = 5;match x {1..=5 => println!("one through five"),_ => println!("something else"),}

        如果 x 是 1、2、3、4 或 5,第一个分支就会匹配。这个语法在匹配多个值时相比使用 | 运算符来表达相同的意思更为方便;如果使用 | 则不得不指定 1 | 2 | 3 | 4 | 5。相反指定范围就简短的多,特别是在希望匹配比如从 1 到 1000 的数字的时候!

        编译器会在编译时检查范围不为空,而 char 和数字值是 Rust 仅有的可以判断范围是否为空的类型,所以范围只允许用于数字或 char 值。

    let x = 'c';match x {'a'..='j' => println!("early ASCII letter"),'k'..='z' => println!("late ASCII letter"),_ => println!("something else"),}

🎯解构并分解值

🎃解构结构体

有两个字段 x 和 y 的结构体 Point,可以通过带有模式的 let 语句将其分解:

struct Point {x: i32,y: i32,
}fn main() {let p = Point { x: 0, y: 7 };let Point { x: a, y: b } = p;assert_eq!(0, a);assert_eq!(7, b);
}

        这段代码创建了变量 a 和 b 来匹配结构体 p 中的 x 和 y 字段。这个例子展示了模式中的变量名不必与结构体中的字段名一致。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。因为变量名匹配字段名是常见的,同时因为 let Point { x: x, y: y } = p; 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。

struct Point {x: i32,y: i32,
}fn main() {let p = Point { x: 0, y: 7 };let Point { x, y } = p;assert_eq!(0, x);assert_eq!(7, y);
}

        这段代码创建了变量 x 和 y,与变量 p 中的 x 和 y 相匹配。其结果是变量 x 和 y 包含结构体 p 中的值。

        也可以使用字面值作为结构体模式的一部分进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。

        展示了一个 match 语句将 Point 值分成了三种情况:直接位于 x 轴上(此时 y = 0 为真)、位于 y 轴上(x = 0)或不在任何轴上的点。

fn main() {let p = Point { x: 0, y: 7 };match p {Point { x, y: 0 } => println!("On the x axis at {x}"),Point { x: 0, y } => println!("On the y axis at {y}"),Point { x, y } => {println!("On neither axis: ({x}, {y})");}}
}

        第一个分支通过指定字段 y 匹配字面值 0 来匹配任何位于 x 轴上的点。此模式仍然创建了变量 x 以便在分支的代码中使用。

        类似的,第二个分支通过指定字段 x 匹配字面值 0 来匹配任何位于 y 轴上的点,并为字段 y 创建了变量 y。第三个分支没有指定任何字面值,所以其会匹配任何其他的 Point 并为 x 和 y 两个字段创建变量。

        在这个例子中,值 p 因为其 x 包含 0 而匹配第二个分支,因此会打印出 On the y axis at 7

        记住 match 表达式一旦找到一个匹配的模式就会停止检查其它分支,所以即使 Point { x: 0, y: 0} 在 x 轴上也在 y 轴上,这些代码也只会打印 On the x axis at 0

🎃解构枚举

不过当时没有明确提到解构枚举的模式需要对应枚举所定义的储存数据的方式。

enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}fn main() {let msg = Message::ChangeColor(0, 160, 255);match msg {Message::Quit => {println!("The Quit variant has no data to destructure.");}Message::Move { x, y } => {println!("Move in the x direction {x} and in the y direction {y}");}Message::Write(text) => {println!("Text message: {text}");}Message::ChangeColor(r, g, b) => {println!("Change the color to red {r}, green {g}, and blue {b}",)}}
}

        这段代码会打印出 Change the color to red 0, green 160, and blue 255。尝试改变 msg 的值来观察其他分支代码的运行。

        对于像 Message::Quit 这样没有任何数据的枚举成员,不能进一步解构其值。只能匹配其字面值 Message::Quit,因此模式中没有任何变量。

        对于像 Message::Move 这样的类结构体枚举成员,可以采用类似于匹配结构体的模式。在成员名称后,使用大括号并列出字段变量以便将其分解以供此分支的代码使用。

🎃解构嵌套的结构体和枚举

        目前为止,所有的例子都只匹配了深度为一级的结构体或枚举,不过当然也可以匹配嵌套的项!

enum Color {Rgb(i32, i32, i32),Hsv(i32, i32, i32),
}enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(Color),
}fn main() {let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));match msg {Message::ChangeColor(Color::Rgb(r, g, b)) => {println!("Change color to red {r}, green {g}, and blue {b}");}Message::ChangeColor(Color::Hsv(h, s, v)) => {println!("Change color to hue {h}, saturation {s}, value {v}")}_ => (),}
}

  match 表达式第一个分支的模式匹配一个包含 Color::Rgb 枚举成员的 Message::ChangeColor 枚举成员,然后模式绑定了 3 个内部的 i32 值。第二个分支的模式也匹配一个 Message::ChangeColor 枚举成员,但是其内部的枚举会匹配 Color::Hsv 枚举成员。我们可以在一个 match 表达式中指定这些复杂条件,即使会涉及到两个枚举。

🎃解构结构体和元组

        甚至可以用复杂的方式来混合、匹配和嵌套解构模式。如下是一个复杂结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来:

    let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });

这将复杂的类型分解成部分组件以便可以单独使用我们感兴趣的值。

通过模式解构是一个方便利用部分值片段的手段,比如结构体中每个单独字段的值。

🎯忽略模式中的值

        有时忽略模式中的一些值是有用的,比如 match 中最后捕获全部情况的分支实际上没有做任何事,但是它确实对所有剩余情况负责。有一些简单的方法可以忽略模式中全部或部分值:使用 _ 模式(我们已经见过了),在另一个模式中使用 _ 模式,使用一个以下划线开始的名称,或者使用 .. 忽略所剩部分的值。让我们来分别探索如何以及为什么要这么做。

🎃使用_使用忽略整个值

        我们已经使用过下划线作为匹配但不绑定任何值的通配符模式了。虽然这作为 match 表达式最后的分支特别有用,也可以将其用于任意模式,包括函数参数中

fn foo(_: i32, y: i32) {println!("This code only uses the y parameter: {}", y);
}fn main() {foo(3, 4);
}

        这段代码会完全忽略作为第一个参数传递的值 3,并会打印出 This code only uses the y parameter: 4

        大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。在一些情况下忽略函数参数会变得特别有用,比如实现 trait 时,当你需要特定类型签名但是函数实现并不需要某个参数时。这样可以避免一个存在未使用的函数参数的编译警告,就跟使用命名参数一样。

🎃使用嵌套的_忽略部分值

        也可以在一个模式内部使用_ 忽略部分值,例如,当只需要测试部分值但在期望运行的代码中没有用到其他部分时。示例 18-18 展示了负责管理设置值的代码。业务需求是用户不允许覆盖现有的自定义设置,但是可以取消设置,也可以在当前未设置时为其提供设置。

    let mut setting_value = Some(5);let new_setting_value = Some(10);match (setting_value, new_setting_value) {(Some(_), Some(_)) => {println!("Can't overwrite an existing customized value");}_ => {setting_value = new_setting_value;}}println!("setting is {:?}", setting_value);

        这段代码会打印出 Can't overwrite an existing customized value 接着是 setting is Some(5)。在第一个匹配分支,我们不需要匹配或使用任一个 Some 成员中的值;重要的部分是需要测试 setting_value 和 new_setting_value 都为 Some 成员的情况。在这种情况,我们打印出为何不改变 setting_value,并且不会改变它。

        对于所有其他情况(setting_value 或 new_setting_value 任一为 None),这由第二个分支的 _ 模式体现,这时确实希望允许 new_setting_value 变为 setting_value

    let numbers = (2, 4, 8, 16, 32);match numbers {(first, _, third, _, fifth) => {println!("Some numbers: {first}, {third}, {fifth}")}}

这会打印出 Some numbers: 2, 8, 32,值 4 和 16 会被忽略。

🎃通过在名字前以一个_开头来忽略未使用的变量

        如果你创建了一个变量却不在任何地方使用它,Rust 通常会给你一个警告,因为未使用的变量可能会是个 bug。但是有时创建一个还未使用的变量是有用的,比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头。示例 18-20 中创建了两个未使用变量,不过当编译代码时只会得到其中一个的警告:

fn main() {let _x = 5;let y = 10;
}

这里得到了警告说未使用变量 y,不过没有警告说使用 _x

        注意,只使用 _ 和使用以下划线开头的名称有些微妙的不同:比如 _x 仍会将值绑定到变量,而 _ 则完全不会绑定。为了展示这个区别的意义:

    let s = Some(String::from("Hello!"));if let Some(_s) = s {println!("found a string");}println!("{:?}", s);

        我们会得到一个错误,因为 s 的值仍然会移动进 _s,并阻止我们再次使用 s。然而只使用下划线本身,并不会绑定值。

    let s = Some(String::from("Hello!"));if let Some(_) = s {println!("found a string");}println!("{:?}", s);

上面的代码能很好的运行;因为没有把 s 绑定到任何变量;它没有被移动。

🎃用..忽略剩余值

        对于有多个部分的值,可以使用 .. 语法来只使用特定部分并忽略其它值,同时避免不得不每一个忽略值列出下划线。.. 模式会忽略模式中剩余的任何没有显式匹配的值部分。

    struct Point {x: i32,y: i32,z: i32,}let origin = Point { x: 0, y: 0, z: 0 };match origin {Point { x, .. } => println!("x is {}", x),}

        这里列出了 x 值,接着仅仅包含了 .. 模式。这比不得不列出 y: _ 和 z: _ 要来得简单,特别是在处理有很多字段的结构体,但只涉及一到两个字段时的情形。

fn main() {let numbers = (2, 4, 8, 16, 32);match numbers {(first, .., last) => {println!("Some numbers: {first}, {last}");}}
}

        这里用 first 和 last 来匹配第一个和最后一个值。.. 将匹配并忽略中间的所有值。

        然而使用 .. 必须是无歧义的。如果期望匹配和忽略的值是不明确的,Rust 会报错。、

fn main() {let numbers = (2, 4, 8, 16, 32);match numbers {(.., second, ..) => {println!("Some numbers: {}", second)},}
}

如果编译上面的例子,会得到下面的错误:

$ cargo runCompiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern--> src/main.rs:5:22|
5 |         (.., second, ..) => {|          --          ^^ can only be used once per tuple pattern|          ||          previously used hereerror: could not compile `patterns` due to previous error

        Rust 不可能决定在元组中匹配 second 值之前应该忽略多少个值,以及在之后忽略多少个值。这段代码可能表明我们意在忽略 2,绑定 second 为 4,接着忽略 816 和 32;抑或是意在忽略 2 和 4,绑定 second 为 8,接着忽略 16 和 32,以此类推。变量名 second 对于 Rust 来说并没有任何特殊意义,所以会得到编译错误,因为在这两个地方使用 .. 是有歧义的。

🎯匹配守卫提供的额外条件

        匹配守卫match guard)是一个指定于 match 分支模式之后的额外 if 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。

        这个条件可以使用模式中创建的变量。示例 18-26 展示了一个 match,其中第一个分支有模式 Some(x) 还有匹配守卫 if x % 2 == 0 (当 x 是偶数的时候为真):

    let num = Some(4);match num {Some(x) if x % 2 == 0 => println!("The number {} is even", x),Some(x) => println!("The number {} is odd", x),None => (),}

        上例会打印出 The number 4 is even。当 num 与模式中第一个分支比较时,因为 Some(4) 匹配 Some(x) 所以可以匹配。接着匹配守卫检查 x 除以 2 的余数是否等于 0,因为它等于 0,所以第一个分支被选择。

        相反如果 num 为 Some(5),因为 5 除以 2 的余数是 1 不等于 0 所以第一个分支的匹配守卫为假。接着 Rust 会前往第二个分支,这次匹配因为它没有匹配守卫所以会匹配任何 Some 成员。

        无法在模式中表达类似 if x % 2 == 0 的条件,所以通过匹配守卫提供了表达类似逻辑的能力。这种替代表达方式的缺点是,编译器不会尝试为包含匹配守卫的模式检查穷尽性。

🎯@绑定

        at 运算符(@)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。示例 18-29 展示了一个例子,这里我们希望测试 Message::Hello 的 id 字段是否位于 3..=7 范围内,同时也希望能将其值绑定到 id_variable 变量中以便此分支相关联的代码可以使用它。可以将 id_variable 命名为 id,与字段同名,不过出于示例的目的这里选择了不同的名称。

    enum Message {Hello { id: i32 },}let msg = Message::Hello { id: 5 };match msg {Message::Hello {id: id_variable @ 3..=7,} => println!("Found an id in range: {}", id_variable),Message::Hello { id: 10..=12 } => {println!("Found an id in another range")}Message::Hello { id } => println!("Found some other id: {}", id),}

        上例会打印出 Found an id in range: 5。通过在 3..=7 之前指定 id_variable @,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。

        第二个分支只在模式中指定了一个范围,分支相关代码没有一个包含 id 字段实际值的变量。id 字段的值可以是 10、11 或 12,不过这个模式的代码并不知情也不能使用 id 字段中的值,因为没有将 id 值保存进一个变量。

        最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 id,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 id 字段的值进行测试:任何值都会匹配此分支。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/37999.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unidbg调用-补环境V2

1.B站 内部依赖自定义的SignedQuery对象,需要找到apk中的类并补充环境。 package com.nb.demo;import com.github.unidbg.AndroidEmulator

llama3模型部署时遇到的问题及解决方案

在llama3模型部署时,会遇到一系列问题,这里就作者所遇到的问题与解决方法分享一下。 注意:这里是从llama3 github主页上给的方法一步步做的,不适用于其他部署大模型的方法。 文章目录 ERROR 403:Forbidden安装依赖时出…

洛谷 P1548 [NOIP1997 普及组] 棋盘问题

题目 洛谷 P1548 [NOIP1997 普及组] 棋盘问题 [NOIP1997 普及组] 棋盘问题 题目背景 NOIP1997 普及组第一题 题目描述 设有一个 N M N \times M NM 方格的棋盘 ( 1 ≤ N ≤ 100 , 1 ≤ M ≤ 100 ) (1≤N≤100,1≤M≤100) (1≤N≤100,1≤M≤100) 求出该棋盘中包含有多少个正…

牛客C++刷题记录

C 运算符优先级 运算符优先级顺口溜:淡云一笔,鞍落三服。 淡:单目运算符; 云:算数运算符; 一:移位运算符; 笔:比较运算符; 鞍:按位运算符&a…

MySQL高级-MVCC-undo log 版本链

文章目录 1、undo log2、undo log 版本链2.1、然后,有四个并发事务同时在访问这张表。2.1.1、修改id为30记录,age改为32.1.2、修改id为30记录,name改为A32.1.3、修改id为30记录,age改为10 2.2、总结 1、undo log 回滚日志&#xf…

文件系统(操作系统实验)

实验内容 (1)在内存中开辟一个虚拟磁盘空间作为文件存储器, 在其上实现一个简单单用户文件系统。 在退出这个文件系统时,应将改虚拟文件系统保存到磁盘上, 以便下次可以将其恢复到内存的虚拟空间中。 (2&…

算法训练(leetcode)第二十一天 | 93. 复原 IP 地址、78. 子集、90. 子集 II

刷题记录 93. 复原 IP 地址78. 子集90. 子集 II 93. 复原 IP 地址 leetcode题目地址 题目有一个很重要的要求:你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。 也就是说ip地址中需要包含整个字符串中的字符且顺序不可变。 ip地址的每一个数…

数字孪生煤矿智能化综合管控平台

煤矿可视化通过图扑 HT 实现实时数据集成和三维建模仿真,呈现井下环境、设备状态和生产状况等多维度数据,帮助管理人员进行直观监控和精准分析。该技术提升了运营效率和安全水平,为煤矿作业提供了智能化的管理解决方案,有助于减少…

黑马点评DAY1|Redis入门、Redis安装

什么是Redis? redis是一种键值型数据库,内部所存的数据都是键值对的形式,例如,我们可以把一个用户数据存储为如下格式: 键值id$1600name张三age21 但是这样的存储方式,数据会显得非常松散,因…

云计算HCIE+RHCE学员的学习分享

大一下学期,我从学长嘴里了解到誉天教育,当时准备考RHCE,我也了解了很多培训机构,然后学长强烈给我推荐誉天,我就在誉天报名了RHCE的课程。 通过杨峰老师的教学,我学到了许多Linux知识,也了解了…

笔记本电脑部署VMware ESXi 6.0系统

正文共:888 字 18 图,预估阅读时间:1 分钟 前面我们介绍了在笔记本上安装Windows 11操作系统(Windows 11升级不了?但Win10就要停服了啊!来,我教你!),也介绍了…

【单片机毕业设计选题24037】-基于STM32的电力系统电力参数无线监控系统

系统功能: 系统上电后,OLED显示“欢迎使用电力监控系统请稍后”,两秒后显示“Waiting..”等待ESP8266初始化完成, ESP8266初始化成功后进入正常页面显示, 第一行显示电压值(单位V) 第二行显示电流值&am…

Java 使用Objects equals 、 != 、equals 比较对象之间的区别?

在Java中,比较对象是否相等的方法主要有三种:Objects.equals() 方法、! 操作符和 equals() 方法。它们之间的区别如下: Objects.equals() 方法: Objects.equals(a, b) 是一个静态方法,用于安全地比较两个对象是否相等。…

FastAPI中的Lifespan和异步上下文管理器:深入理解和实践

FastAPI中的Lifespan和异步上下文管理器:深入理解和实践 FastAPI中的Lifespan和异步上下文管理器:深入理解和实践1. 代码解析2. 异步上下文管理器2.1 什么是异步上下文管理器?2.2 asynccontextmanager装饰器2.3 代码示例 3. FastAPI的Lifespa…

现代信息检索笔记(一)

目录 什么是信息检索 应用一:做搜索引擎 应用二:信息推荐系统 应用三:婚恋网站 信息检索的具体应用 从信息规模上分类 为什么要学习信息检索技术? 市场发展需求大 应用需求多: 课程情况 课程宗旨 国际著名…

互联网大厂核心知识总结PDF资料

我们要敢于追求卓越,也能承认自己平庸,不要低估3,5,10年沉淀的威力 hi 大家好,我是大师兄,大厂工作特点是需要多方面的知识和技能。这种学习和积累一般人需要一段的时间,不太可能一蹴而就&…

使用 FastAPI 实现聊天完成 API 详解

使用 FastAPI 实现聊天完成 API 详解 简介基础概念FastAPIPydanticPyTorch 代码详解1. 定义 API 端点2. 请求验证3. 生成参数字典4. 处理流式响应5. 工具调用处理6. 非流式响应处理7. 处理使用信息和工具调用8. 构建聊天消息9. 构建响应选择10. 更新使用信息11. 返回最终响应 总…

SQL Server触发器深度解析:数据完整性的守护者

标题:SQL Server触发器深度解析:数据完整性的守护者 摘要 在SQL Server中,触发器是一种特殊的存储过程,它在特定数据库事件发生时自动执行。触发器主要用于维护数据的完整性和实施复杂的业务规则。本文将详细介绍SQL Server中触…

ubuntu 添加PATH

在Ubuntu中,PATH是一个环境变量,用于指定系统查找可执行文件的目录列表。如果你想将新的目录添加到PATH中,可以按照以下步骤操作: 临时添加PATH 你可以在终端中使用export命令临时修改PATH环境变量。例如,如果你想将…

Python使用彩虹表来尝试对MD5哈希进行破解

MD5是一种散列算法,它是不可逆的,无法直接解密。它的主要作用是将输入数据进行散列,生成一个固定长度的唯一哈希值。 然而,可以使用预先计算好的MD5哈希值的彩虹表(Rainbow Table)来尝试对MD5进行破解。彩…