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

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, "zhangsan@example.com")

let user = getUserInfo(1)
echo user.name   # 张三
echo user.age    # 28
echo user.email  # zhangsan@example.com

# 解构
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 — 纯函数

funcproc {.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 命名约定

元素约定示例
过程名camelCasegetUserInfo, calculateTotal
类型名PascalCaseUserInfo, ShoppingCart
常量PascalCase 或 snake_caseMaxSize, MAX_SIZE
变量camelCaseuserName, 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隐式返回变量

练习

  1. 编写一个泛型 binarySearch 过程
  2. 实现一个带闭包的 makeAccumulator 工厂函数
  3. 为矩阵类型定义 +*$ 运算符
  4. 使用 method 实现一个简单的图形绘制系统

扩展阅读


上一章:控制流 | 下一章:数据结构