003、一起来玩猜数游戏吧!

news2024/11/16 23:47:30

1. 上篇补充

        在项目 hello_world 中,有一些文件。这里提一下每个文件的用途,了解一下即可,暂时不用深究,后面用到会详细讨论。

1. src :这个文件夹里主要用于存放源代码文件。Rust 项目的源代码文件通常以 .rs 为后缀,这些文件被组织在 src 文件夹中,以便于管理和维护。我们的 main.rs 文件就是在这里。

2. target :在 Rust 项目中,target 文件夹是用于存放编译时产生的中间结果和可执行文件的。这个文件夹由 Cargo 自己管理,一般情况下我们不需要过多关注。

3. .gitignore :这个文件用于配置 Git 版本控制系统忽略特定的文件和文件夹。它是一个纯文本文件,其中每一行都是一个模式,用于匹配要忽略的文件或文件夹。通过 .gitignore 文件,可以管理哪些文件不上传到版本管理服务中去。这个暂时不会用到,不做过多讨论。

4. Cargo.lock :该文件是 Cargo 工具根据同一项目的 TOML 文件生成的项目依赖的详细清单。这个文件记录了项目所有依赖的实际版本,包括库的版本、操作系统、架构等。因此,它通常不用修改。使用 Cargo 可以很方便地构建代码、下载依赖库、测试代码等,所以大多数情况下,推荐使用 Cargo 来构建项目。

5. Cargo.toml :它是 Rust 项目的配置文件,用于描述项目的元信息、依赖关系、编译选项等,是 Cargo 工具特有的项目数据描述文件,由开发者手动编写。Cargo.toml 文件通常包含2个方面的内容,一个是 package 部分:表示该项目的一些信息,其中 edition 字段指定编译的版本,缺省情况下默认是 2015。第二个是 dependencies 部分:表示需要依赖的一些外部的包。

        Cargo.toml 文件是项目构建和编译的基础,如果 Rust 开发者希望项目能够按照期望的方式进行构建、测试和运行,则必须按照合理的方式构建 Cargo.toml 文件。

2. 让玩家输入一个数并打印出来

游戏目标:

        生成一个1到100之间的随机数让玩家去猜,玩家猜的数比随机数大我们就提示他猜大了,比随机数小就提示他猜小了,他可以无限次数猜测下去直到猜对,然后我们就给一个他猜对了的提示并退出程序。

        这个猜数游戏非常简单,但涉及的知识点很多,我们一步步来实现吧!

        还记得怎么创建1个新项目吗?

        在放置练习文件的目录下,打开黑窗口,输入 cargo new guessing_game 。我们给这个猜数游戏的项目起的名称就叫 guessing_namecargo new 就是创建新项目的命令,后面跟的就是你给项目起的名称。

        按回车键后,我们发现练习文件目录下又多了个名为 guessing_game 的文件夹,这个项目就创建好啦!

        接着我们点进这个文件夹,再次打开黑窗口,输入 code . 直接在编译器中打开这个项目,然后在左边菜单栏中点开 src ,找到 main.rs 单击打开它,然后就可以写代码啦!

        写什么呢?这一小节的目标是,获取玩家输入一个数字并打印出来。

use std::io;
fn main() {
    println!("欢迎进入猜数游戏!");
    println!("请输入你要猜测的数字:");

    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("读取失败!");

    println!("你猜测的数字为:{guess}");
}

        代码已经写好啦!但是好像看不太懂?别急,下面我会把每一行代码讲解给你。

1. use std::io;

        use 这个关键字就是导入第三方库的意思,类似 Python 中的 import 、C++ 中的 #include 。标准库(std)是所有标准库功能的默认入口点。io 模块是标准库中的一个模块,它提供了用于输入、输出操作的功能,这里我们要获取玩家猜测的数字,所以要用到这个模块。

2. fn main( ) { }

        这个是一个特殊的函数,被称为程序的入口点。当你运行一个 Rust 程序时,main 函数是第一个被调用的函数。它不必有任何参数,并且总是返回一个 i32 类型的值,该值被解释为程序的退出码。什么是 i32 类型?这个后面会讲到。

3. println!("欢迎进入猜数游戏!");
    println!("请输入你要猜测的数字:");

        这2行就是分别打印2条信息,提示玩家已经进入游戏了,可以开始玩了。println! 是一个宏,它会在打印输出的时候自动在末尾加上一个换行符。输出效果如下图所示:

         所以我们也可以把代码写成下面这样,显示的效果是一样的,\n 换行符在 Rust 中同样适用。

println!("欢迎进入猜数游戏!\n请输入你要猜测的数字:");

4. let mut guess = String::new();

        我们先看等号右边部分 String::new(),这是调用 String 结构的静态方法 new(),它会创建一个新的、空的 String 实例。建好了给谁?就是等号左边名为 guess 的变量。

        let 关键字用于声明一个不可变变量,而 mut 关键字用于声明一个可变变量。但在这里,由于我们想要一个可变的 String,所以我们又在中间加了个 mut ,因为玩家输入的内容并不是固定不变的。

        所以,这行代码的整体意思就是,声明一个可变的空字符串变量,用于后面接收玩家的输入。

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

        这行代码就是读取玩家的输入并传递给变量 guess

        io::stdin():表示调用 io 模块中的 stdin 函数,它返回一个 Stdin 对象,该对象代表标准输入流。

        read_line():这是 Stdin 对象的一个方法,用于从标准输入读取一行文本。这个方法会读取直到遇到换行符(\n),并将该行内容作为一个字符串返回。

        &mut guess:这是一个可变引用,指向我们之前声明的 guess 变量。我们使用 &mut 是因为我们需要修改 guess 的内容。

        expect("读取失败!"):这是对 read_line 方法的结果进行错误处理的简短方式。如果 read_line 成功,那么它的返回值是 Ok(value),其中 value 是读取到的字符串。如果发生错误,它会返回 Err(e)e 是错误类型。使用 expect 方法会将任何错误转换为 panic(注:中文是恐慌的意思),并显示提供的消息:“读取失败!”。

        这个读取失败不是固定写法,你可以在两个双引号之间随便写你想让它在读取失败后显示什么内容。

6. println!("你猜测的数字为:{guess}");

        这行代码也是打印的意思,但是不同于上面的打印,这里多了个  {guess} ,是使用了字符串格式化,通过 { } 花括号 插入了变量 guess 的值。

        你可以把这对花括号理解为占位符,所以这行代码也可以写成下面这样,效果是一样的。

println!("你猜测的数字为:{}", guess);

        在黑窗口中输入 cargo run 命令运行一下,并输入数字 10:

        欧克!木有问题,非常奶思~ 

3. 生成随机数 

         这一小节,我们让程序生成一个随机数,然后打印出来。代码如下:

use std::io;
use rand::Rng;

fn main() {
    println!("欢迎进入猜数游戏!\n请输入你要猜测的数字:");

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

    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("读取失败!");

    println!("你猜测的数字为:{}", guess);
}

        第二行代码 use rand::Rng ,我使用 use 关键字导入了 rand 库中的 Rng trait,用于接下来在函数体中生成随机数。这里的 rand 是一个 Rust 标准库中的随机数生成器库,而 Rng 是这个库中的一个 trait,代表 “随机数生成器”。

        那这个 trait 是个什么东东?它的中文意思是 特征 。在 Rust中,它是一个核心概念,用于定义对象、函数或数据结构的通用行为。

        类似于其他面向对象编程语言中的接口或者抽象类。Rust 中的 trait 是一种泛型类型,意味着它定义了一种可以在多种类型上工作的抽象规范。

        听起来有点晦涩是不是?没有关系,咱们以后再细讲。这里你只需要知道,我们要得到一个随机数,要先导入 rand 库中的随机数生成器 Rng 就行了。

        然后我们再讲一下中间新增的2行代码:

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

        第一行代码的总体意思是:在当前线程中,生成一个1到100之间的随机整数,并将其存储在变量 random_number 中。

        首先,rand::thread_rng():这是 Rust 标准库中的 rand 模块提供的一个函数,用于获取当前线程的默认随机数生成器。每个线程都有自己的随机数生成器,这样可以避免多线程环境下的竞争条件。

        gen_range(1..101):这是 rand 模块中 Rng trait 的一个方法,用于生成一个指定范围内的随机数。这里,它生成一个从 1 到 101 之间的随机整数,包括1但不包括101。

        let random_number:声明一个名为 random_number 的变量,用于接收等号右边生成的随机数。

        然后第二句代码就是把这个随机数打印出来。

        这里需要提一下,我们使用这个随机数生成器不是只要在 .rs 文件中用 use 导入一下就行的,还需要打开 Cargo.toml 文件,在 dependencies 下指定你要导入的 rand 的版本号。如下图所示:

        指定 rand 的版本后,需要先用 cargo build 命令编译一下项目,然后在 .rs 文件中才能正常使用哦!

        编译后,我们点开 Cargo.lock 文件,发现 rand 包的版本号是 0.8.5,但实际我们写的是 0.8.0 。 这是为什么呢?

        Rust 的包管理工具 Cargo 在处理包的版本升级方面具有自动化和灵活的特性。Cargo crate.io 下载依赖包,并允许通过 TOML 文件中的指定版本来限制包的版本。

        默认情况下,只需要在 TOML 文件的 dependencies 部分提供包名和版本号,例如 [dependencies] time = "0.1.12" 。这里,"0.1.12" 是一个 semver 格式的的版本号,符合 "x.y.z" 的形式,其中 x 被称为主版本(major),y 被称为小版本(minor),而 z 被称为补丁版本(patch)。

        这种版本号表示法的特点是,从左到右,版本的影响范围逐步降低,补丁的更新通常无关痛痒,并不会造成 API 的兼容性被破坏。这也是上面为什么 Cargo 会自动将我们指定的版本的小版本号给篡改了,因为这并不会影响什么。

        另外,Cargo 还支持使用 ^ 符号来指定一个版本范围。例如,"^0.1.12" 表示使用 0.1.x 系列中的最新版本,只要新的版本号没有修改最左边的非零数字,即1,那么它就再允许的版本号范围中。这意味着,如果有一个新的版本 0.1.13Cargo 会自动将其引入作为依赖包,因为它仍然在允许的版本范围内。

        这种机制允许开发者依赖于特定版本的库,同时又能享受到新版本的修复和改进。当开发者需要更新依赖包时,他们可以通过更新 TOML 文件中的版本号来主动升级,或者让 Cargo 自动处理版本升级以满足依赖关系。

        综上所述,Rust Cargo 工具允许通过指定具体的版本号或使用版本范围来控制包的依赖关系,包括包的自动升级。这样做有助于确保项目的稳定性和兼容性,同时也能够利用最新的库功能和修复。

        有时候我们会忘记先在 TOML 文件中指定包的版本号,怎么办呢?有个非常简单的办法就是,当你使用 use 关键字导入包之后,在写具体代码时,发现这个包里的函数没有出现代码补全提示,这就说明你还没有给这个包指定版本。

4. 将猜测的数和随机数比较

        我们需要先将玩家猜测的数字和系统生成的随机数比较大小,才能正确提示玩家是猜大了还是猜小了,如果相等,那就提示他猜对了,然后程序退出。

        完整代码如下:

use std::io;
use rand::Rng;
use std::cmp;

fn main() {
    println!("欢迎进入猜数游戏!\n请输入你猜测的数字:");

    let random_number = rand::thread_rng().gen_range(0..101);

    loop {
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("读取失败!");

        let guess:u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };
        
        match guess.cmp(&random_number) {
            cmp::Ordering::Less => {
                println!("你猜的数小了,请再猜测一次:");
                continue;
            },
            cmp::Ordering::Greater => {
                println!("你猜的数大了,请再猜测一次:");
                continue;
            },
            cmp::Ordering::Equal => {
                println!("恭喜,你猜对啦!");
                break;
            },
        }
    }
}

        与之前代码不同的是,我把大部分代码放在了 loop 后面的花括号中。在 Rust 中,loop 关键字用于创建一个无限循环。当执行到 loop 时,程序会一直执行循环体内的代码,直到遇到 break 语句或者外部干预(如操作系统终止程序,注:Ctrl + C 可手动终止黑窗口中的死循环)。

        因此,当玩家猜测的数和随机数相等时,我们就用 break 来终止该循环,反之就用 continue 循环继续,直到玩家猜测正确为止。

        在代码顶部,我们又导入了一个库 cmp ,它是一个用于比较两个值的函数,属于 std::cmp 模块,该模块提供了各种比较函数,如:cmp::Ordcmp::TotalOrd 等。

        cmp 函数比较两个值大小关系后返回一个比较结果,我们可以根据比较结果来决定执行哪些操作。比如在我们这个猜数游戏程序中,是将玩家猜测的数字和随机数进行对比,如果猜测的数字比随机数大,那就打印提示大了,让玩家继续猜,如果比随机数小,那就打印提示小了,也让玩家继续猜,如果相等,那么就打印恭喜猜对并退出程序。

        所以,这里会产生3种比较结果,大于(cmp::Ordering::Greater =>)、小于(cmp::Ordering::Less =>)和相等(cmp::Ordering::Equal =>)。=> 组合符号后面跟的是对应的操作。

        在比较的代码之前还有如下几行代码:

let guess:u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        这几行代码是什么意思呢?在此之前,我们获取玩家猜测的数字并赋给变量 guess ,并且我们给这个变量还创建了一个实例,它是字符串类型的。 而 cmp 是将两个值进行比较,字符串和整数(1到100之间的随机数)是无法进行比较,这样程序就会报错。

        所以,我们在比较它们之前,还需要先将 guess 的值转换成整数类型才行。trim 是对字符串进行修剪,即:移除前后的空白字符。parse 是尝试将这个字符串解析为一个 32位无符号整数,即 u32

        为什么 parse 会知道我们要转换的目标类型是 u32 呢?因为这行代码的等号左边,我们已经显式声明啦!即:guess:u32 

        代码写完了,我们来运行一下吧!

        2次竟然就猜对了?!

        这不会消耗了一些我这辈子本就不多的运气吧?

        我o(╥﹏╥)o了,读者大大们给个赞安慰一下角角吧 ~

5. 问题补充

        有些读者大大可能和角角有一个同样的困惑,那就是如何判断一个库是否要在 TOML 文件中声明?貌似有的需要声明,有的又不需要?

        下面就这个疑问详细解答一下吧!

        在 Rust 中,判断一个库是否需要在 Cargo.toml 文件中声明主要基于以下几点:

5.1 外部依赖库:

        如果你正在使用一个外部的、非 Rust 标准库的库,那么你通常需要在 Cargo.toml 文件中声明它。这是因为外部库可能需要被编译和链接到你的项目中。例如,你可能使用了某个特定的数学库、网络库或 GUI 库。

5.2 内联依赖:

        对于一些内联的、非外部的库,可能不需要在 Cargo.toml 文件中声明。这些通常是项目内部的库或是 Rust 标准库的一部分。例如,Rust 标准库就包含许多常用功能,如:std::collections 等,这些通常不需要在 Cargo.toml 中额外声明。

5.3 crate 类型:

        在 Rust 中,一个 crate 可以是一个库(lib)或一个二进制(bin)。如果你正在创建一个库 crate(即:你的项目类型在 Cargo.toml 被设置为 lib),那么你可能需要声明所有的外部依赖。如果你正在创建一个二进制 crate,那么你可能只需要声明那些被直接使用的外部依赖。

5.4 构建脚本(build scripts)和 proc-macro:

        某些情况可能需要特殊的构建脚本或 proc-macros。这些也需要在 Cargo.toml 中声明。

        总的来说,判断是否需要再 Cargo.toml 中声明一个库,最关键的是看这个库是否需要被编译和链接到你的项目中。如果需要,那么你就需要在 Cargo.toml 中声明它。如果不需要,那么可能就不需要。

 

6. 结语

        由于能力有限、本人也还在学习摸索阶段,文中难免有错漏之处,若有读者大大发现,欢迎在评论区留言。

        最后,码字不易,即便只有一个赞也可以让我动力满满,感谢你的支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1339074.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

给typora更换字体

给typora更换字体 1.字体推荐(程序员) JetBrains MonoFira CodeCascadia Code 如果你不知道用哪个,把所有字体都装上。 2.安装字体 以 JetBrainsMono-1.0.3 为例,打开 ttf 文件夹,选择字体,右键 安装 3.设置typora的字体 文…

three.js 模型 居中

物体不居中 模型的几何中心位置不对, 设置偏离物体实际几何中心,当设置position(0,0,0)时就会出现偏离。 解决方案 此处有两种解决方案 建模师处理模型,将模型的几何中心移动到(0, 0&#…

【Linux】进程查看|fork函数|进程状态

🦄 个人主页——🎐开着拖拉机回家_Linux,大数据运维-CSDN博客 🎐✨🍁 🪁🍁🪁🍁🪁🍁🪁🍁 🪁🍁🪁&am…

Java日期工具类时间校验

Java日期工具类时间校验 嘚吧嘚正则表达式版本一版本二版本三 SimpleDateFormat工具类 嘚吧嘚 时间校验这个问题,我在网上找了很多资料,有用正则表达式的、有用格式工具类的。🤨 其实都能实现时间校验,既然两种方式都能实现&…

Vue3-25-路由-路由的基本使用

对路由的理解 路由 : 就是前端对页面路径的拦截,根据不同的路径渲染不同的组件, 从而实现单页应用中的页面局部刷新的功能。安装路由依赖 根据使用的不同的包管理工具采用不同的命令, 常见的三种包管理工具和对应的命令如下&…

Hex文件介绍及制作

Hex文件介绍 一、文件格式介绍数据格式Hex文件例子常见类型字段 二、CRC校验计算eg.地址文件CRC计算:020000040127D2:0200000400FFFB eg.数据文件计算 三、生成hex文件用Excel生成 一、文件格式介绍 Hex文件是一种十六进制文件格式,可由notpad打开或者HexView app打…

【Hive_05】企业调优1(资源配置、explain、join优化)

1、 计算资源配置1.1 Yarn资源配置1.2 MapReduce资源配置 2、 Explain查看执行计划(重点)2.1 Explain执行计划概述2.2 基本语法2.3 案例实操 3、分组聚合优化3.1 优化说明(1)map-side 聚合相关的参数 3.2 优化案例 4、join优化4.1…

vivado 快速到慢速时钟之间的多循环

快速到慢速时钟之间的多循环 在下面的场景中,启动时钟CLK1是快速时钟,捕获时钟CLK2是慢时钟。如下图所示。 在下一示例中,启动时钟CLK1是快速时钟。捕获时钟CLK2较慢时钟假设CLK1是CLK2的频率的三(3)倍。如下图所示。…

docker小白第七天

docker小白第七天 tomcat安装 docker hub上面查找tomcat镜像 点进tomcat,可以看到下载镜像的命令。但是因为文件太大,并且是国外下载镜像很慢,所以我们从前期配置好的阿里云镜像仓库下载。 docker search tomcat docker pull tomcatdocker…

后端主流框架-SpringMvc-day2

Java中的文件下载 2 文件下载 文件下载:就是将服务器(表现在浏览器中)中的资源下载(复制)到本地磁盘; 2.1 前台代码 前台使用超链接,超链接转到后台控制器,在控制器通过流的方式…

Intel FPGA 技术开放日

概要 时间:2023.11.14 全天 ( 9:00 - 16: 20) 地点:北京望京. 凯悦酒店 主题内容:分享交流了Intel FPGA 产品技术优势和落地实践方案。 会议的议程 开场致词: FPGA业务,是几年前intel收购而…

虚拟环境和Pycharm中均有transforms仍报ModuleNotFoundError:No module named ‘transformers‘

问题:运行新模型,配置了新环境,下载了包后,仍然报ModuleNotFoundError:No module named transformers 错误。 查看Pycharm解释器: 没问题!!!? 命令行查看虚…

Flink Job 执行流程

Flink On Yarn 模式 ​ 基于Yarn层面的架构类似 Spark on Yarn模式,都是由Client提交App到RM上面去运行,然后 RM分配第一个container去运行AM,然后由AM去负责资源的监督和管理。需要说明的是,Flink的Yarn模式更加类似Spark on Ya…

Hive安装笔记——备赛笔记——2024全国职业院校技能大赛“大数据应用开发”赛项——任务2:离线数据处理

将下发的ds_db01.sql数据库文件放置mysql中 12、编写Scala代码,使用Spark将MySQL的ds_db01库中表user_info的全量数据抽取到Hive的ods库中表user_info。字段名称、类型不变,同时添加静态分区,分区字段为etl_date,类型为String&am…

【电商项目实战】基于SpringBoot完成首页搭建

🎉🎉欢迎来到我的CSDN主页!🎉🎉 🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚 🌟推荐给大家我的专栏《电商项目实战》。🎯🎯 &am…

【PyQt学习篇 · ⑭】:QTableView的使用

文章目录 QTableView的使用示例 QTableView的使用 QTableView 是 PyQt 中用于显示表格数据的窗口部件,它提供了一个灵活的方式来显示和编辑数据。下面是一些关于 QTableView 的使用的具体信息: 创建 QTableView 对象: from PyQt5.QtWidgets …

计算机网络基础:OSI参考模型是什么?

一、概述 OSI (Open Systems Interconnection Model,开放式系统互联模型),由ISO ( International Organization for Standardization,国际标准化组织 ) 收录在ISO 7489标准中并于1984年发布。 意义: 在OSI没有出来之前我们的网络有如下问题…

Linux Debian12使用podman安装upload-labs靶场环境

一、upload-labs简介 PHP语言编写,持续收集渗透测试和CTF中针对文件上传漏洞的靶场,总共21关,每一关都包含着不同的上传绕过方式。 二、安装podman环境 Linux Debian系统如果没有安装podman容器环境,可以参考这篇文章先安装pod…

如何在vscode当中预览html文件运行结果

如何在vscode当中预览html文件运行结果 下载拓展内容打开拓展界面下载拓展 运行html文件参考内容 上一篇文章当中讲了如何实现在网页上对html文件的预览,但是这样子其实在运行代码的过程当中效果比较差,那么还需要可以实时预览运行的结果 下载拓展内容 打开拓展界面 下载拓展 …

微信小程序发放红包封面及领取

微信小程序发放红包封面及领取 一、微信红包封面开放平台配置发放的红包封面二、小程序后管平台设置配置录入红包封面奖品信息三、微信小程序调用接口效果 一、微信红包封面开放平台配置发放的红包封面 微信红包封面开放平台 红包封面的发放方式有:领取二维码、领…