欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何使用Rust寫(xiě)個(gè)猜數(shù)字游戲

 更新時(shí)間:2023年12月18日 15:32:28   作者:胡安民  
這篇文章主要介紹了Rust寫(xiě)個(gè)猜數(shù)字游戲,本項(xiàng)目通過(guò)動(dòng)手實(shí)踐,介紹了Rust新概念:let、match、函數(shù)、使用外部 crate 等,接下來(lái)的文章,你會(huì)繼續(xù)深入學(xué)習(xí)這些概念,并且介紹大部分編程語(yǔ)言都有的概念,如變量、數(shù)據(jù)類型和函數(shù),以及如何在 Rust 中使用它們,需要的朋友可以參考下

開(kāi)頭

讓我們一起動(dòng)手完成一個(gè)項(xiàng)目,來(lái)快速上手 Rust!本章將介紹 Rust 中一些常用概念,并通過(guò)真實(shí)的程序來(lái)展示如何運(yùn)用它們。
將會(huì)學(xué)到 let、match、方法(method)、關(guān)聯(lián)函數(shù)(associated function)、外部 crate 等知識(shí)!
后續(xù)文章會(huì)深入探討這些概念的細(xì)節(jié)。
在這篇章,我們將練習(xí)基礎(chǔ)內(nèi)容。

我們會(huì)實(shí)現(xiàn)一個(gè)經(jīng)典的新手編程問(wèn)題:猜猜看游戲。
它是這么工作的:程序?qū)?huì)隨機(jī)生成一個(gè) 1 到 100 之間的隨機(jī)整數(shù)。接著它會(huì)請(qǐng)玩家猜一個(gè)數(shù)并輸入,然后提示猜測(cè)是大了還是小了。如果猜對(duì)了,它會(huì)打印祝賀信息并退出。

準(zhǔn)備一個(gè)新項(xiàng)目

創(chuàng)建的 projects 目錄,使用 Cargo 新建一個(gè)項(xiàng)目,如下:

cargo new guessing_game
cd guessing_game

現(xiàn)在使用 cargo run 命令,一步完成 “Hello, world!” 程序的編譯和運(yùn)行:

截圖_20231213141730.png

當(dāng)你需要在項(xiàng)目中快速迭代時(shí),run 命令就能派上用場(chǎng),正如我們?cè)谶@個(gè)游戲項(xiàng)目中做的,在下一次迭代之前快速測(cè)試每一次迭代。

打開(kāi) src/main.rs 文件。我們將會(huì)在這個(gè)文件中編寫(xiě)全部的代碼。

處理一次猜測(cè)

猜猜看程序的第一部分請(qǐng)求和處理用戶輸入,并檢查輸入是否符合預(yù)期的格式。首先,允許玩家輸入猜測(cè)。在 src/main.rs 中輸入代碼。

use std::io;
fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");
    let mut guess  = String::new();
    //接收用戶輸入,并將其存入guess變量中
    io::stdin()
        .read_line(&mut guess  )
        .expect("Failed to read line");
    //打印用戶輸入
    println!("You guessed: {}", guess);
}

運(yùn)行代碼 cargo run 然后在控制臺(tái)中輸入, 最后結(jié)果如下:

至此為止,游戲的第一部分已經(jīng)完成:我們從鍵盤(pán)獲取輸入并打印了出來(lái)。

這些代碼包含很多信息,我們把陌生的內(nèi)容講解一下:
為了獲取用戶輸入并打印結(jié)果作為輸出,我們需要將 io輸入/輸出庫(kù)引入當(dāng)前作用域。io 庫(kù)來(lái)自于標(biāo)準(zhǔn)庫(kù),也被稱為 std:

use std::io;

默認(rèn)情況下,Rust 設(shè)定了若干個(gè)會(huì)自動(dòng)導(dǎo)入到每個(gè)程序作用域中的標(biāo)準(zhǔn)庫(kù)內(nèi)容,這組內(nèi)容被稱為 預(yù)導(dǎo)入(preclude) 內(nèi)容。你可以在 標(biāo)準(zhǔn)庫(kù)文檔 中查看預(yù)導(dǎo)入的所有內(nèi)容。

如果你需要的類型不在預(yù)導(dǎo)入內(nèi)容中,就必須使用 use 語(yǔ)句顯式地將其引入作用域。std::io 庫(kù)提供很多有用的功能,包括接收用戶輸入的功能。

創(chuàng)建一個(gè) 變量(variable)來(lái)儲(chǔ)存用戶輸入,像這樣:

 let mut guess = String::new();

現(xiàn)在程序開(kāi)始變得有意思了!這一小行代碼發(fā)生了很多事。
我們使用 let 語(yǔ)句來(lái)創(chuàng)建變量。這里是另外一個(gè)例子:

let apples = 5;

這行代碼新建了一個(gè)叫做 apples 的變量并把它綁定到值 5 上。

在 Rust 中,變量默認(rèn)是不可變的,這意味著一旦我們給變量賦值,這個(gè)值就不再可以修改了。我們將會(huì)在后面文章中講解 “變量與可變性” 這個(gè)概念。

下面的例子展示了如何在變量名前使用 mut 來(lái)使一個(gè)變量可變:

let apples = 5; // 不可變
let mut bananas = 5; // 可變

注意:// 語(yǔ)法開(kāi)始一個(gè)注釋,持續(xù)到行尾。Rust 忽略注釋中的所有內(nèi)容,后面文章中將會(huì)詳細(xì)介紹注釋。

回到猜猜看程序中?,F(xiàn)在我們知道了 let mut guess 會(huì)引入一個(gè)叫做 guess 的可變變量。等號(hào)(=)告訴 Rust 我們現(xiàn)在想將某個(gè)值綁定在變量上。等號(hào)的右邊是 guess 所綁定的值,它是 String::new 的結(jié)果,這個(gè)函數(shù)會(huì)返回一個(gè) String 的新實(shí)例。String 是一個(gè)標(biāo)準(zhǔn)庫(kù)提供的字符串類型,它是 UTF-8 編碼的可增長(zhǎng)文本塊。

::new 那一行的 :: 語(yǔ)法表明 new 是 String 類型的一個(gè) 關(guān)聯(lián)函數(shù)(associated function)。關(guān)聯(lián)函數(shù)是針對(duì)類型實(shí)現(xiàn)的,在這個(gè)例子中是 String,而不是 String 的某個(gè)特定實(shí)例。一些語(yǔ)言中把它稱為 靜態(tài)方法(static method)。

new 函數(shù)創(chuàng)建了一個(gè)新的空字符串,你會(huì)發(fā)現(xiàn)很多類型上有 new 函數(shù),因?yàn)樗莿?chuàng)建類型實(shí)例的慣用函數(shù)名。

總的來(lái)說(shuō),let mut guess = String::new(); 這一行創(chuàng)建了一個(gè)可變變量,當(dāng)前它綁定到一個(gè)新的 String 空實(shí)例上。

回憶一下,我們?cè)诔绦虻牡谝恍惺褂?use std::io; 從標(biāo)準(zhǔn)庫(kù)中引入了輸入/輸出功能?,F(xiàn)在調(diào)用 io 庫(kù)中的函數(shù) stdin:

    io::stdin()
        .read_line(&mut guess)

如果程序的開(kāi)頭沒(méi)有使用 use std::io; 引入 io 庫(kù),我們?nèi)钥梢酝ㄟ^(guò)把函數(shù)調(diào)用寫(xiě)成 std::io::stdin 來(lái)使用函數(shù)。

stdin 函數(shù)返回一個(gè) std::io::Stdin 的實(shí)例,這代表終端標(biāo)準(zhǔn)輸入句柄的類型。

代碼的下一部分,.read_line(&mut guess),調(diào)用 read_line 方法從標(biāo)準(zhǔn)輸入句柄獲取用戶輸入。

我們還將 &mut guess 作為參數(shù)傳遞給 read_line() 函數(shù),讓其將用戶輸入儲(chǔ)存到這個(gè)字符串中。

read_line 的工作是,無(wú)論用戶在標(biāo)準(zhǔn)輸入中鍵入什么內(nèi)容,都將其追加(不會(huì)覆蓋其原有內(nèi)容)到一個(gè)字符串中,因此它需要字符串作為參數(shù)。這個(gè)字符串參數(shù)應(yīng)該是可變的,以便 read_line 將用戶輸入附加上去。

& 表示這個(gè)參數(shù)是一個(gè) 引用(reference),它允許多處代碼訪問(wèn)同一處數(shù)據(jù),而無(wú)需在內(nèi)存中多次拷貝。引用是一個(gè)復(fù)雜的特性,Rust 的一個(gè)主要優(yōu)勢(shì)就是安全而簡(jiǎn)單的操縱引用。

現(xiàn)在,我們只需知道它像變量一樣,默認(rèn)是不可變的。因此,需要寫(xiě)成 &mut guess 來(lái)使其可變,而不是 &guess。(第后面文章會(huì)更全面的解釋引用。)

我們還沒(méi)有完全分析完這行代碼因?yàn)榈?8 行代碼換行了,但要注意:它仍是邏輯行(雖然換行了但仍是語(yǔ)句)的一部分。 我們也可以將代碼這樣寫(xiě):

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

不過(guò),過(guò)長(zhǎng)的代碼行難以閱讀,所以最好拆開(kāi)來(lái)寫(xiě)。通常來(lái)說(shuō),當(dāng)使用 .method_name() 語(yǔ)法調(diào)用方法時(shí)引入換行符和空格將長(zhǎng)的代碼行拆開(kāi)是明智的。現(xiàn)在來(lái)看看這行代碼干了什么。

之前提到了 read_line 會(huì)將用戶輸入附加到傳遞給它的字符串中,不過(guò)它也會(huì)返回一個(gè)類型為 Result 的值。 Result 是一種枚舉類型,通常也寫(xiě)作 enum。枚舉類型變量的值可以是多種可能狀態(tài)中的一個(gè)。我們把每種可能的狀態(tài)稱為一種 枚舉成員(variant)。

后面文章將介紹枚舉的更多細(xì)節(jié)。這里的 Result 類型將用來(lái)編碼錯(cuò)誤處理的信息。

Result 的成員是 Ok 和 Err,Ok 成員表示操作成功,內(nèi)部包含成功時(shí)產(chǎn)生的值。Err 成員則意味著操作失敗,并且包含失敗的前因后果。

截圖_20231213144435.png

這些 Result 類型的作用是編碼錯(cuò)誤處理信息。Result 類型的值,像其他類型一樣,擁有定義于其上的方法。Result 的實(shí)例擁有 expect方法。

如果 io::Result 實(shí)例的值是 Err,expect 會(huì)導(dǎo)致程序崩潰,并顯示當(dāng)做參數(shù)傳遞給 expect 的信息。如果 read_line 方法返回 Err,則可能是來(lái)源于底層操作系統(tǒng)錯(cuò)誤的結(jié)果。如果 Result 實(shí)例的值是 Ok,expect 會(huì)獲取 Ok 中的值并原樣返回。在本例中,這個(gè)值是用戶輸入到標(biāo)準(zhǔn)輸入中的字節(jié)數(shù)。
如果不調(diào)用 expect,程序也能編譯,不過(guò)會(huì)出現(xiàn)一個(gè)警告:

截圖_20231213144753.png

Rust 警告我們沒(méi)有使用 read_line 的返回值 Result,說(shuō)明有一個(gè)可能的錯(cuò)誤沒(méi)有處理。

消除警告的正確做法是實(shí)際去編寫(xiě)錯(cuò)誤處理代碼,不過(guò)由于我們就是希望程序在出現(xiàn)問(wèn)題時(shí)立即崩潰,所以直接使用 expect。后面文章中, 會(huì)學(xué)習(xí)如何從錯(cuò)誤中恢復(fù)。

除了位于結(jié)尾的右花括號(hào),目前為止就只有這一行代碼值得討論一下了,就是這一行:

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

這行代碼現(xiàn)在打印了存儲(chǔ)用戶輸入的字符串。里面的 {} 是預(yù)留在特定位置的占位符, 當(dāng)打印變量的值時(shí),變量名可以寫(xiě)進(jìn)大括號(hào)中。
當(dāng)打印表達(dá)式的執(zhí)行結(jié)果時(shí),格式化字符串(format string)中大括號(hào)中留空,格式化字符串后跟逗號(hào)分隔的需要打印的表達(dá)式列表,其順序與每一個(gè)空大括號(hào)占位符的順序一致。
在一個(gè) println! 調(diào)用中打印變量和表達(dá)式的值看起來(lái)像這樣:

let x = 5;
let y = 10;
println!("x = {x} and y + 2 = {}", y + 2);

這行代碼會(huì)打印出 x = 5 and y + 2 = 12。

生成一個(gè)秘密數(shù)字

接下來(lái),需要生成一個(gè)秘密數(shù)字,好讓用戶來(lái)猜。秘密數(shù)字應(yīng)該每次都不同,這樣重復(fù)玩才不會(huì)乏味;范圍應(yīng)該在 1 到 100 之間,這樣才不會(huì)太困難。Rust 標(biāo)準(zhǔn)庫(kù)中尚未包含隨機(jī)數(shù)功能。然而,Rust 團(tuán)隊(duì)還是提供了一個(gè)包含上述功能的 randcrate。

使用 crate 來(lái)增加更多功能
記住,crate 是一個(gè) Rust 代碼包。我們正在構(gòu)建的項(xiàng)目是一個(gè) 二進(jìn)制 crate,它生成一個(gè)可執(zhí)行文件。
randcrate 是一個(gè) 庫(kù) crate,庫(kù) crate 可以包含任意能被其他程序使用的代碼,但是不能自執(zhí)行。

Cargo 對(duì)外部 crate 的運(yùn)用是其真正的亮點(diǎn)所在。在我們使用 rand 編寫(xiě)代碼之前,需要修改 Cargo.toml 文件,引入一個(gè) rand 依賴?,F(xiàn)在打開(kāi)這個(gè)文件并將下面這一行添加到 [dependencies] 片段標(biāo)題之下。在當(dāng)前版本下,請(qǐng)確保按照我們這里的方式指定 rand,否則本教程中的示例代碼可能無(wú)法工作。
文件名:Cargo.toml

[dependencies]
rand = "0.8.5"

截圖_20231213145537.png

在 Cargo.toml 文件中,標(biāo)題以及之后的內(nèi)容屬同一個(gè)片段,直到遇到下一個(gè)標(biāo)題才開(kāi)始新的片段。[dependencies] 片段告訴 Cargo 本項(xiàng)目依賴了哪些外部 crate 及其版本。本例中,我們使用語(yǔ)義化版本 0.8.5 來(lái)指定 rand crate。Cargo 理解 語(yǔ)義化版本(Semantic Versioning)(有時(shí)也稱為 SemVer),這是一種定義版本號(hào)的標(biāo)準(zhǔn)。0.8.5 事實(shí)上是 ^0.8.5 的簡(jiǎn)寫(xiě),它表示任何至少是 0.8.5 但小于 0.9.0 的版本。

Cargo 認(rèn)為這些版本與 0.8.5 版本的公有 API 相兼容,這樣的版本指定確保了我們可以獲取能使本章代碼編譯的最新的補(bǔ)丁(patch)版本。任何大于 0.8.5 的版本不能保證和接下來(lái)的示例采用了相同的 API?,F(xiàn)在,不修改任何代碼,構(gòu)建項(xiàng)目,如:cargo build

$ cargo build
    Updating crates.io index
  Downloaded rand v0.8.5
  Downloaded libc v0.2.127
  Downloaded getrandom v0.2.7
  Downloaded cfg-if v1.0.0
  Downloaded ppv-lite86 v0.2.16
  Downloaded rand_chacha v0.3.1
  Downloaded rand_core v0.6.3
   Compiling libc v0.2.127
   Compiling getrandom v0.2.7
   Compiling cfg-if v1.0.0
   Compiling ppv-lite86 v0.2.16
   Compiling rand_core v0.6.3
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.5
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 2.53s

可能會(huì)出現(xiàn)不同的版本號(hào)(多虧了語(yǔ)義化版本,它們與代碼是兼容的?。?,同時(shí)顯示順序也可能會(huì)有所不同。
現(xiàn)在我們有了一個(gè)外部依賴,Cargo 從 registry 上獲取所有包的最新版本信息,這是一份來(lái)自 Crates.io 的數(shù)據(jù)拷貝。

Crates.io 是 Rust 生態(tài)環(huán)境中的開(kāi)發(fā)者們向他人貢獻(xiàn) Rust 開(kāi)源項(xiàng)目的地方。
在更新完 registry 后,Cargo 檢查 [dependencies] 片段并下載列表中包含但還未下載的 crates。本例中,雖然只聲明了 rand 一個(gè)依賴,然而 Cargo 還是額外獲取了 rand 所需要的其他 crates,因?yàn)?rand 依賴它們來(lái)正常工作。下載完成后,Rust 編譯依賴,然后使用這些依賴編譯項(xiàng)目。

Cargo.lock文件確保構(gòu)建是可重現(xiàn)的

Cargo 有一個(gè)機(jī)制來(lái)確保任何人在任何時(shí)候重新構(gòu)建代碼,都會(huì)產(chǎn)生相同的結(jié)果:Cargo 只會(huì)使用你指定的依賴版本,除非你又手動(dòng)指定了別的。例如,如果下周 rand crate 的 0.8.6 版本出來(lái)了,它修復(fù)了一個(gè)重要的 bug,同時(shí)也含有一個(gè)會(huì)破壞代碼運(yùn)行的缺陷。為了處理這個(gè)問(wèn)題,Rust 在你第一次運(yùn)行 cargo build 時(shí)建立了 Cargo.lock 文件,我們現(xiàn)在可以在_guessing_game_ 目錄找到它。

當(dāng)?shù)谝淮螛?gòu)建項(xiàng)目時(shí),Cargo 計(jì)算出所有符合要求的依賴版本并寫(xiě)入 Cargo.lock 文件。
當(dāng)將來(lái)其他人構(gòu)建項(xiàng)目時(shí),Cargo 會(huì)發(fā)現(xiàn) Cargo.lock 已存在并使用其中指定的版本,而不是再次計(jì)算所有的版本。這使得你擁有了一個(gè)自動(dòng)化的可重現(xiàn)的構(gòu)建。
換句話說(shuō),項(xiàng)目會(huì)持續(xù)使用 0.8.5 直到你顯式升級(jí),多虧有了 Cargo.lock 文件。由于 Cargo.lock 文件對(duì)于“可重復(fù)構(gòu)建”非常重要,因此它通常會(huì)和項(xiàng)目中的其余代碼一樣納入到版本控制系統(tǒng)中。

更新 crate 到一個(gè)新版本
當(dāng)你 確實(shí) 需要升級(jí) crate 時(shí),Cargo 提供了這樣一個(gè)命令,update,它會(huì)忽略 Cargo.lock 文件,并計(jì)算出所有符合 Cargo.toml 聲明的最新版本。Cargo 接下來(lái)會(huì)把這些版本寫(xiě)入 Cargo.lock 文件。不過(guò),Cargo 默認(rèn)只會(huì)尋找大于 0.8.5 而小于 0.9.0 的版本。如果 rand crate 發(fā)布了兩個(gè)新版本,0.8.6 和 0.9.0,在運(yùn)行 cargo update 時(shí)會(huì)出現(xiàn)如下內(nèi)容:

$ cargo update
    Updating crates.io index
    Updating rand v0.8.5 -> v0.8.6

Cargo 忽略了 0.9.0 版本。這時(shí),你也會(huì)注意到的 Cargo.lock 文件中的變化無(wú)外乎現(xiàn)在使用的 rand crate 版本是0.8.6 。如果想要使用 0.9.0 版本的 rand 或是任何 0.9.x 系列的版本,必須像這樣更新 Cargo.toml 文件:

[dependencies]
rand = "0.9.0"

下一次運(yùn)行 cargo build 時(shí),Cargo 會(huì)從 registry 更新可用的 crate,并根據(jù)你指定的新版本重新計(jì)算。
后面文章會(huì)講到 Cargo 及其生態(tài)系統(tǒng)的更多內(nèi)容,不過(guò)目前你只需要了解這么多。通過(guò) Cargo 復(fù)用庫(kù)文件非常容易,因此 Rustacean 能夠編寫(xiě)出由很多包組裝而成的更輕巧的項(xiàng)目。

讓我們開(kāi)始使用 rand 來(lái)生成一個(gè)猜猜看隨機(jī)數(shù)。下一步是更新 src/main.rs

use std::io;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is: {secret_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}");
}

首先,我們新增了一行 use rand::Rng;。Rng 是一個(gè) trait,它定義了隨機(jī)數(shù)生成器應(yīng)實(shí)現(xiàn)的方法,想使用這些方法的話,此 trait 必須在作用域中。后面文章會(huì)詳細(xì)介紹 trait。

接下來(lái),我們?cè)谥虚g還新增加了兩行。第一行調(diào)用了 rand::thread_rng 函數(shù)提供實(shí)際使用的隨機(jī)數(shù)生成器:它位于當(dāng)前執(zhí)行線程的本地環(huán)境中,并從操作系統(tǒng)獲取 seed。
接著調(diào)用隨機(jī)數(shù)生成器的 gen_range 方法。這個(gè)方法由 use rand::Rng 語(yǔ)句引入到作用域的 Rng trait 定義。gen_range 方法獲取一個(gè)范圍表達(dá)式(range expression)作為參數(shù),并生成一個(gè)在此范圍之間的隨機(jī)數(shù)。
這里使用的這類范圍表達(dá)式使用了 start…=end 這樣的形式,也就是說(shuō)包含了上下端點(diǎn),所以需要指定 1…=100 來(lái)請(qǐng)求一個(gè) 1 和 100 之間的數(shù)。

注意:你不可能憑空就知道應(yīng)該 use 哪個(gè) trait 以及該從 crate 中調(diào)用哪個(gè)方法,因此每個(gè) crate 有使用說(shuō)明文檔。
Cargo 有一個(gè)很棒的功能是:在當(dāng)前項(xiàng)目根目錄下運(yùn)行 cargo doc --open 命令來(lái)構(gòu)建所有本地依賴提供的文檔,并在瀏覽器中打開(kāi)。
例如,假設(shè)你對(duì) rand crate 中的其他功能感興趣,你可以運(yùn)行 cargo doc --open 并點(diǎn)擊左側(cè)導(dǎo)航欄中的 rand。

截圖_20231213151316.png

新增加的第 9 行代碼打印出了秘密數(shù)字。這在開(kāi)發(fā)程序時(shí)很有用,因?yàn)榭梢詼y(cè)試它,不過(guò)在最終版本中會(huì)刪掉它。如果游戲一開(kāi)始就打印出結(jié)果就沒(méi)什么可玩的了!

嘗試運(yùn)行程序幾次: 你應(yīng)該能得到不同的隨機(jī)數(shù),同時(shí)它們應(yīng)該都是在 1 和 100 之間的。

比較猜測(cè)的數(shù)字和秘密數(shù)字

現(xiàn)在有了用戶輸入和一個(gè)隨機(jī)數(shù),我們可以比較它們。這個(gè)步驟如示例 2-4 所示。注意這段代碼還不能通過(guò)編譯,我們稍后會(huì)解釋。
文件名:src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is: {secret_number}");
    println!("Please input your guess.");
    let mut guess  = String::new();
    //接收用戶輸入,并將其存入guess變量中
    io::stdin().read_line(&mut guess  ).expect("Failed to read line");
    //打印用戶輸入
    println!("You guessed: {}", guess);
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

首先我們?cè)黾恿肆硪粋€(gè) use 聲明,從標(biāo)準(zhǔn)庫(kù)引入了一個(gè)叫做 std::cmp::Ordering 的類型到作用域中。 Ordering 也是一個(gè)枚舉,不過(guò)它的成員是 Less、Greater 和 Equal。這是比較兩個(gè)值時(shí)可能出現(xiàn)的三種結(jié)果。

接著,底部的五行新代碼使用了 Ordering 類型,cmp 方法用來(lái)比較兩個(gè)值并可以在任何可比較的值上調(diào)用。
它獲取一個(gè)被比較值的引用:這里是把 guess 與 secret_number 做比較。然后它會(huì)返回一個(gè)剛才通過(guò) use 引入作用域的 Ordering 枚舉的成員。使用一個(gè) match 表達(dá)式,根據(jù)對(duì) guess 和 secret_number 調(diào)用 cmp 返回的 Ordering 成員來(lái)決定接下來(lái)做什么。

一個(gè) match 表達(dá)式由 分支(arms) 構(gòu)成。一個(gè)分支包含一個(gè) 模式(pattern)和表達(dá)式開(kāi)頭的值與分支模式相匹配時(shí)應(yīng)該執(zhí)行的代碼。Rust 獲取提供給 match 的值并挨個(gè)檢查每個(gè)分支的模式。match 結(jié)構(gòu)和模式是 Rust 中強(qiáng)大的功能,它體現(xiàn)了代碼可能遇到的多種情形,并幫助你確保沒(méi)有遺漏處理。這些功能將分別在后面文章中介紹。

讓我們看看使用 match 表達(dá)式的例子。假設(shè)用戶猜了 50,這時(shí)隨機(jī)生成的秘密數(shù)字是 38。

比較 50 與 38 時(shí),因?yàn)?50 比 38 要大,cmp 方法會(huì)返回 Ordering::Greater。Ordering::Greater 是 match 表達(dá)式得到的值。它檢查第一個(gè)分支的模式,Ordering::Less 與 Ordering::Greater并不匹配,所以它忽略了這個(gè)分支的代碼并來(lái)到下一個(gè)分支。下一個(gè)分支的模式是 Ordering::Greater,正確 匹配!

這個(gè)分支關(guān)聯(lián)的代碼被執(zhí)行,在屏幕打印出 Too big!。match 表達(dá)式會(huì)在第一次成功匹配后終止,因?yàn)樵搱?chǎng)景下沒(méi)有檢查最后一個(gè)分支的必要。

我們來(lái)看看上面代碼為什不能執(zhí)行的原因

截圖_20231213152224.png

_ types_)。Rust 有一個(gè)靜態(tài)強(qiáng)類型系統(tǒng),同時(shí)也有類型推斷。當(dāng)我們寫(xiě)出 let guess = String::new() 時(shí),Rust 推斷出 guess 應(yīng)該是 String 類型, 并不需要我們寫(xiě)出類型。

另一方面,secret_number,是數(shù)字類型。幾個(gè)數(shù)字類型擁有 1 到 100 之間的值:32 位數(shù)字 i32;32 位無(wú)符號(hào)數(shù)字 u32;64 位數(shù)字 i64 等等。Rust 默認(rèn)使用 i32,所以它是 secret_number 的類型,除非增加類型信息,或任何能讓 Rust 推斷出不同數(shù)值類型的信息。這里錯(cuò)誤的原因在于 Rust 不會(huì)比較字符串類型和數(shù)字類型。

所以我們必須把從輸入中讀取到的 String 轉(zhuǎn)換為一個(gè)真正的數(shù)字類型,才好與秘密數(shù)字進(jìn)行比較。這可以通過(guò)在 main 函數(shù)體中增加如下代碼來(lái)實(shí)現(xiàn):

use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is: {secret_number}");
    println!("Please input your guess.");
    let mut guess  = String::new();
    //接收用戶輸入,并將其存入guess變量中
    io::stdin().read_line(&mut guess  ).expect("Failed to read line");
    //將用戶輸入的字符串轉(zhuǎn)換為數(shù)字
    let guess: u32 = guess.trim().parse().expect("Please type a number!");
    //打印用戶輸入
    println!("You guessed: {}", guess);
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

這行新代碼是 15 行

這里創(chuàng)建了一個(gè)叫做 guess 的變量。不過(guò)等等,不是已經(jīng)有了一個(gè)叫做 guess 的變量了嗎?確實(shí)如此,不過(guò) Rust 允許用一個(gè)新值來(lái) 隱藏 (Shadowing) guess 之前的值。

這個(gè)功能常用在需要轉(zhuǎn)換值類型之類的場(chǎng)景。它允許我們復(fù)用 guess 變量的名字,而不是被迫創(chuàng)建兩個(gè)不同變量,諸如 guess_str 和 guess 之類。后面文章會(huì)介紹 shadowing 的更多細(xì)節(jié),現(xiàn)在只需知道這個(gè)功能經(jīng)常用于將一個(gè)類型的值轉(zhuǎn)換為另一個(gè)類型的值。

我們將這個(gè)新變量綁定到 guess.trim().parse() 表達(dá)式上。表達(dá)式中的 guess 指的是包含輸入的字符串類型 guess 變量。String 實(shí)例的 trim 方法會(huì)去除字符串開(kāi)頭和結(jié)尾的空白字符,我們必須執(zhí)行此方法才能將字符串與 u32 比較,因?yàn)?u32 只能包含數(shù)值型數(shù)據(jù)。

用戶必須輸入 enter 鍵才能讓 read_line 返回并輸入他們的猜想,這將會(huì)在字符串中增加一個(gè)換行(newline)符。例如,用戶輸入 5 并按下 enter(在 Windows 上,按下 enter 鍵會(huì)得到一個(gè)回車符和一個(gè)換行符,\r\n),guess 看起來(lái)像這樣:5\n 或者 5\r\n。\n 代表 “換行”,回車鍵;\r 代表 “回車”,回車鍵。trim 方法會(huì)消除 \n 或者 \r\n,只留下 5。

字符串的parse方法 將字符串轉(zhuǎn)換成其他類型。這里用它來(lái)把字符串轉(zhuǎn)換為數(shù)值。我們需要告訴 Rust 具體的數(shù)字類型,這里通過(guò) let guess: u32 指定。guess 后面的冒號(hào)(:)告訴 Rust 我們指定了變量的類型。Rust 有一些內(nèi)建的數(shù)字類型;u32 是一個(gè)無(wú)符號(hào)的 32 位整型。對(duì)于不大的正整數(shù)來(lái)說(shuō),它是不錯(cuò)的默認(rèn)類型,第后面文章還會(huì)講到其他數(shù)字類型。

另外,程序中的 u32 注解以及與 secret_number 的比較,意味著 Rust 會(huì)推斷出 secret_number 也是 u32 類型?,F(xiàn)在可以使用相同類型比較兩個(gè)值了!

parse 方法只有在字符邏輯上可以轉(zhuǎn)換為數(shù)字的時(shí)候才能工作所以非常容易出錯(cuò)。
例如,字符串中包含 A??%,就無(wú)法將其轉(zhuǎn)換為一個(gè)數(shù)字。因此,parse 方法返回一個(gè) Result 類型。像之前 “使用Result類型來(lái)處理潛在的錯(cuò)誤” 討論的 read_line 方法那樣,再次按部就班的用 expect 方法處理即可。
如果 parse 不能從字符串生成一個(gè)數(shù)字,返回一個(gè) Result 的 Err 成員時(shí),expect 會(huì)使游戲崩潰并打印附帶的信息。如果 parse 成功地將字符串轉(zhuǎn)換為一個(gè)數(shù)字,它會(huì)返回 Result 的 Ok 成員,然后 expect 會(huì)返回 Ok 值中的數(shù)字。
現(xiàn)在讓我們運(yùn)行程序!: cargo run

截圖_20231213155319.png

漂亮!即便是在猜測(cè)之前添加了空格,程序依然能判斷出用戶猜測(cè)了 76。多運(yùn)行程序幾次,輸入不同的數(shù)字來(lái)檢驗(yàn)不同的行為:猜一個(gè)正確的數(shù)字,猜一個(gè)過(guò)大的數(shù)字和猜一個(gè)過(guò)小的數(shù)字。
現(xiàn)在游戲已經(jīng)大體上能玩了,不過(guò)用戶只能猜一次。我們可以增加一個(gè)循環(huán)來(lái)改變它吧!

使用循環(huán)來(lái)允許多次猜測(cè)

loop 關(guān)鍵字創(chuàng)建了一個(gè)無(wú)限循環(huán)。我們會(huì)增加循環(huán)來(lái)給用戶更多機(jī)會(huì)猜數(shù)字:

use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is: {secret_number}");
    loop {//無(wú)限循環(huán)
        println!("Please input your guess.");
        let mut guess  = String::new();
        //接收用戶輸入,并將其存入guess變量中
        io::stdin().read_line(&mut guess  ).expect("Failed to read line");
        //將用戶輸入的字符串轉(zhuǎn)換為數(shù)字
        let guess: u32 = guess.trim().parse().expect("Please type a number!");
        //打印用戶輸入
        println!("You guessed: {}", guess);
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => println!("You win!"),
        }
    }
}

如上所示,我們將提示用戶猜測(cè)之后的所有內(nèi)容移動(dòng)到了循環(huán)中。確保 loop 循環(huán)中的代碼多縮進(jìn)四個(gè)空格,再次運(yùn)行程序。注意這里有一個(gè)新問(wèn)題,因?yàn)槌绦蛑覍?shí)地執(zhí)行了我們的要求:永遠(yuǎn)地請(qǐng)求另一個(gè)猜測(cè),用戶好像無(wú)法退出?。?/p>

用戶總能使用 ctrl-c 終止程序。不過(guò)還有另一個(gè)方法跳出無(wú)限循環(huán),就是 “比較猜測(cè)與秘密數(shù)字” 部分提到的 parse:如果用戶輸入的答案不是一個(gè)數(shù)字,程序會(huì)崩潰。我們可以利用這一點(diǎn)來(lái)退出,輸入 q將會(huì)退出程序,同時(shí)你會(huì)注意到其他任何非數(shù)字輸入也一樣。然而,這并不理想,我們想要當(dāng)猜測(cè)正確的數(shù)字時(shí)游戲停止。

猜測(cè)正確后退出

讓我們?cè)黾右粋€(gè) break 語(yǔ)句,在用戶猜對(duì)時(shí)退出游戲:

use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is: {secret_number}");
    loop { //無(wú)限循環(huán)
        println!("Please input your guess.");
        let mut guess  = String::new();
        //接收用戶輸入,并將其存入guess變量中
        io::stdin().read_line(&mut guess  ).expect("Failed to read line");
        //將用戶輸入的字符串轉(zhuǎn)換為數(shù)字
        let guess: u32 = guess.trim().parse().expect("Please type a number!");
        //打印用戶輸入
        println!("You guessed: {}", guess);
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => { //匹配到相等時(shí),退出循環(huán)
                println!("You win!");
                break;
            }
        }
    }
}

通過(guò)在 You win! 之后增加一行 break,用戶猜對(duì)了神秘?cái)?shù)字后會(huì)退出循環(huán)。退出循環(huán)也意味著退出程序,因?yàn)檠h(huán)是 main 的最后一部分。

處理無(wú)效輸入

為了進(jìn)一步改善游戲性,不要在用戶輸入非數(shù)字時(shí)崩潰,需要忽略非數(shù)字,讓用戶可以繼續(xù)猜測(cè)??梢酝ㄟ^(guò)修改 guess 將 String 轉(zhuǎn)化為 u32 那部分代碼來(lái)實(shí)現(xiàn),如下所示:

use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is: {secret_number}");
    loop { //無(wú)限循環(huán)
        println!("Please input your guess.");
        let mut guess  = String::new();
        //接收用戶輸入,并將其存入guess變量中
        io::stdin().read_line(&mut guess  ).expect("Failed to read line");
        //將用戶輸入的字符串轉(zhuǎn)換為數(shù)字
        let guess: u32 = match  guess.trim().parse() {
            Ok(num) => num, //如果轉(zhuǎn)換成功,返回?cái)?shù)字
            Err(_) => continue, //如果轉(zhuǎn)換失敗,繼續(xù)循環(huán)
        };
        //打印用戶輸入
        println!("You guessed: {}", guess);
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => { //匹配到相等時(shí),退出循環(huán)
                println!("You win!");
                break;
            }
        }
    }
}

我們將 expect 調(diào)用換成 match 語(yǔ)句,以從遇到錯(cuò)誤就崩潰轉(zhuǎn)換為處理錯(cuò)誤。須知 parse 返回一個(gè) Result 類型,而 Result 是一個(gè)擁有 Ok 或 Err 成員的枚舉。這里使用的 match 表達(dá)式,和之前處理 cmp 方法返回 Ordering 時(shí)用的一樣。

如果 parse 能夠成功的將字符串轉(zhuǎn)換為一個(gè)數(shù)字,它會(huì)返回一個(gè)包含結(jié)果數(shù)字的 Ok。這個(gè) Ok 值與 match 第一個(gè)分支的模式相匹配,該分支對(duì)應(yīng)的動(dòng)作返回 Ok 值中的數(shù)字 num,最后如愿變成新創(chuàng)建的 guess 變量。
如果 parse 能將字符串轉(zhuǎn)換為一個(gè)數(shù)字,它會(huì)返回一個(gè)包含更多錯(cuò)誤信息的 Err。Err 值不能匹配第一個(gè) match 分支的 Ok(num) 模式,但是會(huì)匹配第二個(gè)分支的 Err() 模式: 是一個(gè)通配符值,本例中用來(lái)匹配所有 Err 值,不管其中有何種信息。

所以程序會(huì)執(zhí)行第二個(gè)分支的動(dòng)作,continue 意味著進(jìn)入 loop 的下一次循環(huán),請(qǐng)求另一個(gè)猜測(cè)。這樣程序就有效的忽略了 parse 可能遇到的所有錯(cuò)誤!
現(xiàn)在萬(wàn)事俱備,只需運(yùn)行 cargo run:

太棒了!再有最后一個(gè)小的修改,就能完成猜猜看游戲了:還記得程序依然會(huì)打印出秘密數(shù)字。在測(cè)試時(shí)還好,但正式發(fā)布時(shí)會(huì)毀了游戲。刪掉打印秘密數(shù)字的 println!。

println!("The secret number is: {secret_number}");

此時(shí)此刻,你順利完成了猜猜看游戲。恭喜!, 下面是最終的代碼

use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..=100);
    loop { //無(wú)限循環(huán)
        println!("Please input your guess.");
        let mut guess  = String::new();
        //接收用戶輸入,并將其存入guess變量中
        io::stdin().read_line(&mut guess  ).expect("Failed to read line");
        //將用戶輸入的字符串轉(zhuǎn)換為數(shù)字
        let guess: u32 = match  guess.trim().parse() {
            Ok(num) => num, //如果轉(zhuǎn)換成功,返回?cái)?shù)字
            Err(_) => continue, //如果轉(zhuǎn)換失敗,繼續(xù)循環(huán)
        };
        //打印用戶輸入
        println!("You guessed: {}", guess);
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => { //匹配到相等時(shí),退出循環(huán)
                println!("You win!");
                break;
            }
        }
    }
}

總結(jié)

本項(xiàng)目通過(guò)動(dòng)手實(shí)踐,向你介紹了 Rust 新概念:let、match、函數(shù)、使用外部 crate 等等,接下來(lái)的文章,你會(huì)繼續(xù)深入學(xué)習(xí)這些概念。并且介紹大部分編程語(yǔ)言都有的概念,比如變量、數(shù)據(jù)類型和函數(shù),以及如何在 Rust 中使用它們。

到此這篇關(guān)于Rust寫(xiě)個(gè)猜數(shù)字游戲的文章就介紹到這了,更多相關(guān)Rust猜數(shù)字游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Rust中的Option枚舉快速入門(mén)教程

    Rust中的Option枚舉快速入門(mén)教程

    Rust中的Option枚舉用于表示可能不存在的值,提供了多種方法來(lái)處理這些值,避免了空指針異常,文章介紹了Option的定義、常見(jiàn)方法、使用場(chǎng)景以及注意事項(xiàng),感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • 深入了解Rust的生命周期

    深入了解Rust的生命周期

    生命周期指的是引用保持有效的作用域,Rust的每個(gè)引用都有自己的生命周期。本文將通過(guò)示例和大家詳細(xì)說(shuō)說(shuō)Rust的生命周期,需要的可以參考一下
    2022-12-12
  • Rust中泛型的學(xué)習(xí)筆記

    Rust中泛型的學(xué)習(xí)筆記

    在Rust語(yǔ)言中,泛型是一種強(qiáng)大的工具,它允許我們編寫(xiě)可復(fù)用且靈活的代碼,本文主要介紹了Rust中泛型的學(xué)習(xí)筆記,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-03-03
  • Rust使用libloader調(diào)用動(dòng)態(tài)鏈接庫(kù)

    Rust使用libloader調(diào)用動(dòng)態(tài)鏈接庫(kù)

    這篇文章主要為大家介紹了Rust使用libloader調(diào)用動(dòng)態(tài)鏈接庫(kù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • Rust指南之生命周期機(jī)制詳解

    Rust指南之生命周期機(jī)制詳解

    Rust?生命周期機(jī)制是與所有權(quán)機(jī)制同等重要的資源管理機(jī)制,之所以引入這個(gè)概念主要是應(yīng)對(duì)復(fù)雜類型系統(tǒng)中資源管理的問(wèn)題,這篇文章主要介紹了Rust指南之生命周期機(jī)制詳解,需要的朋友可以參考下
    2022-10-10
  • 通過(guò)rust實(shí)現(xiàn)自己的web登錄圖片驗(yàn)證碼功能

    通過(guò)rust實(shí)現(xiàn)自己的web登錄圖片驗(yàn)證碼功能

    本文介紹了如何使用Rust和imagecrate庫(kù)生成圖像驗(yàn)證碼,首先,通過(guò)Cargo.toml文件添加image依賴,然后,生成純色圖片并編輯驗(yàn)證圖片,接著,編寫(xiě)隨機(jī)函數(shù)獲取字符,并通過(guò)循環(huán)生成驗(yàn)證碼圖片,最終,通過(guò)運(yùn)行函數(shù)驗(yàn)證驗(yàn)證碼圖片是否生成,感興趣的朋友一起看看吧
    2025-03-03
  • Rust中的方法與關(guān)聯(lián)函數(shù)使用解讀

    Rust中的方法與關(guān)聯(lián)函數(shù)使用解讀

    在Rust中,方法是定義在特定類型(如struct)的impl塊中,第一個(gè)參數(shù)是self(可變或不可變),方法用于描述該類型實(shí)例的行為,而關(guān)聯(lián)函數(shù)則不包含self參數(shù),常用于構(gòu)造新實(shí)例或提供一些與實(shí)例無(wú)關(guān)的功能,Rust的自動(dòng)引用和解引用特性使得方法調(diào)用更加簡(jiǎn)潔
    2025-02-02
  • Rust中實(shí)例化動(dòng)態(tài)對(duì)象的示例詳解

    Rust中實(shí)例化動(dòng)態(tài)對(duì)象的示例詳解

    這篇文章主要為大家詳細(xì)介紹了Rust中實(shí)例化動(dòng)態(tài)對(duì)象的多種方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-02-02
  • rust中的match表達(dá)式使用詳解

    rust中的match表達(dá)式使用詳解

    在rust中提供了一個(gè)極為強(qiáng)大的控制流運(yùn)算符match,這篇文章主要介紹了rust中的match表達(dá)式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • Tauri?打開(kāi)本地文件踩坑分析解決

    Tauri?打開(kāi)本地文件踩坑分析解決

    這篇文章主要為大家介紹了Tauri?打開(kāi)本地文件踩坑分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04

最新評(píng)論