Rust 控制流

2023-03-22 15:08 更新
ch03-05-control-flow.md

commit 4284e160715917a768d25265daf2db897c683065

根據(jù)條件是否為真來(lái)決定是否執(zhí)行某些代碼,以及根據(jù)條件是否為真來(lái)重復(fù)運(yùn)行一段代碼的能力是大部分編程語(yǔ)言的基本組成部分。Rust 代碼中最常見(jiàn)的用來(lái)控制執(zhí)行流的結(jié)構(gòu)是 ?if ?表達(dá)式和循環(huán)。

if 表達(dá)式

?if ?表達(dá)式允許根據(jù)條件執(zhí)行不同的代碼分支。你提供一個(gè)條件并表示 “如果條件滿足,運(yùn)行這段代碼;如果條件不滿足,不運(yùn)行這段代碼?!?/p>

在 projects 目錄新建一個(gè)叫做 branches 的項(xiàng)目,來(lái)學(xué)習(xí) ?if ?表達(dá)式。在 src/main.rs 文件中,輸入如下內(nèi)容:

文件名: src/main.rs

fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }

所有的 ?if ?表達(dá)式都以 ?if ?關(guān)鍵字開(kāi)頭,其后跟一個(gè)條件。在這個(gè)例子中,條件檢查變量 ?number ?的值是否小于 5。在條件為真時(shí)希望執(zhí)行的代碼塊位于緊跟條件之后的大括號(hào)中。?if ?表達(dá)式中與條件關(guān)聯(lián)的代碼塊有時(shí)被叫做 arms,就像第二章 “比較猜測(cè)的數(shù)字和秘密數(shù)字” 部分中討論到的 ?match ?表達(dá)式中的分支一樣。

也可以包含一個(gè)可選的 ?else ?表達(dá)式來(lái)提供一個(gè)在條件為假時(shí)應(yīng)當(dāng)執(zhí)行的代碼塊,這里我們就這么做了。如果不提供 ?else ?表達(dá)式并且條件為假時(shí),程序會(huì)直接忽略 ?if ?代碼塊并繼續(xù)執(zhí)行下面的代碼。

嘗試運(yùn)行代碼,應(yīng)該能看到如下輸出:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was true

嘗試改變 ?number ?的值使條件為 ?false ?時(shí)看看會(huì)發(fā)生什么:

let number = 7;

再次運(yùn)行程序并查看輸出:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was false

另外值得注意的是代碼中的條件 必須 是 ?bool ?值。如果條件不是 ?bool ?值,我們將得到一個(gè)錯(cuò)誤。例如,嘗試運(yùn)行以下代碼:

文件名: src/main.rs

fn main() {
let number = 3; if number { println!("number was three"); } }

這里 ?if ?條件的值是 ?3?,Rust 拋出了一個(gè)錯(cuò)誤:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: mismatched types --> src/main.rs:4:8 | 4 | if number { | ^^^^^^ expected `bool`, found integer For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` due to previous error

這個(gè)錯(cuò)誤表明 Rust 期望一個(gè) ?bool ?卻得到了一個(gè)整數(shù)。不像 Ruby 或 JavaScript 這樣的語(yǔ)言,Rust 并不會(huì)嘗試自動(dòng)地將非布爾值轉(zhuǎn)換為布爾值。必須總是顯式地使用布爾值作為 ?if ?的條件。例如,如果想要 ?if ?代碼塊只在一個(gè)數(shù)字不等于 ?0? 時(shí)執(zhí)行,可以把 ?if ?表達(dá)式修改成下面這樣:

文件名: src/main.rs

fn main() { let number = 3; if number != 0 { println!("number was something other than zero"); } }

運(yùn)行代碼會(huì)打印出 ?number was something other than zero?。

使用 else if 處理多重條件

可以將 ?else if? 表達(dá)式與 ?if ?和 ?else ?組合來(lái)實(shí)現(xiàn)多重條件。例如:

文件名: src/main.rs

fn main() { let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3, or 2"); } }

這個(gè)程序有四個(gè)可能的執(zhí)行路徑。運(yùn)行后應(yīng)該能看到如下輸出:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` number is divisible by 3

當(dāng)執(zhí)行這個(gè)程序時(shí),它按順序檢查每個(gè) ?if ?表達(dá)式并執(zhí)行第一個(gè)條件為真的代碼塊。注意即使 6 可以被 2 整除,也不會(huì)輸出 ?number is divisible by 2?,更不會(huì)輸出 ?else ?塊中的 ?number is not divisible by 4, 3, or 2?。原因是 Rust 只會(huì)執(zhí)行第一個(gè)條件為真的代碼塊,并且一旦它找到一個(gè)以后,甚至都不會(huì)檢查剩下的條件了。

使用過(guò)多的 ?else if? 表達(dá)式會(huì)使代碼顯得雜亂無(wú)章,所以如果有多于一個(gè) ?else if? 表達(dá)式,最好重構(gòu)代碼。為此,第六章會(huì)介紹一個(gè)強(qiáng)大的 Rust 分支結(jié)構(gòu)(branching construct),叫做 ?match?。

在 let 語(yǔ)句中使用 if

因?yàn)?nbsp;?if ?是一個(gè)表達(dá)式,我們可以在 ?let ?語(yǔ)句的右側(cè)使用它,例如在示例 3-2 中:

文件名: src/main.rs

fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {number}"); }

示例 3-2:將 ?if ?表達(dá)式的返回值賦給一個(gè)變量

?number ?變量將會(huì)綁定到表示 ?if ?表達(dá)式結(jié)果的值上。運(yùn)行這段代碼看看會(huì)出現(xiàn)什么:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/branches` The value of number is: 5

記住,代碼塊的值是其最后一個(gè)表達(dá)式的值,而數(shù)字本身就是一個(gè)表達(dá)式。在這個(gè)例子中,整個(gè) ?if ?表達(dá)式的值取決于哪個(gè)代碼塊被執(zhí)行。這意味著 ?if ?的每個(gè)分支的可能的返回值都必須是相同類(lèi)型;在示例 3-2 中,?if ?分支和 ?else ?分支的結(jié)果都是 ?i32 ?整型。如果它們的類(lèi)型不匹配,如下面這個(gè)例子,則會(huì)出現(xiàn)一個(gè)錯(cuò)誤:

文件名: src/main.rs

fn main() {
let condition = true; let number = if condition { 5 } else { "six" }; println!("The value of number is: {number}"); }

當(dāng)編譯這段代碼時(shí),會(huì)得到一個(gè)錯(cuò)誤。?if ?和 ?else ?分支的值類(lèi)型是不相容的,同時(shí) Rust 也準(zhǔn)確地指出在程序中的何處發(fā)現(xiàn)的這個(gè)問(wèn)題:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: `if` and `else` have incompatible types --> src/main.rs:4:44 | 4 | let number = if condition { 5 } else { "six" }; | - ^^^^^ expected integer, found `&str` | | | expected because of this For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` due to previous error

?if ?代碼塊中的表達(dá)式返回一個(gè)整數(shù),而 ?else ?代碼塊中的表達(dá)式返回一個(gè)字符串。這不可行,因?yàn)樽兞勘仨氈挥幸粋€(gè)類(lèi)型。Rust 需要在編譯時(shí)就確切的知道 ?number ?變量的類(lèi)型,這樣它就可以在編譯時(shí)驗(yàn)證在每處使用的 ?number ?變量的類(lèi)型是有效的。如果?number?的類(lèi)型僅在運(yùn)行時(shí)確定,則 Rust 無(wú)法做到這一點(diǎn);且編譯器必須跟蹤每一個(gè)變量的多種假設(shè)類(lèi)型,那么它就會(huì)變得更加復(fù)雜,對(duì)代碼的保證也會(huì)減少。

使用循環(huán)重復(fù)執(zhí)行

多次執(zhí)行同一段代碼是很常用的,Rust 為此提供了多種 循環(huán)loops)。一個(gè)循環(huán)執(zhí)行循環(huán)體中的代碼直到結(jié)尾并緊接著回到開(kāi)頭繼續(xù)執(zhí)行。為了實(shí)驗(yàn)一下循環(huán),讓我們新建一個(gè)叫做 loops 的項(xiàng)目。

Rust 有三種循環(huán):?loop?、?while ?和 ?for?。我們每一個(gè)都試試。

使用 loop 重復(fù)執(zhí)行代碼

?loop ?關(guān)鍵字告訴 Rust 一遍又一遍地執(zhí)行一段代碼直到你明確要求停止。

作為一個(gè)例子,將 loops 目錄中的 src/main.rs 文件修改為如下:

文件名: src/main.rs

fn main() { loop { println!("again!"); } }

當(dāng)運(yùn)行這個(gè)程序時(shí),我們會(huì)看到連續(xù)的反復(fù)打印 ?again!?,直到我們手動(dòng)停止程序。大部分終端都支持一個(gè)快捷鍵,ctrl-c,來(lái)終止一個(gè)陷入無(wú)限循環(huán)的程序。嘗試一下:

$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.29s Running `target/debug/loops` again! again! again! again! ^Cagain!

符號(hào) ?^C? 代表你在這按下了ctrl-c。在 ?^C? 之后你可能看到也可能看不到 ?again!? ,這取決于在接收到終止信號(hào)時(shí)代碼執(zhí)行到了循環(huán)的何處。

幸運(yùn)的是,Rust 提供了一種從代碼中跳出循環(huán)的方法??梢允褂?nbsp;?break ?關(guān)鍵字來(lái)告訴程序何時(shí)停止循環(huán)?;貞浺幌略诘诙虏虏驴从螒虻?nbsp;“猜測(cè)正確后退出” 部分使用過(guò)它來(lái)在用戶猜對(duì)數(shù)字贏得游戲后退出程序。

我們?cè)诓轮i游戲中也使用了 ?continue?。循環(huán)中的 ?continue ?關(guān)鍵字告訴程序跳過(guò)這個(gè)循環(huán)迭代中的任何剩余代碼,并轉(zhuǎn)到下一個(gè)迭代。

從循環(huán)返回值

?loop ?的一個(gè)用例是重試可能會(huì)失敗的操作,比如檢查線程是否完成了任務(wù)。然而你可能會(huì)需要將操作的結(jié)果傳遞給其它的代碼。如果將返回值加入你用來(lái)停止循環(huán)的 ?break ?表達(dá)式,它會(huì)被停止的循環(huán)返回:

fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("The result is {result}"); }

在循環(huán)之前,我們聲明了一個(gè)名為 ?counter ?的變量并初始化為 ?0?。接著聲明了一個(gè)名為 ?result ?來(lái)存放循環(huán)的返回值。在循環(huán)的每一次迭代中,我們將 ?counter ?變量加 ?1?,接著檢查計(jì)數(shù)是否等于 ?10?。當(dāng)相等時(shí),使用 ?break ?關(guān)鍵字返回值 ?counter * 2?。循環(huán)之后,我們通過(guò)分號(hào)結(jié)束賦值給 ?result ?的語(yǔ)句。最后打印出 ?result ?的值,也就是 20。

循環(huán)標(biāo)簽:在多個(gè)循環(huán)之間消除歧義

如果存在嵌套循環(huán),?break ?和 ?continue ?應(yīng)用于此時(shí)最內(nèi)層的循環(huán)。你可以選擇在一個(gè)循環(huán)上指定一個(gè) 循環(huán)標(biāo)簽loop label),然后將標(biāo)簽與 ?break ?或 ?continue ?一起使用,使這些關(guān)鍵字應(yīng)用于已標(biāo)記的循環(huán)而不是最內(nèi)層的循環(huán)。下面是一個(gè)包含兩個(gè)嵌套循環(huán)的示例

fn main() { let mut count = 0; 'counting_up: loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {count}"); }

外層循環(huán)有一個(gè)標(biāo)簽 ?counting_up?,它將從 0 數(shù)到 2。沒(méi)有標(biāo)簽的內(nèi)部循環(huán)從 10 向下數(shù)到 9。第一個(gè)沒(méi)有指定標(biāo)簽的 ?break ?將只退出內(nèi)層循環(huán)。?break 'counting_up;? 語(yǔ)句將退出外層循環(huán)。這個(gè)代碼打印:

$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.58s Running `target/debug/loops` count = 0 remaining = 10 remaining = 9 count = 1 remaining = 10 remaining = 9 count = 2 remaining = 10 End count = 2

while 條件循環(huán)

在程序中計(jì)算循環(huán)的條件也很常見(jiàn)。當(dāng)條件為真,執(zhí)行循環(huán)。當(dāng)條件不再為真,調(diào)用 ?break? 停止循環(huán)。這個(gè)循環(huán)類(lèi)型可以通過(guò)組合 ?loop?、?if?、?else ?和 ?break ?來(lái)實(shí)現(xiàn);如果你喜歡的話,現(xiàn)在就可以在程序中試試。

然而,這個(gè)模式太常用了,Rust 為此內(nèi)置了一個(gè)語(yǔ)言結(jié)構(gòu),它被稱(chēng)為 ?while ?循環(huán)。示例 3-3 使用了 ?while?:程序循環(huán)三次,每次數(shù)字都減一。接著,在循環(huán)結(jié)束后,打印出另一個(gè)信息并退出。

文件名: src/main.rs

fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } println!("LIFTOFF!!!"); }

示例 3-3: 當(dāng)條件為真時(shí),使用 ?while ?循環(huán)運(yùn)行代碼

這種結(jié)構(gòu)消除了很多使用 ?loop?、?if?、?else ?和 ?break ?時(shí)所必須的嵌套,這樣更加清晰。當(dāng)條件為真就執(zhí)行,否則退出循環(huán)。

使用 for 遍歷集合

可以使用 ?while ?結(jié)構(gòu)來(lái)遍歷集合中的元素,比如數(shù)組。例如,看看示例 3-4。

文件名: src/main.rs

fn main() { let a = [10, 20, 30, 40, 50]; let mut index = 0; while index < 5 { println!("the value is: {}", a[index]); index += 1; } }

示例 3-4:使用 ?while ?循環(huán)遍歷集合中的元素

這里,代碼對(duì)數(shù)組中的元素進(jìn)行計(jì)數(shù)。它從索引 ?0? 開(kāi)始,并接著循環(huán)直到遇到數(shù)組的最后一個(gè)索引(這時(shí),?index < 5? 不再為真)。運(yùn)行這段代碼會(huì)打印出數(shù)組中的每一個(gè)元素:

$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.32s Running `target/debug/loops` the value is: 10 the value is: 20 the value is: 30 the value is: 40 the value is: 50

數(shù)組中的所有五個(gè)元素都如期被打印出來(lái)。盡管 ?index ?在某一時(shí)刻會(huì)到達(dá)值 ?5?,不過(guò)循環(huán)在其嘗試從數(shù)組獲取第六個(gè)值(會(huì)越界)之前就停止了。

但這個(gè)過(guò)程很容易出錯(cuò);如果索引長(zhǎng)度或測(cè)試條件不正確會(huì)導(dǎo)致程序 panic。例如,如果將 ?a? 數(shù)組的定義改為包含 4 個(gè)元素而忘記了更新條件 ?while index < 4?,則代碼會(huì) panic。這也使程序更慢,因?yàn)榫幾g器增加了運(yùn)行時(shí)代碼來(lái)對(duì)每次循環(huán)進(jìn)行條件檢查,以確定在循環(huán)的每次迭代中索引是否在數(shù)組的邊界內(nèi)。

作為更簡(jiǎn)潔的替代方案,可以使用 ?for ?循環(huán)來(lái)對(duì)一個(gè)集合的每個(gè)元素執(zhí)行一些代碼。?for ?循環(huán)看起來(lái)如示例 3-5 所示:

文件名: src/main.rs

fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } }

示例 3-5:使用 ?for ?循環(huán)遍歷集合中的元素

當(dāng)運(yùn)行這段代碼時(shí),將看到與示例 3-4 一樣的輸出。更為重要的是,我們?cè)鰪?qiáng)了代碼安全性,并消除了可能由于超出數(shù)組的結(jié)尾或遍歷長(zhǎng)度不夠而缺少一些元素而導(dǎo)致的 bug。

例如,在示例 3-4 的代碼中,如果你將 ?a? 數(shù)組的定義改為有四個(gè)元素,但忘記將條件更新為 ?while index < 4?,代碼將會(huì) panic。使用 ?for ?循環(huán)的話,就不需要惦記著在改變數(shù)組元素個(gè)數(shù)時(shí)修改其他的代碼了。

?for ?循環(huán)的安全性和簡(jiǎn)潔性使得它成為 Rust 中使用最多的循環(huán)結(jié)構(gòu)。即使是在想要循環(huán)執(zhí)行代碼特定次數(shù)時(shí),例如示例 3-3 中使用 ?while ?循環(huán)的倒計(jì)時(shí)例子,大部分 Rustacean 也會(huì)使用 ?for ?循環(huán)。這么做的方式是使用 ?Range?,它是標(biāo)準(zhǔn)庫(kù)提供的類(lèi)型,用來(lái)生成從一個(gè)數(shù)字開(kāi)始到另一個(gè)數(shù)字之前結(jié)束的所有數(shù)字的序列。

下面是一個(gè)使用 ?for ?循環(huán)來(lái)倒計(jì)時(shí)的例子,它還使用了一個(gè)我們還未講到的方法,?rev?,用來(lái)反轉(zhuǎn) range:

文件名: src/main.rs

fn main() { for number in (1..4).rev() { println!("{number}!"); } println!("LIFTOFF!!!"); }

這段代碼看起來(lái)更帥氣不是嗎?

總結(jié)

你做到了!這是一個(gè)大章節(jié):你學(xué)習(xí)了變量、標(biāo)量和復(fù)合數(shù)據(jù)類(lèi)型、函數(shù)、注釋、 ?if ?表達(dá)式和循環(huán)!如果你想要實(shí)踐本章討論的概念,嘗試構(gòu)建如下程序:

  • 相互轉(zhuǎn)換攝氏與華氏溫度。
  • 生成 n 階斐波那契數(shù)列。
  • 打印圣誕頌歌 “The Twelve Days of Christmas” 的歌詞,并利用歌曲中的重復(fù)部分(編寫(xiě)循環(huán))。

當(dāng)你準(zhǔn)備好繼續(xù)的時(shí)候,讓我們討論一個(gè)其他語(yǔ)言中 并不 常見(jiàn)的概念:所有權(quán)(ownership)。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)