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: ... |
练习
- 使用
case语句实现一个简单计算器(+、-、*、/) - 编写自定义迭代器
primes(n)生成前 n 个素数 - 实现一个带标签
break的嵌套循环搜索 - 用
for和filterIt过滤一个序列中的偶数并求平方和