rust编程初探-猜数游戏(chapter 2)

news2025/1/17 15:59:56

目录

1. 创建项目

2. 猜数的输入

3. 随机数生成

3.1 rand库依赖

3.2 随机数生成

4. 猜数和随机数的比对

4.1 std::cmp::Ordering类型

4.2 match表达式(expression)

4.3 输入类型的转换

5. 支持多次猜测(使用循环)

6. 错误输入的处理


本章节以一个精心设计的实际工程项目,先来初次尝试一下rust语言编程和工程应用的实践。该项目是一个:猜数游戏,主要工作流程如下:

1)程序生成1~100之间的一个随机整数;2)用户输入猜测的数字;3)程序输出猜测的数字大于或小于生成数;4)用户猜测正确,结束运行,用户猜测错误,可重复2、3步骤。

1. 创建项目

 # cargo new guessing_game && cd guessing_game
      Created binary (application) `guessing_game` package
 # cargo run
   Compiling guessing_game v0.1.0 (/doc/jiangxiaoqing/rust/chapter2/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/guessing_game`
Hello, world!
# ls
Cargo.lock  Cargo.toml  src  target
# tree
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
    ├── CACHEDIR.TAG
    └── debug
        ├── build
        ├── deps
        │   ├── guessing_game-2e7ea747ef88670f
        │   └── guessing_game-2e7ea747ef88670f.d
        ├── examples
        ├── guessing_game
        ├── guessing_game.d
        └── incremental
            └── guessing_game-1vpxsh8m6pzam
                ├── s-gfeqim8j6x-ein778-3lplmqmartfnp
                │   ├── 1ez1ge870eb6gq7l.o
                │   ├── 1sx3ehn655us4o1e.o
                │   ├── 1u60bgs93g3f9qgi.o
                │   ├── 2zd4w7ck950yptxq.o
                │   ├── 4ugvrlcdoppn14n.o
                │   ├── 57xo5z3lc17i2l94.o
                │   ├── dep-graph.bin
                │   ├── query-cache.bin
                │   └── work-products.bin
                └── s-gfeqim8j6x-ein778.lock

9 directories, 18 files

生成一个目标项目,可以直接先运行一下,后面步骤编辑main.rs来完成该项目。

2. 猜数的输入

首先,我们来处理用户输入的交互部分:允许用户输入一个数字

use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

以上代码使用了rust标准库(std)中的io库,默认情况下,rust在标准库中的某些项能被自动载入。而非自动载入的需要用use语句来引入。如代码,io库中的某些功能能让我们处理用户输入。

let可以定义新的变量,并绑定到后面的字符串对象。这里的mut标识该变量是允许修改的,后面第3章节会解释。String::new函数会创建并实例化一个空的字符串。

std::io::stdin()函数,返回一个std::io::Stdin的对象实例,代表一个到terminal终端标准输入的句柄。后面的.read_line()函数接收用户的一行输入,并append到字符串对象guess中。& 符号指示参数使用引用,而不需要拷贝参数。引用 是rust语言中的一个比较复杂的特性,默认情况下是不可变 的,因此这里使用了&mut 来使其是可修改的。

readline()函数返回一个io::Result() 的类型对象。rust标准库中包含一系列的Result命名的类型。io中的Result是一个枚举类型,包含固定数量变量的一个集合。枚举类型通常与match 一起使用,match是一个条件匹配。这里的Result可以取值OkErr

Ok 表示当前操作是成功的,Ok内部存储了生成的值;Err 表示当前操作失败,Err内部存储了失败的信息。

io::Result对象也有方法,例如expect方法。如果io::Result是一个Err对象,则程序crash,并打印一条expect调用参数的字符串。如果不调用expect()方法,则编译会报一个warning,警告未处理可能发生的错误。

3. 随机数生成

3.1 rand库依赖

使用一个外部库rand crate,来生成伪随机数。可以多次玩猜数游戏。

cargo管理了线上(crates.io)的库注册,以及本地工程的crates依赖,首先我们要添加一个rand依赖到cargo的配置文件Cargo.toml中。

 在配置文件的dependencies section中添加依赖的crate名和版本号。这里的0.8.5 是一个0.8.*最低版本以及不高于0.9.0版本的语义,而非特定版本,即:高于0.8.5版本小于0.9版本的都可以。cargo会从线上拉取一个最新的满足版本要求的rand下来到${home}/.cargo目录中。在这个过程中,rand自身依赖的crates同样被递归拉下来了。 这种工程的库和版本依赖管理,对于rust工程开发非常重要。

# cargo build
    Updating crates.io index
  Downloaded ppv-lite86 v0.2.17
  Downloaded getrandom v0.2.8
  Downloaded rand_chacha v0.3.1
  Downloaded rand v0.8.5
  Downloaded rand_core v0.6.4
  Downloaded cfg-if v1.0.0
  Downloaded libc v0.2.137
  Downloaded 7 crates (791.9 KB) in 0.97s
   Compiling libc v0.2.137
   Compiling cfg-if v1.0.0
   Compiling ppv-lite86 v0.2.17
   Compiling getrandom v0.2.8
   Compiling rand_core v0.6.4
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.5
   Compiling guessing_game v0.1.0 (/doc/jiangxiaoqing/rust/chapter2/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 4.55s
# ls ~/.cargo/registry/src/github.com-1ecc6299db9ec823/
cfg-if-1.0.0  getrandom-0.2.8  libc-0.2.137  ppv-lite86-0.2.17  rand-0.8.5  rand_chacha-0.3.1  rand_core-0.6.4

  • Cargo.lock文件包含了什么?

留意,在项目根目录有一个Cargo.lock文件,该文件的作用是什么呢?

Cargo.lock本质上一个crates依赖的本地缓存,会在第一次cargo build时产生。 假设首次build时,从crate.io拉到了0.8.5版本的rand,而过了一段时间之后,0.8.6版本在线上被发布,但该版本无法在我们的guessing_game中运行。cargo本身总是拉取符合条件的最高版本,但存在Cargo.lock时,则优先根据历史缓存中的版本来拉取和构建。这保证了我们工程总是可以重复构建成功的,即使线上registry中发布了符合我们版本期望但却导致我们工程无法编译的crates版本。那么,如何更新到线上最新版本呢?通过执行cargo update拉拉取线上符合条件的最新版本的各个crates。

我们将在14章节,重点介绍Cargo生态。

3.2 随机数生成

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

fn main() {
    println!("Guest the number:");
    let secret_number = rand::thread_rng().gen_range(1..101);
    println!("The secret number is: {}", secret_number);
    println!("Please input your guesss.");
    let mut guess = String::new();

    io::stdin().read_line(&mut guess).expect("Failed to read line");

    println!("You guessed: {}", guess);
} 

rand::Rng中的Rng 是一个trait ,在第10章将介绍什么是trait 。Rng中定义了生成随机数的方法。

thread_rng函数返回一个随机数发生器对象,只跟当前线程相关的随机数发生器,使用了操作系统决定的随机种子。gen_range()函数利用随机数发生器,生成指定参数范围内的伪随机数。如上:1..101表示1到100之间的数字。

range表达式start..end的范围时[start, end),即不包括end。也可以写作,start..=end,表示[start, end]。

小技巧:

使用cargo doc --open会编译出本地工程以及依赖的所有crates的相关使用文档,就可以查询特定crate中函数的使用手册。也可以在crate.io线上搜索相关库的crate文档手册。

4. 猜数和随机数的比对

有了生成的随机数和用户输入的数字,就可以进行比对。

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

fn main() {
    println!("Guest the number:");
    let secret_number = rand::thread_rng().gen_range(1..101);
    println!("The secret number is: {}", secret_number);
    println!("Please input your guesss.");
    let mut guess = String::new();

    io::stdin().read_line(&mut guess).expect("Failed to read line");

    let guess: u32 = guess.trim().parse().expect("Please type a number!");

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

4.1 std::cmp::Ordering类型

该类型是标准库提供的一个枚举类型,可以取值Less、Greater、Equalcmp 函数可以比较任意两个“可比较”的类型值,并返回一个std::cmp::Ordering类型对象。

4.2 match表达式(expression)

类似C语言中的switch...case语句,match表达式包含多个arms(case语句?)。每个arm是一个匹配模式,rust会依次尝试匹配每一个arm。第6和第18章节将详细介绍。

4.3 输入类型的转换

由于输入的捕获是一种字符串类型,无法与生成伪随机数的数字类型进行匹配比对。rust是强类型的编程语言。生成的随机数默认是一个i32类型,(32位的有符号整型数字)。

第2个定义的guest将字符串的guest给覆盖了,trim()函数是字符串guest对象的一个方法,将我们输入的字符串前后的空格给过滤掉,然后用parse()尝试转换成u32类型的数字。

5. 支持多次猜测(使用循环)

到此,我们可以进行一次猜测,接下来使用rust的循环语句loop运行用户进行多次猜测,直到猜成功。

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

fn main() {
    println!("Guest the number:");
    let secret_number = rand::thread_rng().gen_range(1..101);
    // println!("The secret number is: {}", secret_number);
    loop {
        println!("Please input your guesss.");
        let mut guess = String::new();

        io::stdin().read_line(&mut guess).expect("Failed to read line");

        let guess: u32 = guess.trim().parse().expect("Please type a number!");

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

上述代码,用户无限猜测,直到猜数成功,或者输入非法(非数字)。parse()失败,程序会直接crash退出,并输出错误提示“Please type a number!”。

6. 错误输入的处理

到目前为止,用户输入非法(非数字)时会导致程序直接crash(输出一个错误消息)。为了不使程序崩溃,更友好提示用户重新输入,做如下改造:忽略用户的非法输入,并运行再次输入。

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

parse()返回的是一个Result类型对象,而Result对象是一个具有Ok和Err两个枚举值的枚举类型。这样,我们同样可以用match表达式(match是表达式expression,而非语句statement) ,来匹配并区分处理。

如果parse()成功将字符串转换为一个数字,将返回一个包含结果数字的Ok枚举值,则直接返回该数字;如果失败,返回一个包含错误消息的Err枚举值,Err(_)中下划线的语义是匹配任意值,即匹配所有类型的Err,不论内部信息如何。continue将返回到loop循环的下一轮循环开始处运行。 因此,上述代码,忽略了parse()遇到的所有错误。

最终版本代码如下:

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

fn main() {
    println!("Guest the number:");
    let secret_number = rand::thread_rng().gen_range(1..101);
    // println!("The secret number is: {}", secret_number);
    loop {
        println!("Please input your guesss.");
        let mut guess = String::new();

        io::stdin().read_line(&mut guess).expect("Failed to read line");

        // let guess: u32 = guess.trim().parse().expect("Please type a number!");
        let guess: u32 = match guess.trim().parse() {
                    Ok(num) => num,
                    Err(_) => continue,
        };
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
        //println!("You guessed: {}", guess);
    }
}

运行测试:

# cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/guessing_game`
Guest the number:
Please input your guesss.
60
Too small!
Please input your guesss.
sd
Please input your guesss.
90
Too big!
Please input your guesss.
75
Too small!
Please input your guesss.
83
Too big!
Please input your guesss.
78
You win!

关于作者:

犇叔,浙江大学计算机科学与技术专业,研究生毕业,而立有余。先后在华为、阿里巴巴和字节跳动,从事技术研发工作,资深研发专家。主要研究领域包括虚拟化、分布式技术和存储系统(包括CPU与计算、GPU异构计算、分布式块存储、分布式数据库等领域)、高性能RDMA网络协议和数据中心应用、Linux内核等方向。

专业方向爱好:数学、科学技术应用

关注犇叔,期望为您带来更多科研领域的知识和产业应用。

内容坚持原创,坚持干货有料。坚持长期创作,关注犇叔不迷路

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

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

相关文章

MySql学习之慢SQL优化和慢SQL案例

一、慢SQL优化思路 慢查询日志记录慢SQLexplain查询SQL的执行计划profile分析执行耗时Optimizer Trace分析详情 1、慢查询日志记录慢SQL show variables like slow_query_log%; show variables like long_query_time;查看下慢查询日志配置,我们可以使用show vari…

对MMVAE中IWAE代码实现的理解

原始的IWAE 优化目标: LIWAE(x1:M)Ez1:K∼qΦ(z∣x1:M)[log⁡∑k1K1KpΘ(zk,x1:M)qΦ(zk∣x1:M)](1)\mathcal{L}_{\mathrm{IWAE}}\left(\boldsymbol{x}_{1: M}\right)\mathbb{E}_{\boldsymbol{z}^{1: K} \sim q_{\Phi}\left(\boldsymbol{z} …

JavaScript

目录 1、JavaScript简介 2、JavaScript引入方式 2.1、内部脚本 2.2、外部脚本 3、JavaScript基础语法 3.1、书写语法 3.2、输出语句 3.3、变量 3.4、数据类型 3.5、运算符 3.5.1、 和 的区别 3.5.2、类型转换 3.6、流程控制语句 3.6.1、if语句 3.6.3、for循环语…

【毕业设计】时间序列天气预测系统 - LSTM

文章目录0 前言1 数据集介绍2 开始分析2.1 单变量分析2.1.1 温度变量2.2 将特征和标签切片2.3 建模2.4 训练模型2.5 多变量分析2.5.1 压强、温度、密度随时间变化绘图2.5.2 将数据集转换为数组类型并标准化2.5.3 多变量建模训练训练3 最后0 前言 🔥 Hi,…

vue项目身份认证,vuex,token

vuex存储用户登录信息以及解决页面刷新vuex数据丢失问题 我的文章:vuex页面刷新数据丢失问题的多种解决方法 有写到 身份认证 虽然完成了登录功能,但实际上现在用户没登录也能访问(对应的url),这样的话显得登录功能毫无意义。 为了让登录变得有意义&am…

【Linux】Linux系统管理详解

目录一.服务管理1.Linux中的进程和服务2.systemctl(CentOS 7版本)(1)基本语法(2)经验技巧二.系统运行级别1.CentOS的运行级别三.配置服务开机自启和关闭防火墙1.图形化服务开机自启2.命令行服务开机自启3.关闭防火墙自启动四.关机重启以下内容都是基于Ce…

排序算法-希尔排序

希尔排序 概念 希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。 希尔排序又称缩小增量排序,因 DL.Shell 于 1959 年提出而得名。 它通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小&a…

通达信最新交易接口系统开发源码有哪些?

通达信最新交易接口其实跟市面上的自动交易接口api是比较安全稳定接口,只需要通过第三方证券公司完成交易,也或者是个人与机构做私募量化投资也是可以的。但是最近小编就有注意到,在此之前的通达信接口已经完成了再次升级,那么&am…

JAVA开发(分布式SpringCloud全家桶一些组件读法)

配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话构成SpringCloud的集合。 Eureka服务注册与发现(Eureka:怎么读?&…

没基础的大学生如何自学c语言 ?

C语言具有高效、灵活、功能丰富、表达力强和较高的可移植性等特点,在程序设计中备受青睐。真的太多人学也有太多要学的东西了,以至于后台总有人问C语言该怎么学,甚至还有具体问编程问题的。 这一点专门针对「大部分时间都在写着重复的代码&a…

uniapp初步搭建:如何引入uview库(跨移动多端ui库)

uView是uni-app生态专用的UI框架,uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码, 可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台(引言自uni-app网) 1. 查看项目根目录有没有p…

公钥密码学中的公钥和私钥

公钥密码学解释:它是什么? 公钥基础设施 (PKI) 用于管理互联网通信中的身份和安全性。 启用 PKI 的核心技术是公钥密码术,这是一种依赖于使用两个相关密钥(公钥和私钥)的加密机制。 这两个密钥一起用于加密和解密消息。…

CM311-3_YST_晨星MSO9385_2+8_安卓9.0_TTL免费升级固件【含教程】

新魔百盒CM311-3_YST_晨星MSO9385_28_安卓9.0_TTL免费升级固件【含教程】 固件特点: 1、修改dns,三网通用; 2、开放原厂固件屏蔽的市场安装和u盘安装apk; 3、无开机广告,无系统更新,不在被强制升级&…

网站攻击技术,一篇打包带走!

大家好,今天给大家介绍一下,Web安全领域常见的一些安全问题。 1. SQL 注入 SQL注入攻击的核心在于让Web服务器执行攻击者期望的SQL语句,以便得到数据库中的感兴趣的数据或对数据库进行读取、修改、删除、插入等操作,达到其邪恶的…

分布式应用之监控平台zabbix的认识与搭建

内容预知 1.监控系统的相关知识 1.1 监控系统运用的原因 1.2 网站的可用性 1.3 市面上常用的监控系统 2.zabbix的相关知识 2.1 zabbix的概述 2.2 zabbix 是什么? 2.3 zabbix的监控原理 2.4 zabbix监控系统中五个常用程序 3. zabbix 服务端的部署 4. 部署…

C++ 不知图系列之基于链接表的无向图最短路径搜索

1. 前言 图的常用存储方式有 2 种: 邻接炬阵。 链接表。 邻接炬阵的优点和缺点都很明显。优点是简单、易理解,但是对于大部分图结构而言,都是稀疏的,使用矩阵存储,空间浪费就较大。 链接表相比较邻接矩阵存储方案…

团队的Code Review实践

高效地进行 Code Review 一直是我们想要做的事情,如何持续保持高效 Review 也是我们日常开发中所亟需解决的问题。 在疫情爆发之前,团队大多是线下一起办公。大家会聚在一起 Code Review,把讨论出的反馈记录在便利贴并贴在白板上。而当远程、…

Baklib|如何为你的营销计划制作Wiki页面

当你需要快速查找信息时,你会怎么做?很有可能,你会直接去谷歌——在输入你的查询之后,十有八九,搜索引擎会带你去wiki百科。wiki百科是一个巨大的在线百科全书。在这个数据库中,几乎所有的文章都有链接。 现在想象一…

Maven之POM介绍

POM介绍前言POM基础为什么要学习POM什么是POMSuper POMMinimal POM(POM的最低配置)POM特点Project Inheritance(项目继承性)Project Aggregation(项目聚合)Project Inheritance VS Project AggregationProject Interpolation and …

《FFmpeg Basics》中文版-00-简介

欢迎 亲爱的读者们, 欢迎来到这本书,它将使您熟悉FFmpeg项目的许多有趣的特性。下面的几个大公司都是FFmpeg使用者: Facebook,最大的社交网络,用FFmpeg技术处理用户的视频。Google Chrome,流行的web浏览器&#xff0…