Nim支持显示类型转换和隐式类型转换
使用casts操作符完成显示类型转换工作,
显示类型转换工作是编译期完成的工作,是位模式的
隐式类型转换也是编译期完成的工作,不是位模式的
让我们来看一下下面的代码
proc getID(x: Person): int =
Student(x).id
在上面的代码中
如果x变量的类型不是Student类型(Person类型是Student类型的父类型)
那么会抛出一个InvalidObjectConversionError异常。
通常情况下,可变的对象类型实际意义更大一些
(译注:这是面向对象三大要素之一“多态”的重要组成部分)
来看下面的代码:
# This is an example how an abstract syntax tree could be modelled in Nim
type
NodeKind = enum # the different node types
nkInt, # a leaf with an integer value
nkFloat, # a leaf with a float value
nkString, # a leaf with a string value
nkAdd, # an addition
nkSub, # a subtraction
nkIf # an if statement
Node = ref NodeObj
NodeObj = object
case kind: NodeKind # the ``kind`` field is the discriminator
of nkInt: intVal: int
of nkFloat: floatVal: float
of nkString: strVal: string
of nkAdd, nkSub:
leftOp, rightOp: PNode
of nkIf:
condition, thenPart, elsePart: PNode
var n = PNode(kind: nkFloat, floatVal: 1.0)
# the following statement raises an `FieldError` exception, because
# n.kind's value does not fit:
n.strVal = ""
从上面的代码中可以看出
有继承关系的对象之间做类型转换非常简单
访问无效的对象属性会引发一个异常
对象的方法有两个缺点
Nim为了避免类似的问题,不分配方法到类中
后面我们将了解Nim的动态绑定方法的特性
可以使用点操作符调用对象的方法
obj.method(args)
而不是
method(obj,args)
如果没有参数,则可以省略小括号
obj.method
方法调用是不受对象限制的,来看看下面的代码:
echo("abc".len) # is the same as echo(len("abc"))
echo("abc".toUpper())
echo({'a', 'b', 'c'}.card)
stdout.writeln("Hallo") # the same as writeln(stdout, "Hallo")
我们再来看看面向对象的写法
import strutils
stdout.writeln("Give a list of numbers (separated by spaces): ")
stdout.write(stdin.readLine.split.map(parseInt).max.`$`)
stdout.writeln(" is the maximum!")
是不是感觉很糟糕呢?
请看如下代码来了解对象的属性
type
Socket* = ref object of RootObj
FHost: int # cannot be accessed from the outside of the module
# the `F` prefix is a convention to avoid clashes since
# the accessors are named `host`
proc `host=`*(s: var Socket, value: int) {.inline.} =
## setter of hostAddr
s.FHost = value
proc host*(s: Socket): int {.inline.} =
## getter of hostAddr
s.FHost
var s: Socket
new s
s.host = 34 # same as `host=`(s, 34)
这个Socket类型,有一个host属性,获取这个属性的值时
执行第二个方法,设置这个属性的值时,执行第一个方法
(这个例子中也演示了inline方法)
我们可以在类型中重载方括号,以提供与数组相类似的属性
请看如下代码:
type
Vector* = object
x, y, z: float
proc `[]=`* (v: var Vector, i: int, value: float) =
# setter
case i
of 0: v.x = value
of 1: v.y = value
of 2: v.z = value
else: assert(false)
proc `[]`* (v: Vector, i: int): float =
# getter
case i
of 0: result = v.x
of 1: result = v.y
of 2: result = v.z
else: assert(false)
需要使用method关键字来代替proc关键字
才能使用动态指派的特性
来看下面的代码
type
PExpr = ref object of RootObj ## abstract base class for an expression
PLiteral = ref object of PExpr
x: int
PPlusExpr = ref object of PExpr
a, b: PExpr
# watch out: 'eval' relies on dynamic binding
method eval(e: PExpr): int =
# override this base method
quit "to override!"
method eval(e: PLiteral): int = e.x
method eval(e: PPlusExpr): int = eval(e.a) + eval(e.b)
proc newLit(x: int): PLiteral = PLiteral(x: x)
proc newPlus(a, b: PExpr): PPlusExpr = PPlusExpr(a: a, b: b)
echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))
再来看看下面的代码
type
Thing = ref object of RootObj
Unit = ref object of Thing
x: int
method collide(a, b: Thing) {.inline.} =
quit "to override!"
method collide(a: Thing, b: Unit) {.inline.} =
echo "1"
method collide(a: Unit, b: Thing) {.inline.} =
echo "2"
var a, b: Unit
new a
new b
collide(a, b) # output: 2
因为决议是从左到右执行的
所以最后一个collide方法优于前面两个collide方法
毕竟a和b都是Unit类型的
注意:Nim不产生虚方法表(C#.net是需要虚方法表的),
但是会生成调用树(这样做可以提升性能表现)