rust流程控制

控制流
根据条件是否为真来决定是否执行某段代码,以及在条件为真时反复执行某段代码,是绝大多数编程语言的基本组成模块。在 Rust 中,最常见的控制执行流程的结构是 if 表达式和循环。

if 表达式

if 表达式让你根据条件对代码进行分支。你提供一个条件,然后声明:“如果条件成立,就执行这段代码;如果条件不成立,就不执行这段代码。”

projects 目录下新建一个名为 branches 的项目来探索 if 表达式。在 src/main.rs 文件中输入以下内容:

文件名:src/main.rs

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

所有 if 表达式都以关键字 if 开头,后跟一个条件。上例中的条件检查变量 number 的值是否小于 5。在条件后用花括号包裹的代码块是条件为真时要执行的内容。与 if 表达式中条件关联的代码块有时被称为“分支”(arm),类似于第 2 章“猜数游戏”一节中讨论的 match 表达式的分支。

可以选择性地添加 else 表达式(如上例所示),以便在条件为假时执行另一段代码。如果不提供 else,条件为假时程序会直接跳过 if 块,继续执行后面的代码。

运行这段代码,你会看到以下输出:

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

number 的值改成使条件为假的值,看看会发生什么:

let number = 7;

再次运行程序,输出如下:

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

注意:这里的条件必须是 bool 类型。如果条件不是 bool,会得到一个错误。例如,运行以下代码:

文件名:src/main.rs(这段代码无法编译!)

fn main() {
    let number = 3;

    if number {
        println!("number was three");
    }
}

此时 if 条件的值是整数 3,Rust 会报错:

$ 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

错误提示 Rust 期望 bool 却得到了整数。与 Ruby 或 JavaScript 等语言不同,Rust 不会自动把非布尔类型转换为布尔类型。必须显式地提供布尔值作为 if 的条件。如果想让代码块只在数字不为 0 时运行,可以这样写:

文件名:src/main.rs

fn main() {
    let number = 3;

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

运行这段代码会打印 number was something other than zero

else if 处理多重条件

可以把 ifelse 组合成 else if 表达式来处理多重条件。例如:

文件名: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");
    }
}

这段程序有四种可能的执行路径。运行后输出:

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

程序会依次检查每个 if 表达式,执行第一个条件为真的分支。注意 6 也能被 2 整除,但不会打印 number is divisible by 2,也不会执行 else 块。Rust 一旦找到第一个为真的条件就停止检查其余条件。

过多的 else if 会让代码变得杂乱,如果超过一个,可以考虑用第 6 章介绍的 match 结构来重构。

let 语句中使用 if

因为 if 是表达式,所以可以把它放在 let 语句的右侧,把结果赋值给一个变量,如示例 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 表达式的结果赋给变量

变量 number 将绑定到 if 表达式求值后的结果。运行这段代码:

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

记住,代码块的值是其最后一个表达式的值,数字本身也是表达式。此时整个 if 表达式的值取决于哪个分支被执行。这意味着每个分支可能返回的值必须是同一类型;在示例 3-2 中,if 分支和 else 分支的结果都是 i32 整数。如果类型不匹配,如下例所示,会得到编译错误:

文件名:src/main.rs(这段代码无法编译!)

fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" };

    println!("The value of number is: {number}");
}

编译这段代码会报错,ifelse 分支的类型不兼容:

$ 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`

if 分支返回整数,else 分支返回字符串,这行不通,因为变量必须有单一类型,而 Rust 在编译时就要确定变量 number 的类型。如果类型只能在运行时确定,编译器将变得更复杂,对代码的保证也会减少。


用循环实现重复

多次执行一段代码往往很有用。Rust 提供了几种循环,它们会从头到尾执行循环体,然后立刻回到开头。为了实验循环,我们新建一个名为 loops 的项目。

Rust 有三种循环:loopwhilefor。我们逐一尝试。

loop 重复执行

关键字 loop 告诉 Rust 反复执行一段代码,直到你显式让它停止。

loops 目录下的 src/main.rs 改成以下内容:

文件名:src/main.rs

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

运行后你会看到 again! 不断打印,直到手动停止。大多数终端支持用 ctrl-c 中断陷入无限循环的程序:

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

符号 ^C 表示你按下了 ctrl-c,其后是否出现 again! 取决于中断信号到达时循环的位置。

Rust 也支持用代码跳出循环。在循环体内使用 break 关键字即可停止循环。还记得第 2 章猜数游戏中,当用户猜对数字时我们用 break 退出程序。

我们也在猜数游戏中用过 continue,它让程序跳过本次迭代剩余代码,直接开始下一次迭代。

从循环返回值

有时需要在循环中重试可能失败的操作,例如检查线程是否完成任务,并把结果传递给外部代码。可以在用于停止循环的 break 后加上要返回的值,该值会被传出循环,示例如下:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

循环前声明变量 counter 并初始化为 0,然后声明变量 result 存放循环返回值。每次迭代 counter 加 1,当等于 10 时用 break counter * 2 返回值。循环后用分号结束赋值语句,最后打印 result 的值 20。

你也可以在循环内使用 returnbreak 仅退出当前循环,return 会立即退出整个函数。

用循环标签消除歧义

如果有嵌套循环,breakcontinue 默认作用于最内层循环。可以给循环加上标签,然后用 breakcontinue 指定作用于哪个标签的循环。标签以单引号开头。以下示例包含两个嵌套循环:

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}");
}

外层循环标签为 'counting_up,从 0 数到 2;无标签的内层循环从 10 倒计到 9。第一个不带标签的 break 只退出内层循环;break 'counting_up; 会退出外层循环。输出如下:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished `dev` profile [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

程序通常需要在循环中评估条件。条件为真时循环运行,条件为假时调用 break 停止循环。虽然可以用 loopifelsebreak 组合实现,但这种模式非常常见,Rust 提供了内置的 while 循环。示例 3-3 中用 while 让程序倒数 3 次,然后打印消息并退出。

文件名:src/main.rs

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

示例 3-3:用 while 循环在条件为真时运行代码

这种写法避免了使用 loopifelsebreak 时必须的嵌套,更清晰。条件为真时运行代码,否则退出循环。

for 遍历集合

也可以用 while 结构遍历集合(如数组)的元素。示例 3-4 中的循环打印数组 a 的每个元素。

文件名: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 循环遍历集合元素

代码从索引 0 开始,循环到数组最后一个索引。运行后打印:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished `dev` profile [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

然而这种方法容易出错:如果索引或测试条件写错,可能导致 panic。例如,把数组改成 4 个元素却忘了把条件改成 index < 4,就会 panic。此外,每次迭代都要运行时检查索引是否越界,效率较低。

更简洁安全的做法是使用 for 循环,为集合中的每一项执行代码。示例 3-5 展示了 for 循环的写法。

文件名:src/main.rs

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

示例 3-5:用 for 循环遍历集合元素

运行后输出与示例 3-4 相同。更重要的是,代码更安全,消除了越界或遗漏元素的可能性。如果数组元素数量改变,无需像示例 3-4 那样修改其他代码。

由于安全与简洁,for 循环是 Rust 中最常用的循环结构。即使像示例 3-3 那样需要固定次数的倒数,大多数 Rustaceans 也会用 for 循环。可以使用标准库提供的 Range 生成连续整数序列,并用 rev 反转范围:

文件名:src/main.rs

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

这段代码看起来更简洁吧?


总结

本章内容不少:你学习了变量、标量和复合数据类型、函数、注释、if 表达式和循环!为了巩固本章概念,可以尝试编写以下程序:

  • 实现华氏度与摄氏度的相互转换。
  • 生成第 n 个斐波那契数。
  • 利用歌曲中的重复结构打印圣诞颂歌《圣诞十二日》的歌词。

准备好后,我们将讨论 Rust 中其他语言不常见的概念:所有权。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值