美文网首页
Rust流程控制

Rust流程控制

作者: 端碗吹水 | 来源:发表于2022-06-09 09:47 被阅读0次

    [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;
                }
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Rust流程控制

          本文链接:https://www.haomeiwen.com/subject/nqmumrtx.html