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/

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

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

  • 相关阅读:
    Android 主题theme说明 摘记
    Android开发 去掉标题栏方法 摘记
    安卓项目五子棋代码详解(二)
    关于 ake sure class name exists, is public, and has an empty constructor that is public
    百度地图3.0实现图文并茂的覆盖物
    android onSaveInstanceState()及其配对方法。
    关于集成科大讯飞语音识别的 一个问题总结
    android 关于 webview 控制其它view的显示 以及更改view数据失败的问题总结
    C# 解析 json Newtonsoft果然强大,代码写的真好
    c#数据类型 与sql的对应关系 以及 取值范围
  • 原文地址:https://www.cnblogs.com/chnmig/p/14577991.html
Copyright © 2011-2022 走看看