型別轉換
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是需要虛方法表的),
但是會生成呼叫樹(這樣做可以提升效能表現)