侧边栏壁纸
博主头像
Dioxide-CN博主等级

茶边话旧,看几许星迢露冕,从淮海南来。

  • 累计撰写 50 篇文章
  • 累计创建 49 个标签
  • 累计收到 21 条评论

目 录CONTENT

文章目录

Rust学习笔记:2.1-2.3 猜数游戏

Dioxide-CN
2021-11-19 / 0 评论 / 3 点赞 / 108 阅读 / 7,292 字
温馨提示:
本文最后更新于 2022-08-02,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

猜数游戏

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);
}

解析

  1. new() 是 String 标准库下的一个关联函数。
    • 关联函数 是针对于类型本身来实现的而不是针对字符串的某个特定实例来实现的。这类似于 Java 和 C# 中的静态方法。
  2. 关键字 mut 将变量修饰为可变变量,默认无 mut 修饰的变量都为不可变变量。
  3. io::stdin() 来自 std 标准库,若不显式地使用标准库 use std::io; 则需要在主函数中使用全名 std::io::stdin()
  4. read_line(mut guess) 该字符串类型作为参数是需要根据用户的输入不断改变,所以需要使用 mut 关键字进行修饰。
  5. read_line(&mut guess) 使用取地址符号 & 表示该参数是一个引用类型,通过引用就可以在代码的不同地方访问同一块数据。则参数与外部的 guess 指向同一块地址。

也就是Java中,方法的参数是按引用进行传递的。
read_line() 执行完成后会返回一个 io::Result 对象。

  1. 引用:&guess 在 Rust 中也是默认不可变的。
  2. 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 对象没有被使用过。
目的是为了提高代码的安全性。

变量与常量的区别

  1. let(不可变变量)与 const(常量)是不相同的。
  2. let 可以只声明不赋值 let a:i32; let 也可以边声明边赋值 let a = 1; 这里使用了类型推导,不需要显式地加类型注释。
  3. const 声明的常量是大写字母组成,而且必须强制地加上类型注释 const a:i32 = 1; ,而且必须“声明”与“赋值”一起。
  4. let 与 const 最大区别在于初始化的方式不同。

运行结果

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 进行下载。

情况一:重新编译结果

xinggongwuyue@DioxideCN-MacBook-Air guessing_game % cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s

情况二:修改代码后重新编译结果

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

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

代码

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);
}

运行结果

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 比较猜测数字和神秘数字

代码

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!")
    }
}

解析

  1. cmp(&value)(compare) 方法返回的是一个 Ordering 变体
  2. match val.cmp(&value) {} 方法将 cmp 方法返回的 Ordering 变体进行一个比较。每个比较体称作 分支(arm) ,并且按照从上至下的顺序进行匹配。
    • 若返回的 Ordering 与 Less 匹配成功,则执行该分支下的 println!("Too small!") 代码块。
  3. let guess:u32 在 Rust 中允许使用同名的新变量来 隐藏(shadow) 原来同名的旧变量(通常用于需要类型转换的场景中)。
  4. trim() 将字符串前后的 空白、空格、\n 全部移除。
  5. parse() 将字符串解析成数值类型,并返回一个 io::Result 对象,需要通过 expect(msg: &str) 进行异常抛出的处理。

运行结果

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 允许多次猜测

目标

  • 猜错不中断程序执行
  • 直至猜对后再中断程序执行

代码

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;
            },
        }
    }
}

解析

  1. loop {} 在 Rust 中,使用 loop 表示循环。while 在 Rust 中更侧重于 while condition 模式(while a < b) 而不是 while true。
    • 这体现了 Rust 的设计哲学之一:高度一致性。正如 match 中最后一个 arm 可选择性地添加逗号。
  2. break 中断当前循环继续执行后面的代码。
  3. match guess.trim().parse() 使用 match 来解决异常情况,优化体验效果。

运行结果

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!
3

评论区