Talk is cheap. Show me your code

Rust 走马观花(二)—— 猜数字游戏

本文是《Rust 程序设计语言 - 猜数字游戏》的学习笔记

会涉及到:条件判断, 循环, 获取输入内容, 异常捕获 等知识点

建议有一定编程基础的小伙伴阅读~ 

 

一、需求描述

猜数字游戏的规则如下:

1. 程序运行之后,会随机生成一个 1 ~ 100 之间的整数,这个数字是不可见的;

2. 需要玩家输入一个数字作为猜测;

3. 如果猜错,程序提示猜测数字偏大还是偏小,玩家需要重新输入数字;

4. 如果猜对,程序提示祝贺信息并退出。

先用 cargo 创建一个新项目

cargo new hello-rust

接下来删除 src/main.rs 中的已有内容,let's coding...

 

二、生成随机数

Rust 标准库中尚未包含随机数功能,需要引入 rand

cargo add rand

然后修改 main.rs 为:

use rand::{thread_rng, Rng};

fn main() {
// gen_range 这个方法,在不同版本的 rand 包中接收的参数不同 let secret_number
= thread_rng().gen_range(1..=100); // 打印秘密数字,调试用,开发完成后应当删除这行打印 println!("生成的秘密数字为:{secret_number}"); }

这里的第一行代码  use ::rand::{thread_rng, Rng} 指的是从 rand 这个代码包中导出 thread_rng 和 Rng

类似于 JS 中的  import { thread_rng, Rng } from 'rand' 

这里的 Rng 是一个 trait,是一种将方法签名组合起来的方法,类似于 TypeScript 中的 interface

但 trait 只是作为签名,不能直接使用(ts 中的 interface 也不能作为值使用)

比如上面的代码中,Rng trait 下面有一个 thread_rng 方法,但 Rng.thread_rng() 这种写法会报错 

需要改为 rand::thread_rng() 才能正确解析,而第一行代码已经从 rand 中解构出了 thread_rng,所以可以直接调用


然后 cargo run 运行一下代码,看看效果

 

三、输入猜测数字

在 main() 函数中追加一行代码

let mut guess = String::new();

这里创建了一个可变的字符串变量 guess,用来接收玩家的输入

对比上面创建随机数的代码 let secret_number = xxx,这里多了一个 mut 关键字

Rust 中创建的变量默认是不可变的,所以下面这样的代码会报错:

而加上 mut 关键字,这个变量就是一个可变变量,可以在后续的代码中重新赋值


接下来将 main.rs 改为如下内容:

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

fn main() {
    println!("猜数字游戏!");

    println!("请输入你的猜测:");

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

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("读取输入内容失败");

    println!("生成的秘密数字为:{secret_number}");
    println!("你的猜测为:{guess}");
}

这里使用了标准库 std 里面的 io输入/输出库,用于获取用户输入

// Rust 会默认引入 std 中的预导入内容,其外的内容需要手动 use

这里 read_line 的作用就是读取一行用户输入

传给 read_line 的参数用到了&,这是指针的标记,类似于 JS 中的引用类型

如果不标记为指针,read_line 就不会修改 guess 的值,也就拿不到用户输入

另外,最后一行的 expect 是一个错误处理,会在第六小节介绍

 

四、比较数字

接下来追加这部分代码:

match guess.cmp(&secret_number) {
    Ordering::Less => println!("你的猜测太小"),
    Ordering::Greater => println!("你的猜测太大"),
    Ordering::Equal => println!("你猜对了~"),
}

这里使用了 cmp 方法,用输入的猜测数字 guess 和目标数字 secret_number 作对比

cmd 的返回值是 Ordering 类型,需要从标准库引入:

use std::cmp::Ordering;

Ordering 是一个枚举,也就是说 cmp() 的结果会包含多个可能性

可以使用 match 语句来处理枚举类型的多种结果,类似于 JS 中的 switch...case


但上面的代码还存在问题,错误信息为“类型不匹配”:

这是因为 guess 是一个 String 类型,而 secret_number 是通过 gen_range 方法生成的整数,默认为 i32 类型

Rust 是静态强类型语言,不同类型的变量无法直接比较。这里需要将 guess 转为数字类型:

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

fn main() {
    println!("猜数字游戏!");

    println!("请输入你的猜测:");

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

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("读取输入内容错误");

    let guess: u32 = guess.trim().parse().expect("请输入数字!");

    println!("你的猜测为:{guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("你的猜测太小"),
        Ordering::Greater => println!("你的猜测太大"),
        Ordering::Equal => println!("你猜对了~"),
    }
}

新增的代码为:

let guess: u32 = guess.trim().parse().expect("请输入数字!");

这里将 guess 转为数字类型  u32,但由于 guess 在声明的时候已经是 String 类型,所以需要使用 let 重新声明一个变量

虽然之前已经创建了一个 guess 变量,但 Rust 允许同名变量的覆盖(隐藏 shadowing

这个功能在类型转换的场景很常用,它允许我们复用变量名,而不用创建两个不同变量

 

五、多次输入

现在整个程序的主流程已经完成,可以 run 一下看看效果

现在只能猜一次数字,我们还需要加一个循环,让用户能一直输入猜测,直到猜对 

在 Rust 中可以通过 loop 创建一个无限循环

fn main() {
    println!("猜数字游戏!");

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

    loop {
        println!("请输入你的猜测:");

        // ...

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("你的猜测太小"),
            Ordering::Greater => println!("你的猜测太大"),
            Ordering::Equal => {
                println!("你猜对了~");
                break;
            },
        }
    }
}

和其他语言的 for 语句类似,loop 也有 continue、break 关键字

continue 用于跳过当前循环,直接进入下一个循环

break 用于结束整个循环

 

六、错误处理

上面的代码有两个地方( parse、read_line )用到了 expect 方法,这是一个错误处理

比如 read_line 方法,它会返回一个 Result,这也是一个枚举,其成员为 Ok 和 Err

// 有点像 JS 中的 Promise,返回 then 和 catch

Result 实例拥有 expect 方法,这个方法会原样返回 Ok。而对于 Err,expect 会打印报错信息(函数入参),并退出程序

对于 read_line,报错的情况下直接退出程序,这是我们期望的结果

但对于 parse,报错之后应该让用户重新输入,就不合适用 expect 来处理错误

由于 Result 是一个枚举类型,可以用 match 来处理错误

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

 

最终完整的代码如下:

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

fn main() {
    println!("猜数字游戏!");

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

    loop {
        println!("请输入你的猜测:");

        let mut guess = String::new();

        io::stdin().read_line(&mut guess).expect("读取输入内容错误");

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

        println!("你的猜测为:{guess}");

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("你的猜测太小"),
            Ordering::Greater => println!("你的猜测太大"),
            Ordering::Equal => {
                println!("你猜对了~");
                break;
            }
        }
    }
}

 

posted @ 2023-01-12 10:51  Wise.Wrong  阅读(181)  评论(0编辑  收藏  举报