建立第一個物件

行動比較快的觀眾,說不定已經想起開頭時,我們想要讓一號機器人有蛇行的功能,而二號機器人懂得來回巡邏,而開始參考上一章所介紹的繼承功能,修改範例程式碼了。

struct 會巡邏的角色: 角色 {
    func 巡邏() { ... }
}

struct 會蛇行的角色: 角色 {
    func 蛇行() { ... }
}

等等,先聽我說話呀,別寫下去了!

Swift:: Error: inheritance from non-protocol type '角色'
struct 會巡邏的角色: 角色 {}
       ^

按下編譯果然看到錯誤了,這個訊息的意思是:角色並不是一個能被繼承的協定。為什麼?我們在上一章使用工程師繼承人類的時候,不是好好的嗎?定睛一看,原來程式碼有細微的差異,角色是一個struct人類工程師都是一個class

這裡讀者開始出現了不滿的情緒,原來我們都上兩週課了,教主你教的還是一個連物件都算不上的struct?等等啊!先放下那把雙手劍,我們好好說話...先試著自己寫一個真正物件的程式碼出來如何?

class 真正的物件 {
    var 名字:String
}

var 一顆球 = 真正的物件(名字:"球")

編譯看看...這麼簡單的程式碼居然會有錯?

Swift:: Error: '真正的物件' cannot be constructed because it has no accessible initializers
var 一顆球 = 真正的物件(名字:"球")

記性好的同學可能想起了我們在介紹自訂型態時,也出現過類似的錯誤,我們回憶一下struct型態是怎麼初始化的:

struct 一個型態 {
    var x:Int
    var y:Int
    var z:Int
    ...
}
var 一個實體 = 一個型態(x:0, y:1, z:2...)

為什麼class不能用類似的指令來達成初始化呢?這個問題和程式語言本身的設計有關,也和物件導向程式設計的特色有關,但我們會把這點放在後面的課程解說,否則講完太陽可能都下山了,現在或許你會思考上一章提到的多型、繼承等等特色,發現可以產生實際物件的class果然和struct有所不同(例如在涉及繼承的時候,怎麼知道初始化的時候繼承了多少屬性?),使用這種初始化方式確實有些疑慮,這就暫時足夠了。

我們先學習怎麼更改程式碼,才能把本週的課程繼續進行下去:

class 真正的物件 {
    var 名字:String = "沒有名字"
}

var 一顆球 = 真正的物件()
一顆球.名字 = "球"

struct類似的做法,我們在class的領地內,提供所有的常數或變數一個預設值,就可以成功初始化了。

另外一種方法是自己撰寫初始化的指令:

class 真正的物件 {
    var 名字:String

    init(名字: String) {
        self.名字 = 名字
    }
}
var 一顆球 = 真正的物件.init(名字:"球")
var 另一顆球 = 真正的物件(名字:"球")     // 省略init也可以

這裡我們學到了兩個新語法,initselfinit是所有物件都必須擁有的一個指令(如果我們沒寫,程式也會自動產生),在物件被初始化的時候會自動使用這個指令執行,這也是在我們的程式碼中,初次介紹一個指令的()包裹的程式碼是用來做什麼的:這是告訴使用這個class類別的程式碼,可以在init()指令中傳入一個名為名字String,它會成為這個指令的領地:{}包裹的程式碼範圍中的一個常數。

可是真正的物件裡面也有一個名為名字String,這不就混淆了嗎?在這種衝突發生時,大多數情況下都會以最下層的領主(即是最接近事發地點的{})為優先,然而,我們撰寫init()指定是為了設定真正的物件名字屬性,它才能被正確的初始化,那麼我要用什麼語法才能讀取到真正的物件名字屬性呢?這就是self的用處:在任何物件的領地裡面,使用self都等於在告訴電腦,我要使用的是這個物件本身的屬性或方法,因此就能和init()領地中同名的名字做出區別了。

results matching ""

    No results matching ""