Nim 完全指南 / 05 运算符
第 05 章:运算符
5.1 算术运算符
| 运算符 | 说明 | 示例 | 结果 |
|---|
+ | 加法 | 5 + 3 | 8 |
- | 减法 | 5 - 3 | 2 |
* | 乘法 | 5 * 3 | 15 |
/ | 浮点除法 | 5 / 3 | 1.666... |
div | 整数除法 | 5 div 3 | 1 |
mod | 取模 | 5 mod 3 | 2 |
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 == 5 → true |
!= | 不等于 | 5 != 3 → true |
< | 小于 | 3 < 5 → true |
<= | 小于等于 | 5 <= 5 → true |
> | 大于 | 5 > 3 → true |
>= | 大于等于 | 5 >= 3 → true |
<=> | 三路比较(spaceship) | 5 <=> 3 → 1 |
# 基本比较
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 0b1010 → 0b1000 |
or | 按位或 | 0b1100 or 0b1010 → 0b1110 |
xor | 按位异或 | 0b1100 xor 0b1010 → 0b0110 |
not | 按位取反 | not 0 → -1 |
shl | 左移 | 1 shl 3 → 8 |
shr | 右移 | 8 shr 3 → 1 |
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 | ==, <, >, !=, <=, >= | 无 |
| 6 | and, 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 <= >= |
| 赋值 | += -= *= &= |
| 自定义 | 任意合法字符组合 |
练习
- 为复数类型定义四则运算和
$ 函数 - 创建一个管道运算符
|> 实现链式调用 - 使用位运算实现一个简单的标志(flag)系统
- 自定义一个
<~> 运算符,用于两个序列的合并去重
扩展阅读
← 上一章:变量与类型 | 下一章:控制流 →