@ -2,9 +2,9 @@
> [ch03-05-control-flow.md ](https://github.com/rust-lang/book/blob/main/src/ch03-05-control-flow.md )
> < br >
> commit 4b86611b0e63151f6e166edc9ecf870d553e1f09
> commit 1b8746013079f2e2ce1c8e85f633d9769778ea7f
根据条件是否为真来决定是否执行某些代码, 以及根据条件是否为真来重复运行一段代码是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 `if` 表达式和循环。
根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段代码的能力 是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 `if` 表达式和循环。
### `if` 表达式
@ -15,19 +15,9 @@
< 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* ,就像第二章 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 部分中讨论到的 `match` 表达式中的分支一样。
也可以包含一个可选的 `else` 表达式来提供一个在条件为假时应当执行的代码块,这里我们就这么做了。如果不提供 `else` 表达式并且条件为假时,程序会直接忽略 `if` 代码块并继续执行下面的代码。
@ -35,27 +25,19 @@ fn main() {
尝试运行代码,应该能看到如下输出:
```console
$ 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
{{#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}}
```
再次运行程序并查看输出:
```console
$ 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
{{#include ../listings/ch03-common-programming-concepts/no-listing-27-if-false/output.txt}}
```
另外值得注意的是代码中的条件 ** 必须** 是 `bool` 值。如果条件不是 `bool` 值,我们将得到一个错误。例如,尝试运行以下代码:
@ -63,32 +45,13 @@ condition was false
< 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 抛出了一个错误:
```console
$ 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
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308` .
error: could not compile `branches`
To learn more, run the command again with --verbose.
{{#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` 表达式修改成下面这样:
@ -96,13 +59,7 @@ To learn more, run the command again with --verbose.
< 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` 。
@ -114,29 +71,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}}
```
这个程序有四个可能的执行路径。运行后应该能看到如下输出:
```console
$ 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
{{#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 只会执行第一个条件为真的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。
@ -150,16 +91,7 @@ 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 >
@ -167,11 +99,7 @@ fn main() {
`number` 变量将会绑定到表示 `if` 表达式结果的值上。运行这段代码看看会出现什么:
```console
$ 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
{{#include ../listings/ch03-common-programming-concepts/listing-03-02/output.txt}}
```
记住,代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。在这个例子中,整个 `if` 表达式的值取决于哪个代码块被执行。这意味着 `if` 的每个分支的可能的返回值都必须是相同类型;在示例 3-2 中,`if` 分支和 `else` 分支的结果都是 `i32` 整型。如果它们的类型不匹配,如下面这个例子,则会出现一个错误:
@ -179,38 +107,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 也准确地指出在程序中的何处发现的这个问题:
```console
$ 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
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308` .
error: could not compile `branches`
To learn more, run the command again with --verbose.
{{#include ../listings/ch03-common-programming-concepts/no-listing-31-arms-must-return-same-type/output.txt}}
```
`if` 代码块中的表达式返回一个整数,而 `else` 代码块中的表达式返回一个字符串。这不可行, 因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 `number` 变量的类型,这样它就可以在编译时验证在每处使用的 `number` 变量的类型是有效的。如果`number`的类型仅在运行时确定,则 Rust 无法做到这一点;且编译器必须跟踪每一个变量的多种假设类型,那么它就会变得更加复杂,对代码的保证也会减少。
@ -230,11 +133,7 @@ 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 > ,来终止一个陷入无限循环的程序。尝试一下:
@ -260,65 +159,21 @@ again!
如果存在嵌套循环,`break` 和 `continue` 应用于此时最内层的循环。你可以选择在一个循环上指定一个 ** 循环标签**( *loop label*),然后将标签与 `break` 或 `continue` 一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。下面是一个包含两个嵌套循环的示例
```rust
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);
}
{{#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
$ 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
{{#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。
@ -332,17 +187,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 >
@ -356,16 +201,7 @@ 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 >
@ -373,33 +209,19 @@ fn main() {
这里,代码对数组中的元素进行计数。它从索引 `0` 开始,并接着循环直到遇到数组的最后一个索引(这时,`index < 5 ` 不再为真)。运行这段代码会打印出数组中的每一个元素:
```console
$ 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
{{#include ../listings/ch03-common-programming-concepts/listing-03-04/output.txt}}
```
数组中的所有五个元素都如期被打印出来。尽管 `index` 在某一时刻会到达值 `5` ,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了。
但这个过程很容易出错;如果索引长度或测试条件不正确会导致程序 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 >
@ -415,12 +237,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}}
```
这段代码看起来更帅气不是吗?