详解Rust中三种循环(loop,while,for)的使用

 更新时间:2022年09月29日 14:53:44   作者:古明地觉  
我们常常需要重复执行同一段代码,针对这种场景,Rust 提供了多种循环(loop)工具。一个循环会执行循环体中的代码直到结尾,并紧接着回到开头继续执行。而 Rust 提供了 3 种循环:loop、while 和 for,下面逐一讲解

楔子

我们常常需要重复执行同一段代码,针对这种场景,Rust 提供了多种循环(loop)工具。一个循环会执行循环体中的代码直到结尾,并紧接着回到开头继续执行。

而 Rust 提供了 3 种循环:loop、while 和 for,下面逐一讲解。

loop 循环

我们可以使用 loop 关键字来指示 Rust 反复执行某一段代码,直到我们显式地声明退出为止。

fn main() {
    loop {
        println!("hello world");
    }
}

这段代码会不停地在终端中打印 hello world,我们只能使用 Ctrl + C 来终止这种陷入无限循环的程序。当然,Rust 提供了另外一种更加可靠的循环退出方式,可以在循环中使用 break 关键字来通知程序退出循环。

fn main() {
    let mut x = 1;  // x 可变
    loop {
        println!("hello world");
        if x == 5 {
            break;
        }
        // 注意 x 必须是可变的
        // 否则此处报错
        x += 1;
    }
    /*
    hello world
    hello world
    hello world
    hello world
    hello world
     */
}

打印了五遍就停止了,没什么好说的。但 loop 循环还支持返回值,我们举个例子:

fn main() {
    let mut x = 1;
    let y = loop {
        if x == 5 {
            // break 之后的值就是整个 loop 的返回值
            break x * 2;
        }
        x += 1;
    };
    println!("y = {}", y);  // y = 10
}

如果 break 后面没有值,那么整个 loop 返回的就是空元组:

fn main() {
    let mut x = 1;
    let y = loop {
        if x == 5 {
            break;
        }
        x += 1;
    };
    println!("y = {:?}", y);  // y = ()
}

需要说明的是,无论 break 后面有没有分号,它都是整个 loop 循环的返回值。

既然是 loop 循环是一个表达式,那么除了赋值给一个变量之外,肯定也可以作为函数的返回值:

fn f() -> i32 {
    let mut x = 1;
    loop {
        if x == 5 {
            break x * 2;
        }
        x += 1;
    } // 此处结尾不可以有分号
}

fn main() {
    println!("{}", f());  // 10
}

注意 loop 循环的最后一定不能加分号,因为加了就会变成语句,而语句不会返回任何内容。所以在 if 表达式的时候我们啰嗦了那么多关于表达式、分号的内容,就是因为这些概念在循环中同样会体现。

下面的做法是错误的:

fn f() -> i32 {
    let mut x = 1;
    loop {
        if x == 5 {
            break x * 2;
        }
        x += 1;
    };  // 这里加上了分号
}

我们一定不能这么做,因为这会让 loop 循环变成语句,而下面又没有内容了,因此函数 f 会默认返回空元组。而函数的返回值签名是 i32,于是出现矛盾,造成编译错误。那么下面这个例子可以吗?

fn f() -> i32 {
    let mut x = 1;
    loop {
        if x == 5 {
            // break 语句结尾有没有分号
            // 并不重要
            break x * 2;
        }
        x += 1;
    }
    33
}

答案是依旧不行,因为 loop 循环是一个表达式,而它下面还有表达式,违反了我们之前说的函数末尾只能有一个表达式的原则。但是有一个例外,相信你已经猜到了,就是当 loop 表达式返回元组的时候,那么会忽略掉。

fn f() -> i32 {
    let mut x = 1;
    loop {
        if x == 5 {
            // 等价于 break;
            break ();  
        }
        x += 1;
    }
    33
}

此时是没有问题的,以上就是 loop 循环。

while 循环

另外一种常见的循环模式是在每次执行循环体之前都判断一次条件,如果条件为真,则执行代码片段,如果条件为假、或在执行过程中碰到 break 就退出当前循环。

这种模式可以通过 loop、if、else 及 break 关键字的组合使用来实现,有兴趣的话可以试着完成这一功能。不过由于这种模式太过于常见,所以 Rust 为此提供了一个内置的语言结构:while 条件循环。

fn main() {
    let mut x = 1;
    while x <= 5 {
        println!("hello world");
        x += 1;
    }
}

执行完之后会打印 5 次 hello world,然后是返回值的问题,while 循环不可以像 loop 一样 break 一个值,也就是说它只能默认返回空元组。

fn f() -> i32 {
    let mut x = 1;
    while x <= 5 {
        if x == 3 {
            break;
        }
        x += 1
    }
    // 没有下面这个 33,那么该函数就是非法的
    33
}

fn main() {
    println!("{:?}", f());  // 33

    // 当然 while 循环也可以赋值给一个变量
    // 因为只可能返回空元组,所以这么做没有什么意义
    let x = while 1 <= 2 {
        break;
    };
    println!("{:?}", x);  // ()
}

而当 break 后面有值的时候,会编译错误,假设我们 break 123。

告诉我们带有值的 break 只能出现在 loop 循环中,而 while 循环是不支持的。另外即便 break 一个空元组也是不允许的,尽管 while 循环会默认返回空元组。

for 循环

我们遍历一个数组可以选择 loop 循环、while 循环,但是这样容易因为使用了不正确的索引长度而使程序崩溃。

fn traverse1() {
    let arr = [1, 2, 3, 4, 5];
    let mut sum: i32 = 0;
    let mut index: usize = 0;
    loop {
        if index < 5 {
            // 通过索引获取元素
            // 索引必须是 usize 类型
            sum += arr[index];
        } else {
            break;
        }
        index += 1;
    }
    println!("sum([1, 2, 3, 4, 5]) = {}", sum);
}

fn traverse2() {
    let arr = [1, 2, 3, 4, 5];
    let mut sum: i32 = 0;
    let mut index: usize = 0;
    while index < 5 {
        sum += arr[index];
        index += 1;
    }
    println!("sum([1, 2, 3, 4, 5]) = {}", sum);
}

fn main() {
    traverse1();  
    // sum([1, 2, 3, 4, 5]) = 15
    traverse2();  
    // sum([1, 2, 3, 4, 5]) = 15
}

虽然成功遍历了,但如果索引越界的话就会发生错误,因此可以使用 for 循环这种更简明的方法来遍历集合中的每一个元素。

fn traverse() {
    let arr = [1, 2, 3, 4, 5];
    let mut sum: i32 = 0;
    for element in arr {
        sum += element;
    }
    println!("sum([1, 2, 3, 4, 5]) = {}", sum);
}

fn main() {
    traverse();  
    // sum([1, 2, 3, 4, 5]) = 15
}

结果是一样的,但我们增强了代码的安全性,不会出现诸如越界访问或漏掉某些元素之类的问题。

假如后期修改代码,我们从 arr 数组中移除了某个元素,却忘记将循环中的条件更新为 while index < 4,那么再次运行代码就会发生崩溃。而使用 for 循环的话,就不需要时常惦记着在更新数组元素数量时,还要去修改代码的其他部分。

for 循环的安全性和简捷性使它成为了 Rust 中最为常用的循环结构,即便是为了实现循环特定次数的任务,大部分的 Rust 开发者也会选择使用 for 循环。我们可以配合标准库中提供的 Range 来实现这一目的,它被用来生成从一个数字开始到另一个数字结束之前的所有数字序列。

fn main() {
    for number in 1..4 {
        println!("number = {}", number);
    }
    /*
    number = 1
    number = 2
    number = 3
     */

    // 还可以逆序输出
    for number in (1..4).rev() {
        println!("number = {}", number);
    }
    /*
    number = 3
    number = 2
    number = 1
     */
}

代码是不是更加精炼了呢。

到此这篇关于详解Rust中三种循环(loop,while,for)的使用的文章就介绍到这了,更多相关Rust循环内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Tauri 打开本地文件踩坑分析解决

    Tauri 打开本地文件踩坑分析解决

    这篇文章主要为大家介绍了Tauri 打开本地文件踩坑分析解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Rust HashMap详解及单词统计示例用法详解

    Rust HashMap详解及单词统计示例用法详解

    HashMap在Rust中是一个强大的工具,通过合理使用可以简化很多与键值对相关的问题,在实际开发中,我们可以充分利用其特性,提高代码的效率和可读性,本文将深入介绍HashMap的特性,以及通过一个单词统计的例子展示其用法,感兴趣的朋友一起看看吧
    2024-02-02
  • C和Java没那么香了,Serverless时代Rust即将称王?

    C和Java没那么香了,Serverless时代Rust即将称王?

    Serverless Computing,即”无服务器计算”,其实这一概念在刚刚提出的时候并没有获得太多的关注,直到2014年AWS Lambda这一里程碑式的产品出现。Serverless算是正式走进了云计算的舞台
    2021-06-06
  • 深入探究在Rust中函数、方法和关联函数有什么区别

    深入探究在Rust中函数、方法和关联函数有什么区别

    在 Rust 中,函数、方法和关联函数都是用来封装行为的,它们之间的区别主要在于它们的定义和调用方式,本文将通过一个简单的rust代码示例来给大家讲讲Rust中函数、方法和关联函数区别,需要的朋友可以参考下
    2023-08-08
  • Rust实现一个表达式Parser小结

    Rust实现一个表达式Parser小结

    这篇文章主要为大家介绍了Rust实现一个表达式Parser小结,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Rust中的模块系统之控制作用域与私有性详解

    Rust中的模块系统之控制作用域与私有性详解

    这篇文章总结了Rust模块系统的基本规则,包括如何声明模块、路径访问、私有性与公开性,以及如何使用`use`关键字简化路径引用,通过一个餐厅系统示例,展示了如何利用模块划分功能,并介绍了如何在其他模块或二进制crate中使用这些模块
    2025-02-02
  • Rust中的Trait与Trait Bounds详解

    Rust中的Trait与Trait Bounds详解

    Rust中的Trait与TraitBounds通过《西游记》的故事背景进行解释,Trait是一种接口定义机制,用于描述角色的能力;TraitBounds用于限制函数或结构体的参数类型必须实现某些trait;BlanketImplementations可以为所有实现了某类trait的类型提供默认的trait实现
    2025-02-02
  • Rust控制流运算符match的用法详解

    Rust控制流运算符match的用法详解

    match 是Rust中一个极为强大的控制流运算符,用于模式匹配和控制流的选择,它允许将一个值与一系列的模式相比较,根据匹配的模式执行相应代码,本文给大家详细介绍了Rust控制流运算符match的用法,需要的朋友可以参考下
    2024-01-01
  • 通过rust实现自己的web登录图片验证码功能

    通过rust实现自己的web登录图片验证码功能

    本文介绍了如何使用Rust和imagecrate库生成图像验证码,首先,通过Cargo.toml文件添加image依赖,然后,生成纯色图片并编辑验证图片,接着,编写随机函数获取字符,并通过循环生成验证码图片,最终,通过运行函数验证验证码图片是否生成,感兴趣的朋友一起看看吧
    2025-03-03
  • RUST语言函数的定义与调用方法

    RUST语言函数的定义与调用方法

    定义一个RUST函数使用fn关键字,下面通过本文给大家介绍RUST语言函数的定义与调用方法,感兴趣的朋友跟随小编一起看看吧
    2024-04-04

最新评论