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

Nim 完全指南 / 05 运算符

第 05 章:运算符

5.1 算术运算符

运算符说明示例结果
+加法5 + 38
-减法5 - 32
*乘法5 * 315
/浮点除法5 / 31.666...
div整数除法5 div 31
mod取模5 mod 32
let a = 10
let b = 3

echo a + b       # 13
echo a - b       # 7
echo a * b       # 30
echo a / b       # 3.333333333333333
echo a div b     # 3
echo a mod b     # 1

# 混合运算需要显式转换
let x: int = 10
let y: float = 3.0
echo float(x) + y   # 13.0

# 一元运算符
echo +5     # 5
echo -5     # -5

# 幂运算(使用 math 模块)
import std/math
echo pow(2, 10)   # 1024.0
echo 2 ^ 10       # 1024(整数幂运算符)

5.2 比较运算符

运算符说明示例
==等于5 == 5true
!=不等于5 != 3true
<小于3 < 5true
<=小于等于5 <= 5true
>大于5 > 3true
>=大于等于5 >= 3true
<=>三路比较(spaceship)5 <=> 31
# 基本比较
echo 5 == 5       # true
echo 5 != 3       # true
echo 3 < 5        # true
echo 5 <= 5       # true

# 字符串比较(字典序)
echo "abc" < "abd"    # true
echo "hello" == "hello"  # true

# 三路比较(返回 -1、0、1)
import std/cmputils
echo cmp(5, 3)    # 1
echo cmp(3, 5)    # -1
echo cmp(5, 5)    # 0

# 浮点数比较
import std/math
let a = 0.1 + 0.2
let b = 0.3
echo a == b           # false(浮点精度问题)
echo almostEqual(a, b)  # true

5.3 逻辑运算符

运算符说明短路求值
and逻辑与✅ 左边为 false 时不求值右边
or逻辑或✅ 左边为 true 时不求值右边
not逻辑非
let a = true
let b = false

echo a and b    # false
echo a or b     # true
echo not a      # false

# 短路求值
proc expensiveCheck(): bool =
  echo "This is expensive!"
  true

if false and expensiveCheck():
  echo "Won't reach here"
# expensiveCheck 不会被调用

if true or expensiveCheck():
  echo "Won't reach here either"
# expensiveCheck 不会被调用

5.4 位运算符

运算符说明示例
and按位与0b1100 and 0b10100b1000
or按位或0b1100 or 0b10100b1110
xor按位异或0b1100 xor 0b10100b0110
not按位取反not 0-1
shl左移1 shl 38
shr右移8 shr 31
let a = 0b1100  # 12
let b = 0b1010  # 10

echo toHex(a and b)   # 8
echo toHex(a or b)    # E
echo toHex(a xor b)   # 6
echo toHex(not a)     # FFFFFFFFFFFFFFF3

# 移位操作
echo 1 shl 10     # 1024
echo 1024 shr 10  # 1

# 位掩码应用
const
  FlagRead =    0b0001
  FlagWrite =   0b0010
  FlagExecute = 0b0100

var permissions = FlagRead or FlagWrite
echo (permissions and FlagRead) != 0    # true
echo (permissions and FlagExecute) != 0 # false

permissions = permissions or FlagExecute
echo (permissions and FlagExecute) != 0 # true

5.5 字符串运算符

# 字符串拼接
let hello = "Hello"
let world = "World"
echo hello & ", " & world & "!"   # Hello, World!

# 字符串重复
echo "Ha".repeat(3)    # HaHaHa
echo "=-".repeat(20)   # =--=--=--=--=--=--=--=--=--=--

# 字符串包含检查
echo "Hello" in "Hello, World!"     # true
echo "xyz" notin "Hello, World!"    # true

# 序列连接
let a = @[1, 2, 3]
let b = @[4, 5, 6]
echo a & b    # @[1, 2, 3, 4, 5, 6]

5.6 集合运算符

let s1 = {1, 2, 3, 4}
let s2 = {3, 4, 5, 6}

# 并集
echo s1 + s2      # {1, 2, 3, 4, 5, 6}

# 交集
echo s1 * s2      # {3, 4}

# 差集
echo s1 - s2      # {1, 2}

# 对称差集
echo s1 -+- s2    # {1, 2, 5, 6}

# 子集/超集检查
echo {1, 2} <= s1     # true({1,2} 是 s1 的子集)
echo s1 >= {1, 2}     # true(s1 是 {1,2} 的超集)

# 成员检查
echo 3 in s1      # true
echo 7 notin s1   # true

5.7 赋值运算符

var x = 10

x += 5      # x = x + 5 → 15
echo x

x -= 3      # x = x - 3 → 12
echo x

x *= 2      # x = x * 2 → 24
echo x

x = x div 3 # Nim 没有 div= 运算符 → 8
echo x

x = x mod 5 # Nim 没有 mod= 运算符 → 3
echo x

# 字符串赋值
var s = "Hello"
s &= " World"   # s = s & " World"
echo s           # Hello World

5.8 自定义运算符

Nim 最强大的特性之一:你可以定义自己的运算符,甚至发明全新的运算符符号。

5.8.1 重定义现有运算符

# 为向量类型定义运算符
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})"

let
  a = Vec2(x: 1.0, y: 2.0)
  b = Vec2(x: 3.0, y: 4.0)

echo a + b       # (4.0, 6.0)
echo a - b       # (-2.0, -2.0)
echo a * 2.0     # (2.0, 4.0)

5.8.2 定义全新运算符

# 定义一个新的中缀运算符 `**`(点积)
proc `**`(a, b: Vec2): float =
  a.x * b.x + a.y * b.y

echo a ** b   # 11.0

# 定义前缀运算符
proc `-`(v: Vec2): Vec2 =
  Vec2(x: -v.x, y: -v.y)

echo -a   # (-1.0, -2.0)

# 定义后缀运算符
proc `°`(x: float): float =
  x * PI / 180.0

echo 90.0°    # 1.570796326794897

5.8.3 自定义符号运算符

# Nim 允许使用以下字符创建运算符:
# + - * / \ < > = @ $ ~ & % ! ? ^ . |

# 定义 `<=>` 三路比较
proc `<=>`(a, b: int): int =
  if a < b: -1
  elif a > b: 1
  else: 0

echo 5 <=> 3   # 1
echo 3 <=> 5   # -1
echo 3 <=> 3   # 0

# 定义 `=~` 正则匹配运算符
import std/re
proc `=~`(s: string, pattern: string): bool =
  s.contains(re(pattern))

echo "Hello123" =~ r"\d+"   # true
echo "Hello" =~ r"\d+"      # false

# 定义 `|>` 管道运算符(函数组合)
proc `|>`[T, U](x: T, f: proc(a: T): U): U =
  f(x)

proc double(x: int): int = x * 2
proc addOne(x: int): int = x + 1

echo 5 |> double |> addOne   # 11

5.8.4 运算符优先级

Nim 的运算符优先级由运算符首字符决定:

优先级首字符运算符方向
10(最高)$, ^右结合
9*, /, %, \左结合
8+, -, |, ~左结合
7==, <, >, !=, <=, >=
6and, or, not, xor左结合
5最低
# 自定义优先级
proc `**`(a, b: int): int =
  a ^ b   # 使用内置的幂运算

# `**` 的首字符是 `*`,所以优先级为 9
echo 2 ** 3 + 1   # 9(先算 2**3=8,再 8+1=9)
echo 2 + 3 ** 2   # 11(先算 3**2=9,再 2+9=11)

# 使用 `infix` pragma 指定优先级
proc `><`(a, b: int): int {.inline.} =
  (a + b) div 2

# `><` 首字符是 `>`,优先级为 7
echo 10 >< 6   # 8

5.9 运算符与方法语法

Nim 中 a.f(b) 等价于 f(a, b),这使得运算符重载非常自然:

type Matrix = object
  rows, cols: int
  data: seq[float]

proc `[]`(m: Matrix, r, c: int): float =
  m.data[r * m.cols + c]

proc `[]=`(m: var Matrix, r, c: int, val: float) =
  m.data[r * m.cols + c] = val

proc `$`(m: Matrix): string =
  result = ""
  for r in 0..<m.rows:
    for c in 0..<m.cols:
      result &= &"{m[r, c]:6.2f} "
    result &= "\n"

var m = Matrix(rows: 2, cols: 3, data: newSeq[float](6))
m[0, 0] = 1.0; m[0, 1] = 2.0; m[0, 2] = 3.0
m[1, 0] = 4.0; m[1, 1] = 5.0; m[1, 2] = 6.0
echo m
#  1.00   2.00   3.00
#  4.00   5.00   6.00

5.10 实战示例

🏢 场景:权限系统

type
  Permission = enum
    Read, Write, Execute, Admin
  
  PermissionSet = set[Permission]

proc grant(perm: var PermissionSet, p: Permission) =
  perm.incl(p)

proc revoke(perm: var PermissionSet, p: Permission) =
  perm.excl(p)

proc has(perm: PermissionSet, p: Permission): bool =
  p in perm

proc describe(perm: PermissionSet): string =
  if perm == {Read, Write, Execute, Admin}:
    "Full access"
  elif Admin in perm:
    "Admin access"
  elif Write in perm:
    "Read/Write access"
  elif Read in perm:
    "Read-only access"
  else:
    "No access"

var userPerm: PermissionSet = {}
userPerm.grant(Read)
userPerm.grant(Write)
echo userPerm.describe()       # Read/Write access
echo userPerm.has(Admin)       # false
userPerm.grant(Admin)
echo userPerm.describe()       # Admin access

🏢 场景:向量数学库

import std/math

type Vec3 = object
  x, y, z: float

proc vec3(x, y, z: float): Vec3 = Vec3(x: x, y: y, z: z)
proc `$`(v: Vec3): string = &"({v.x:.2f}, {v.y:.2f}, {v.z:.2f})"

proc `+`(a, b: Vec3): Vec3 = vec3(a.x+b.x, a.y+b.y, a.z+b.z)
proc `-`(a, b: Vec3): Vec3 = vec3(a.x-b.x, a.y-b.y, a.z-b.z)
proc `*`(v: Vec3, s: float): Vec3 = vec3(v.x*s, v.y*s, v.z*s)
proc `*`(s: float, v: Vec3): Vec3 = v * s

proc dot(a, b: Vec3): float = a.x*b.x + a.y*b.y + a.z*b.z
proc cross(a, b: Vec3): Vec3 =
  vec3(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x)
proc length(v: Vec3): float = sqrt(v.x*v.x + v.y*v.y + v.z*v.z)
proc normalize(v: Vec3): Vec3 = v * (1.0 / v.length)

let
  a = vec3(1, 0, 0)
  b = vec3(0, 1, 0)

echo "a + b = ", a + b         # (1.00, 1.00, 0.00)
echo "a · b = ", dot(a, b)    # 0.0
echo "a × b = ", cross(a, b)  # (0.00, 0.00, 1.00)
echo "|a| = ", a.length()      # 1.0
echo "|a+b| = ", (a+b).length() # 1.414

本章小结

类别运算符
算术+ - * / div mod ^
比较== != < <= > >= <=>
逻辑and or not
位运算and or xor not shl shr
字符串& (拼接), in (包含)
集合+ - * in <= >=
赋值+= -= *= &=
自定义任意合法字符组合

练习

  1. 为复数类型定义四则运算和 $ 函数
  2. 创建一个管道运算符 |> 实现链式调用
  3. 使用位运算实现一个简单的标志(flag)系统
  4. 自定义一个 <~> 运算符,用于两个序列的合并去重

扩展阅读


上一章:变量与类型 | 下一章:控制流