Update src/ch03-05-control-flow.md

This commit is contained in:
Aaran Xu 2022-01-18 23:03:51 +08:00
parent 7f3f867509
commit 8965bfdd86

View File

@ -11,74 +11,43 @@
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-26-if-true/src/main.rs}}
```
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
所有的 `if` 表达式都以 `if` 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 `number` 的值是否小于 5。在条件为真时希望执行的代码块位于紧跟条件之后的大括号中。`if` 表达式中与条件关联的代码块有时被叫做 *arms*,就像第 2 章 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 部分中讨论到的 `match` 表达式中的分支一样。
所有的 `if` 表达式都以 `if` 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 `number` 的值是否小于 5。在条件为真时希望执行的代码块位于紧跟条件之后的大括号中。`if` 表达式中与条件关联的代码块有时被叫做 *arms*,就像第 2 章[“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number]<!-- ignore -->部分中讨论到的 `match` 表达式中的分支一样。
也可以包含一个可选的 `else` 表达式来提供一个在条件为假时应当执行的代码块,这里我们就这么做了。如果不提供 `else` 表达式并且条件为假时,程序会直接忽略 `if` 代码块并继续执行下面的代码。
尝试运行代码,应该能看到如下输出:
```text
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
Running `target/debug/branches`
condition was true
```console
{{#include ../listings/ch03-common-programming-concepts/no-listing-26-if-true/output.txt}}
```
尝试改变 `number` 的值使条件为 `false` 时看看会发生什么:
```rust,ignore
let number = 7;
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-27-if-false/src/main.rs:here}}
```
再次运行程序并查看输出:
```text
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
Running `target/debug/branches`
condition was false
```console
{{#include ../listings/ch03-common-programming-concepts/no-listing-27-if-false/output.txt}}
```
另外值得注意的是代码中的条件 **必须** `bool` 值。如果条件不是 `bool` 值,我们将得到一个错误。例如,尝试运行以下代码:
另外值得注意的是代码中的条件**必须**是 `bool` 值。如果条件不是 `bool` 值,我们将得到一个错误。例如,尝试运行以下代码:
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
fn main() {
let number = 3;
if number {
println!("number was three");
}
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-28-if-condition-must-be-bool/src/main.rs}}
```
这里 `if` 条件的值是 `3`Rust 抛出了一个错误:
```text
error[E0308]: mismatched types
--> src/main.rs:4:8
|
4 | if number {
| ^^^^^^ expected bool, found integer
|
= note: expected type `bool`
found type `{integer}`
```console
{{#include ../listings/ch03-common-programming-concepts/no-listing-28-if-condition-must-be-bool/output.txt}}
```
这个错误表明 Rust 期望一个 `bool` 却得到了一个整数。不像 Ruby 或 JavaScript 这样的语言Rust 并不会尝试自动地将非布尔值转换为布尔值。必须总是显式地使用布尔值作为 `if` 的条件。例如,如果想要 `if` 代码块只在一个数字不等于 `0` 时执行,可以把 `if` 表达式修改成下面这样:
@ -86,13 +55,7 @@ error[E0308]: mismatched types
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let number = 3;
if number != 0 {
println!("number was something other than zero");
}
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-29-if-not-equal-0/src/main.rs}}
```
运行代码会打印出 `number was something other than zero`
@ -104,29 +67,13 @@ fn main() {
<span class="filename">文件名: src/main.rs</span>
```rust
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");
}
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-30-else-if/src/main.rs}}
```
这个程序有四个可能的执行路径。运行后应该能看到如下输出:
```text
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
Running `target/debug/branches`
number is divisible by 3
```console
{{#include ../listings/ch03-common-programming-concepts/no-listing-30-else-if/output.txt}}
```
当执行这个程序时,它按顺序检查每个 `if` 表达式并执行第一个条件为真的代码块。注意即使 6 可以被 2 整除,也不会输出 `number is divisible by 2`,更不会输出 `else` 块中的 `number is not divisible by 4, 3, or 2`。原因是 Rust 只会执行第一个条件为真的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。
@ -140,28 +87,15 @@ number is divisible by 3
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-02/src/main.rs}}
```
<span class="caption">示例 3-2`if` 表达式的返回值赋给一个变量</span>
`number` 变量将会绑定到表示 `if` 表达式结果的值上。运行这段代码看看会出现什么:
```text
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.30 secs
Running `target/debug/branches`
The value of number is: 5
```console
{{#include ../listings/ch03-common-programming-concepts/listing-03-02/output.txt}}
```
记住,代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。在这个例子中,整个 `if` 表达式的值取决于哪个代码块被执行。这意味着 `if` 的每个分支的可能的返回值都必须是相同类型;在示例 3-2 中,`if` 分支和 `else` 分支的结果都是 `i32` 整型。如果它们的类型不匹配,如下面这个例子,则会出现一个错误:
@ -169,35 +103,13 @@ The value of number is: 5
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
fn main() {
let condition = true;
let number = if condition {
5
} else {
"six"
};
println!("The value of number is: {}", number);
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-31-arms-must-return-same-type/src/main.rs}}
```
当编译这段代码时,会得到一个错误。`if` 和 `else` 分支的值类型是不相容的,同时 Rust 也准确地指出在程序中的何处发现的这个问题:
```text
error[E0308]: if and else have incompatible types
--> src/main.rs:4:18
|
4 | let number = if condition {
| __________________^
5 | | 5
6 | | } else {
7 | | "six"
8 | | };
| |_____^ expected integer, found &str
|
= note: expected type `{integer}`
found type `&str`
```console
{{#include ../listings/ch03-common-programming-concepts/no-listing-31-arms-must-return-same-type/output.txt}}
```
`if` 代码块中的表达式返回一个整数,而 `else` 代码块中的表达式返回一个字符串。这不可行因为变量必须只有一个类型。Rust 需要在编译时就确切地知道 `number` 变量的类型,这样它就可以在编译时验证在每处使用的 `number` 变量的类型是有效的。若 `number` 的类型仅在运行时确定,则 Rust 将无法做到这一点;而且若编译器必须跟踪任意变量的多种假设类型,则编译器会变得更复杂,并且对代码的保证也会减少。
@ -217,19 +129,21 @@ Rust 有三种循环:`loop`、`while` 和 `for`。我们每一个都试试。
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
fn main() {
loop {
println!("again!");
}
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-32-loop/src/main.rs}}
```
当运行这个程序时,我们会看到连续的反复打印 `again!`,直到我们手动停止程序。大部分终端都支持一个快捷键,<span class="keystroke">ctrl-c</span>,来终止一个陷入无限循环的程序。尝试一下:
```text
<!-- manual-regeneration
cd listings/ch03-common-programming-concepts/no-listing-32-loop
cargo run
CTRL-C
-->
```console
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/loops`
again!
again!
@ -240,26 +154,28 @@ again!
符号 `^C` 代表你在这按下了<span class="keystroke">ctrl-c</span>。在 `^C` 之后你可能看到也可能看不到 `again!` ,这取决于在接收到终止信号时代码执行到了循环的何处。
幸运的是Rust 提供了另一种更可靠的退出循环的方式。可以使用 `break` 关键字来告诉程序何时停止循环。回忆一下在第 2 章猜数字游戏的 [“猜测正确后退出”][quitting-after-a-correct-guess] 部分使用过它来在用户猜对数字赢得游戏后退出程序。
幸运的是Rust 也提供了一种从代码中跳出循环的方法。可以使用 `break` 关键字来告诉程序何时停止循环。回忆一下在第 2 章猜数字游戏的 [“猜测正确后退出”][quitting-after-a-correct-guess]<!-- ignore --> 部分使用过它来在用户猜对数字赢得游戏后退出程序。
我们在猜数字游戏中也使用了 `continue`。循环中的 `continue` 关键字告诉程序跳过这个循环迭代中的任何剩余代码,并转到下一个迭代。
如果存在嵌套循环,`break` 和 `continue` 应用于此时最内层的循环。你可以选择在一个循环上指定一个**循环标签***loop label*),然后将标签与 `break``continue` 一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。下面是一个包含两个嵌套循环的示例:
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-32-5-loop-labels/src/main.rs}}
```
外层循环有一个标签 `counting_ up`,它将从 0 数到 2。没有标签的内部循环从 10 向下数到 9。第一个没有指定标签的 `break` 将只退出内层循环。`break'counting_up;` 语句将退出外层循环。这个代码打印:
```console
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-32-5-loop-labels/output.txt}}
```
#### 从循环返回
`loop` 的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果传递给其它的代码。如果将返回值加入你用来停止循环的 `break` 表达式,它会被停止的循环返回:
```rust
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-33-return-value-from-loop/src/main.rs}}
```
在循环之前,我们声明了一个名为 `counter` 的变量并初始化为 `0`。接着声明了一个名为 `result` 来存放循环的返回值。在循环的每一次迭代中,我们将 `counter` 变量加 `1`,接着检查计数是否等于 `10`。当相等时,使用 `break` 关键字返回值 `counter * 2`。循环之后,我们通过分号结束赋值给 `result` 的语句。最后打印出 `result` 的值,也就是 20。
@ -273,17 +189,7 @@ fn main() {
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number = number - 1;
}
println!("LIFTOFF!!!");
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-03/src/main.rs}}
```
<span class="caption">示例 3-3: 当条件为真时,使用 `while` 循环运行代码</span>
@ -297,57 +203,36 @@ fn main() {
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index = index + 1;
}
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-04/src/main.rs}}
```
<span class="caption">示例 3-4使用 `while` 循环遍历集合中的元素</span>
这里,代码对数组中的元素进行计数。它从索引 `0` 开始,并接着循环直到遇到数组的最后一个索引(这时,`index < 5` 不再为真)。运行这段代码会打印出数组中的每一个元素
在这里,代码对数组中的元素进行计数。它从索引 `0` 开始,并接着循环直到遇到数组的最后一个索引(这时,`index < 5` 不再为真)。运行这段代码会打印出数组中的每一个元素
```text
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50
```console
{{#include ../listings/ch03-common-programming-concepts/listing-03-04/output.txt}}
```
数组中的所有五个元素都如期被打印出来。尽管 `index` 在某一时刻会到达值 `5`,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了。
但这个过程很容易出错;如果索引长度不正确会导致程序 panic。这也使程序更慢因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
但这个过程很容易出错;如果索引长度或测试条件不正确会导致程序 panic。这也使程序更慢因为编译器增加了运行时代码来对每次循环进行条件检查以确定在循环的每次迭代中索引是否在数组的边界内。
但是这个过程很容易出错;如果索引值或测试条件不正确会导致程序 panic。例如如果将 `a` 数组的定义更改为包含 4 个元素,但忘记将条件更新为`while index < 4`则代码会出现 panic这也使程序更慢因为编译器增加了运行时代码来对每次循环进行条件检查以确定在循环的每次迭代中索引是否在数组的边界内
作为更简洁的替代方案,可以使用 `for` 循环来对一个集合的每个元素执行一些代码。`for` 循环看起来如示例 3-5 所示:
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-05/src/main.rs}}
```
<span class="caption">示例 3-5使用 `for` 循环遍历集合中的元素</span>
当运行这段代码时,将看到与示例 3-4 一样的输出。更为重要的是,我们增强了代码安全性,并消除了可能由于超出数组的结尾或遍历长度不够而缺少一些元素而导致的 bug。
例如,在示例 3-4 的代码中,如果从数组 `a` 中移除一个元素但忘记将条件更新为 `while index < 4`,代码将会 panic。使用 `for` 循环的话,就不需要惦记着在改变数组元素个数时修改其他的代码了。
使用 `for` 循环的话,就不需要惦记着在改变数组元素个数时修改其他的代码了,就像使用清单 3-4 中使用的方法一样
`for` 循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。即使是在想要循环执行代码特定次数时,例如示例 3-3 中使用 `while` 循环的倒计时例子,大部分 Rustacean 也会使用 `for` 循环。这么做的方式是使用 `Range`,它是标准库提供的类型,用来生成从一个数字开始到另一个数字之前结束的所有数字的序列。
@ -356,12 +241,7 @@ fn main() {
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-34-for-range/src/main.rs}}
```
这段代码看起来更帅气不是吗?
@ -374,9 +254,9 @@ fn main() {
* 生成 n 阶斐波那契数列。
* 打印圣诞颂歌 “The Twelve Days of Christmas” 的歌词,并利用歌曲中的重复部分(编写循环)。
当你准备好继续的时候,让我们讨论一个其他语言中 **并不** 常见的概念所有权ownership
当你准备好继续的时候,让我们讨论一个其他语言中**并不**常见的概念所有权ownership
[comparing-the-guess-to-the-secret-number]:
ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number
ch02-00-guessing-game-tutorial.html#比较猜测的数字和秘密数字
[quitting-after-a-correct-guess]:
ch02-00-guessing-game-tutorial.html#quitting-after-a-correct-guess
ch02-00-guessing-game-tutorial.html#猜测正确后退出