Nim-控制流

Nim 控制流

一个基本的程序拥有循环和分支是一件毫无争议的事情。修改以下之前最初始的代码,让我们一起看看新的东西。

if 分支选择语句

1
2
3
4
5
6
7
let name = readLine(stdin)
if name == "":
echo "Poor soul, you lost your name?"
elif name == "name":
echo "Very funny, your name is name."
else:
echo "Hi, ", name, "!"

程序中可以存在零个或者多个elif片段,else也是可选的。关键字elif相比其他语言中的else if更加简短,他也是else if的缩写,这可以避免产生过多的缩进。
上端代码首先从键盘中接受一串字符串,如果名字是空字符串(用户直接打了一个回车),则输出“Poor soul, you lost your name?” 如果用户输入的是”name”, 输入就是”Very funny, your name is name”。其他的情况就认为属于合理情况全部放到了else, 都输出 “hi + name + !”

case 语句

case 语句提供了另一种分支方式。 case 语句允许多个分支:

1
2
3
4
5
6
7
8
9
10
let name = readLine(stdin)
case name
of "":
echo "Poor soul, you lost your name?"
of "name":
echo "Very funny, your name is name."
of "Dave", "Frank":
echo "Cool name!"
else:
echo "Hi, ", name, "!"

可以看出,对于 of 分支,还允许使用逗号分隔的值列表。

case中放置需要进行比较的值,每一个of 就是每个条件分支。 粗略看一眼感觉和switch...case差不多,不过这里的case 语句可以处理整数、其他序数类型和字符串。 (很快就会解释什么是序数类型。)对于整数或其他序数类型,值范围也是可能的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from std/strutils import parseInt

let score = parseInt(readLine(stdin))
case score
of 10, 9:
echo "A"
of 8:
echo "B"
of 7:
echo "C"
of 6:
echo "D"
else:
echo "F"

因为使用readLine()函数读取到的是一个字符串,然后我们这里需要用到一个int, 那么就需要做一个转换来完成这个事情,这就用到了parseInt(), 然后此函数需要导入一个包才能够使用,这就用到了第一句话from std/strutils import parseInt

1
2
3
4
5
6
7
8
# this statement will be explained later:
from std/strutils import parseInt

echo "A number please: "
let n = parseInt(readLine(stdin))
case n
of 0..2, 4..7: echo "The number is in the set: {0, 1, 2, 4, 5, 6, 7}"
of 3, 8: echo "The number is 3 or 8"

这段代码看起来不错,用来分辨一个数字,但是实际上这段代码无法通过编译。因为case语句并没有覆盖所有的情况,列出所有的情况也不是一个很实际的动作,更倾向于忽略掉剩下的情况。

1
2
3
4
case n
of 0..2, 4..7: echo "The number is in the set: {0, 1, 2, 4, 5, 6, 7}"
of 3, 8: echo "The number is 3 or 8"
else: discard

这个空的discard语句会告诉编译器这里什么也不做,编译器知道else部分的语句不会判断失败,因此错误会消失。注意,不可能涵盖所有可能的字符串值:这就是为什么字符串情况总是需要一个else分支。

while 语句

1
2
3
4
5
echo "What's your name? "
var name = readLine(stdin)
while name == "":
echo "Please tell me your name: "
name = readLine(stdin) # no `var`, because we do not declare a new variable here

这里给的案例比较简单,使用while判断name是不是一个空字符串,如果是空字符串就执行while里面的内容,就是输出”Please tell me your name” 然后让用户再输入一次。当然,如果输入的字符串不是空的,那就跳出了循环。

for语句

for语句是循环遍历迭代器提供的任何元素的构造。该示例使用内置计数迭代器:

1
2
3
4
echo "Counting to ten: "
for i in countup(1, 10):
echo i
# --> Outputs 1 2 3 4 5 6 7 8 9 10 on different lines

变量I在 for循环中隐式的被定义为int 类型,这取决于countup()返回了的类型。在循环中i会从1开始经过每次循环最后成为10。使用while也能完成此操作。

1
2
3
4
5
6
echo "Counting to 10: "
var i = 1
while i <= 10:
echo i
inc i # increment i by 1
# --> Outputs 1 2 3 4 5 6 7 8 9 10 on different lines

上面的代码中,通过inc i的方式让i每次循环都自身+1,他的效果和其他语言中使用到的++i是一样的。

由于计算时常在程序中发生,所以还有一个做同样事情的迭代器

1
2
for i in 1 .. 10:
...

这样会从100一直递减到1

1
2
3
4
var sum = 0
for i in countdown(100,1):
sum += i
echo sum

默认情况下,数的左右都是闭区间, 可以用..<表示不到这个数

1
2
3
4
5
6
7
8
9
10
11
for i in 0 ..< 10:
# the same as 0 .. 9

var s = "some string"
for i in 0 ..< s.len:
# do something

var s = "some string"
for idx, c in s[0 .. ^1]:
# ^1 is the last element, ^2 would be one before it, and so on
# ^1是最后一个元素, ^2是倒数第二个元素

还有一些其他有用的迭代器:

  • Items和mitems,分别提供不可变元素和可变元素
  • Pairs和mpairs,提供元素和索引号(分别是不可变的和可变的)
    1
    2
    3
    4
    for index, item in ["a","b"].pairs:
    echo item, " at index ", index
    # => a at index 0
    # => b at index 1

作用域和块语句

控制流语句有一个尚未涉及的特性:它们打开一个新的作用域。这意味着在下面的例子中,x在循环之外是不可访问的:

1
2
3
while false:
var x = "hi"
echo x # x在这里不工作

while (for)语句引入了一个隐式块。标识符只在声明它们的块中可见。block语句可用于显式地打开一个新块:
这个效果类似在C#或者C++中用花括号开了一个block

1
2
3
block myblock:
var x = "hi"
echo x # does not work either

break 语句

可以使用break语句提前离开块。break语句可以留下while语句、for语句或block语句。它离开最里面的构造,除非给出了块的标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
block myblock:
echo "entering block"
while true:
echo "looping"
break # 离开了循环但是还在myblock块之中
echo "still in block"
echo "outside the block"

block myblock2:
echo "entering block"
while true:
echo "looping"
break myblock2 # 离开了block,当然也离开了循环
echo "still in block" # it won't be printed
echo "outside the block"

continue 语句

和其他语言一样,continue会结束本次循环进入下一个循环中

1
2
3
for i in 1 .. 5:
if i <= 3: continue
echo i # will only print 4 and 5

when 语句

1
2
3
4
5
6
7
8
when system.hostOS == "windows":
echo "running on Windows!"
elif system.hostOS == "linux":
echo "running on Linux!"
elif system.hostOS == "macosx":
echo "running on Mac OS X!"
else:
echo "unknown operating system"

when语句与if语句几乎相同,但有以下区别:

  • 每个条件必须是一个常量表达式,因为它是由编译器求值的。
  • 分支中的语句不会打开新的作用域。
  • 编译器检查语义,并仅为属于第一个条件且计算结果为true的语句生成代码。

when语句对于编写特定于平台的代码很有用,类似于C编程语言中的#ifdef构造。

语句和缩进

现在我们已经介绍了基本的控制流语句,让我们回到Nim缩进规则。

在Nim中,简单语句和复杂语句是有区别的。简单语句不能包含其他语句:赋值、过程调用或返回语句都是简单语句。if、when、for、while等复杂语句可以包含其他语句。为了避免歧义,复杂语句必须总是缩进,但单个简单语句不需要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# no indentation needed for single-assignment statement:
if x: x = false

# indentation needed for nested if statement:
if x:
if y:
y = false
else:
y = true

# indentation needed, because two statements follow the condition:
if x:
x = false
y = false

表达式是语句的一部分,通常会产生一个值。if语句中的条件是表达式的一个例子。表达式可以在某些地方包含缩进以获得更好的可读性:

1
2
3
4
if thisIsaLongCondition() and
thisIsAnotherLongCondition(1,
2, 3, 4):
x = true

根据经验,表达式中的缩进可以在操作符、开括号和逗号之后进行。

使用括号和分号(;)可以使用只允许表达式的语句:

1
2
# computes fac(4) at compile time:
const fac4 = (var x = 1; for i in 1..4: x *= i; x)

Nim-控制流
http://cvrain.cloudvl.cn/2023/11/23/Nim/nim-control-flow/
作者
ClaudeRainer
发布于
2023年11月23日
许可协议