建立第一個物件
行動比較快的觀眾,說不定已經想起開頭時,我們想要讓一號機器人
有蛇行的功能,而二號機器人
懂得來回巡邏,而開始參考上一章所介紹的繼承功能,修改範例程式碼了。
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也可以
這裡我們學到了兩個新語法,init
和self
。init
是所有物件都必須擁有的一個指令(如果我們沒寫,程式也會自動產生),在物件被初始化的時候會自動使用這個指令執行,這也是在我們的程式碼中,初次介紹一個指令的(
和)
包裹的程式碼是用來做什麼的:這是告訴使用這個class
類別的程式碼,可以在init()
指令中傳入一個名為名字
的String
,它會成為這個指令的領地:{
和}
包裹的程式碼範圍中的一個常數。
可是真正的物件
裡面也有一個名為名字
的String
,這不就混淆了嗎?在這種衝突發生時,大多數情況下都會以最下層的領主(即是最接近事發地點的{
和}
)為優先,然而,我們撰寫init()
指定是為了設定真正的物件
的名字
屬性,它才能被正確的初始化,那麼我要用什麼語法才能讀取到真正的物件
的名字
屬性呢?這就是self
的用處:在任何物件的領地裡面,使用self
都等於在告訴電腦,我要使用的是這個物件本身的屬性或方法,因此就能和init
的(
、)
領地中同名的名字
做出區別了。