C2 一个简单的示例项目 Guessing Game
https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
这个示例项目用于介绍 Rust 的一些基本概念, 包括 let
, match
, 关联函数, 外部 crate 等.
这个示例项目的需求为: app 内部生成一个 1 到 100 的随机数, 然后提示用户输入一个数, 如果输入的数和生成的一致, 则玩家赢. 同时玩家可以多次猜数, 如果输入的数大了, 则提示大了, 如果输 入的数小了, 则提示小了.
开发过程
获取用户输入
-
使用
cargo new guessing_game
创建一个新工程. -
首先需要让用户输入一个数, 在 Rust 中获取标准输入可以通过如下方式:
io::stdin().read_line(&mut buf).unwrap();
要使用上述代码, 首先需要引入标准库中的
io
库use std::io;
. Rust 提供有prelude
机制, 可以自动隐式引入一些常用的库, 如果需要的库不在prelude
范围, 则手动显式引入即可. -
将输入的数放入到 buf 中, 此时可以使用如下:
let mut guess = String::new();
其中
new
为 String 类型的关联函数(可以理解为类方法).此时可以将标准输入的值放入到 guess 中:
io::stdin().read_line(&mut guess);
其中
io::stdin()
用于获取一个标准输入句柄对象, 在其上调用read_line
对象方法, 该方法需要一个缓冲区参数, 传入我们创建的 String 即可.在传入时使用的是
&
表示传入缓冲区的引用, 引用的作用是可以让代码中多处位置使用相同的数据, 而无需将数据复制多份. 而&mut
的作用是表示数据是可变的引用. -
read_line
的结果是一个Result
, 使用unwrap
或expect
可以在其值为Ok
的时候返回该值, 而Err
时触发 Panic.
生成随机数
为生成随机数, 可以使用 rand
crate, 在 Cargo.toml
中添加如下依赖:
[dependencies]
rand = "0.8.5"
添加依赖后, 可以发现生成了一个 Cargo.lock
文件, 它是 Cargo 用来保证每次 Build 均能生成一致产物的机制, 其中记录了添加时指定的依赖版本. 除非用户手动执行 cargo update
更新依赖版本(在更新依赖版本时, 会按指定的 minor 更新, 而不会更新到大版本).
下面使用 rand
生成随机数:
use rand::Rng;
// ...
let number = rand::thread_rng().gen_range(1..=100);
当不确定该使用依赖中什么类型/什么方法时, 可以执行
cargo doc --open
命令生成依赖列表中所有依赖的文档并在本地打开, 这样就可以通过文档来看到了.
对比生成随机数和用户输入
对比时, 可以调用 guess
的 cmp
方法, 其结果是一个 enum, 包含 Ordering
下的 Less
, Greater
, Equal
三种情况. Rust 提供了一个 match
可以进行模式匹配:
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
但此时对比是无法进行的, 因为 guess
和 secret_number
的类型不一致(一个是 String
, 一个是 u32
). 我们可以通过如下方式将 String 转换为 u32:
let guess: u32 = guess.trim().parse().expect("Please type a number");
- 这里再定义了一个和用户输入 String 同名的 guess 变量, Rust 有变量名覆盖机制, 这样可以让名字在下一次仍然可用.
- trim 用于将字符串中的空白符移除
- parse 用于将字符串转换为对应类型的值, 若转换失败, 产生 Result 的
Err
.
完成阶段性目标
到目前为止, 已经完成了 生成随机数 -> 用户输入并解析为数字 -> 对比
三个步骤, 下面是汇总代码:
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!"),
}
我们还需要实现一个需求: 用户能反复猜数字.
反复猜数字
Rust 提供了一个实现无限循环的办法: loop
循环可以达到这个目的:
// 生成随机数并解析...
loop {
// 用户输入...
// 对比并输出结果...
}
通过上述结构结合第一阶段代码, 即可实现完整功能.
还有, 如果想让用户猜对后退出循环并结束, 也非常简单, 使用 break
中断循环即可:
// ...
loop {
// ...
match guess.cmp(&secret_number) {
//...
Ordering::Equal => {
println!("You win!");
break; // 可以中断外层 loop
}
}
}
用户无效输入的处理
针对用户的输入 parse
, 我们之前是直接终止程序运行的, 需要优化体验, 而办法也非常简单, 只需要处理 parse 时候的 Result 即可:
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
完整代码
完整代码如下:
use std::io;
use rand::Rng;
fn main() {
let secret_number: u32 = rand::thread_rng().gen_range(1..=100);
loop {
println!("Please input your guess");
let mut guess = String::new();
io::stdin().read_line(&mut guess).unwrap();
let guess: u32 = match guess.trim().parse() {
Ok(n) => n,
Err(_) => {
continue;
}
};
println!("Your guess is: {guess}");
match guess.cmp(&secret_number) {
std::cmp::Ordering::Less => {
println!("Too small");
continue;
}
std::cmp::Ordering::Greater => {
println!("Too big");
continue;
}
std::cmp::Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
C2 完结, C3 通用编程概念 待续.