Nim 完全指南 / 07 函数与过程
第 07 章:函数与过程
7.1 过程(proc)
proc 是 Nim 中最基本的子程序形式:
# 基本过程
proc greet(name: string) =
echo "Hello, ", name, "!"
greet("Nim") # Hello, Nim!
# 带返回值的过程
proc add(a, b: int): int =
return a + b
echo add(3, 4) # 7
# 使用 result 变量(隐式返回值)
proc factorial(n: int): int =
result = 1
for i in 2..n:
result *= i
echo factorial(5) # 120
7.1.1 result 变量
result 是每个有返回值的 proc 自动声明的变量,函数结束时自动返回 result 的值:
proc buildString(): string =
result = ""
result.add("Hello")
result.add(", ")
result.add("World!")
echo buildString() # Hello, World!
# 也可以显式 return 提前返回
proc findPositive(data: seq[int]): int =
for x in data:
if x > 0:
return x
result = -1 # 未找到
7.1.2 参数传递方式
| 修饰符 | 说明 | 可修改 | 传参方式 |
|---|---|---|---|
| 无(默认) | let 参数 | ❌ | 传值/引用(编译器优化) |
var | 可变参数 | ✅ | 传引用 |
ref | 引用参数 | ✅ | 传引用 |
lent | 借用参数 | ❌ | 零拷贝借用 |
# 默认参数(只读)
proc printValue(x: int) =
echo x
# x = 10 # ❌ 不能修改
# var 参数(可修改原值)
proc doubleIt(x: var int) =
x *= 2
var n = 10
doubleIt(n)
echo n # 20
# 多个 var 参数
proc swap(a, b: var int) =
let temp = a
a = b
b = temp
var (x, y) = (1, 2)
swap(x, y)
echo x, y # 21
7.2 多返回值
7.2.1 使用元组
proc divmod(a, b: int): (int, int) =
(a div b, a mod b)
let (quotient, remainder) = divmod(17, 5)
echo &"17 / 5 = {quotient} 余 {remainder}"
# 17 / 5 = 3 余 2
# 忽略不需要的返回值
let (q, _) = divmod(17, 5)
echo q # 3
7.2.2 使用命名元组
proc getUserInfo(id: int): tuple[name: string, age: int, email: string] =
("张三", 28, "[email protected]")
let user = getUserInfo(1)
echo user.name # 张三
echo user.age # 28
echo user.email # [email protected]
# 解构
let (name, age, email) = getUserInfo(1)
echo &"{name}, {age}岁, {email}"
7.2.3 使用 out 参数
# 使用 var 参数作为输出
proc parseTime(seconds: int, outMinutes, outSeconds: var int) =
outMinutes = seconds div 60
outSeconds = seconds mod 60
var m, s: int
parseTime(125, m, s)
echo &"{m}分{s}秒" # 2分5秒
7.3 默认参数与命名参数
# 默认参数
proc createWindow(title: string, width = 800, height = 600, visible = true) =
echo &"Window '{title}': {width}x{height}, visible={visible}"
createWindow("My App")
# Window 'My App': 800x600, visible=true
createWindow("My App", 1024, 768)
# Window 'My App': 1024x768, visible=true
# 命名参数(顺序无关)
createWindow("My App", height = 900, width = 1600)
# Window 'My App': 1600x900, visible=true
# 混合使用
createWindow("My App", 1024, visible = false)
# Window 'My App': 1024x600, visible=false
⚠️ 注意:默认参数的值必须是编译期常量或纯表达式。
7.4 泛型函数
# 基本泛型
proc max[T](a, b: T): T =
if a > b: a else: b
echo max(10, 20) # 20
echo max(3.14, 2.71) # 3.14
echo max("abc", "def") # def
# 多个类型参数
proc pair[A, B](a: A, b: B): (A, B) =
(a, b)
let p = pair(42, "hello")
echo p # (42, "hello")
# 泛型约束
proc sum[T: SomeNumber](values: seq[T]): T =
result = T(0)
for v in values:
result += v
echo sum(@[1, 2, 3, 4, 5]) # 15
echo sum(@[1.1, 2.2, 3.3]) # 6.6
# 概念约束(Concepts)
type
Container[T] = concept c
c.len is int
c[int] is T
proc firstElement[T](c: Container[T]): T =
c[0]
echo firstElement(@[10, 20, 30]) # 10
echo firstElement(@["a", "b", "c"]) # "a"
7.5 函数重载
# 重载基于参数类型
proc process(x: int) =
echo "Processing int: ", x
proc process(x: float) =
echo "Processing float: ", x
proc process(x: string) =
echo "Processing string: ", x
process(42) # Processing int: 42
process(3.14) # Processing float: 3.14
process("hello") # Processing string: hello
# 重载基于参数数量
proc log(msg: string) =
echo "[INFO] ", msg
proc log(msg: string, level: int) =
echo "[LEVEL ", level, "] ", msg
log("System started")
log("Critical error", 5)
7.6 闭包(Closures)
# 过程类型
type
IntProc = proc(x: int): int
# 闭包——捕获外部变量
proc makeCounter(): proc(): int =
var count = 0
result = proc(): int =
count += 1
return count
let counter = makeCounter()
echo counter() # 1
echo counter() # 2
echo counter() # 3
# 闭包作为参数
proc apply(data: seq[int], fn: proc(x: int): int): seq[int] =
result = newSeq[int](data.len)
for i, v in data:
result[i] = fn(v)
let nums = @[1, 2, 3, 4, 5]
let doubled = apply(nums, proc(x: int): int = x * 2)
let squared = apply(nums, proc(x: int): int = x * x)
echo doubled # @[2, 4, 6, 8, 10]
echo squared # @[1, 4, 9, 16, 25]
# do 表示法(更简洁的闭包语法)
let tripled = nums.apply() do (x: int) -> int:
x * 3
echo tripled # @[3, 6, 9, 12, 15]
7.7 方法(Method)
在面向对象语境中,method 用于多态分发:
type
Shape = ref object of RootObj
Circle = ref object of Shape
radius: float
Rectangle = ref object of Shape
width, height: float
method area(s: Shape): float {.base.} =
raise newException(CatchableError, "Not implemented")
method area(c: Circle): float =
PI * c.radius * c.radius
method area(r: Rectangle): float =
r.width * r.height
let shapes: seq[Shape] = @[
Circle(radius: 5.0),
Rectangle(width: 4.0, height: 6.0),
]
for s in shapes:
echo s.area()
# 78.53981633974483
# 24.0
⚠️ 注意:method 使用动态分发(多态),有额外开销。普通情况用 proc 即可。
7.8 运算符重载
type Vec2 = object
x, y: float
proc `+`(a, b: Vec2): Vec2 =
Vec2(x: a.x + b.x, y: a.y + b.y)
proc `-`(a, b: Vec2): Vec2 =
Vec2(x: a.x - b.x, y: a.y - b.y)
proc `*`(v: Vec2, s: float): Vec2 =
Vec2(x: v.x * s, y: v.y * s)
proc `$`(v: Vec2): string =
&"({v.x}, {v.y})"
proc dot(a, b: Vec2): float =
a.x * b.x + a.y * b.y
let a = Vec2(x: 1, y: 2)
let b = Vec2(x: 3, y: 4)
echo a + b # (4.0, 6.0)
echo a * 2.0 # (2.0, 4.0)
7.9 func — 纯函数
func 是 proc {.noSideEffect.} 的语法糖,保证函数没有副作用:
# func 不允许修改外部状态
func square(x: int): int =
x * x
func abs(x: int): int =
if x < 0: -x else: x
echo square(5) # 25
echo abs(-42) # 42
# ❌ 编译错误:func 不能有副作用
# func bad(x: int): int =
# echo x # echo 是副作用
# return x
7.10 命名约定
| 元素 | 约定 | 示例 |
|---|---|---|
| 过程名 | camelCase | getUserInfo, calculateTotal |
| 类型名 | PascalCase | UserInfo, ShoppingCart |
| 常量 | PascalCase 或 snake_case | MaxSize, MAX_SIZE |
| 变量 | camelCase | userName, totalPrice |
| 导出 | 后缀 * | proc myProc*() |
type
UserProfile* = object
name*: string
age*: int
const MaxRetries* = 3
proc createUser*(name: string, age: int): UserProfile =
UserProfile(name: name, age: age)
proc displayName*(user: UserProfile) =
echo user.name
7.11 实战示例
🏢 场景:策略模式
type
DiscountStrategy = proc(price: float): float
proc noDiscount(price: float): float = price
proc percentageDiscount(pct: float): DiscountStrategy =
result = proc(price: float): float = price * (1 - pct / 100)
proc fixedDiscount(amount: float): DiscountStrategy =
result = proc(price: float): float = max(0.0, price - amount)
proc calculateTotal(items: seq[float], strategy: DiscountStrategy): float =
result = 0
for price in items:
result += strategy(price)
let cart = @[29.99, 49.99, 9.99, 149.99]
echo &"原价: ${calculateTotal(cart, noDiscount):.2f}"
echo &"八折: ${calculateTotal(cart, percentageDiscount(20)):.2f}"
echo &"减30: ${calculateTotal(cart, fixedDiscount(30)):.2f}"
🏢 场景:回调函数注册系统
type
EventHandler = proc(event: string, data: string)
EventBus = object
handlers: seq[tuple[event: string, handler: EventHandler]]
proc newEventBus(): EventBus =
EventBus(handlers: @[])
proc on(bus: var EventBus, event: string, handler: EventHandler) =
bus.handlers.add((event, handler))
proc emit(bus: EventBus, event: string, data: string) =
for (evt, handler) in bus.handlers:
if evt == event:
handler(event, data)
var bus = newEventBus()
bus.on("user.login", proc(event, data: string) =
echo &"用户登录: {data}")
bus.on("user.login", proc(event, data: string) =
echo &"记录日志: {event} - {data}")
bus.on("order.create", proc(event, data: string) =
echo &"新订单: {data}")
bus.emit("user.login", "张三")
bus.emit("order.create", "订单#12345")
本章小结
| 特性 | 关键字 | 说明 |
|---|---|---|
| 过程 | proc | 基本子程序 |
| 纯函数 | func | 无副作用的过程 |
| 方法 | method | 支持多态分发 |
| 泛型 | [T] | 类型参数化 |
| 闭包 | proc() = | 捕获外部变量的匿名函数 |
| 多返回值 | 元组 | proc f(): (int, int) |
| 默认参数 | = | proc f(x: int = 10) |
| 命名参数 | name: | f(name = "value") |
result | — | 隐式返回变量 |
练习
- 编写一个泛型
binarySearch过程 - 实现一个带闭包的
makeAccumulator工厂函数 - 为矩阵类型定义
+、*、$运算符 - 使用
method实现一个简单的图形绘制系统