zoukankan      html  css  js  c++  java
  • <<Rust程序设计语言>>个人版(1: 入门/2: 编写猜谜游戏)

    安装

    brew

    brew install rust

    其他

    官网

    校验是否安装成功

    安装后在命令行输入 rustc --version 如果看到rust的版本号即可, 例如 rustc 1.42.0 (b8cedc004 2020-03-09)

    打开内置的文档

    命令行输入 rustup doc, 会调用默认浏览器打开本地的内置文档

    VsCode配置插件

    插件商城输入 rust, 排第一的就是, 安装后打开rust项目会提示下载依赖, 同意即可

    Hello,world

    编写一个输出 hello, world 的程序

    编写与执行

    在某个地方新建文件夹 hello_world , 在文件夹下新建文件 main.rs , 编写代码如下

    fn main() {
        println!("hello, world");
    }
    

    保存后在当前目录下执行命令 rustc main.rs 会生成可执行文件 main, 执行这个文件, 打印 hello, world, 例如

    ➜  hello_world rustc main.rs 
    ➜  hello_world ./main 
    hello, world
    ➜  hello_world 
    

    解析

    • rust的代码写在 .rs 后缀的文件中
    • rust推荐使用下划线方法来命名, 比如 hello_world 而不是 helloWorld 或者 helloworld
    • rust的缩进是4个空格而不是一个tab

    我们来解析代码

    fn main() {
    }
    

    fn代表定义一个函数, 这里定义了一个 main 函数, main函数是特殊的函数, 每个可执行的rust程序都从main函数开始, main() 代表这个函数不需要任何参数, 如果有的话应该定义在 () 中.

    () 后直接跟的 {} 代表函数本体, rust要求函数内部的代码都被这个大括号包裹, 而 main(){} 也代表了这个函数没有任何的返回值, 一般来说 { 与函数名在同一行, 并使用空格分开

    rust的新版本自带了代码的格式化工具 rust fmt, 使用他可以将你的代码格式化的更加规范

    在当前命令行中使用 rustfmt main.rs

    查看格式化后的代码, 如果你的代码原来是

    fn main() {println!("hello, world");}
    

    格式化之后变成了

    fn main() {
        println!("hello, world");
    }
    

    而其中的

        println!("hello, world");
    

    则是函数的逻辑, 上面说到了缩进是四个空格

    println! 是rust内置的宏(macro), 类似于内置的语法, 而引入内置的函数则是 println, 这个之后再说明

    "hello, world"是传入这个宏的参数, 是一个字符串, 然后该字符串就会被输出到命令行中

    rust中使用 ; 作为一行的结束, 代表这个表达式结束, 下一个表达式开始

    编译

    rust是一门静态语言, 他也是需要编译的, 还记得我们写完后在命令行输入 rustc main.rs 吗, 这就是在编译代码, 编译完成后会生成编译平台可执行的文件

    hello, cargo

    认识cargo

    cargo 是rust的构建系统和依赖管理工具, 类似于golang的 go mod

    在实际的编写项目中, 常常需要引入第三方的函数, 此时我们就需要 cargo 来帮助我们管理

    cargo在安装 rust 时就已经安装到了你的电脑中

    你可以在命令行中输入

    cargo --version
    

    来查看cargo的版本, 例如 cargo 1.42.0 (86334295e 2020-01-31)

    使用cargo建立项目

    正常情况下我们一般使用 cargo 来建立新项目, 而不是手动建立文件夹

    ➜  student ls
    ➜  student cargo new hello_cargo
         Created binary (application) `hello_cargo` package
    ➜  student ls
    hello_cargo
    

    可以看到, 在执行 cargo new hello_cargo 之后, 原本空的文件夹下出现了 hello_cargo 文件夹, 这与我们执行 new 时的输入是相同的

    我们的项目就在这里

    里面已经多了几个文件, 目录树如下

    .
    ├── .git
    │   ├── HEAD
    │   ├── config
    │   ├── description
    │   ├── hooks
    │   │   └── README.sample
    │   ├── info
    │   │   └── exclude
    │   ├── objects
    │   │   ├── info
    │   │   └── pack
    │   └── refs
    │       ├── heads
    │       └── tags
    ├── .gitignore
    ├── Cargo.toml
    └── src
        └── main.rs
    

    可以看到, cargo会在 hello-cargo 下初始化 git, 并且为我们生成了 .gitignore 忽略文件, 其中默认忽略了 /target 目录, 我们之后再介绍这个文件夹

    同时生成了 cargo.toml 文件, toml格式是一种配置文件的格式, 他的内容大致如下

    [package]
    name = "hello_cargo"
    version = "0.1.0"
    authors = ["example <example@example.com>"]
    edition = "2018"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [dependencies]
    

    其中, package下为项目的信息, name为项目名, version为版本, authors为创建人(读取git的信息), edition这个项目创建时的大版本号, 目前是2018

    里面的Value内容是可以修改的, 如果信息不准确你可以手动进行修改

    我们真正的代码在 src/main.rs 中, 里面已经默认添加了 main 函数

    fn main() {
        println!("Hello, world!");
    }
    
    • rust希望你将所有的源代码放置在这个src目录下, 在根目录只放置 README.md/ 配置文件 等不涉及到代码的东西
    • 如果当前目录已经在 git 的控制下, 那么使用 cargo new 默认不会创建git相关的文件, 但是你可以使用参数 --vcs=git 来强制创建, 更多用法可查看 cargo new --help

    执行cargo

    cargo build

    在命令行执行

    ➜  hello_cargo git:(master) ✗ cargo build
       Compiling hello_cargo v0.1.0 (/Users/Work/Code/Rust/student/hello_cargo)
        Finished dev [unoptimized + debuginfo] target(s) in 0.76s
    

    我们会发现在目录下生成文件夹 /target 在里面的 /debug 中则有可执行文件 hello_cargo

    我们执行他

    ➜  hello_cargo git:(master) ✗ ./target/debug/hello_cargo
    Hello, world!
    

    所以, 我们 build 后会把生成的文件放置在 /target/debug 下, 当然 target 下还有很多文件, 我们之后再说

    当某个项目第一次build, 还会在根目录生成 Cargo.lock 文件, 里面会存放一些具体的依赖, 一般不需要我们修改

    cargo run

    我们也可以使用 cargo run 来直接运行 build 出的程序, 与上面的方式没有区别

    ➜  hello_cargo git:(master) ✗ cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.00s
         Running `target/debug/hello_cargo`
    Hello, world!
    

    更强大的是, 如果你修改了源代码, 此时你无需执行 build, 直接执行 run 可自行 build然后执行

    比如我们将 main.rs 修改为

    fn main() {
        println!("Hello, world!");
        println!("Hello, world!");
    }
    

    保存后直接执行 cargo run

    ➜  hello_cargo git:(master) ✗ cargo run
       Compiling hello_cargo v0.1.0 (/Users/Work/Code/Rust/student/hello_cargo)
        Finished dev [unoptimized + debuginfo] target(s) in 0.27s
         Running `target/debug/hello_cargo`
    Hello, world!
    Hello, world!
    

    如果你事先没有 build, 也就是没有 target 文件夹, 执行 cargo run也会自己build

    cargo check

    如果你只想确认你编写的代码是否可以运行而不想build出文件, 可以执行 cargo check, 可以在不生成可执行文件的前提下检查你的代码是否可以编译

    ➜  hello_cargo git:(master) ✗ cargo check
        Checking hello_cargo v0.1.0 (/Users/chenming/Work/Code/Rust/student/hello_cargo)
        Finished dev [unoptimized + debuginfo] target(s) in 0.13s
    

    cargo release

    当你的项目需要发布或者迁出稳定版的时候, 使用 cargo build --release , 与普通的build不同的是, 该操作会优化生成的可执行文件, 提高执行速度等等, 当然, 因为优化原因, release 的耗时会比普通的 build 慢一些, 编译完成后会放置在 /target/release 文件夹下, 建议每次发布时都采用此方式编译

    代码格式化

    之前提到对某一个 rs 文件格式化代码使用 rustfmt xx.rs

    如果对整个项目都格式化, 使用 cargo fmt 即可

    如果你只想查看哪些是需要格式化的而不是直接修改好文件, 使用 cargo fmt -- --check 即可

    最好对每个项目都是用targo来管理, 这样对项目来讲是最好的

    设计猜谜游戏程序

    这个程序的流程是, 用户输入一个数字, 然后程序自己随机出一个数字, 将这两个数字进行对比

    建立新项目

    使用 cargo 建立新项目并进入目录

    ➜  student cargo new guessing_game
         Created binary (application) `guessing_game` package
    ➜  student ls
    guessing_game
    ➜  student cd guessing_game 
    

    此时在 /src/main.rs 中已经有一个 main 函数了, 我们修改 main 函数

    处理用户输入的数字

    我们将代码修改成下面的

    use std::io;  // 引入标准库
    
    fn main() {
        println!("输入你的猜测数字: ");  // 打印
        let mut guess = String::new();  // 创建一个字符串变量guess
        io::stdin()  // 调用stdin
            .read_line(&mut guess)  // 调用stdin的方法read_line获取输入值
            .expect("读取失败");  // 如果获取错误打印警告
        println!("你猜测的数字是: {}", guess)  // 输出获取到的输入
    }
    

    值得注意的是, rust的注释是 //, 他不会编译//后的内容直到本行的结尾

    这段代码的目的是读取用户输入的数字并输出

    在第一行, 我们使用 use 语法来引入标准库 std::io, 类似于Python/Go的import

    我们首先在输入框中输出提示 输入你的猜测数字:

    let

    在第5行, 我们使用 let 来创建一个 变量, 但是与其他语言不同的是, 如果你只是使用 let, 那么你创建的变量的值是不可变的, 例如以下代码

        let a = "1212";
        a = "1111" ;
    

    我们使用 cargo check 来检查会爆出错误

    error[E0384]: cannot assign twice to immutable variable `a`
     --> src/main.rs:5:5
      |
    4 |     let a = "1212";
      |         -
      |         |
      |         first assignment to `a`
      |         help: make this binding mutable: `mut a`
    5 |     a = "1111" ;
      |     ^^^^^^^^^^ cannot assign twice to immutable variable
    
    error: aborting due to previous error
    

    告诉我们不能对不可变的变量进行二次赋值

    所以, 如果这个变量在以后需要重新赋值, 我们需要使用 let mut

        let mut a = "1212";
        a = "1111" ;
    

    对于新建变量来说, 还是要根据用途来进行设计

        let mut a = "1212";  // 可变变量
        let b = "dddd"  // 不可变变量
    

    回到代码中, 我们看到, 我们新建了一个可变变量 guess, 他的值是 String::new() , 代表着使用了 Stringnew 方法, 生成了一个 String 实例, String 代表着 rust 的字符串类型, 那么, String::new()代表生成了一个字符串类型的初始值, 也就是空字符串

    ::new 代表调用 new 方法, new对于String 来说是关联函数(associated function) , 通常我们认为newString静态方法, 一些面向对象的语言通常都有这个概念, 指的是类自有的方法, 而不需要对象去调用

    在 rust 中, 新建一个新的类型的初始值通常使用 ::new

        io::stdin()  // 调用stdin
            .read_line(&mut guess)  // 调用stdin的方法read_line获取输入值
            .expect("读取失败");  // 如果获取错误打印警告
    

    这三行代码其实是一条语句, 我们之前提到, ; 代表一段代码的结束, 所以rust会将这三行合并成一条语句来解析, 所以我们也可以将这三行缩短为, 这样会减少代码的可读性

        io::stdin().read_line(&mut guess).expect("读取失败");
    

    这段代码的意思是调用 io 模块的静态方法 stdin, 此方法回传一个实例 stdin, 匹配标准输入控制

    然后继续调用实例的 read_line 方法, 此方法对标准输入代码执行 read_line 命令, 来获取使用者的输入信息, 我们还将生成的可变变量 guess 传递进去, 代表将用户输入的结果传递给 guess

    & 代表这个参数是引用(reference) , 这让代码中的多个部分共享一个值, 不需要每次都复制值到一个新的地址, 引用是个复杂的概念, rust 可以让你安全又轻松的使用引用, 这个之后再讲, 你现在需要知道的是, 引用默认也是不可变的, 因此在后面加上 mut 代表这个是可变引用, 因此你必须写 &mut guess 而不是 &guess

    使用Result类型处理可能出现的错误

    .expect("读取失败"); 这个代码处理了 read_line 方法可能出现的错误, 因为 read_line 返回了 io::Result 对象, 在 rust 中有很多库都有 Result类, Result实际上是种枚举(enumerations/enums), 枚举是拥有固定的值的类型, 这些值被称为枚举的变体(variants), 对于Result 来说, 变体有 okError, ok代表成功, error 代表失败, 返回 Result 的目的是让使用者在错误发生时去处理这个问题, io::Result 自带了方法 expect, 当Result返回 时便会触发 expect, 如果是 ok 则代表没有出现问题, expect 不会对数据进行任何操作

    当我们不对 expect 进行捕捉, 我们在编译时就会出现如下警告

    use std::io; // 引入标准库
    
    fn main() {
        println!("输入你的猜测数字: "); // 打印
        let mut guess = String::new(); // 创建一个字符串变量guess
        io::stdin() // 调用函数stdin
            .read_line(&mut guess); // 调用stdin的方法read_line获取输入值
            // .expect("读取失败"); // 如果获取错误打印警告
        println!("你猜测的数字是: {}", guess) // 输出获取到的输入
    }
    
    
    ➜  guessing_game git:(master) ✗ cargo build
      Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
    warning: unused `std::result::Result` that must be used
    --> src/main.rs:6:5
     |
    6 | /     io::stdin() // 调用函数stdin
    7 | |         .read_line(&mut guess); // 调用stdin的方法read_line获取输入值
     | |_______________________________^
     |
     = note: `#[warn(unused_must_use)]` on by default
     = note: this `Result` may be an `Err` variant, which should be handled
    
       Finished dev [unoptimized + debuginfo] target(s) in 0.62s
    

    提示你没有处理可能出现的错误, 这里我们先添加 expect 保证其编译时不警告, 至于怎么去使用 expect 我们之后再说

    println!打印输入的数字

    println!("你猜测的数字是: {}", guess) // 输出获取到的输入
    

    这里是将用户输入的数字打印出来, {}代表占位符, 将 guess 的值填充到 {} 中, {} 可以多个, 例如

    let x = 5;
    let y = 10;
    
    println!("x = {} 而且 y = {}", x, y);
    

    输出

    x = 5 而且 y = 10
    

    运行第一个部分

    显然的, 程序还没有编写完成, 目前只能获取到用户的输入并打印出来

    ➜  guessing_game git:(master) ✗ cargo run
       Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
        Finished dev [unoptimized + debuginfo] target(s) in 0.60s
         Running `target/debug/guessing_game`
    输入你的猜测数字: 
    3
    你猜测的数字是: 3
    

    生成随机的秘密数字

    既然是猜谜游戏, 那么系统需要生成一个随机的数字来进行比对决定对错

    使用Crate来获取更多功能

    crate.io 是rust官方的仓库, 里面是rust各种各样的包(类似Python的pypi), 我们需要使用其中的一个包 rand 来帮助我们生成随机数字

    如何将rand添加进我们的项目呢? 我们找到项目根目录下的Cargo.toml 文件, 并编辑

    [package]
    name = "guessing_game"
    version = "0.1.0"
    authors = ["xx <xx@Outlook.com>"]
    edition = "2018"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [dependencies]
    rand = "0.5"
    

    我们在 [dependencies] 下新增包 rand 设置其版本为 0.5 ,这代表限制rand的版本在0.5`中, 避免出现兼容问题

    然后我们使用 cargo build 能看到cargo自动下载了我们指定的包, 如果我们指定的包需要依赖其他的包, cargo也会自动帮我们下载(比如下面的 rand_core)

    ➜  guessing_game git:(master) ✗ cargo build
        Updating crates.io index
      Downloaded rand v0.5.6
      Downloaded rand_core v0.3.1
      ......
        Finished dev [unoptimized + debuginfo] target(s) in 11.13s
    

    crate.io受网络影响, 在国内可能会出现通信问题, 可以使用第三方源比如USTC

    如果你出现Blocking waiting for file lock on package cache错误, 尝试删除 ~/.cargo/.package-cache 文件

    通过Cargo.lock确保项目依赖的正确性

    当我们通过 cargo 安装新的依赖后, 我们会发现 Cargo.lock 文件也发生了变化, 增加了我们添加的依赖的具体的信息, 比如

    [[package]]
    name = "rand"
    version = "0.5.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
    dependencies = [
     "cloudabi",
     "fuchsia-cprng",
     "libc",
     "rand_core 0.3.1",
     "winapi",
    ]
    

    cargo通过该文件来保证依赖的完整性和准确性, 当我们联合开发时, 只要将 Cargo.lock Cargo.toml 上传至项目中, 通过 cargo build 即可下载同样的依赖

    升级Crate依赖

    如果依赖有小版本的升级, 这通常是修复BUG和提高性能, 使用 cargo update 可以更新依赖, cargo会根据 cargo.toml 的语义化版本升级兼容该版本的小版本更新, 然后更新 cargo.tomlcargo.lock 中的内容

    例如 你指定了rand 版本为 rand=0.8 则如果当前的版本是 0.8.3 新版本是 0.8.5会自动更新

    如果新版本为 0.9.1 则与你指定的版本不同, 则不更新

    如果你想更新到 0.9 版本, 需手动的编辑 toml 文件然后执行 update, 但是因为大版本的更新可能伴随着功能的修改, 必须要在手动升级前确认新版本的兼容性

    生成随机数字

    修改代码为

    use std::io; // 引入标准库
    use rand::Rng;
    
    fn main() {
        println!("输入你的猜测数字: "); // 打印
        let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
        println!("生成的数字是: {}", secret_number);  // 打印生成的数字
        let mut guess = String::new(); // 创建一个字符串变量guess
        io::stdin() // 调用函数stdin
            .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
            .expect("读取失败"); // 如果获取错误打印警告
        println!("你猜测的数字是: {}", guess) // 输出获取到的输入
    }
    

    注意到我们新增了两行代码

        use rand::Rng;
        ....
    		let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
        println!("生成的数字是: {}", secret_number);  // 打印生成的数字
    

    在最开始, 我们引入了新的模块, rand::Rng, 需要注意的是, 我们在下面使用 rand 的时候并没有使用 Rng, 实际上这个 Rng 被称作 特征(trait)

    它定义了数字生成器的工作方法, 必须将其引入才可以正常生成数字, 关于特征的更多情况, 我们之后再做讲解

    重要的是生成随机数字这一段, 我们使用 rand包的thread_rng方法, 这会返回一个随机数字的生成器, 以当前的工作进程做为种子, 然后我们调用这个生成器的 gen_range 方法, 这个方法是由我们引入的 Rng 定义的, 他接受两个参数, 最大值和最小值, 我们传入 1, 101 代表生成 1-100的数字, 他是顾头不顾尾的, 所以我们要填到101

    我们将其打印出来运行, 目的是测试功能是否正常, 事实上程序并未完成

    ➜  guessing_game git:(master) ✗ cargo run       
       Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
        Finished dev [unoptimized + debuginfo] target(s) in 0.51s
         Running `target/debug/guessing_game`
    输入你的猜测数字: 
    生成的数字是: 6
    1
    你猜测的数字是: 1
    

    将猜测的数字与生成的数字进行比较

    在上面, 我们生成了数字并获取到了用户输入的数字, 这一步我们将这两个数字进行比较

    use std::io; // 引入标准库
    use rand::Rng;
    use std::cmp::Ordering;
    
    fn main() {
        println!("输入你的猜测数字: "); // 打印
        let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
        println!("生成的数字是: {}", secret_number);  // 打印生成的数字
        let mut guess = String::new(); // 创建一个字符串变量guess
        io::stdin() // 调用函数stdin
            .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
            .expect("读取失败"); // 如果获取错误打印警告
        println!("你猜测的数字是: {}", guess); // 输出获取到的输入
        match guess.cmp(&secret_number){
            Ordering::Less => println!("太小了"),
            Ordering::Greater => println!("太大了"),
            Ordering::Equal => println!("正确"),
        }
    }
    

    我们先引入了 std::cmp::Ordering , Ordering是一个枚举, 其变体为 Less, Greater, Equal. 当你对两个数字进行比较时会有这三种结果

    cmp方法会对数字进行比较, 我们传入引用 secret_number , 目的是将 guess 与 secret_number 进行比较, 他会回传我们引入的 Ordering 变体

    match

    有时候, 我们需要对某个枚举的变体进行分别处理, 比如这里, 我们根据数字比较结果来提示用户数字的范围

    所以我们使用 match 语法

    match 由分支(arms)组成, 每个分支包含一个模式(pattern)和他的处理逻辑, 当匹配到这个模式, 就会执行对应的逻辑. 在执行完毕后则退出 math 不再进行下面的匹配, match的具体细节会在之后讲解

    但是我们运行时会发现他会报错

    ➜  guessing_game git:(master) ✗ cargo run
       Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
    error[E0308]: mismatched types
      --> src/main.rs:15:21
       |
    15 |     match guess.cmp(&secret_number){
       |                     ^^^^^^^^^^^^^^ expected struct `std::string::String`, found integer
       |
       = note: expected reference `&std::string::String`
                  found reference `&{integer}`
    
    error: aborting due to previous error
    
    For more information about this error, try `rustc --explain E0308`.
    error: could not compile `guessing_game`.
    
    To learn more, run the command again with --verbose.
    

    这是因为, guess的类型是字符串, 而secret_number是数值,这就会导致 型別無法配對(mismatched types), rust是静态类型的语言, 这两个是无法进行直接比较的

    关于数值, rust有多个数值类型, 分别为 i3232位元数字, u32无符号32位元数字, i6464位元数字, u6464位无符号元数字等等, 默认的类型为 i32, 因此 secret_number 为 i32

    同时我们也面临一个问题, 就是用户输入的不是数字怎么办

    此时我们需要将guess转换为数字, 让他们可以比较, 将代码修改为

    use std::io; // 引入标准库
    use rand::Rng;
    use std::cmp::Ordering;
    
    fn main() {
        println!("输入你的猜测数字: "); // 打印
        let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
        println!("生成的数字是: {}", secret_number);  // 打印生成的数字
        let mut guess = String::new(); // 创建一个字符串变量guess
        io::stdin() // 调用函数stdin
            .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
            .expect("读取失败"); // 如果获取错误打印警告
        let guess: u32 = guess.trim().parse().expect("请输入正确的数字");
        println!("你猜测的数字是: {}", guess); // 输出获取到的输入
        match guess.cmp(&secret_number){
            Ordering::Less => println!("太小了"),
            Ordering::Greater => println!("太大了"),
            Ordering::Equal => println!("正确"),
        }
    }
    

    我们添加了一行

        let guess: u32 = guess.trim().parse().expect("请输入正确的数字");
    

    注意到, 之前有定义过 guess 为 string 类型, 这里是将他重新定义为一个u32类型的数值, 这在rust中被称为遮蔽(shadow), 我们遮蔽了原来的guess, 遮蔽让我们可以重复的使用一个变量名, guess.trim()可以去除这个字符串前后的空白和换行, 而.parse()则可以将字符串转换为数值类型, 同时我们捕捉expect, 如果转换失败, 则告诉用户出现错误了, 我们来执行代码

    ➜  guessing_game git:(master) ✗ cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.00s
         Running `target/debug/guessing_game`
    输入你的猜测数字: 
    生成的数字是: 54
      1
    你猜测的数字是: 1
    太小了
    
    ➜  guessing_game git:(master) ✗ cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.00s
         Running `target/debug/guessing_game`
    输入你的猜测数字: 
    生成的数字是: 9
    ffff111
    thread 'main' panicked at '请输入正确的数字: ParseIntError { kind: InvalidDigit }', src/main.rs:13:22
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    

    使用户循环输入

    目前的程序, 用户输入一次失败后就结束了程序, 为了提升体验, 我们需要让用户可以循环输入

    loop

    loop语法可以生成一个死循环, 我们将代码修改为

    use std::io; // 引入标准库
    use rand::Rng;
    use std::cmp::Ordering;
    
    fn main() {
        let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
        println!("生成的数字是: {}", secret_number);  // 打印生成的数字
        loop{
            println!("输入你的猜测数字: "); // 打印
            let mut guess = String::new(); // 创建一个字符串变量guess
            io::stdin() // 调用函数stdin
                .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
                .expect("读取失败"); // 如果获取错误打印警告
            let guess: u32 = guess.trim().parse().expect("请输入正确的数字");
            println!("你猜测的数字是: {}", guess); // 输出获取到的输入
            match guess.cmp(&secret_number){
                Ordering::Less => println!("太小了"),
                Ordering::Greater => println!("太大了"),
                Ordering::Equal => println!("正确"),
            }
        }
    }
    

    loop是一个死循环, 因此我们不停的输入数字去比较, 退出的方法只有强制关闭程序比如 ctrl+c, 或者输入一个无法解析为数值的字符串让他报错

    ➜  guessing_game git:(master) ✗ cargo run
       Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
        Finished dev [unoptimized + debuginfo] target(s) in 0.40s
         Running `target/debug/guessing_game`
    生成的数字是: 79
    输入你的猜测数字: 
    11
    你猜测的数字是: 11
    太小了
    输入你的猜测数字: 
    ff
    thread 'main' panicked at '请输入正确的数字: ParseIntError { kind: InvalidDigit }', src/main.rs:14:26
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    

    break

    我们需要在用户猜中之后让他结束这个游戏, 使用 break 语法可以退出循环

    use std::io; // 引入标准库
    use rand::Rng;
    use std::cmp::Ordering;
    
    fn main() {
        let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
        println!("生成的数字是: {}", secret_number);  // 打印生成的数字
        loop{
            println!("输入你的猜测数字: "); // 打印
            let mut guess = String::new(); // 创建一个字符串变量guess
            io::stdin() // 调用函数stdin
                .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
                .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;  
                },
            }
        }
    }
    

    当猜中时, 使用break退出循环, 因为下面没有代码了,就直接退出

    ➜  guessing_game git:(master) ✗ cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.00s
         Running `target/debug/guessing_game`
    生成的数字是: 25
    输入你的猜测数字: 
    11
    你猜测的数字是: 11
    太小了
    输入你的猜测数字: 
    25
    你猜测的数字是: 25
    你猜中了, 游戏结束
    

    对错误进行处理

    现在, 用户输入一个无法转成数值的字符串会导致程序结束, 我们现在需要处理这个错误, 让他在错误时重新输入, 不让代码结束, 我们应该对每个错误都捕捉处理, 避免程序关闭, 这对每个程序都是一样的

    我们将expect的处理变成一个match, 当分支检测到Error时进行特殊的处理

    use std::io; // 引入标准库
    use rand::Rng;
    use std::cmp::Ordering;
    
    fn main() {
        let secret_number = rand::thread_rng().gen_range(1, 101);  // 随机生成一个1-100的数字
        println!("生成的数字是: {}", secret_number);  // 打印生成的数字
        loop{
            println!("输入你的猜测数字: "); // 打印
            let mut guess = String::new(); // 创建一个字符串变量guess
            io::stdin() // 调用函数stdin
                .read_line(&mut guess) // 调用stdin的方法read_line获取输入值
                .expect("读取失败"); // 如果获取错误打印警告
            let guess: u32 = match guess.trim().parse() {
                Ok(num) => num,
                Err(_) => {
                    println!("输入了一个无法解析的字符串");
                    continue;
                },
            };
            println!("你猜测的数字是: {}", guess); // 输出获取到的输入
            match guess.cmp(&secret_number){
                Ordering::Less => println!("太小了"),
                Ordering::Greater => println!("太大了"),
                Ordering::Equal => {
                  println!("你猜中了, 游戏结束");
                  break;  
                },
            }
        }
    }
    

    我们在上面说过, parse返回一个枚举, 那我们就使用match判断这个枚举的分支, 他会有两种情况, OKErr, 如果是OK则代表正确, 我们将回传的num原样返回给guess, 如果Err 代表失败, 在失败时我们就不关心num是什么了, 所以我们使用_ 接受, 然后打印告诉用户出现了错误, 使用break关键字跳出当前的循环

    ➜  guessing_game git:(master) ✗ cargo run
       Compiling guessing_game v0.1.0 (/Users/Work/Code/Rust/student/guessing_game)
        Finished dev [unoptimized + debuginfo] target(s) in 0.44s
         Running `target/debug/guessing_game`
    生成的数字是: 82
    输入你的猜测数字: 
    11
    你猜测的数字是: 11
    太小了
    输入你的猜测数字: 
    ff
    输入了一个无法解析的字符串
    输入你的猜测数字: 
    82
    你猜测的数字是: 82
    你猜中了, 游戏结束
    

    如果是真实的程序, 应该将程序生成的数值打印去除, 这里保留只是为了方便调试

        作者:ChnMig

        出处:http://www.cnblogs.com/chnmig/

        本文版权归作者和博客园所有,欢迎转载。转载请在留言板处留言给我,且在文章标明原文链接,谢谢!

        如果您觉得本篇博文对您有所收获,觉得我还算用心,请点击左下角的 [推荐],谢谢!

  • 相关阅读:
    .NET实现之(WebService数据提供程序)
    站内搜索(主要技术点:Luncene.Net搜索引擎核心,Log4Net:日志,定时框架:quartz.Net,Jquery,Json,AJAX)
    非常棒的 ASP.NET Web Forms 和 ASP.NET MVC 免费培训视频!
    NET实现之(简易ORM)
    Razor4Orchard v1.2
    MVC和三层 【转】热闹
    .NET简谈平台大局观
    Trigger4Orchard
    步步为营 .NET 代码重构学习笔记 一、为何要代码重构
    步步为营 .NET 代码重构学习笔记 三、内联方法(Inline Method)
  • 原文地址:https://www.cnblogs.com/chnmig/p/14577991.html
Copyright © 2011-2022 走看看