【在主画面加入捷径】
       
【选择语系】
繁中 简中

Rust 程序设计教学:控制流程 (Control Structure)

【赞助商连结】

    到目前为止,我们的程序都是由上往下依序执行。透过控制流程,可以藉由改变程序执行的顺序而达到不同的效果。

    控制流程分为两种:

    • 条件控制 (condition)
    • 循环 (loop)

    我们将在本文中介绍这些控制流程。

    条件控制

    条件控制的作用在判断,符合特定条件时,才执行特定区块内的程序代码,使得程序可以依不同情境改变其行为。Rust 的条件控制有 ifmatch

    if

    最常用的条件叙述为 if,其想法如以下流程图:

    if 区块

    伪代码 (pseudo code) 是一种介于文字叙述和程序代码间的程序表达方式,在撰写程序代码前,可先使用伪代码整理流程,其好处在于可用抽象的方式表达程序的执行步骤。伪代码没有固定的撰写方式,有些会写得很像文字,有些会写得很像数学公式或程序代码。在撰写伪代码时,会先省略程序代码的实现细节,专注在抽象思路上。

    if 写成伪代码如下:

    以下是一个实例:

    if 还可以选择性加入 else 区块,其想法如下:

    if-else 区块

    写成伪代码如下:

    以下是一个实例:

    如果有更复杂的条件,可以用 if-else if-else 区块,其想法如下:

    if-else if-else 区块

    写成伪代码如下:

    其中 else if 区块可视需要重覆多次,而最后的 else 区块可省略。

    以下是一个实例:

    if 也可以作为回传值,如下例:

    利用 if 回传值时,要注意两点。首先,回传值不要加分号 ;,第二,if 区块后要加分号。在 Rust 中,没加分号的程序代码为表达式 (expression),而有加分号的程序代码为叙述 (statement)。在本程序中,”A”、”B” … “F” 皆为回传值,视为表达式,if 区块本身也为表达式。除了回传值以外,程序代码都要写成叙述,故最后需再加上分号。

    match

    对于多个 if-else if-else,可以用 match 简化。范例如下:

    虽然 Rust 的 match 类似其他语言的 switch,但是 match 要枚举出所有可能的情形,否则会引发错误。像是下列看似正常的程序:

    却引发了以下错误:

    通常,就是在最后一个条件加上一个底线 _,作为哑变量,代表所有其他未符合的情形即可。

    matchif 相同,也可以做为回传值。如下例:

    在本程序中,只要符合 vowel 的,都会回传 vowel,所以,我们在第二个条件以所有的英文字母为条件也没关系,符合 vowel 的部分已排除。

    循环

    循环的作用在重覆,符合特定条件时,重覆执行某区块的程序代码,减少输入重覆的程序代码。Rust 的循环有 loopwhilefor

    loop

    loop 是最简单的一种循环,其想法如下:

    loop 循环

    一进入 loop 循环后,就不间断地反覆执行区块内的程序代码,也就是俗称的无穷循环。无穷循环可能是循环条件没写好所造成的 bug,不过,某些程序也是会用到无穷循环,像是 游戏引擎就是一个很大的无穷循环。通常无穷循环会搭配中断叙述,在本章后面会提到相关的内容。

    如果写成伪代码则是以下形式:

    以下是程序范例:

    注:读者执行此程序后,可按 Ctrl + c 中断此程序。

    while

    while 在程序满足终止条件前,会不间断地执行该区块内的程序代码。其想法如下:

    while

    我们在使用 while 时,会在其区块内加入改变程序状态的程序代码,否则,就变无穷循环了,这通常不会是我们期待的效果。

    若写成伪代码,其形式如下:

    以下是实例:

    以下的 while 循环和 loop 循环等价:

    但是,Rust 官方手册有提到,如果在程序代码中明确想用无穷循环时,loopwhile true 来得好,这会影响到程序的优化。

    for

    for 循环和 while 循环不同,for 会有明确的执行次数。Rust 的 for 循环的想法如下:

    Rust 的 for 循环

    在这个图中,迭代器 (iterator) 是一个相对陌生的概念。首先,要知道容器 (collection) 的概念,容器用来存放数据,程序设计者可操作容器,藉此处理数据。以下是一个假想的线性容器:

    容器

    读者可以想像得到,对于不同的容器,走访其内部的数据的方式各自不同。透过迭代器,使用者可以在不知道容器内部实现的情形下,走访某个容器中所有的数据。for 循环会自动走访迭代器,并在迭代器结束时中断循环。

    以伪代码的形式表示如下:

    在 Rust 中,透过 range 即可提供迭代器。假设我们要从 1 数到 10,用 range 会写成 1..11,其中包括起始点 1,但不包括结尾点 11。以下为实例:

    如果我们不需要使用迭代器的计数,只要执行特定的次数,可使用哑变量,实例如下:

    如果我们的计数不是以 1 递增呢?现阶段来说,要使用一些函数式程序设计的方法,对初学者来说可能略为困难。实例如下:

    Rust nightly 版本 (于 2017 年 8 月 23 日实测),使用了新的函数 step_by,使得计数的撰写变简单。实例如下:

    由于此项目尚未稳定下来,虽然有这个方法,但不建议于平日的程序代码中使用。或者,可以用等效的 while 循环代替,范例如下:

    改变循环运作

    我们看一下以下的范例:

    在这个程序中,我们使用变量 flag 搭配 while 循环来控制循环的运作。除了利用程序状态外,Rust 提供 breakcontinue 这两个关键字,使得循环的控制更简洁,而不需要额外的 flag。

    break 的作用是中断循环,通常会写在条件控制内。以上的范例可以改写如下:

    continue 不会中断循环,但会跳过同一个循环内该指令之后的程序代码。范例如下:

    循环标签

    对于较复杂的循环,如果想要精确地控制循环的运作,可用 breakcontinue 搭配循环标签 (loop label)。范例如下 (摘自 Rust 官方文件):

    (案例选读) 终极密码

    我们以终极密码这个常见的游戏做为本节的案例。本游戏采用以下的守则:给定某个特定范围,随机选定一个数字。玩家试着猜这个数字,如果猜对,游戏就结束,否则,就继续猜。

    将我们本案例的想法以伪代码表示如下:

    我们做了一些小改良,在以下范例中,会判断使用者的输入值,限制使用者输入的值在一个合理的范围内。在本范例中,为了要产生乱数,我们使用 rand 套件,要修改 *Cargo.toml*,加入以下内容:

    这里附上范例程序代码,仅供参考:

    由于从使用者接收到的输入是字串,必需要转型为整数后才能使用。字串转换整数那段程序代码用到了 enum 和 generics 等新的概念。基本上,这段程序代码的意思是,接收 parse 回传的结果,根据不同的回传值给予相对的的行为。我们将于后续章节介绍相关的概念。