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.4 猜数游戏Pt.4 循环询问

2.4.0 本篇知识点

这是猜数游戏的最后一部分。在本篇中,你将学到:

  • loop 循环
  • break
  • continue
  • match 的灵活使用
  • 如何处理枚举

2.4.1 游戏目标

  • 生成一个 1 到 100 间的随机数
  • 提示玩家输入一个猜测
  • 猜完之后,程序会提示猜测是太大了还是太小了
  • 循环询问。如果猜测正确,那么打印一个庆祝信息,程序退出(本篇会涉及)

2.4.2 代码实现

Step 1:实现循环

在之前的代码中,我们实现了一次输入和比较。接下来,我们需要让程序反复询问和比较,直到用户猜到正确的数字。

以下是截止到 2.3 猜数游戏Pt.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);
}

而我们需要重复执行的代码,就是从提示用户输入,到比较猜测并打印结果的部分:

#![allow(unused)]
fn main() {
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"),
}
}

Rust 提供了用于无限循环的关键字 loop,其结构如下:

#![allow(unused)]
fn main() {
loop {
	// Write code here that wants to loop indefinitely
}
}

只需要把需要重复执行的代码放入这个结构中即可:

#![allow(unused)]
fn main() {
loop {
    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"),
    }
}
}

Step 2:退出程序的条件

但是需要注意的是,虽然这样实现了循环询问,程序却会一直询问下去、永远不会退出。按逻辑,用户猜对并打印祝贺信息后,程序就应该停止询问。这里就需要用于跳出循环的关键字 break,把它放在 Ordering::Equal 这个分支(分支的概念在 2.3 猜数游戏Pt.3 输入数与随机数的对比 中已有解释,这里不再重复)后面即可。还要记住:如果一个分支需要执行多行代码,就要用 {} 把代码块包起来

#![allow(unused)]
fn main() {
match guess.cmp(&range_number) {
    Ordering::Less => println!("Too small"),
    Ordering::Greater => println!("Too big"),
    Ordering::Equal => {
        println!("You win");
        break;
    }
}
}

Step 3:错误输入的处理

这段代码还有另一个问题:如果用户的输入不是整数,.parse() 会返回 Err.expect() 会立即终止程序。正确的行为是打印错误信息,然后让用户再试一次。

这该怎么办呢?在 2.1 猜数游戏Pt.1 一次猜测 中我们见过,read_line() 返回的是一个 Result 枚举。在 2.3 猜数游戏Pt.3 输入数与随机数的对比 里,.parse() 同样返回 Result:如果转换成功,返回值是 Ok 加上转换后的内容;如果失败,返回值是 Err 加上失败原因。那我们之前在哪里用 match 处理过枚举呢?没错,2.3 猜数游戏Pt.3 输入数与随机数的对比 介绍了 Ordering 枚举。在那里,我们用 match 处理了大于、小于和等于的情况。这里同样可以用 match 处理 .parse() 的返回值,并对不同情况执行不同操作:如果转换成功,继续执行;如果失败,打印错误信息,跳过循环体剩余代码并开始下一次迭代。Rust 中跳过当前循环迭代的关键字和其他语言一样,都是 continue

具体该怎么改代码呢?就是把 let guess: u32 = guess.trim().parse().expect("Please enter a number"); 替换为:

#![allow(unused)]
fn main() {
let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => {
        println!("Please enter a number");
        continue;
    }
};
}
  • Ok(num) => num:这个分支处理转换成功的情况。返回值是 Ok 加上转换后的值。Ok 是这个枚举的一个变体,Ok 后面括号里的值就是转换后的内容(u32)。这里写成 num 表示把转换后的内容绑定到 num,然后 num 会作为 match 表达式的结果,最终赋给 guess
  • Err(_) => { ... continue; }:这个分支处理转换失败的情况。Err 是枚举变体,Err 后面括号里的值是错误值(解析整数时是 ParseIntError)。_ 表示我们不关心错误细节,只需要知道它是 Err。这里先打印一条简短提示,再 continue 进入下一轮循环。

match 代替 .expect() 来处理错误,是 Rust 中的常见做法。

2.4.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");
    loop {
        println!("Guess a number");

        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("Could not read the line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("Please enter a number");
                continue;
            },
        };

        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");
                break;
            },
        }
    }

    println!("The secret number is: {}", range_number);
}

效果(一次本地运行;你的神秘数字和猜中路径会不同):

Number Guessing Game
Guess a number
10
The number you guessed is:10
Too small
Guess a number
50
The number you guessed is:50
Too small
Guess a number
100
The number you guessed is:100
You win
The secret number is: 100