控制流
根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段
代码的能力是大部分编程语言的基本组成部分。Cairo 代码中最常见的用来控制执行
流的结构是 if
表达式和循环。
if
表达式
if
表达式允许根据条件执行不同的代码分支。你提供一个条件并表示 “如果条件满
足,运行这段代码;如果条件不满足,不运行这段代码。”
文件名: src/lib.cairo
use debug::PrintTrait; fn main() { let number = 3; if number == 5 { 'condition was true'.print(); } else { 'condition was false'.print(); } }
所有的 if
表达式都以关键字 if
开始,其后跟一个条件。在这个例子中,条件检查
变量 number
的值是否等于 5。在条件为 true
时希望执行的代码块位于紧跟条件
之后的大括号中。
另外,我们也可以包含一个可选的 else
表达式来提供一个在条件为 false
时应当执行的代码块,这里我们就这么做了。如果不提供 else
表达式并且条件为 false
时,程序会直接忽略 if
代码块并继续执行下面的代码。
尝试运行这段代码;你应该看到以下输出:
$ cairo-run main.cairo
[DEBUG] condition was false
让我们试着改变 number
的值使条件为 true
时看看会发生什么:
let number = 5;
$ cairo-run main.cairo
condition was true
还值得注意的是,这段代码中的条件必须是一个 bool
值。如果该条件不是 bool
值,我们会得到一个错误。
$ cairo-run main.cairo
thread 'main' panicked at 'Failed to specialize: `enum_match<felt252>`. Error: Could not specialize libfunc `enum_match` with generic_args: [Type(ConcreteTypeId { id: 1, debug_name: None })]. Error: Provided generic argument is unsupported.', crates/cairo-lang-sierra-generator/src/utils.rs:256:9
用else if
处理多个条件
你可以通过在一个 else if 表达式中结合 if 和 else 来使用多个条件。比如说:
文件名: src/lib.cairo
use debug::PrintTrait; fn main() { let number = 3; if number == 12 { 'number is 12'.print(); } else if number == 3 { 'number is 3'.print(); } else if number - 2 == 1 { 'number minus 2 is 1'.print(); } else { 'number not found'.print(); } }
这个程序有四种可能的路径。运行后,你应该看到以下输出:
[DEBUG] number is 3
执行该程序时,它会依次检查每个 if
表达式,并执行条件求值为 true
的第一个体。请注意,即使 number - 2 == 1
是 true
,我们也看不到输出 number minus 2 is 1'.print()
,也看不到 else
块中的 number not found
文本。这是因为 Cairo 只执行第一个真条件的代码块,一旦找到一个真条件,就不会再检查其他条件。使用过多的 else if
表达式会使代码变得杂乱无章,所以如果你有一个以上的 else if
表达式,你可能需要重构你的代码。Chapter 6 介绍了一种强大的Cairo语言分支结构,称为 "match",用于处理这些情况。
在 let
语句中使用 if
因为 if 是一个表达式,我们可以在 let 语句的右边使用它,将结果分配给一个变量。
文件名: src/lib.cairo
use debug::PrintTrait; fn main() { let condition = true; let number = if condition { 5 } else { 6 }; if number == 5 { 'condition was true'.print(); } }
$ cairo-run main.cairo
[DEBUG] condition was true
number
变量将会绑定到表示 if
表达式结果的值上。这里将是 5。
使用循环重复执行
多次执行同一段代码是很常用的,Cairo 为此提供了多种 循环(loops)。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。为了实验一下循环,让我们新建一个叫做 loops 的项目。
Cairo 目前只有一种循环:loop
。
使用 loop
重复执行代码
loop
关键字告诉 Cairo 一遍又一遍地执行一段代码直到你明确要求停止。
作为一个例子,将你的_loops_目录下的_src/lib.cairo_文件修改如下:
文件名: src/lib.cairo
use debug::PrintTrait; fn main() { let mut i: usize = 0; loop { if i > 10 { break ; } 'again!'.print(); } }
当运行这个程序时,我们会看到程序不停的反复打印 again!
,直到我们手动停止程序,因为程序从未达到停止条件。
虽然编译器阻止我们编写没有停止条件(break
语句)的程序,但该停止条件可能永远不会达到,从而会程序导致无限循环。
大多数终端支持键盘快捷键 ctrl-c 来中断卡在无限循环的程序。试一试吧:
$ scarb cairo-run --available-gas=20000000
[DEBUG] again (raw: 418346264942)
[DEBUG] again (raw: 418346264942)
[DEBUG] again (raw: 418346264942)
[DEBUG] again (raw: 418346264942)
Run panicked with err values: [375233589013918064796019]
Remaining gas: 1050
注意:Cairo通过包含一个 gas 计量器来防止我们运行无限循环的程序。 gas 计量器是一种限制程序中可进行的计算量的机制。通过给
--available-gas
标志设置一个值,我们可以设置程序的最大可用 gas 量。gas 是一个计量单位,表示一条指令的计算成本。当设置的最大gas值耗尽时,程序将停止。在这种情况下,程序会抛出 gas 耗尽的错误(panic),尽管从未达到停止条件。 对于部署在 Starknet 上的智能合约,它特别重要,因为它可以防止在网络上运行无限循环。 如果你正在编写一个需要运行循环的程序,你需要在执行时将--available-gas
标志设置为一个足够大的值来运行该程序。
要退出循环,您可以在循环内部放置 break
语句,告诉程序何时停止循环。
我们可以通过在这个程序里加入可达的停止条件 i > 10
,来修复无限循环。
use debug::PrintTrait; fn main() { let mut i: usize = 0; loop { if i > 10 { break; } 'again'.print(); i += 1; } }
关键字 continue
告诉程序进入循环的下一个迭代,并跳过现在这个迭代中的其他代码。让我们给我们的循环添加一个continue
语句,使得当i
等于5
时跳过print
语句。
use debug::PrintTrait; fn main() { let mut i: usize = 0; loop { if i > 10 { break; } if i == 5 { i += 1; continue; } i.print(); i += 1; } }
当i
等于5
时,执行这个程序将不会打印i
的值。
从循环中返回值
loop
的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你
可能会需要将操作的结果传递给其它的代码。如果将返回值加入你用来停止循环的
break
表达式,它会被停止的循环返回,如下所示:
use debug::PrintTrait; fn main() { let mut counter = 0; let result = loop { if counter == 10 { break counter * 2; } counter += 1; }; 'The result is '.print(); result.print(); }
在循环之前,我们声明一个名为 counter
的变量,并将其初始化为 0
。然后我们声明一个名为 result
的变量,用来保存从循环中返回的值。
在循环的每一次迭代中,我们检查 counter
是否等于 10
,然后在 counter
变量中加 1
。当条件得到满足时,我们使用 break
关键字,其值为 counter * 2
。在循环之后,我们用一个
分号来结束给result
赋值的语句。最后,我们打印result
中的值,在本例中是20
。
总结
你成功了!这一章很重要:你学到了变量、数据类型、函数、注释、
if
表达式和循环!要练习本章讨论的概念、
尝试编写程序来完成下列操作:
- 产生第 n 个斐波那契数。
- 计算一个数字的阶乘 n 。
现在,我们将在下一章回顾 Cairo 中常见的集合类型。