楔子
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循环内容请搜索软件开发网以前的文章或继续浏览下面的相关文章希望大家以后多多支持软件开发网!