猜数游戏
2.1 一次猜测
方向
- let、match 等方法的使用
- 相关的函数
- 外部的 crate
- …
目标
- 生成一个 1 到 100 间的随机数
- 提示用户输入一个猜测
- 猜完之后,程序会提示猜测时太大了还是太小了
- 如果猜测正确,那么答应出一个庆祝的信息,程序退出
代码
首先使用命令创建工程:cargo new guessing_game
use std::io; // prelude 预导入
fn main() {
println!("猜数游戏");
println!("猜测一个数");
// let mut foo = 1; //mutable
// let bar = foo; // immutable
// bar = 2; // immutable
let mut guess = String::new(); //UTF-8
io::stdin().read_line(&mut guess).expect("无法读取行");
// io::Result Ok, Err
println!("你猜测的数是 {}", guess);
}
解析
- new() 是 String 标准库下的一个关联函数。
- 关联函数 是针对于类型本身来实现的而不是针对字符串的某个特定实例来实现的。这类似于 Java 和 C# 中的静态方法。
- 关键字
mut
将变量修饰为可变变量,默认无 mut 修饰的变量都为不可变变量。 io::stdin()
来自 std 标准库,若不显式地使用标准库use std::io;
则需要在主函数中使用全名std::io::stdin()
。read_line(mut guess)
该字符串类型作为参数是需要根据用户的输入不断改变,所以需要使用mut
关键字进行修饰。read_line(&mut guess)
使用取地址符号&
表示该参数是一个引用类型,通过引用就可以在代码的不同地方访问同一块数据。则参数与外部的guess
指向同一块地址。
也就是Java中,方法的参数是按引用进行传递的。 read_line() 执行完成后会返回一个 io::Result 对象。
- 引用:
&guess
在 Rust 中也是默认不可变的。 expect()
方法来自io::Result
,其工作原理为:- 若 io::Result 返回了一个 Err 枚举类型,则 expect() 方法会中断当前程序并将方法中传入的字符串信息显示出来。
- 若 io::Result 返回的是一个 Ok 枚举类型,则 expect() 方法会提取出其中的附加值,并将这个附加的值的结果返回给用户。
- 若使用 expect() 函数,则在编译阶段会返回一个警告:
xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo build
Compiling guessing_game v0.1.0 (/Users/xinggongwuyue/Desktop/项目工程/rust/guessing_game)
warning: unused `Result` that must be used
--> src/main.rs:11:5
|
11 | io::stdin().read_line(&mut guess);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled
warning: `guessing_game` (bin "guessing_game") generated 1 warning
Finished dev [unoptimized debuginfo] target(s) in 0.35s
其意思为:read_line() 返回的 io::Result 对象没有被使用过。 目的是为了提高代码的安全性。
变量与常量的区别
- let(不可变变量)与 const(常量)是不相同的。
- let 可以只声明不赋值
let a:i32;
let 也可以边声明边赋值let a = 1;
这里使用了类型推导,不需要显式地加类型注释。 - const 声明的常量是大写字母组成,而且必须强制地加上类型注释
const a:i32 = 1;
,而且必须“声明”与“赋值”一起。 - let 与 const 最大区别在于初始化的方式不同。
运行结果
代码语言:javascript复制xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo run
Finished dev [unoptimized debuginfo] target(s) in 0.01s
Running `target/debug/guessing_game`
猜数游戏
猜测一个数
34
你猜测的数是 34
2.2 生成神秘数字
添加 rand 依赖库
库的地址: https://crates.io/crates/rand
在 Cargo.toml
文件下的 [dependencies]
中添加依赖:
#...
[dependencies]
rand = "0.3.14"
其中 rand = "0.3.14"
的完整写法为 rand = "^0.3.14"
,其含义为:任何与 0.3.14 这个版本的公共 API 兼容的版本都可以使用。
保存后若启动了 Rust: Start the Rust server
则会在后台自动拉去相应的包(使用字节跳动的镜像源:reproxy.cn)。执行 build 就能看到 rand 包被编译构建。
xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo build
Compiling libc v0.2.124
Compiling rand v0.4.6
Compiling rand v0.3.23
Compiling guessing_game v0.1.0 (/Users/xinggongwuyue/rust/guessing_game)
Finished dev [unoptimized debuginfo] target(s) in 0.89s
或者也可以使用
cargo install rand
进行下载。
情况一:重新编译结果
代码语言:javascript复制xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo build
Finished dev [unoptimized debuginfo] target(s) in 0.02s
情况二:修改代码后重新编译结果
代码语言:javascript复制xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo build
Compiling guessing_game v0.1.0 (/Users/xinggongwuyue/rust/guessing_game)
Finished dev [unoptimized debuginfo] target(s) in 0.30s
Cargo.lock
是由 cargo build 之后生成的文件,后续 build 构建都会在 cargo.lock 文件中进行检索并使用其中指定的包的版本,就不再需要重新找别的版本。直至手动更新包的版本
或使用命令 cargo update
忽略 Cargo.lock 文件中的内容,直接按照 Cargo.toml 文件中的指定版本进行包的更新操作。
例如:在 Cargo.toml 中将版本更新至 0.6.1
代码语言:javascript复制xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo build
Compiling rand_core v0.4.2
Compiling rand_core v0.3.1
Compiling rand_pcg v0.1.2
Compiling rand_os v0.1.3
Compiling rand_jitter v0.1.4
Compiling rand_chacha v0.1.1
Compiling rand_hc v0.1.0
Compiling rand_xorshift v0.1.1
Compiling rand_isaac v0.1.1
Compiling rand v0.6.5
Compiling guessing_game v0.1.0 (/Users/xinggongwuyue/rust/guessing_game)
Finished dev [unoptimized debuginfo] target(s) in 0.98s
代码
代码语言:javascript复制use std::io; // prelude
use rand::Rng; // trait
fn main() {
println!("猜数游戏!");
//在[1,100]之间生成随机数
let secret_num = rand::thread_rng().gen_range(1..=100);
println!("生成的神秘数字是: {}", secret_num);
println!("猜测一个数");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
println!("你猜测的数是 {}", guess);
}
运行结果
代码语言:javascript复制xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo build
Compiling guessing_game v0.1.0 (/Users/xinggongwuyue/rust/guessing_game)
Finished dev [unoptimized debuginfo] target(s) in 0.18s
xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo run
Finished dev [unoptimized debuginfo] target(s) in 0.00s
Running `target/debug/guessing_game`
猜数游戏
生成的神秘数字是: 27
猜测一个数
31
你猜测的数是 31
对于低版本(版本 < 0.8)
- rand::thread_rng() 作用域下的 gen_range() 方法如下:
// 在区间[1,101)之间随机生成数
let secret_num = rand::thread_rng().gen_range(1,101);
对于高版本(版本 >= 0.8)
- rand::thread_rng() 作用域下的 gen_range() 方法如下:
// 左闭右开:[a,b)
let n: u32 = rand::thread_rng().gen_range(0..10);
println!("{}", n);
let m: f64 = rand::thread_rng().gen_range(-40.0..1.3e5);
println!("{}", m);
// 全闭:[a,b]
let n: u32 = rand::thread_rng().gen_range(0..=10);
println!("{}", n);
2.3 比较猜测数字和神秘数字
代码
代码语言:javascript复制use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("猜数游戏");
// 在[1,100]之间生成随机数
let secret_num = rand::thread_rng().gen_range(1..=100); //i32 u32 i64
println!("生成的神秘数字是: {}", secret_num);
println!("猜测一个数");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
// 指定 parse() 转换后为 u32 数值类型
let guess:u32 = guess.trim().parse().expect("请输入一个数字");
println!("你猜测的数是 {}", guess);
// compare => cmp 比较类型必须相同
match guess.cmp(&secret_num) { // 此时 secret_num 由 i32 变为 u32
Ordering::Less => println!("Too small!"), // arm
Ordering::Greater => print!("Too big!"),
Ordering::Equal => println!("You win!")
}
}
解析
cmp(&value)
(compare) 方法返回的是一个 Ordering 变体match val.cmp(&value) {}
方法将 cmp 方法返回的 Ordering 变体进行一个比较。每个比较体称作 分支(arm) ,并且按照从上至下的顺序进行匹配。- 若返回的 Ordering 与 Less 匹配成功,则执行该分支下的
println!("Too small!")
代码块。
- 若返回的 Ordering 与 Less 匹配成功,则执行该分支下的
let guess:u32
在 Rust 中允许使用同名的新变量来 隐藏(shadow) 原来同名的旧变量(通常用于需要类型转换的场景中)。trim()
将字符串前后的 空白、空格、n 全部移除。parse()
将字符串解析成数值类型,并返回一个 io::Result 对象,需要通过 expect(msg: &str) 进行异常抛出的处理。
运行结果
代码语言:javascript复制xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo run
Finished dev [unoptimized debuginfo] target(s) in 0.00s
Running `target/debug/guessing_game`
猜数游戏
生成的神秘数字是: 1
猜测一个数
50
你猜测的数是 50
Too big!
xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo run
Finished dev [unoptimized debuginfo] target(s) in 0.00s
Running `target/debug/guessing_game`
猜数游戏
生成的神秘数字是: 75
猜测一个数
30
你猜测的数是 30
Too small!
xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo run
Finished dev [unoptimized debuginfo] target(s) in 0.00s
Running `target/debug/guessing_game`
猜数游戏
生成的神秘数字是: 13
猜测一个数
13
你猜测的数是 13
You win!
2.4 允许多次猜测
目标
- 猜错不中断程序执行
- 直至猜对后再中断程序执行
代码
代码语言:javascript复制use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("猜数游戏");
let secret_num = 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_num) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
},
}
}
}
解析
loop {}
在 Rust 中,使用loop
表示循环。while
在 Rust 中更侧重于while condition
模式(while a < b) 而不是 while true。- 这体现了 Rust 的设计哲学之一:高度一致性。正如 match 中最后一个 arm 可选择性地添加逗号。
break
中断当前循环继续执行后面的代码。match guess.trim().parse()
使用 match 来解决异常情况,优化体验效果。
运行结果
代码语言:javascript复制xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo run
Finished dev [unoptimized debuginfo] target(s) in 0.00s
Running `target/debug/guessing_game`
猜数游戏
猜测一个数
5
你猜测的数是 5
Too small!
猜测一个数
40
你猜测的数是 40
Too big!
猜测一个数
35
你猜测的数是 35
Too small!
猜测一个数
39
你猜测的数是 39
You win!