Beginng_Rust(译):控制执行流程

在本章中,您将学习:
•如何使用if语句基于布尔条件执行不同的语句

•如何使用表达式根据布尔条件生成不同的值

•只要布尔条件成立,如何使用while语句重复某些语句

•如何使用语句重复定义一些语句的次数

•变量的有效范围是什么

条件陈述(if)

布尔值的主要用途是决定如何让程序的执行继续进行。 假设您要打印一个单词,但仅当给定数字为正数时:

let n = 4;
if n > 0 { print!("positive"); }

这将打印positive。

第二行包含if语句,类似于许多其他语言。 在这样的语句中,将评估if关键字后面的布尔表达式。 这种表达被称为“条件”。 只有在评估条件的结果为真时,才会执行以下括号中的语句。

大括号可以包含零个或多个语句。 因此,它们类似于“主要”功能的主体。 通常,括在括号中的一系列语句被命名为“block”。

此语法与其他语言(如C)的语法不同,原因如下:

•条件必须是布尔类型。 因此,不允许使用以下语法:if 4 {print!(“four”);}。

•不需要将条件括在括号之间,通常不会这样做。 实际上,如果您这样做,编译器会报告警告。

•在条件之后,需要一个块(语句)。 因此,不允许使用以下语法:如果4> 0 print!(“four”); 并且以下都没有:if(4> 0)print!(“four”);.
如果您希望在条件评估为false的情况下执行某些操作,则可以引入替代案例,前面带有else关键字:

let n = 0;
if n > 0 {
print!("number is");
print!(" positive");
}
else {
print!("non positive");
}

这将打印non positive。

正如您所看到的,因为整个“if”语句非常长,它已被分成七行,并且块中包含的语句已缩进四列,以显示块。

当然,如果有两种以上的情况,你可以用这种方式在块内插入if语句:

let n = 4;
if n > 1000 {
print!("big");
}
else {
if n > 0 {
print!("small");
}
else {
if n < 0 {
print!("negative");
}
else {
print!("neither positive nor negative");
}
}
}

但是,允许使用以下等效语法使其更具可读性。

let n = 4;
if n > 1000 {
print!("big");
}
else if n > 0 {
print!("small");
}
else if n < 0 {
print!("negative");
}
else {
print!("neither positive nor negative");
}

请注意,在else关键字之后,您必须放置一个块或另一个if语句。

条件表达式

您可以编写等效代码,而不是编写以前的代码:

let n = 4;
print!("{}",
if n > 1000 {
"big"
}
else if n > 0 {
"small"
}
else if n < 0 {
"negative"
}
else {
"neither positive nor negative"
}
);

这里,使用“条件表达式”,类似于C语言三元运算符“?:”。 C语言中的相应代码是:

#include <stdio.h>
int main(int argc, char **argv) {
int n = 4;
printf("%s",
n > 1000 ?
"big" :
n > 0 ?
"small" :
n < 0 ?
"negative" :
"neither positive nor negative");
}

最后一个Rust程序,而不是包含四个“print”宏的调用,只包含一个调用,有两个参数。第一个参数只是打印第二个参数,第二个参数是一个扩展超过12行的表达式。与前面的示例中一样,这样的表达式使用“if”和“else”关键字,但是,它不包含块中的语句,而是包含简单的文字字符串。

对这样的复合表达式的评估以类似于前一个示例中的if-else语句的方式进行,但是,在确定应该执行哪个块之后,在前面的示例中,包含在执行块,在该示例中,评估块中包含的表达式,并且从这种评估获得的值被认为是整个“if-else”表达式的值。之后,宏将使用这样的值进行打印。

与C语言的“?:”运算符的主要区别在于,在Rust中,条件表达式具有与条件语句相同的语法。它们作为条件表达式的特征在于它的所有块都以分号结束。在这种情况下,块中包含的表达式给出了复合if表达式的值。

为了使复合表达式具有唯一类型,要求此类表达式至少具有一个else分支,并且其所有块都以相同类型的表达式结束。 例如,不允许写入:

let a = if true { "abc" };

因为在else的情况下a的值不会被定义; 并且也不允许写

let a = if true { "abc" } else { 12 };

因为第一个块有一个字符串作为其值,而第二个块具有整数,因此编译器将无法确定该变量的类型。 相反,它允许编写语句:

let _a = if true { "abc" } else { "xy" };
let _b = if true { 3456 } else { 12 };
let _c = if true { 56.9 } else { 12. };

因为,在第一个语句中,我们只有字符串,在第二个只有整数,在第三个只有浮点数; 所以_a是一个字符串,_b是一个整数,_c是一个浮点数。

条件循环(while)

假设您要打印1到10的整数,包括1和10的平方。 您可以使用语句执行此操作;

let mut i = 1;
while i <= 10 {
print!("{} ", i * i);
i += 1;
}

将打印1 4 9 16 25 36 49 64 81 100。

这里使用“while”关键字,类似于C语言。 计算其后的布尔条件,如果其值为true,则执行以下块。 while语句与“if”语句不同,因为在执行了块之后,它重复了条件的评估并可能重新执行块,直到条件被评估为false,或者直到块退出其他一些 原因。

对于与“if”语句的语法相同的方面,此语法与其他语言(如C)不同。

在我们的示例中,可变变量“i”被声明为整数,并且它被初始化为1.这样的值表示要打印方形的数量。
“while”语句检查这样的值是否小于或等于10的值,并且如此,执行该块,然后再次评估布尔条件。 在块内部,“i”的值递增,因此在十次迭代之后,“i”的值为11; 此时,循环的条件不再令人满意,因此“while”语句的执行结束。

虽然在C语言中也有do … while语句,但在Rust中没有这样的语句。 相反,Rust中也存在相同的C语言的break和continue语句。 它们的目的分别是从整个循环中过早退出,并且从循环的唯一当前迭代中过早地退出。

例如,如果对于1到50的每个整数,不能被3整除,我们想打印它的正方形,只要这个正方形不大于400,我们就可以写:

let mut i = 0;
while i < 50 {
i += 1;
if i % 3 != 0 {
if i * i <= 400 {
print!("{} ", i * i);
}
}
}

或者,使用continue和break,我们可以编写等效代码:

let mut i = 0;
while i < 50 {
i += 1;
if i % 3 == 0 { continue; }
if i * i > 400 { break; }
print!("{} ", i * i);
}

这两个版本具有相同的前三行。

在第一个版本中,如果i除以3的余数不等于零,即数字不能被3整除,则迭代继续,而在程序的第二个版本中,如果数字可被3整除 ,执行continue语句,因此当前迭代立即结束,并且到达下一次迭代。

请注意,为了让下一次迭代处理下一个数字,需要增加这样的数字。 因此,这种增量放在迭代的开始。

continue关键字的使用允许我们提高可读性,因为它将块的嵌套级别减少了一个。

此外,在程序的第一个版本中,检查方块是否大于400,并且仅在未超过限制时才打印该值。 在第二个版本中,考虑到这样一个事实:假定数字稳定增加,一旦它的平方超过400,它将在随后的每次迭代中超过它; 因此,一旦square超过400,就会执行break语句,其效果是立即退出整个循环。

break关键字的使用允许我们提高可读性,类似于continue关键字,但它也允许我们提高速度,因为它避免了几个无用的迭代。

无限循环(loop)

它恰好执行所谓的无限循环,这意味着执行流只有在强制终止程序时才会从这些循环中退出,或者通过执行退出循环的语句(如break关键字)。 例如,要打印小于200的所有正方形,我们可以编写

let mut i = 1;
while true {
let ii = i * i;
if ii >= 200 { break; }
print!("{} ", ii);
i += 1;
}

将打印1 4 9 16 25 36 49 64 81 100 121 144 169 196。

但是,编译器会建议使用loop {...}来表示无限循环。 编译器建议用更具表现力的loop关键字替换while true子句,因此获取等效的程序:

let mut i = 1;
loop {
let ii = i * i;
if ii >= 200 { break; }
print!("{} ", ii);
i += 1;
}

计数循环(for)

而着名的C语言循环(以及许多其他语言)? 即使在Rust中也有for关键字,而且在Rust中它用于迭代一定次数,但它在Rust中的使用与C语言中的使用非常不同。
这是一个程序,解决了从1到10打印整数的问题,包括1和10,平方:

for i in 1..11 {
print!("{} ", i * i);
}

在open括号之后,for语句的语法与while语句的语法相同,这意味着允许break和continue语句,但语句的第一部分是非常不同的。就在for关键字之后,有一个将以这种方式创建的变量的名称。 在我们的例子中,它是“i”。然后,有in关键字,后跟两个整数数字表达式,用符号“…”分隔。

执行此循环意味着为i变量分配第一个数值,然后执行具有i变量值的块,然后将i的值递增1,再执行具有此值i的块的另一个时间,依此类推。 当i的值达到第二个数值时,不执行该块,并且for语句结束。 因此,虽然第一个限制包含在使用值的序列中,但第二个限制被排除在外。

由于第二个限制从循环中排除,要从1到10迭代,您必须写入1…11。
正如我们所说,循环变量由循环语句声明,并在循环结束时被销毁。 如果已经有一个具有相同名称的变量,那么对于整个循环,这个变量将被遮蔽,被忽略,并且在循环之后它将再次变为有效,如下面的代码所示:

let index = 8;
for index in 0..4 { print!("{} ", index); }
print!(":{}", index);

这将打印:“0 1 2 3:8”。

请注意,这两个限制可能是复杂的表达式,但无论如何这两个表达式都是在循环开始之前计算的。 该程序

let mut limit = 4;
for i in 1..limit {
limit -= 1;
print!("{} ", i);
}
print!(":{}", limit);

将打印“1 2 3:1”。 让我们看看为什么。

首先,创建limit变量,并将其初始化为4值。

然后,评估循环的极值。 包括的第一个极端是1,而最后的极端,排除在外,是4。

因此块执行三次; 第一个是i = 1,第二个是i = 2,第三次是i = 3。 每次执行块时,limit的值减1,因此从4传递到1.但是,这不会影响将要执行的迭代次数。

这与以下C语言程序不同:

#include <stdio.h>
int main() {
int limit = 4;
for (int i = 1; i < limit; i++) {
limit -= 1;
printf("%d ", i);
}
printf(":%d ", limit);
return 0;
}

这将打印“1 2:2”,因为随着限制的减少,在第二次迭代后不再满足循环条件)。

变量范围

我们已经看到许多使用块的语句:“main”函数,“if”语句/表达式,“while”语句,“loop”语句和“for”语句。

在这种情况下,需要使用块,尽管您可以在任何想要包含某些语句的块中使用块。 例如:

print!("1");
{
print!("2");
print!("3");
{
print!("4");
{
print!("5");
{ { } }
print!("6");
}
}
print!("7");
}

这将打印“1234567”。 当然,大括号必须正确配对。

缩进和其他空格是可选的,它们不会更改生成的可执行文件; 他们的目的只是为了使源代码更具可读性。

但是所有这些括号的目的是什么? 在上面的示例中,它们没有用处,但是如果您编译以下代码:

{ let i = 10; }
print!("{} ", i);

你得到的错误在第二行的这个范围内找不到值i,好像变量i还没有被声明。 它发生了,因为每个变量都不再存在于声明它的块的末尾。

声明变量的块称为变量的“范围”。

因此,在我们的示例中,i的范围仅持续一行。

变量的范围还包括嵌套在已声明变量的块中的可能块。 这段代码

{
let i = 10;
{
let j = 4;
{
print!("{} ", i);
}
print!("{}", i + j);
} // End of the scope of "j"
} // End of the scope of "i"

将打印10 14.在这种情况下,i的范围持续9行,而j的范围持续6行。 现在让我们来看看这段代码:

{
let i = 10;
{
let i = 4;
print!("{} ", i);
} // End of the scope of the second "i"
print!("{} ", i);
} // End of the scope of the first "i"

那将打印4 10。

我们已经说过,允许定义一个与已经存在的变量具有相同名称的变量,并且在这种情况下,这个最后一个变量被“阴影”而不是被新声明“覆盖”。

这里,首先,声明一个名为i的变量,并使用值10初始化它。然后声明另一个具有相同名称的变量,并使用值4初始化它。然后,具有此类名称的变量的值为打印。 当第二个变量影响第一个变量时,使用第二个变量的值,因此打印4。 然后,第二个变量超出其范围,因此它被销毁,因此第一个变量再次变为可见。 因此,第二个print语句使用第一个变量的值,该变量尚未被销毁。

因此,我们看到可以插入成对的大括号以有意限制变量的范围。

但是变量可见性规则也适用于“if”,“while”和“for”语句的块。 因此以下代码:

let mut _i = 1;
if true { let _i = 2; }
print!("{} ", _i);
while _i > 0 { _i -= 1; let _i = 5; }
print!("{} ", _i);

将打印1 0。
实际上,在第二行中,语句允许_i = 2;执行,但这种语句的效果是创建一个新变量,并在同一行中销毁它,因此在第三行中打印第一个_i命名变量的值。

在最后一行中,由于_i最初大于零,因此while语句的主体首次执行。在这样的主体内部,_i的值递减为零,然后创建具有相同名称_i的另一个变量并立即销毁。鉴于现在原始_i为零,while语句结束,最后打印第一个(仅剩下的)变量的值。

您应该认为任何变量声明都会创建一个对象,并且当声明此类变量的范围结束时,此类对象将被销毁。

每次使用变量名称时,除了声明它之外,实际引用的变量是具有此类名称且尚未销毁的最新声明变量。

使用隐喻语言,对象的创造可以被认为是它的诞生,对象的破坏可以被认为是它的死亡。因此,我们可以说变量名的每次使用都是指具有此名称的最年轻的实时变量。

猜你喜欢

转载自blog.csdn.net/m0_37696990/article/details/82795153
今日推荐