跟我一起学rust-猜数游戏

后端 / 笔记 / 2021-11-04

说明

我们一起手动完成一个项目,加速上手rust的的速度。

他是这样工作的:
程序随机生成1-10内的随机数,然后我们用户输入一个数,来进行猜测,然后提示猜大了,还是猜小了,如果猜对了,则退出程序。

新建一个项目

cargo new guessing_game

处理用户输入

use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin().read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

引入io库

use std::io;

引入io库,因为我们要接收用户输入

let mut guess = String::new();

这里我们创建一个变量。这里的let mut 是mutable 的缩写,代表这个变量是可被修改的,如果不加mut 代表是一个常量。

创建一个字符串

String::new()

用来创建一个字符串实例,是一个可增长的UTF8字符序列。

io::stdin().read_line(&mut guess)
    .expect("Failed to read line");

上面我们通过use命令引入了io库
这里
io::stdin() 表示一个输入流

read_line 表示从键盘读入一行.

&mut guess

这里就比较有意思了, & 代表这是一个引用,那么mut我们前面学过了表示是一个可变的。结合到一块就是可变的引用

异常处理

.expect("Failed to read line");

这里是一种异常处理手段

read_line() 返回值是 Result 类型

关于Rust类型rust中是这样定义的

enum Result<T, E> {
   Ok(T),
   Err(E),
}

相信有别的编程语言经验的人,一眼就能看出这是一个枚举类型。

Ok表示操作成功,Err表示出现错误.

那么这里的 expect方法的作用是,如果Result的值为Err那么,输出错误信息Failed to read line,如果是Ok,则不报错。

占位输出

println!("You guessed: {}", guess);

这里的{}是占位符,表示把后面的guess填充到前面{}

生成随机数

引入第三方crate

这里我们需要使用第三方craterand

在Cargo.toml中添加依赖.

[dependencise]
rand = "0.8.3"
cargo update

生成一个随机数

use std::io;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

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

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

use rand::Rng Rng是一个trait,定义了随机数生成器应实现的方法,想用这些方法的话,此trait必须在作用域中。

rand::thread_rng()

位于当前执行线程本地环境中,从操作系统获取seed,然后使用gen_range方法来实现生成一个随机数。1..101 是一个范围表达式,表示从取值范围为100 以内

数字比较

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {

    // ---snip---

    println!("You guessed: {}", guess);

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

第一行代码是use 从标准库中引入了一个std::cmp::Ordering类型,他和Result一样也是枚举类型.

pub enum Ordering {
    Less,
    Equal,
    Greater,
}

三种值表示三种不同的比较结果

然后用match表达式由分支构成,一个分支包含一个模式类似于其他语言的 switch case 语句.

类型转换

// --snip--

    let mut guess = String::new();

    io::stdin().read_line(&mut guess)
        .expect("Failed to read line");

    let guess: u32 = guess.trim().parse()
        .expect("Please type a number!");

    println!("You guessed: {}", guess);

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

上面我们接受了用户输入数据,我们需要对数据进行处理。

trim() 用来消除空格,parse()用来进行类型转换。

let guess: u32 = guess.trim().parse().expect("Please type a number!");

这里我们创建了一个 guess变量,等等,上面不是也有个 guess 变量吗?这里用到了变量隐藏的概念. rust允许复用之前使用的变量名称,而不是创建两个不同的变量,意味着后面的变量会将前面的变量完全覆盖。let guess:u32 表示常量类型为 unsigned int32无符号32位整型。parse方法调用容易产生错误,因此需要expect来进行异常处理。

多次猜测

// --snip--

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

    loop {
        println!("Please input your guess.");

        // --snip--

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => println!("You win!"),
        }
    }
}

这里 loop 表示创建一个死循环,不断地执行循环体内容。但是这并不是我们想要的,我们游戏应当在用户猜测正确后自动退出,改进上面代码.

// --snip--

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

哈哈哈,这里的 break 和别的语言一样,表示跳出循环。

处理无效输入

如果我们使用 expect函数的话,会导致程序崩溃,进而导致程序终止。虽然从一定程度上解决了输错问题,但是降低了游戏的可玩性。
因此我们要在用户输错时进行,处理。

通过前面的学习我们知道 parse() 返回值是一个 Result 类型,那么我们可用 match来进行 匹配,我们只需要捕获 Err

// --snip--

io::stdin().read_line(&mut guess)
    .expect("Failed to read line");

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

println!("You guessed: {}", guess);

// --snip--

然后调用 continue 语句,跳过本次循环,进而解决问题。

完整代码

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin().read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}