2.3 猜数游戏Pt.3 输入数与随机数的对比
2.3.0. 本篇知识点
在本篇中,你将学到:
match的用法- 类型遮蔽
- 类型强制转换
Odering类型
2.3.1. 游戏目标
- 生成一个1到100间的随机数
- 提示玩家输入一个猜测
- 猜完之后,程序会提示猜测是太大了还是太小了(本篇会涉及)
- 如果猜测正确,那么打印一个庆祝信息,程序退出
2.3.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是i32类型(有符号的 32 位整数类型,gen_range 方法返回的类型与范围中的数值类型一致。在这里,1 和 101 是 i32 类型,因此返回值类型也是 i32,数据类型将会在下一章提到)。这两个变量类型不同,不能直接比较。需要强制把字符串类型转换为整数类型。
#![allow(unused)]
fn main() {
let guess:u32 = guess.trim().parse().expect("请输入一个数字 Please enter a number")
}
-
let guess:u32:这里声明了一个类型为u32(没有符号的32位整数类型,换句话来说就是不能表示负数),名字叫做guess的变量。 但这里有一个问题:在之前的代码中(let mut guess = String::new();)已经声明了一个叫做guess的变量,不会报错吗?答案是不会,因为Rust允许使用同名新变量来隐藏原来同名的新变量,学名叫做类型遮蔽(当一个变量、函数或类型的名称在当前作用域中被重新定义时,隐藏了外部作用域中同名的变量、函数或类型),它允许代码复用这个变量名而无需声明新的变量,这个特性会在下一章仔细介绍。这里可以举一个例子:
fn main(){
let a = 1;
println!("{}",a);
let a = "one";
println!("{}",a);
}
这么做程序不会报错,并且打印出了:
1
one
当程序执行到第二行时,a被赋值为1,所以打印出的是1;在第四行,程序注意到a被复用了,就会抛弃原来的值1,把a赋值为“one“,所以下一行打印的就是one。这就是类型遮蔽。
-
=:赋值 -
guess.trim():这里的guess指的是老的guess,类型为字符串,代表用户输入。因为read_line()这个方法会把用户的回车也记录进去,所以需要使用.trim()。.trim()的作用是除掉字符串中前后的空格和回车都去掉(类似于python中的.strip())。 -
.parse():它可以把字符串解析为某种数值类型,用户的正常输入肯定是1到100间的数,这个数i32、u32和i64等等数据类型都可以容纳,这种情况下解析后到底是那种类型呢?你就得告诉Rust你要那种类型,所以在声明变量时才要显式声明(声明变量时在后面加上:想要的类型,类似于python中静态检查写法)为u32类型。 当然转换有可能会失败,比如说输入xyz,这个时候就没法解析成整数,Rust非常聪明地把.parse()的返回值设成了Result类型(在Pt.1中讲到过),这种枚举类型有两个值:Ok和Err。如果能成功转换,那这个枚举类型就会返回Ok和转换后的结果;如果不能读取,就会返回Err和转换失败的原因。 -
.expect():它是Result类型(与.parse()的返回值同类型)的一个方法。如果读取失败,按上文所述,.parse()就会返回Err,.expect()接收到后会直接触发panic!终止当前程序并打印expect中的错误信息。反之,.parse()就会返回Ok,.expect()接收到后就会把附加的值返回给用户,也就是把转换好的值赋给变量。
Step 2: 数字的比较
在成功转换数据类型后,我们就可以比较两个数字: 先在代码开头声明引用库:
#![allow(unused)]
fn main() {
use std::cmp::Ordering
}
这段代码表示从std标准库里引入一个叫做Ordering的类型,Ordering是一个枚举类型,它有三个变体(可以把它理解为有三个可能的值):Ordering:Less、Ordering::Greater和Ordering::Equal,分别表示小于、大于和等于。
再在主函数里写下对比代码:
#![allow(unused)]
fn 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);这一行(第五行):
可以看到,没有写到match时,IDE提示range_number的数据类型是i32,写了match这段之后,IDE提示range_number的数据类型是u32,这是为什么呢?这是因为match这段中的guess.cmp(&range_number),这里进行了比较大小,虽然range_number的类型没有被显式定义,但是guess已经被显式定义为了u32,得益于Rust编译器强大的上下文类型推断功能,range_number的类型被guess.cmp(&range_number)的需求推导为u32。而没有match时,因为Rust 默认的整数类型是i32且没有任何其他约束需要将range_number设置为其他类型,所以,Rust编译器会把range_number定义为i32。 -
match:它是Rust的中的模式匹配表达式,它让我们可以根据.cmp()方法返回的Odering这个枚举类型的值来决定下一步的操作。一个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::Equal,match找到它与第三个分支匹配,然后执行这个分支下的代码块,也就是println!("你赢了 You win")。match在进行匹配时按照从上到下的顺序。在这个程序里就是先匹配Ordering:Less,再匹配Ordering::Greater,最后匹配Ordering::Equal。在下一章会详细地讲
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);
}
效果如下:
