Nim学习:Object
2025-07-01 20:55:10

#定义对象类型

定义对象类型有三种形式:

  1. object
  2. ref object
  3. ptr object

#object

1
2
3
type
MyObj1 = object
age: int

本质上就是结构体,从生成的 C 代码中可看出来:

1
2
3
struct tyObject_MyObj1__inljzvlFXKezToqKwQKx9bg {
NI age;
};

区别在使用上,例子:

1
2
3
4
proc foo1() =
var obj1: MyObj1
obj1.age = 5
echo obj1.age

这是对应的 C 代码,为了方便理解删除了一些无关的代码:

1
2
3
4
5
6
N_LIB_PRIVATE N_NIMCALL(void, foo1__main_u9)(void) {
tyObject_MyObj1__inljzvlFXKezToqKwQKx9bg obj1;
nimZeroMem((void*)(&obj1), sizeof(tyObject_MyObj1__inljzvlFXKezToqKwQKx9bg));
obj1.age = ((NI)5);
nimln_(14); colontmpD_ = dollar___systemZdollars_u8(obj1.age);
}

从代码中可以看出来,object声明的对象默认在栈上分配,初始化会清零。如果改用new分配则会在堆上,且会被 GC 回收

1
2
3
4
5
6
7
8
N_LIB_PRIVATE N_NIMCALL(void, foo1__main_u9)(void) {
tyObject_MyObj1__inljzvlFXKezToqKwQKx9bg* obj1;
obj1 = NIM_NIL;
obj1 = new__main_u10();
nimln_(13); (*obj1).age = ((NI)5);
nimln_(14); colontmpD_ = dollar___systemZdollars_u8((*obj1).age);
eqdestroy___main_u22(obj1);
}

#ref object

使用ref关键字定义对象总是在堆上分配:

1
2
3
4
5
6
7
type
MyObj2 = ref object
age: int

var obj2: MyObj2
obj2.age = 5
echo obj2.age

程序运行会报错:SIGSEGV: Illegal storage access. (Attempt to read from nil?)
看看对应的 C 代码:

1
2
3
4
5
6
7
N_LIB_PRIVATE N_NIMCALL(void, foo1__main_u9)(void) {
tyObject_MyObj2colonObjectType___oLKZp5hB19b9cuVViusoCnEQ* obj2;
obj2 = NIM_NIL;
(*obj2).age = ((NI)5);
nimln_(14); colontmpD_ = dollar___systemZdollars_u8((*obj2).age);
eqdestroy___main_u14(obj2);
}

错误原因就是访问空指针了,所以ref object必须用new关键字分配,同时它会被 GC 回收。

#ptr object

ptr objectref object一样都是指针类型,但ptr object不会被 GC 回收,需要手动管理生命周期:

1
2
3
4
5
proc foo1() =
var obj3 = cast[MyObj3](alloc(sizeof(MyObj3)))
obj3.age = 5
echo obj3.age
dealloc(obj3)

和 C 一样,申请一块内存然后强制转换为类型指针。不过一般不会这么用。

#不显示使用 ref 或 ptr 关键字

实际上,可以不在定义类型时用refptr关键字,而是在声明变量时决定其类型,这样更灵活:

1
2
var obj1: ref MyObj1
var obj2: ptr MyObj2

记住:

  1. ref类型要用 new 系列方法创建,该类型可被 GC 回收。
  2. ptr类型要用 create 系列方法创建,该类型需要手动释放。


创建对象主要用以下几种方法:

函数 释放内存 初始化清零 说明
new GC
create dealloc
createU dealloc
createShared freeShared 在共享堆上分配
createSharedU freeShared 在共享堆上分配


另外,在定义类型是用refptr是有意义的,这可以强制要求对象在堆上分配,比如在与 C 交互时,对象的生命周期由用户控制。

#RootObj

RootObj 是什么?简单一句话就是:当你要实现类的多态性时就要从它继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type
Animal = object of RootObj
Dog = object of Animal
Cat = object of Animal

method vocalize(self: Animal): string {.base.} = "..."
method vocalize(self: Dog): string = "Woof!"
method vocalize(self: Cat): string = "Meow!"

let
animal = Animal()
dog = Dog()
cat = Cat()

echo animal.vocalize() # 输出: "..."
echo dog.vocalize() # 输出: "Woof!"
echo cat.vocalize() # 输出: "Meow!"

#函数参数类型

看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type
MyObj1 = object
age: int

MyObj2 = ref object
age: int

MyObj3 = object
age: int

proc foo1(obj: var MyObj1) =
obj.age = 10

proc foo2(obj: MyObj2) =
obj.age = 20

proc foo3(obj: ptr MyObj3) =
obj.age = 30

var x1 = MyObj1(age: 1)
foo1(x1)

var x2 = new(MyObj2)
foo2(x2)

var x3 = create(MyObj3)
foo3(x3)
dealloc(x3)

从这段代码得到的知识点:

  • 如果参数是值类型,默认不可修改成员变量。要修改的化需要用var关键字。
  • 如果是refptr类型则可以直接修改成员变量而不需要var关键字。
  • new创建的是ref类型,create创建的是ptr类型。
  • 如果类型需要支持用create创建,则在定义时不要加ptr(示例中的 MyObj3 类型),否则会得到一个二级指针。
1
2
3
4
type
MyObj3 = ptr object

var x = create(MyObj3) # x 是二级指针

#相关阅读

Nim Tutorial (Part II)
Object Oriented Programming
Why inherit RootObj?