Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_numberi32类型(有符号的 32 位整数类型,gen_range 方法返回的类型与范围中的数值类型一致。在这里,1101i32 类型,因此返回值类型也是 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间的数,这个数i32u32i64等等数据类型都可以容纳,这种情况下解析后到底是那种类型呢?你就得告诉Rust你要那种类型,所以在声明变量时才要显式声明(声明变量时在后面加上:想要的类型,类似于python中静态检查写法)为u32类型。 当然转换有可能会失败,比如说输入xyz,这个时候就没法解析成整数,Rust非常聪明地把.parse()的返回值设成了Result类型(在Pt.1中讲到过),这种枚举类型有两个值:OkErr。如果能成功转换,那这个枚举类型就会返回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:LessOrdering::GreaterOrdering::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:LessOrdering::GreaterOrdering::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);
}

效果如下: