Nim 完全指南 / 04 变量与类型系统
第 04 章:变量与类型系统
4.1 变量声明
Nim 有三种声明变量的关键字,各有不同语义:
| 关键字 | 可变性 | 求值时机 | 类比 |
|---|---|---|---|
let | 不可变 | 运行时 | Rust 的 let、Swift 的 let |
var | 可变 | 运行时 | 大多数语言的变量 |
const | 不可变 | 编译期 | C 的 #define、Rust 的 const |
4.1.1 let — 不可变绑定
let x = 10
# x = 20 # ❌ 编译错误:'x' cannot be assigned to
let name = "Nim"
let pi = 3.14159
let isActive = true
# 类型推断
let age = 25 # int
let score = 98.5 # float64
let greeting = "Hi" # string
let flag = false # bool
# 显式类型标注
let width: float = 100.0
let count: int = 42
let letter: char = 'A'
4.1.2 var — 可变变量
var counter = 0
counter += 1 # ✅ 可以修改
echo counter # 1
var name = "Alice"
name = "Bob" # ✅ 可以重新赋值
echo name # Bob
# var 可以声明未初始化的变量(需要指定类型)
var x: int # 默认值为 0
var s: string # 默认值为 ""
var b: bool # 默认值为 false
echo x, " ", s.len, " ", b # 0 0 false
4.1.3 const — 编译期常量
const
MaxSize = 1024
AppName = "MyApp"
Version = "1.0.0"
Pi = 3.14159265358979
# const 必须在编译期可确定
const
Width = 100
Height = 200
Area = Width * Height # ✅ 编译期计算
echo Area # 20000
⚠️ 注意:const 的值必须在编译期可求值,不能依赖运行时输入或函数返回值:
# ❌ 错误
# const input = stdin.readLine()
# ✅ 正确:使用纯函数
const factorial5 = block:
var result = 1
for i in 1..5:
result *= i
result
echo factorial5 # 120
4.1.4 变量声明对比
# 完整对比示例
let immutableVal = 100 # 不可变,运行时绑定
var mutableVal = 100 # 可变,运行时绑定
const compileTimeVal = 100 # 不可变,编译期绑定
mutableVal = 200 # ✅ OK
# immutableVal = 200 # ❌ 错误
# compileTimeVal = 200 # ❌ 错误
4.2 基本类型
4.2.1 整数类型
| 类型 | 大小 | 范围 | 后缀 |
|---|---|---|---|
int8 | 1 字节 | -128 ~ 127 | — |
int16 | 2 字节 | -32768 ~ 32767 | — |
int32 | 4 字节 | -2^31 ~ 2^31-1 | — |
int64 | 8 字节 | -2^63 ~ 2^63-1 | — |
int | 平台相关 | 通常 64 位 | — |
uint8 | 1 字节 | 0 ~ 255 | — |
uint16 | 2 字节 | 0 ~ 65535 | — |
uint32 | 4 字节 | 0 ~ 2^32-1 | — |
uint64 | 8 字节 | 0 ~ 2^64-1 | — |
uint | 平台相关 | 无符号 | — |
var
a: int8 = 127
b: int32 = 1_000_000 # 下划线分隔,提高可读性
c: int64 = 9_223_372_036_854_775_807
d: uint = 42
echo "int size: ", sizeof(int), " bytes" # 8 (64位系统)
echo "int32 size: ", sizeof(int32), " bytes" # 4
# 十六进制、八进制、二进制
let hex = 0xFF # 255
let oct = 0o77 # 63
let bin = 0b1010_1010 # 170
echo hex, " ", oct, " ", bin
⚠️ 注意:优先使用 int,除非有明确的大小需求。int 在 64 位系统上是 64 位,足以处理绝大多数场景。
4.2.2 浮点类型
| 类型 | 大小 | 精度 |
|---|---|---|
float32 | 4 字节 | ~7 位有效数字 |
float64 | 8 字节 | ~15 位有效数字 |
float | 等同于 float64 | ~15 位有效数字 |
var
x: float32 = 3.14
y: float64 = 3.14159265358979
z: float = 2.71828 # 等同于 float64
echo "float size: ", sizeof(float), " bytes" # 8
# 科学计数法
let avogadro = 6.022e23
let planck = 6.626e-34
echo avogadro, " ", planck
# 浮点数特殊值
import std/math
echo Inf # 正无穷
echo -Inf # 负无穷
echo NaN # 非数字
echo isNaN(NaN) # true
4.2.3 字符与布尔
# 字符类型
let ch: char = 'A'
echo ord(ch) # 65(ASCII 码)
echo chr(97) # 'a'
let digit = '5'
echo digit.isDigit() # true
echo digit.ord - '0'.ord # 5(转数字)
# 布尔类型
let
t: bool = true
f: bool = false
echo t and f # false
echo t or f # true
echo not f # true
echo t == f # false
4.2.4 字符串类型
# 字符串(可变)
var s: string = "Hello"
s.add(", World!")
echo s # Hello, World!
# 多行字符串
let poem = """Roses are red,
Violets are blue,
Nim is awesome,
And so are you."""
echo poem
# 字符串是值类型
let a = "hello"
var b = a
b.add(" world")
echo a # "hello"(不受影响)
echo b # "hello world"
# cstring — C 兼容字符串
let cs: cstring = "C string"
echo cs.len
4.2.5 类型总结表
| 类型 | 默认值 | 可变性示例 | 内存布局 |
|---|---|---|---|
int | 0 | var x = 5; x = 10 | 值类型 |
float | 0.0 | var f = 1.5; f = 2.5 | 值类型 |
bool | false | var b = true; b = false | 值类型 |
char | ‘\0’ | var c = 'A'; c = 'B' | 值类型 |
string | "" | var s = "hi"; s.add("!") | 引用计数 |
seq | @[] | var s = @[1]; s.add(2) | 引用计数 |
4.3 类型转换
4.3.1 显式转换
Nim 不支持隐式类型转换,必须使用 int()、float() 等进行显式转换:
# 整数 ↔ 浮点
let i: int = 42
let f: float = float(i) # int → float
let j: int = int(3.14) # float → int(截断,不是四舍五入)
echo f # 42.0
echo j # 3
# 字符串 ↔ 数字
let s: string = "123"
let n: int = parseInt(s) # string → int
let fs: string = "3.14"
let nf: float = parseFloat(fs) # string → float
echo n # 123
echo nf # 3.14
# 数字 → 字符串
let si: string = $42 # int → string
let sf: string = $3.14 # float → string
let sb: string = $true # bool → string
echo si, " ", sf, " ", sb # 42 3.14 true
# 字符 ↔ 整数
let c: char = 'A'
let ci: int = int(c) # char → int
let cj: char = char(97) # int → char
echo ci # 65
echo cj # 'a'
4.3.2 安全转换
# 使用 BigEndian/LittleEndian 类型安全转换
import std/endians
# 大小端转换
var val: int32 = 0x12345678
var bigEndian: int32
bigEndian32(addr bigEndian, addr val)
echo bigEndian.toHex()
# 使用 parseutils 进行安全解析
import std/parseutils
var parsed: int
let count = "123abc".parseInt(parsed)
echo parsed # 123
echo count # 3(解析的字符数)
4.3.3 类型断言
# 类型断言(用于泛型场景)
proc process[T](value: T) =
when T is int:
echo "Processing int: ", value
elif T is float:
echo "Processing float: ", value
elif T is string:
echo "Processing string: ", value
else:
echo "Processing unknown type"
process(42) # Processing int: 42
process(3.14) # Processing float: 3.14
process("hello") # Processing string: hello
# 运行时类型检查
proc describe(x: int | float | string) =
when x is int:
echo "Integer: ", x
elif x is float:
echo "Float: ", x
elif x is string:
echo "String: ", x
4.4 类型推断
Nim 的类型推断非常强大,大多数情况下不需要显式标注类型:
# 基本推断
let x = 10 # int
let y = 3.14 # float64
let s = "hello" # string
let b = true # bool
# 从表达式推断
let sum = x + 5 # int
let product = y * 2 # float64
let greeting = s & " world" # string
# 从函数返回值推断
proc add(a, b: int): int = a + b
let result = add(1, 2) # int
# 泛型推断
proc identity[T](x: T): T = x
let val = identity(42) # int
let fval = identity(3.14) # float64
# 容器类型推断
let nums = @[1, 2, 3] # seq[int]
let pairs = @[(1, "a"), (2, "b")] # seq[(int, string)]
4.5 类型别名
# 使用 type 关键字创建类型别名
type
UserId = int
UserName = string
Score = float
FilePath = string
# 使用类型别名提高代码可读性
proc getUser(id: UserId): UserName =
"User_" & $id
proc saveScore(userId: UserId, score: Score) =
echo &"User {userId} scored {score}"
let uid: UserId = 42
let name = getUser(uid)
saveScore(uid, 98.5)
# 复杂类型别名
type
Callback = proc(x: int): string
Matrix = seq[seq[float]]
Dictionary = Table[string, int]
# 使用 Callback
proc processValue(cb: Callback, val: int) =
echo cb(val)
processValue(proc(x: int): string = "Value: " & $x, 10)
4.6 范围类型
# 范围类型——限制值的范围
type
Percentage = range[0..100]
Month = range[1..12]
Digit = range[0..9]
Letter = range['a'..'z']
var p: Percentage = 50
echo p
# p = 150 # ❌ 运行时错误:value out of range
# 编译期范围检查
proc setMonth(m: Month) =
echo "Month: ", m
setMonth(6) # ✅ OK
# setMonth(13) # ❌ 运行时错误
# 字符范围
type HexDigit = range['0'..'9'] | range['a'..'f'] | range['A'..'F']
4.7 枚举类型
type
Color = enum
Red, Green, Blue, Yellow
Direction = enum
North = "N", # 自定义字符串值
South = "S",
East = "E",
West = "W"
# 指定序号值
Priority = enum
Low = 1
Medium = 5
High = 10
Critical = 100
# 使用枚举
var c: Color = Red
echo c # Red
echo ord(c) # 0
echo c == Red # true
# 遍历枚举
for d in Direction:
echo d, " -> ", $d
# 枚举在 case 中使用
proc handleDirection(d: Direction) =
case d
of North: echo "Going up"
of South: echo "Going down"
of East: echo "Going right"
of West: echo "Going left"
handleDirection(North) # Going up
4.8 子范围类型
type
# 整数子范围
Byte = range[0..255]
Year = range[1900..2100]
# 字符子范围
UpperChar = range['A'..'Z']
LowerChar = range['a'..'z']
Digit = range['0'..'9']
# 编译期会检查赋值
var b: Byte = 255
# b = 256 # ❌ 编译错误(如果是编译期常量)或运行时错误
# 安全的子范围操作
proc setVolume(vol: range[0..100]) =
echo "Volume: ", vol
setVolume(50) # ✅ OK
# setVolume(150) # ❌ 错误
4.9 集合类型
type
CharSet = set[char]
DigitSet = set[0..9] # 需要编译期知道范围
Flag = enum
Bold, Italic, Underline, Strikethrough
FlagSet = set[Flag]
# 字符集操作
var letters: CharSet = {'a'..'z', 'A'..'Z'}
echo 'a' in letters # true
echo '1' in letters # false
letters.incl('1') # 添加
letters.excl('a') # 移除
echo 'a' in letters # false
# 枚举集合
var style: FlagSet = {Bold, Italic}
echo Bold in style # true
style.incl(Underline)
style.excl(Bold)
echo style # {Italic, Underline}
# 集合运算
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}
4.10 nil 类型与可选值
# 引用类型可以是 nil
type
Node = ref object
value: int
next: Node
var n: Node = nil # nil 是引用类型的默认值
echo n.isNil # true
n = Node(value: 42)
echo n.isNil # false
# 使用 Option 类型处理可选值
import std/options
let someValue = some(42)
let noValue = none[int]()
echo someValue.isSome # true
echo someValue.get() # 42
echo noValue.isNone # true
echo noValue.get(0) # 0(默认值)
4.11 实战示例
🏢 场景:用户数据处理
import std/[strutils, strformat, options]
type
UserRole = enum
Admin, Editor, Viewer
User = object
id: int
name: string
email: string
role: UserRole
score: float
active: bool
proc createUser(id: int, name, email: string, role: UserRole): User =
User(
id: id,
name: name,
email: email,
role: role,
score: 0.0,
active: true
)
proc formatUser(u: User): string =
&"[{u.role}] {u.name} <{u.email}> (score: {u.score:.1f})"
when isMainModule:
var user = createUser(1, "张三", "[email protected]", Editor)
user.score = 95.5
echo formatUser(user)
# [Editor] 张三 <[email protected]> (score: 95.5)
本章小结
| 概念 | 说明 |
|---|---|
let | 不可变绑定,运行时求值 |
var | 可变变量,运行时求值 |
const | 编译期常量 |
int | 默认整数类型(64位) |
float | 默认浮点类型(64位) |
| 类型转换 | 必须显式转换,使用 int()、float() 等 |
| 类型推断 | 编译器自动推断大多数类型 |
| 范围类型 | range[a..b] 限制值域 |
| 枚举类型 | enum 定义命名常量集合 |
| 集合类型 | set[T] 表示值的集合 |
练习
- 声明各种类型的变量并观察默认值
- 编写程序演示各种类型转换,包括安全和不安全的情况
- 创建一个枚举类型和对应的 case 语句处理
- 使用范围类型创建一个安全的百分比计算器