Nim是多范式编程语言,当然也支持面向对象编程,在Nim中,没有class关键字,定义一个类类型也是用object
type
Person = object
FAge: int # 私有变量
# 只读属性的效果
proc name(this: Person): string =
return "jack"
# age属性,getter
proc age(this: Person): int =
return this.FAge
# age属性,setter
proc `age=`(this: var Person, val: int) =
this.FAge = val
# 成员方法
proc say(this: Person, friend: string): string =
result = "Hi," & friend & ",my name's " & this.name
var obj1 = Person()
obj1.age = 18
echo obj1.name
echo obj1.age
echo obj1.say("tony")
可以看到,类的设计也是非侵入式的,这一点和Go语言是一模一样的。
一个类无非就是有成员变量、属性、方法,在Nim中,是没有属性获取器的,而是用方法替代,而类属性设置器是利用其方法重载特性来实现的(巧妙的方法),所以属性可以是只读、只写或者读写,都没问题!
在讨论引用类型之前我们需要了解一下object在内存中的布局是怎样的
type
Animal = object
x1: int
x2: byte
x3: int
var
obj1: Animal
echo sizeof(obj1) # 12
echo obj1.addr.repr # ref 0042BD98 --> [x1 = 0,x2 = 0,x3 = 0]
echo obj1.x1.addr.repr # ref 0042BD98 --> 0
echo obj1.x2.addr.repr # ref 0042BD9C --> 0
echo obj1.x3.addr.repr # ref 0042BDA0 --> 0
可以看到object的内存地址就是第一个成员的内存地址,成员在内存中也是连续排放。
生成的C代码
struct tyObject_Animal_7IFGbFoQwcnDBZdA7Fan6Q {
NI x1;
NU8 x2;
NI x3;
};
默认情况下,Nim最终生成的是C语言代码,所以没有真正意义上的class,object就是C语言中的结构体。
这里注意x2成员,占用了4个字节,了解C语言的都知道怎么回事,编译器会进行字节对齐处理。
如果想按1字节对齐,可以用{.packed.}编译指令
type
Animal {.packed.} = object
x1: int
x2: byte
x3: int
var
obj1: Animal
echo sizeof(obj1) # 9
echo obj1.addr.repr # ref 0042BD98 --> [x1 = 0,x2 = 0,x3 = 0]
echo obj1.x1.addr.repr # ref 0042BD98 --> 0
echo obj1.x2.addr.repr # ref 0042BD9C --> 0
echo obj1.x3.addr.repr # ref 0042BD9D --> 0
对应的C代码变为
struct __attribute__((__packed__)) tyObject_Animal_7IFGbFoQwcnDBZdA7Fan6Q {
NI x1;
NU8 x2;
NI x3;
};
字节对齐在某些场景下是必须的,比如调用Win32 API,网络封包等等。
注意,字节对齐不可以与ref类型同时使用,比如
type
Animal {.packed.} = ref object
x1: int
x2: byte
x3: int
你会得到一个编译错误。
更多的编译指令可以参考官方手册:https://nim-lang.org/docs/manual.html#pragmas
现在知道了object本质就是struct,它不需要去你new它,结构之间的赋值就是拷贝所有成员变量
type
Animal = object
x1: int
x2: byte
x3: int
var
obj1: Animal
obj2: Animal
obj1.x1 = 5
obj2 = obj1
obj2.x1 = 10
echo obj1.x1 # 5
echo obj2.x1 # 10
实例之间不会互相影响。第一节说过,Nim中class是非侵入式的,调用成员方法等于调用一个普通函数
type
Animal = object
x: int
var
obj1: Animal
proc foo(this: Animal) =
this.x = 5
obj1.foo
foo的参数是按值传递的,会发生一次拷贝,降低程序执行效率,还好对于有赋值行为的代码,Nim是不让编译的。对于成员方法应该传递引用
type
Animal = object
x: int
var
obj1: Animal
proc foo(this: var Animal) = # 加上var按引用传递
this.x = 5
obj1.foo
echo obj1.x # 5
到现在为止我们知道了默认情况下类之间都是按值传递的
用过C#、Python等高级语言的朋友应该知道,类默认都是引用类型的,在Nim中实现默认按引用需要再定义一个引用的类型
type
Animal = object
x: int
AnimalRef = ref Animal # 通过ref来定义一个引用类型
var
obj1: AnimalRef
proc foo(this: AnimalRef) =
this.x = 5
obj1 = new AnimalRef
obj1.foo
echo obj1.x # 5
# 增加引用
var obj2 = obj1
obj2.x = 10 # 对obj2赋值,同时影响obj1
echo obj1.x # 10
引用类型用ref关键字定义,需要使用new来创建实例,成员方法可以不再使用var关键字了。
type
Animal = ref object {.inheritable.} # inheritable表示此类可以被继承
Person = ref object of Animal
method say(this: Animal): string {.base.} =
result = "..."
method say(this: Person): string =
result = "hello world"
var
obj1: Animal = new Animal
obj2: Animal = new Person
echo obj1.say # ...
echo obj2.say # hello world
如果一个类可能会被继承,则它的祖先必须是RootObj,这样子类才能继承,并实现多态性。而且virtual方法需要是用method来定义,而不是proc,这个在之前的文章中说过:https://blog.csdn.net/aqtata/article/details/82591854
参考:
https://nim-lang.org/docs/manual.html#multi-methods