【Nim】面向对象编程:类

一、定义

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

你可能感兴趣的:(Nim)