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

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 整数类型

类型大小范围后缀
int81 字节-128 ~ 127
int162 字节-32768 ~ 32767
int324 字节-2^31 ~ 2^31-1
int648 字节-2^63 ~ 2^63-1
int平台相关通常 64 位
uint81 字节0 ~ 255
uint162 字节0 ~ 65535
uint324 字节0 ~ 2^32-1
uint648 字节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 浮点类型

类型大小精度
float324 字节~7 位有效数字
float648 字节~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 类型总结表

类型默认值可变性示例内存布局
int0var x = 5; x = 10值类型
float0.0var f = 1.5; f = 2.5值类型
boolfalsevar 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, "张三", "zhangsan@example.com", Editor)
  user.score = 95.5
  echo formatUser(user)
  # [Editor] 张三 <zhangsan@example.com> (score: 95.5)

本章小结

概念说明
let不可变绑定,运行时求值
var可变变量,运行时求值
const编译期常量
int默认整数类型(64位)
float默认浮点类型(64位)
类型转换必须显式转换,使用 int()float()
类型推断编译器自动推断大多数类型
范围类型range[a..b] 限制值域
枚举类型enum 定义命名常量集合
集合类型set[T] 表示值的集合

练习

  1. 声明各种类型的变量并观察默认值
  2. 编写程序演示各种类型转换,包括安全和不安全的情况
  3. 创建一个枚举类型和对应的 case 语句处理
  4. 使用范围类型创建一个安全的百分比计算器

扩展阅读


上一章:第一个程序 | 下一章:运算符