2.1 单次猜测

代码

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

fn main() {
println!("猜数游戏");
println!("请猜测一个数:");

let mut guess = String::new();

io::stdin().read_line(&mut guess).expect("无法读取行");
println!("你猜的数是: {}", guess);
}

解析

  • std::io代表调用标准库std中的io库,使用use命令

  • Rust中所有的变量都是默认不可变的(immutable),例如

    1
    2
    let foo = 1;
    let bar = foo;

    如果希望变量可变,则需要加上mut关键字

  • 在Rust中,字符串类型为String,包含在标准库中,而String::new()返回字符串的一个新的实例,默认为UTF8编码的空的字符串

    • 这里的::和C++的含义不同,代表new()String类型的一个关联函数,
    • 关联函数是针对类型的函数,而不是针对特定实例的函数
  • 函数stdin()会返回Stdin类型的一个实例,而方法read_line()会获取用户的输入,并将输入存到字符串guess

    • 该方法要求字符串必须是mutable的,因此需要&mut关键字
    • 其中&代表该参数是一个引用,引用的目的是在代码的不同地方访问同一块数据,即代表&mut guess中的guess和前面定义的guess指向同一块数据
    • 此处方法的参数是要通过引用来传递的
  • 方法read_line()的返回值为枚举类型io::Result<usize>,若读取成功,将返回Ok且其包含结果值;否则返回Err并附带失败原因。对于该枚举类型,其还定义了一些方法,例如此处使用的expect()

    • 若该实例io::Result<usize>返回Err,则expect()方法会中断当前程序,并显示传入的字符串信息
    • 否则,expect()方法会提取Ok中附加的值,并将该值作为结果返回给用户
  • 最后一行println!()中的{}为占位符,类似Python中的print(),会被后面的变量替换作为输出

2.2 生成神秘数字

Rust的标准库中没有提供生成随机数的功能,需要借助名为Randcrate

Rust中的crate分为两种

  • 二进制crate:可独立运行
  • library crate:不能独立运行,即需要被调用,和项目一起生成二进制文件

为了添加Rand这个crate的依赖,我们需要编辑Cargo.toml文件,并添加在````[dependencies]```模块下,例如

1
2
[dependencies]
rand = "0.3.14"
  • 0.3.14为版本号

  • 如果写作"^0.3.14",则^代表任何与公共API兼容的版本都可以

这个crate将在cargo build的时候进行下载,包括相关的依赖库


为了保证代码的复现性,在首次进行build之后,会生成Cargo.lock文件,它会记录第一次build使对应的crate的版本信息(对应的小版本号的最新版,如0.3.23),即便后续修改Cargo.toml文件中对应库的版本再进行build操作,也不会更新库的版本

换言之,只要Cargo.lock文件存在,Cargo将一直使用该文件中的库版本,直到运行cargo update命令,才会重新根据Cargo.toml中的版本来更新Cargo.lock中编译用的版本


为了生成随机数,我们使用

1
use rand::Rng;

其中Rngrand这个库里的trait,类似其它语言中API,上面会定义很多方法

  • Rng这个trait里面定义了很多随机数生成器的方法

为了生成随机数,我们使用语句

1
let secrete_number = rand::thread_rng().gen_range(1..101);

其中

  • 函数thread_rng()返回的是ThreadRng类型,该类型是一个随机数生成器,其位于本地线程空间,并通过操作系统来获取随机数种子
  • gen_range()Rng这个trait定义的方法,会生成范围[1, 101)内的一个随机整数

综上,我们修改后的最新完整代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::io;
use rand::Rng;

fn main() {
println!("猜数游戏");
println!("请猜测一个数:");

let secrete_number = rand::thread_rng().gen_range(1..101);
println!("神秘数字是:{}", secrete_number);

let mut guess = String::new();

io::stdin().read_line(&mut guess).expect("无法读取行");
println!("你猜的数是: {}", guess);
}

2.3 比大小

Rust在赋值时可以像C++的auto语句一样进行强类型推断,例如let mut guess可以通过右面表达式推断guess的静态类型要被声明成为String

至于随机数生成部分的代码,由于i32u32u64均满足该条件,若无更多信息被指定来用于类型推断,否则将默认指定为i32类型


为了比较secrete_numberguess,我们需要先进行类型转换,即

1
let guess: u32 = guess.trim().parse().expect("请输入一个合法的数字!");
  • trim()方法用于去掉字符串两端的空白,包括tab和回车

  • parse()方法用于将其解析成u32类型,这个u32由等号前的对应关键字来指定,该解析可能会失败,例如传入xyz的时候

    • 为了处理这种情况,parse()方法会返回Result这个枚举类型
    • 和前面的read_line()方法类似,我们可以调用该返回类型的expect()方法来报错
  • 这里的guess: u32是声明了一个新的guess且为u32类型,其会进行shadow操作,即它会覆盖前面出现的字符串类型的guess,且在其之后,程序调用的guess都是这个新定义的guess


1
2
3
4
5
match guess.cmp(&secrete_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}

这里引入名为std::cmp::ordering这一枚举类型,其有三个变体LessGreaterEqual

此外,match 表达式后面跟多个arm(分支),如果match后面表达式的值可以和某个arm匹配,则执行对应语句

在有这一比较的语句出现之后,编译器会将secrete_number解释称对应的u32类型,而不再是i32类型


最终的完整代码为(注意开头的use std::cmp::Ordering;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::io;
use rand::Rng;
use std::cmp::Ordering;

fn main() {
println!("猜数游戏");
println!("请猜测一个数:");

let secrete_number = rand::thread_rng().gen_range(1..101);
println!("神秘数字是:{}", secrete_number);

let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");

let guess: u32 = guess.trim().parse().expect("请输入一个合法的数字!");
println!("你猜的数是: {}", guess);

match guess.cmp(&secrete_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}

2.4 允许多次猜测

为了允许多次猜测直至用户才对,需要实现一个无限循环,这将借助loop语句和break来跳出循环:

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
use std::io;
use rand::Rng;
use std::cmp::Ordering;

fn main() {
println!("猜数游戏");
println!("请猜测一个数:");

let secrete_number = rand::thread_rng().gen_range(1..101);
println!("神秘数字是:{}", secrete_number);

loop {
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");

let guess: u32 = guess.trim().parse().expect("请输入一个合法的数字!");
println!("你猜的数是: {}", guess);

match guess.cmp(&secrete_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}