【Rust学习记录】2. 猜数游戏——尝试代码编写

TwoSix Lv3

输入与输出的尝试

创建项目

1
2
3
cargo new guess_game
cd .\guess_game\
cargo run

就是之前介绍的,用 cargo 创建项目的步骤,run 成功了就表明没问题啦

1
2
3
4
5
6
7
8
9
use std::io;

fn main(){
println!("欢迎来玩猜数游戏!");
println!("输入一个数字:");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("读取输入失败!");
println!("你输入的数字是:{}", guess);
}
  • use:就是 Rust 里的导入语句,这里的意思是导入 std 库里的 io 模块。
  • let:就是 Rust 里的定义语句,let a = b; 就是定义一个新的变量 a,值为 b。但值得一提的是,和其他所有程序都不一样,Rust 里直接定义的变量都是 const 常量,不可变!需要用 mut 关键词声明这个变量是可变的变量。
  • string::new():没什么好说的,new 了一个 String 类型,是空白的字符串。
  • io::stdin().read_line(&mut guess):也就是调用 io 模块里的 stdin 实例的 read_line 函数,如果没有写 use,也可以用 std::io::stdin 表示,& 和 C++ 里一样,是引用的概念,也就是说,定义了一个新的引用,和上面的 guess 指向同一个地址,用来接输入。
  • .expect 就是异常处理语句,在执行完 read_line 后,会返回一个 Result 类型的值,通常是一个枚举类型,OkErr 两个值,Ok 就是执行成功,并且附带代码产生的结果值,这里就是输入的字节数;Err 就是执行错误,附带错误原因。用 expect 就可以很方便地处理异常,而不用再写各 if-else。
  • 值得一提的是,不写 expect 的话,虽然能编译通过,但 Rust 会提示你有个地方没处理异常,就像这样:

img

(听说有人不喜欢写异常处理是吧)

最后一句,就是标准的格式化花括号占位输出,没什么好说的。

引入第三方包的尝试

声明依赖

一般语言的标准库都有生成随机数的函数,但rust没有。所以我们需要引入rust官方提供的一个随机数包——rand

打开Cargo.toml文件,在dependencies后面添加rand包。

1
2
3
4
5
6
7
8
9
[package]
name = "guess_game"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.3.14"

0.3.14版本就是版本号。

然后再build一次项目,cargo就会自动帮你搜索包以及对应的并下载了,包的信息一般是从 crates.io 获取

cargo.lock和cargo update

cargo引入了一个独特的机制来保证依赖的版本问题,让所有人在构建这个项目的时候都得到相同的结果。你第一次构建项目的时候,cargo就会遍历我们声明的依赖以及版本号,把它写到 lock 文件里,后面再构建的时候,就会都使用这个版本的依赖了,除非手动升级到其他版本。

如果实在想升级,使用 cargo update 命令就会无视 lock 强行升级依赖

使用match进行分支控制的尝试

rust提供了match语法来进行更简洁的分支控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main(){
println!("欢迎来玩猜数游戏!");
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("正确答案是:{}", secret_number);
println!("输入一个数字:");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("读取输入失败!");
let guess: i32 = guess.trim().parse()
.expect("请输入正确的数字!");
match guess.cmp(&secret_number){
Ordering::Less => {
println!("猜小啦");
println!("再猜一次:");
},
Ordering::Greater => println!("猜大啦"),
Ordering::Equal => println!("猜对啦"),
Other => println!("猜错啦"),
}
println!("你输入的数字是:{}", guess);
}

这段代码里:

  • use 了一个 Ordering 的模块,这个模块提供了顺序对应的枚举类型,也就是 Less, Greater, Equal
  • trim().parse() 语句用来作类型转换,因为 gen_range 生成的 secret_numberi32 类型,无法直接与输入的字符串进行比较,所以作了一个类型转换;其中 trim() 就是去掉首尾多余的字符,空格换行什么的;parse() 是用于将字符串解析为对应数值类型的方法,同样也会抛出 Result 可以用于异常处理
  • match guess.cmp:就是通过 match 语法进行分支控制,把 guess.cmp() 的结果丢到下面去匹配,匹配到什么就执行什么的语句。其中 cmp 返回的就是 Ordering 这个枚举类型。实际中,这个枚举类型也可以自己定义,方便自己的分支控制。传统的 if-else 也能实现这个逻辑就是。

PS:大概了解了一下 if-elsematch 的比较,两者应该主要是在可读性上的区别比较明显。

在可读性上,if-else 只接受 bool 值的二类判断。当有复杂条件的时候,就需要多层嵌套 if-else 比较难看。相对的,match 可以自定义枚举类型,在多类判断的时候,写法也更简洁,可读性更佳。当然,二类判断当属 if-else

在性能上,当匹配的模式非常多的情况下,match 可以在编译时就完成判断,而 if-else 是在运行的时候完成判断,在极端的情况下,match 的性能会更佳(但我觉得在这种语句上纠性能的意义并不大)。

另外了解了一下,if-else 的语法和 C++ 基本一样,就不另外写了,书上目前也没有介绍 if-else

循环的尝试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main(){
println!("欢迎来玩猜数游戏!");
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("正确答案是:{}", secret_number);
loop{
println!("输入一个数字:");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("读取输入失败!");
println!("你输入的数字是:{}", guess);
let guess: i32 = guess.trim().parse()
.expect("请输入正确的数字!");
match guess.cmp(&secret_number){
Ordering::Less => {
println!("猜小啦");
println!("再猜一次:");
},
Ordering::Greater => {
println!("猜大啦");
println!("再猜一次:");
},
Ordering::Equal => {
println!("猜对啦");
break;
}
}
}
}

想使用一个 while True 循环也很简单,在 Rust 里就是一个 loop 语法,外带 break,没什么多说的,这里注意定义 guess 变量要在 loop 里,不然 read_line 会不断的在 guess 后面添加字符,就会导致无法转换成数字,报错退出。

那么在循环里有一个问题就很明显了,那就是 expect 语句不是我所理解常规的 try-catch 异常处理语句,它并没有捕获+后处理的步骤,所以它是会导致程序报错退出的。

那怎么来进行异常处理呢?之前也说过,Result 返回的是一个枚举类型 OkErr。那不就是,用 match 来处理就完了嘛?~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main(){
println!("欢迎来玩猜数游戏!");
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("正确答案是:{}", secret_number);
loop{
println!("输入一个数字:");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("读取输入失败!");
println!("你输入的数字是:{}", guess);
let guess: i32 = match guess.trim().parse(){
Ok(num) => num,
Err(_) => {
println!("请输入一个正确的数字!");
continue;
}
};
match guess.cmp(&secret_number){
Ordering::Less => {
println!("猜小啦");
println!("再猜一次:");
},
Ordering::Greater => {
println!("猜大啦");
println!("再猜一次:");
},
Ordering::Equal => {
println!("猜对啦");
break;
}
}
}
}

修改后的代码是这样的,我们用 match 来处理 parse() 的返回值,之前也说过,Ok 会附带执行成功的值,所以 Ok(num) 表示,用 num 来匹配 Ok 里面带的成功的返回值,=> 表示返回 num 值;Err(_) 就是表示,用 _ 来匹配错误信息,因为不需要用,所以用 _ 就完了,然后执行错误处理,输出+继续输入;值得一提的是这里好像说明了 Rust 的函数返回机制,好像不需要写 return,直接一个变量名就是返回了。

OK,到目前为止,初步的编程尝试已经结束了,看了下后面的章节介绍,应该会是更结构化的东西。

  • 标题: 【Rust学习记录】2. 猜数游戏——尝试代码编写
  • 作者: TwoSix
  • 创建于 : 2023-03-25 14:45:41
  • 更新于 : 2024-07-04 23:52:28
  • 链接: https://twosix.page/2023/03/25/【Rust学习记录】2-猜数游戏——尝试代码编写/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论