【Rust自学】2.3. 猜数游戏Pt.3 输入数与随机数的对比
2.3 猜数游戏Pt.3 输入数与随机数的对比2.3.0 本篇知识点在本篇中你将学到:-match的用法- 遮蔽shadowing- 类型转换-Ordering类型2.3.1 游戏目标生成一个 1 到 100 间的随机数提示玩家输入一个猜测猜完之后程序会提示猜测是太大了还是太小了本篇会涉及如果猜测正确那么打印一个庆祝信息程序退出2.3.2 代码实现这是截止到 2.2 猜数游戏Pt.2 生成随机数 所写出来的代码:use std::io; use rand::Rng; fn main() { let range_number rand::thread_rng().gen_range(1..101); println!(Number Guessing Game); println!(Guess a number); let mut guess String::new(); io::stdin().read_line(mut guess).expect(Could not read the line); println!(The number you guessed is:{}, guess); println!(The secret number is: {}, range_number); }Step 1数据类型的转换由代码可知guess是字符串而range_number是整数。这两个变量类型不同不能直接比较。我们需要把字符串转换成整数——这里会使用u32无符号 32 位整数。range_number最终被推断成哪一种整数类型会在下文写出比较代码后更清楚。let guess: u32 guess.trim().parse().expect(Please enter a number);let guess: u32声明一个名为guess、类型为u32无符号 32 位整数也就是不能表示负数的变量。但这里有一个问题在之前的代码中let mut guess String::new();已经声明了一个叫做guess的变量不会报错吗答案是不会因为 Rust 允许用同名的新变量遮蔽旧变量。这叫做遮蔽shadowing当一个变量、函数或类型的名称在当前作用域中被重新定义时会隐藏外部作用域中同名的变量、函数或类型。它允许代码复用同一个变量名而无需声明新的变量。这个特性会在 3.1 变量与可变性 仔细介绍。这里可以举一个例子:fn main() { let a 1; println!({}, a); let a one; println!({}, a); }这么做程序不会报错并且会打印出:1 one当程序执行到第一个let绑定处时a被赋值为1所以紧接着的println!打印出的是1当第二个let复用名字a时它会用one遮蔽旧值所以下一行打印的就是one。这就是遮蔽。赋值guess.trim()这里的guess指的是旧的guess类型为字符串内容是用户输入。因为read_line()也会把用户的回车记录进去所以需要使用.trim()。.trim()的作用是去掉字符串前后的空格和换行类似于 Python 中的.strip()。.parse()它可以把字符串解析为某种数值类型。用户的正常输入会是 1 到 100 间的数这个数可以放进i32、u32或i64等类型。那么解析后到底是哪种类型呢你得告诉 Rust 你要哪种类型所以在声明变量时才要显式指定为u32类似于 Python 中的静态类型标注在变量名后面加上:desired_type。当然转换有可能会失败。比如说输入xyz就没法解析成整数。Rust 足够聪明让.parse()返回一个Result类型在 2.1 猜数游戏Pt.1 一次猜测 中讲到过。这种枚举有两个变体Ok和Err。如果转换成功枚举就会返回Ok和转换后的结果如果失败就会返回Err和失败原因。.expect()它是Result类型上的一个方法与.parse()的返回值类型相同。如果解析失败.parse()就会返回Err.expect()会立即触发panic!终止当前程序并打印expect中的错误信息。反之.parse()就会返回Ok.expect()会把附加的值返回出来也就是把转换好的数字赋给guess。Step 2数字的比较在成功转换数据类型后我们就可以比较这两个数字了。先在代码开头导入类型:use std::cmp::Ordering;这段代码表示从std标准库里引入一个叫做Ordering的类型。Ordering是一个枚举它有三个变体可以把它理解为三个可能的值Ordering::Less、Ordering::Greater和Ordering::Equal分别表示小于、大于和等于。再在main里写下对比代码:match guess.cmp(range_number) { Ordering::Less println!(Too small), Ordering::Greater println!(Too big), Ordering::Equal println!(You win), }guess.cmp(range_number)guess上有一个方法叫.cmp()cmp是 compare 的缩写。它会比较点号前面的值和括号里的值。这里点号前面的值是guess括号里的值是对range_number的引用是取地址符代表引用。.cmp()的返回值类型是Ordering也就是上文导入的类型。这里还涉及到了 Rust 的类型推断。这里有两张 IDE 截图一张是还没写这段match表达式时一张是写了之后。注意看let range_number rand::thread_rng().gen_range(1..101);这一行第 5 行可以看到没有写match表达式时IDE 提示range_number的类型是i32写了match之后IDE 提示range_number的类型是u32。这是为什么呢因为guess.cmp(range_number)进行了比较虽然range_number没有被显式标注类型但guess已经被显式定义为u32。得益于 Rust 强大的基于上下文的类型推断guess.cmp(range_number)的需求会让range_number被推断为u32。而没有match时因为 Rust 默认的整数类型是i32且没有任何其他约束要求range_number是别的类型所以编译器会推断为i32。matchRust 的模式匹配表达式。它让我们可以根据.cmp()返回的Ordering枚举值来决定下一步操作。一个match表达式由多个分支也叫臂英文是 arm组成。每个分支都包含一个匹配模式用来匹配输入值的条件和一个要执行的代码块模式匹配成功时运行的代码。如果match后面的值在这个程序中就是guess.cmp(range_number)匹配上了某个分支程序就会执行这个分支下的代码。在这个程序中Ordering::Less、Ordering::Greater和Ordering::Equal就是匹配模式println!(Too small)、println!(Too big)和println!(You win)就是对应的代码块。举个例子如果guess等于range_number.cmp()就会返回Ordering::Equalmatch找到与之匹配的第三个分支然后执行这个分支的代码块也就是println!(You win)。match会从上到下检查分支。在这个程序里就是先检查Ordering::Less再检查Ordering::Greater最后检查Ordering::Equal。我们会在 6.3 控制流运算符-match 中更详细地讲解match。2.3.3 代码效果以下是截至目前的完整代码:use std::io; use rand::Rng; use std::cmp::Ordering; fn main() { let range_number rand::thread_rng().gen_range(1..101); println!(Number Guessing Game); println!(Guess a number); let mut guess String::new(); io::stdin().read_line(mut guess).expect(Could not read the line); let guess: u32 guess.trim().parse().expect(Please enter a number); println!(The number you guessed is:{}, guess); match guess.cmp(range_number) { Ordering::Less println!(Too small), Ordering::Greater println!(Too big), Ordering::Equal println!(You win), } println!(The secret number is: {}, range_number); }效果如下每次运行的神秘数字都会不同这里猜测10偏小:Number Guessing Game Guess a number 10 The number you guessed is:10 Too small The secret number is: 48