欢迎来到学习 Rust 的第三天!我参考的是 Steve Klabnik 的《Rust 编程语言》一书。今天我们要用 Rust 来制作一个猜数字的游戏。
引言
我们将创建一个游戏,它会在 1 到 100 之间随机选择一个数字,用户需要猜出这个数字,猜对了就赢了。
算法
伪代码如下所示:
rust
secret_number = (生成一个随机数)
loop {
写入"请输入你的猜测"
读取猜测
写入"你的猜测:${guess}"
if(猜测 > secret_number){
写入"太高了!"
}else if(猜测 < secret_number){
写入"太低了!"
}else if(猜测==数字){
写入"你赢了!"
跳出循环
}
}
步骤一:设置新项目
使用 cargo new
命令来设置一个项目:
bash
$ cargo new guessing_game
$ cd guessing_game
步骤二:声明变量并读取用户输入
文件名:main.rs
rust
use std::io;
fn main() {
println!("猜数字游戏");
println!("请输入你的猜测");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("读取失败");
println!("你的猜测:{}", guess);
}
让我们逐行解析这段代码:
use std::io;
这一行将std::io
(标准输入/输出)库引入到作用域中。std::io
库提供了许多处理输入/输出的有用功能。fn main(){...}
这是主函数,程序的执行从这里开始。println!("猜数字游戏");
println!
是一个宏,用于将文本打印到控制台。println!("请输入你的猜测");
这一行提示用户输入他们的猜测。let mut guess = String::new();
这一行声明了一个可变变量guess
并将其初始化为空字符串。mut
表示该变量是可变的,即其值可以改变。String::new()
创建了一个新的空字符串。io::stdin().read_line(&mut guess).expect("读取失败");
这一行从标准输入(键盘)读取用户输入。输入的内容被放入guess
字符串中。如果这个过程失败,程序将停止执行并显示"读取失败"的消息。println!("你的猜测:{guess}");
这一行打印出字符串"你的猜测:",后跟用户输入的内容。
步骤三:生成一个随机数
为了增加可玩性,每次游戏都应该产生不同的数字。Rust 标准库没有包含随机数功能,但 Rust 团队提供了一个 rand
crate 来实现这个目的。
一个 Rust crate 就像是一个整齐打包的代码盒子,你可以轻松地在 Rust 编程语言中使用和与他人分享。
使用 rand
crate:
文件名:Cargo.toml
toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5" #添加这一行
理解 Cargo.toml
文件:
- [package]
name = "guessing_game"
:指定 Rust 包(或 crate)的名称为 "guessing_game"。version = "0.1.0"
:指定 crate 的版本为 "0.1.0"。edition = "2021"
:指定要使用的 Rust 版本(在本例中是 2021 版)。
- [dependencies]
rand = "0.8.5"
:添加对 "rand" crate 版本 "0.8.5" 的依赖。这意味着 "guessing_game" crate 依赖于 "rand" crate 提供的功能,具体版本为 0.8.5。
简单来说,这个配置文件(Cargo.toml)就像是你 Rust 项目的配方,指定了它的名称、版本、Rust 版本以及任何外部依赖项(在本例中是 "rand" crate)。
在这之后,不改变任何代码,运行 cargo build
,为什么我们要这么做呢?
- 获取依赖项:
cargo build
获取并更新项目中在Cargo.toml
中指定的依赖项。 - 解决依赖关系: 它解决并确保安装了正确版本的依赖项。
- 构建过程: 将 Rust 代码和依赖项编译成可执行文件或库。
- 检查错误: 识别和报告编译错误,确保代码一致性。
- 更新锁文件: 更新
Cargo.lock
文件以记录确切的依赖版本,以保证可重现性。
现在让我们看看代码:
rust
use std::io;
use rand::Rng;
fn main() {
println!("猜数字游戏!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("秘密数字:{}", secret_number);
println!("请输入你的猜测");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("读取失败");
println!("你的猜测:{}", guess);
}
运行程序:
rust
$ cargo run
猜数字游戏!
秘密数字:69
请输入你的猜测
32
你的猜测:32
让我们来看看这里做了什么:
use rand::Rng;
:这一行是一个导入语句,将Rng
trait 引入到作用域中,允许你使用它的方法。rand::thread_rng()
:这部分初始化一个特定于当前线程的随机数生成器。rand::thread_rng()
函数返回一个实现了Rng
trait 的类型。.gen_range(1..=100)
:利用随机数生成器(Rng
trait),此代码调用gen_range
方法生成一个指定范围内的随机数。范围被定义为1..=100
,意味着生成的数字应该在 1 到 100 之间(包括边界)。
第四步:将猜测与用户输入进行比较
现在我们已经获取了用户输入,程序将用户的猜测与秘密数字进行比较,以确定他们是否猜对了。如果猜测与秘密数字相匹配,用户就成功了;否则,程序将评估猜测是太大还是太小。
让我们看看代码,然后分解一下:
rust
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("猜数字游戏开始!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("秘密数字:{}", secret_number);
println!("请猜测一个数字");
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!("你赢了!"),
}
}
运行程序:
rust
$ cargo run
猜数字游戏开始!
秘密数字 : 48
请猜测一个数字
98
你的猜测:98
太大了!
解释:
let guess: u32 = guess.trim().parse().expect("请输入一个数字!");
:重新定义了变量guess
,将字符串解析为无符号32位整数。如果解析失败,则打印错误消息。match guess.cmp(&secret_number) { ... }
:使用match
语句将用户的猜测与秘密数字进行比较,处理三种情况:小于、大于或等于秘密数字。
变量重新定义:
- 变量重新定义是指声明了与现有变量相同名称的新变量,从而有效地"遮蔽"了先前的变量。
- 在这段代码中,
let guess: u32 = ...
就是一个变量重新定义的例子。第二个guess
遮蔽了第一个,将其类型从String
更改为u32
。变量重新定义通常用于重新分配具有新值和类型的变量,同时保持相同的名称。
第五步:循环直到用户赢得游戏
在第五步中,程序实现了一个循环结构,重复提示用户猜测,直到他们正确猜出秘密数字,就像我们在算法中看到的那样。
和往常一样,先看代码,再解释:
rust
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("猜数字游戏开始!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("秘密数字:{}", secret_number);
loop {
println!("请猜测一个数字");
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!("你赢了!");
break;
},
}
}
}
运行程序:
bash
$ cargo run
猜数字游戏开始!
秘密数字:23
请猜测一个数字
4
你的猜测:4
太小了!
请猜测一个数字
76
你的猜测:76
太大了!
请猜测一个数字
23
你的猜测:4
你赢了!
解释:
loop{...}
语句用于创建一个无限循环。- 我们使用
break
语句来退出程序,当变量guess
和secret_number
相同时。
第六步:异常处理
在第六步中,我们要处理无效输入和由此引起的错误。例如:在提示中输入一个字符串
rust
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("猜数字游戏开始!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("秘密数字:{}", secret_number);
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;
},
}
}
}
运行程序:
bash
$ cargo run
猜数字游戏开始!
秘密数字:98
请猜测一个数字
喵喵
请猜测一个数字
43
你的猜测:43
太小了!
解析用户输入:
- 尝试将
guess
中的字符串解析为无符号32位整数。 - 使用
trim
方法从用户的输入中去除前导和尾随空白。
匹配语句:
match
语句检查解析操作的结果。Ok(num) => num
:如果解析成功,则将解析的数字赋给变量guess
。
错误处理:
Err(_) => continue
:如果解析过程中出现错误,占位符_
匹配任何错误,循环内的代码继续执行,提示用户再次输入。
总结
在本文中,我们通过构建一个猜数字游戏开始了我们的 Rust 第三天学习之旅。以下是涉及的关键步骤和概念的总结:
简介
- 目标是创建一个游戏,让用户猜一个在1到100之间随机生成的数字。
算法
- 概述了一个通用的算法,提供了游戏逻辑的高层概览。
第一步:设置一个新项目
- 使用
cargo new
创建了一个名为 "guessing_game" 的新 Rust 项目。
第二步:声明变量和读取用户输入
- 使用
std::io
介绍了基本的输入/输出功能。 - 展示了如何读取用户输入,初始化变量和打印消息。
第三步:生成随机数
- 添加了
rand
crate 作为依赖项来生成随机数。 - 使用
rand::thread_rng().gen_range(1..=100)
生成了一个介于1和100之间的随机数。
第四步:将猜测与用户输入进行比较
- 引入了变量重定义,并将用户输入与秘密数字进行比较。
- 使用
match
语句处理不同的比较结果。
第五步:循环直到用户赢得游戏
- 实现了循环结构,允许用户重复猜测,直到他们正确猜出秘密数字为止。
第六步:异常处理
- 通过为用户输入解析过程添加错误处理来解决潜在错误。
- 使用
continue
语句在发生错误时跳过当前迭代,并再次提示用户。