强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

06 控制流

第 06 章:控制流

6.1 if / elif / else

Nim 的 if 语句使用缩进语法,不使用大括号不需要 then 关键字

# 基本 if-elif-else
let score = 85

if score >= 90:
  echo "优秀"
elif score >= 80:
  echo "良好"
elif score >= 60:
  echo "及格"
else:
  echo "不及格"
# 输出:良好

6.1.1 if 作为表达式

if 可以返回值(类似三元运算符),必须有 else 分支

let x = 10
let label = if x > 0: "正数"
            elif x < 0: "负数"
            else: "零"
echo label  # "正数"

# 函数中使用 if 表达式
proc classify(n: int): string =
  if n mod 2 == 0: "偶数"
  else: "奇数"

echo classify(4)  # "偶数"
echo classify(7)  # "奇数"

6.1.2 when — 编译期 if

when 类似 if,但在编译期求值:

const Platform = "linux"

when Platform == "linux":
  echo "Running on Linux"
elif Platform == "windows":
  echo "Running on Windows"
else:
  echo "Unknown platform"

# when 与 if 的区别
# when: 编译期求值,条件必须是编译期常量
# if:   运行时求值
when defined(release):
  echo "Release mode"
else:
  echo "Debug mode"

⚠️ 注意when 的未选中分支不会被编译,因此可以使用平台特有的 API 而不会在其他平台报错。

6.2 case 语句

case 语句是模式匹配的简洁形式,比 if-elif 链更清晰:

6.2.1 基本用法

let day = "Monday"

case day
of "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
  echo "工作日"
of "Saturday", "Sunday":
  echo "周末"
else:
  echo "未知"

6.2.2 整数范围

let grade = 78

case grade
of 90..100:
  echo "A"
of 80..89:
  echo "B"
of 70..79:
  echo "C"
of 60..69:
  echo "D"
of 0..59:
  echo "F"
else:
  echo "无效分数"
# 输出:C

6.2.3 枚举匹配

type Color = enum Red, Green, Blue

let c = Green

case c
of Red:
  echo "红色"
of Green:
  echo "绿色"
of Blue:
  echo "蓝色"
# 不需要 else,Nim 知道枚举的所有可能值

6.2.4 case 作为表达式

type ShapeKind = enum Circle, Rectangle, Triangle

proc area(kind: ShapeKind, a, b: float): float =
  case kind
  of Circle: PI * a * a
  of Rectangle: a * b
  of Triangle: 0.5 * a * b

echo area(Circle, 5.0, 0)       # 78.539...
echo area(Rectangle, 4.0, 6.0)  # 24.0
echo area(Triangle, 3.0, 8.0)   # 12.0

6.2.5 字符范围

proc classify(c: char): string =
  case c
  of 'a'..'z': "小写字母"
  of 'A'..'Z': "大写字母"
  of '0'..'9': "数字"
  of ' ', '\t', '\n': "空白字符"
  else: "其他字符"

echo classify('A')   # 大写字母
echo classify('5')   # 数字

6.3 for 循环

6.3.1 范围迭代

# 基本 for 循环
for i in 0..5:
  echo i
# 0 1 2 3 4 5

# 半开区间(不包含右端点)
for i in 0..<5:
  echo i
# 0 1 2 3 4

# 反向迭代
for i in countdown(10, 0):
  echo i
# 10 9 8 7 6 5 4 3 2 1 0

# 步长迭代
for i in countup(0, 20, 3):
  echo i
# 0 3 6 9 12 15 18

6.3.2 序列迭代

let fruits = @["苹果", "香蕉", "橙子", "葡萄"]

# 直接迭代值
for fruit in fruits:
  echo fruit

# 迭代索引和值
for i, fruit in fruits:
  echo &"{i}: {fruit}"

# 迭代字符串
for ch in "Hello":
  echo ch

6.3.3 表(Table)迭代

import std/tables

let scores = {"张三": 95, "李四": 87, "王五": 92}.toTable

for name, score in scores:
  echo &"{name}: {score}分"

# 只迭代键
for name in scores.keys:
  echo name

# 只迭代值
for score in scores.values:
  echo score

6.3.4 自定义迭代器

# 定义迭代器
iterator fibonacci(n: int): int =
  var (a, b) = (0, 1)
  for i in 0..<n:
    yield a
    (a, b) = (b, a + b)

for fib in fibonacci(10):
  echo fib
# 0 1 1 2 3 5 8 13 21 34

# 带过滤的迭代器
iterator evenNumbers(upTo: int): int =
  for i in countup(0, upTo, 2):
    yield i

for n in evenNumbers(20):
  echo n
# 0 2 4 6 8 10 12 14 16 18 20

6.4 while 循环

# 基本 while
var i = 0
while i < 5:
  echo i
  inc i

# 读取输入直到满足条件
var input = ""
while input != "quit":
  stdout.write "> "
  input = stdin.readLine()
  echo "You said: ", input

# 无限循环
var counter = 0
while true:
  inc counter
  if counter >= 100:
    break
echo "Stopped at: ", counter

6.5 break 和 continue

6.5.1 break

# 基本 break
for i in 0..100:
  if i == 10:
    break
  echo i
# 输出 0-9

# 在嵌套循环中使用标签
block outer:
  for i in 0..5:
    for j in 0..5:
      if i * j > 12:
        break outer
      echo &"({i}, {j})"
# 当 i*j > 12 时跳出外层循环

6.5.2 continue

# 跳过偶数
for i in 0..10:
  if i mod 2 == 0:
    continue
  echo i
# 1 3 5 7 9

# 跳过特定条件
let data = @[1, -2, 3, -4, 5, -6, 7]
var positiveSum = 0
for x in data:
  if x <= 0:
    continue
  positiveSum += x
echo positiveSum  # 16

6.5.3 block 和 break

block 创建一个命名代码块,break 可以跳出指定的块:

# break 返回值
let result = block:
  var sum = 0
  for i in 1..100:
    sum += i
    if sum > 1000:
      break sum
  sum

echo result  # 第一个大于 1000 的累加和

# 嵌套块
block level1:
  block level2:
    echo "Inside level2"
    break level1  # 跳出 level1
  echo "This won't be printed"
echo "Outside level1"

6.6 三元运算符替代方案

Nim 没有 C 风格的 ?: 三元运算符,使用 if 表达式替代:

# C 风格:a ? b : c
# Nim 风格:
let a = 10
let result = if a > 5: "big" else: "small"

# 嵌套(不推荐,可读性差)
let label = if a > 100: "huge"
            elif a > 10: "big"
            elif a > 0: "small"
            else: "negative or zero"

# 使用 proc 封装
proc choose[T](cond: bool, a, b: T): T =
  if cond: a else: b

echo choose(5 > 3, "yes", "no")  # "yes"

6.7 异常控制流

# try-except-finally
proc divide(a, b: int): float =
  if b == 0:
    raise newException(DivByZeroDefect, "Division by zero")
  return a.float / b.float

try:
  echo divide(10, 2)
  echo divide(10, 0)
except DivByZeroDefect:
  echo "Cannot divide by zero!"
except CatchableError as e:
  echo "Error: ", e.msg
finally:
  echo "Cleanup complete"

6.8 实战示例

🏢 场景:命令行菜单

import std/strutils

proc showMenu() =
  echo ""
  echo "=== 文件管理器 ==="
  echo "1. 列出文件"
  echo "2. 创建文件"
  echo "3. 删除文件"
  echo "0. 退出"
  stdout.write "请选择: "

proc listFiles() =
  echo "列出文件... (模拟)"

proc createFile() =
  echo "创建文件... (模拟)"

proc deleteFile() =
  echo "删除文件... (模拟)"

proc run() =
  var running = true
  while running:
    showMenu()
    let choice = stdin.readLine().strip()
    
    case choice
    of "1":
      listFiles()
    of "2":
      createFile()
    of "3":
      deleteFile()
    of "0":
      running = false
      echo "再见!"
    else:
      echo "无效选择,请重试"

run()

🏢 在景:数据过滤管道

import std/[strutils, sequtils, algorithm]

type
  Record = object
    name: string
    age: int
    city: string

let records = @[
  Record(name: "张三", age: 28, city: "北京"),
  Record(name: "李四", age: 35, city: "上海"),
  Record(name: "王五", age: 22, city: "北京"),
  Record(name: "赵六", age: 45, city: "广州"),
  Record(name: "钱七", age: 31, city: "北京"),
]

# 过滤:北京,年龄大于 25
let filtered = records.filterIt(it.city == "北京" and it.age > 25)

# 排序
let sorted = filtered.sortedByIt(it.age)

for r in sorted:
  echo &"{r.name}, {r.age}岁, {r.city}"
# 张三, 28岁, 北京
# 钱七, 31岁, 北京

本章小结

语句用途示例
if/elif/else条件分支if x > 0: echo "正"
when编译期条件when defined(linux): ...
case模式匹配case c of 'a'..'z': ...
for迭代循环for i in 0..10: ...
while条件循环while x < 100: ...
break跳出循环/块break outer
continue跳过当前迭代continue
block命名代码块block: ...

练习

  1. 使用 case 语句实现一个简单计算器(+、-、*、/)
  2. 编写自定义迭代器 primes(n) 生成前 n 个素数
  3. 实现一个带标签 break 的嵌套循环搜索
  4. forfilterIt 过滤一个序列中的偶数并求平方和

扩展阅读


上一章:运算符 | 下一章:函数与过程