Rust流程控制

[TOC]

Rust流程控制


表达式的多种形式

语句?表达式?

语句在英文中是 statement,表达式则是 expression。我们可能常常听说过“赋值语句”或者“算数表达式”这些名词,但是你有想过为什么不是“赋值表达式”吗?语句和表达式有一个重要的区别在于,表达式总是返回一个值,而语句不会。例如:

1 + 1;     // 这是表达式
let a = 1; // 这是语句

Rust 是一个基于表达式的语言,这意味着它的大多数代码都有一个返回值。除了以下几种语法:

  • 变量声明
  • 模块声明
  • 函数声明
  • 结构体声明
  • 枚举声明

你可能会奇怪为什么 if…else… 不在上面的列表中,事实上,在 Rust 中,条件与循环并不是语句,而是表达式,这意味着它可以有返回值!这可能是你首先会疑惑的地方:这看起来和 C 不太一样!

if 表达式,实现类似 C 语言中的三元表达式的功能:

let cond = true;
let a = if cond {
    42
} else {
    24
};

loop 表达式的 break 语句后可跟着一个返回值返回:

let mut s = 0;
let mut n = 10;
let a = loop {
    if n < 0 {
        break s;
    }
    s += n;
    n -= 1;
};
println!("{:?}", a);

if-else选择结构

Rust 中的 if-else 语法与其他语言类似,与许多语言不同,if 后的布尔条件不需要用括号括起来。

如果使用 if-else 返回一个值,则所有分支必须返回相同的类型:

fn main() {
    let n = 5;

    if n < 0 {
        print!("{} is negative", n);
    } else if n > 0 {
        print!("{} is positive", n);
    } else {
        print!("{} is zero", n);
    }

    let m = if n < 0 {
        2.0
    } else {
        3.0
    };
    println!("{}", m);
}

使用loop循环

Rust 提供了一个 loop 关键字来表示无限循环。

break 语句可用于随时退出循环,而 continue 语句可用于跳过其余的迭代并开始新的循环:

// 计算 1 + 2 + ... + 100
fn main() {
    let mut sum = 0;
    let mut n = 0;
    loop {
        sum += n;
        n += 1;
        if n > 100 {
            break
        }
    }
    println!("{}", sum);
}

break 后可带上一个值,返回给一个变量,例如:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

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

    assert_eq!(result, 20);
}

上面这种写法一般用于重试操作。


使用while循环

while 是带循环条件的 loop。当条件为假时,结束循环。我们使用一个例子介绍 while 的语法。

fizzbuzz 是一个非常简单的编程任务,它的描述是:编写一个程序,打印从 1 到 100 的数字,对于 3 的倍数,打印 Fizz 而不是数字,对于 5 的倍数,打印 Buzz。

例如:

1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14,
Fizz Buzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26,
Fizz, 28, 29, Fizz Buzz, 31, 32, Fizz, 34, Buzz, Fizz, ...

代码示例:

fn main() {
    // A counter variable
    let mut n = 1;

    // Loop while `n` is less than 101
    while n < 101 {
        if n % 15 == 0 {
            println!("fizzbuzz");
        } else if n % 3 == 0 {
            println!("fizz");
        } else if n % 5 == 0 {
            println!("buzz");
        } else {
            println!("{}", n);
        }
        // Increment counter
        n += 1;
    }
}

使用for_range进行迭代

Rust 中的 for … in … 语法可以用来遍历一个迭代器。有多种方式可以创建一个迭代器,最简单也是最常用的方式如下所示:

  • a..b:这将创建一个包含 a 而不包含 b,步长为 1 的迭代器。
  • a..=b:这将创建一个包含 a 且包含 b,步长为 1 的迭代器。
fn main() {
    // 下面的代码将打印出 0, 1, 2, 3, 4
    for i in 0..5 {
        println!("{}", i);
    }
    // 下面的代码将打印出 0, 1, 2, 3, 4, 5
    for i in 0..=5 {
        println!("{}", i);
    }
}

for … in … 语法的第二个重要使用场景是遍历数组,但这需要我们首先将数组转换为一个迭代器,这可以通过 .iter().iter_mut() 实现,区别在于后者是可变的。

fn main() {
    let mut myarray = [1, 2, 3];
    for i in myarray.iter() {
        println!("{}", i);
    }

    for i in myarray.iter_mut() {
        *i *= 2;
    }
    for i in myarray.iter() {
        println!("{}", i);
    }
}

Rust中的match

match 是 Rust 中的模式匹配语法,它允许开发者将一个值与一系列模式进行比较,然后根据模式匹配的结果执行特定的代码。它与其它语言中的 switch … case … 语法相近,但显然更加强大。在先前的课程中,我们已经知道 match 语法可以配合 enum 一起使用。

enum Alphabet {
    A,
    B,
}

fn main() {
    let letter = Alphabet::A;

    match letter {
        Alphabet::A => {
            println!("It's A");
        }
        Alphabet::B => {
            println!("It's B")
        }
    }
}

另一方面,match 也经常用来匹配整型数据,例如当我们想知道一个 u8 整数是否是某几个特殊数字时:

fn main() {
    let n: u8 = 42;

    match n {
        42 => {
            println!("bingo!")
        }
        _ => {
            println!("{}", n);
        }
    }
}

if_let语法糖

if let 是 Rust 中的一个语法糖,它主要简化了 match 操作。如果我们仅仅想当匹配发生时做某些操作,那么就可以使用 if let 替代 match。

例如当我们只想要变量 letter 为 A 时,打印消息,而忽略所有其它选项。可分别使用 match 或 if let 实现。

enum Alphabet {
    A,
    B,
}

fn main() {
    let letter = Alphabet::A;

    match letter {
        Alphabet::A => {
            println!("It's A");
        }
        _ => {}
    }

    if let Alphabet::A = letter {
        println!("It's A");
    }
}

if let 同样可以匹配带参数的枚举

enum Symbol {
    Char(char),
    Number,
}

fn main() {
    let symbol = Symbol::Char('A');

    if let Symbol::Char(char) = symbol {
        println!("{:?}", char);
    }
}

while_let语法糖

与 if let 相似的还有一个 while let 语法糖,只是 while let 语法糖很少被使用:

enum Alphabet {
    A,
    B,
}

fn main() {
    let mut letter = Alphabet::A;

    while let Alphabet::A = letter {
        println!("It's A");
        letter = Alphabet::B;
    }
}

函数与方法

函数

函数的定义以 fn 开始,它的参数是带类型注释的,就像变量一样,如果函数返回值,则必须在箭头 -> 之后指定返回类型。例如如下的斐波那契函数:

fn fibonacci(n: u64) -> u64 {
    if n < 2 {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

方法

方法是附加到对象的函数,这些方法可以通过 self 关键字访问对象及其其他方法的数据。方法在 impl 块下定义。访问对象中的方法有两种方式,如果方法带 self 参数,使用 . ,否则使用 :: 。示例:

#[derive(Debug)]
struct Point {
    x: u64,
    y: u64,
}

impl Point {
    fn new(x: u64, y: u64) -> Point {
        Point { x, y }
    }

    fn get_x(&self) -> u64 {
        return self.x;
    }

    // 如果需要修改结构体中的数据, self 前面需要带上 mut
    fn set_x(&mut self, x: u64) {
        self.x = x;
    }
}

fn main() {
    let mut p = Point::new(1, 2);
    println!("{:?}", p);
    println!("{:?}", p.get_x());
    p.set_x(3);
    println!("{:?}", p.get_x());
}

函数与闭包

Rust 的闭包是一种匿名函数,它可以从它的上下文中捕获变量的值。闭包使用 || -> 语法定义。闭包可以被保存在变量中:

fn main() {
    let myclosures = |n: u32| -> u32 { n * 3 };
    println!("{}", myclosures(1))
}

move 关键字可以从闭包的运行环境中捕获值,它最常用的场景是将主线程中的一个变量传递到子线程中,如下所示:

use std::thread;

fn main() {
    let hello_message = "Hello World!";

    thread::spawn(move || println!("{}", hello_message)).join();
}

作业: 斐波那契数列

斐波那契数列(Fibonacci)在数学上以递归的方法来定义:

F(0) = 0
F(1) = 1
F(n) = F(n - 1) + F(n - 2)

用文字来说,就是斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。首几个斐波那契数是:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233

请编写程序,程序可输出斐波那契的第任意项数字。


高阶函数

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入
  • 输出一个函数

在数学中它们也叫做算子(运算符)或泛函。高阶函数是函数式编程中非常重要的一个概念。

将函数作为参数传递:

fn calc(method: fn(u32, u32) -> u32, a: u32, b: u32) -> u32 {
    method(a, b)
}

fn add(a: u32, b: u32) -> u32 {
    a + b
}

fn sub(a: u32, b: u32) -> u32 {
    a - b
}

fn main() {
    println!("{}", calc(add, 10, 20));
    println!("{}", calc(sub, 20, 10));
}

将函数作为返回值:

fn calc(method: &str) -> fn(u32, u32) -> u32 {
    match method {
        "add" => add,
        "sub" => sub,
        _ => unimplemented!(),
    }
}

fn add(a: u32, b: u32) -> u32 {
    a + b
}

fn sub(a: u32, b: u32) -> u32 {
    a - b
}

fn main() {
    println!("{}", calc("add")(10, 20));
    println!("{}", calc("sub")(20, 10));
}

发散函数

发散函数永远不会被返回,它们的返回值被标记为 !,这是一个空类型。

fn foo() -> ! {
    panic!("This call never returns.");
}

发散函数与空返回值函数不同,空返回值函数可以被返回:

fn some_fn() {
    ()
}

fn main() {
    let a: () = some_fn();
    println!("This function returns and you can see this line.")
}

发散函数最大的用处是通过 Rust 的类型检查系统,例如,在前面的小节中我们知道 Rust 的 if-else 表达式必须返回相同的类型, 但是如果使用发散函数,下面的代码也是能通过编译的:

fn foo() -> ! {
    panic!("This call never returns.");
}

fn main() {
    let a = if true {
        10
    } else {
        foo()
    };
    println!("{}", a);
}

实践:猜数字游戏

这道题来自 Rust 的官方文档。我们将实现一个经典的初学者编程问题:猜谜游戏。它的工作原理是:程序将生成一个介于 1 和 100 之间的随机整数,然后提示玩家输入猜测。输入猜测后,程序将指示猜测是过低还是过高。如果猜测正确,游戏将打印一条祝贺信息并退出。

需要使用到的一些工具:

  • 使用 rand 库生成随机数
  • 使用 std::io 标准库获取用户输入

课程代码:

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 1、if 1. if-else if -else 2. 表达式语句 2、for 1. for(x = 0; x <...
    xiongzenghui阅读 685评论 0 2
  • RUST 学习日记 第17课 ——流程控制 0x00 回顾与开篇 有关Rust数据类型的知识暂时告一段落了,从这...
    L我是小学生阅读 293评论 0 0
  • 通用编程概念 变量与可变性 变量默认不可变,如需要改变,可在变量名前加 mut 使其可变。例如:let mut a...
    soojade阅读 12,532评论 2 30
  • 一、简介 Rust是Mozilla公司推出的一门全新的编程语言,1.0版本于2015年5月15日正式对外发布。作为...
    区块链习生阅读 2,253评论 1 1
  • 一、简介 Rust是Mozilla公司推出的一门全新的编程语言,1.0版本于2015年5月15日正式对外发布。作为...
    区块链习生阅读 2,484评论 0 1