在上一篇文章,我們成功用 struct
設計並製造出了遙控車。但現在,它還只是一個靜靜待在那裡的資料集合。今天,我們要學習如何賦予它「行為」,讓它能真正地跑起來!
要讓車子跑,我們需要寫一個 Drive
的功能。這個功能會做兩件事:減少電量、增加距離。
一個很自然的想法是寫一個函式:
// 驅動車子前進一次
func Drive(car Car) Car {
// 檢查電量夠不夠
if car.battery >= car.batteryDrain {
car.battery -= car.batteryDrain // 扣電量
car.distance += car.speed // 加距離
}
return car // 把更新後的車子狀態回傳
}
注意!這裡有個超級重要的觀念!
在 Go 語言中,當你把 car
這樣一個 struct
丟進函式裡,函式收到的是一份 複製品 (Copy)。它不是你原本的那台車!所以,函式裡對 car
的修改,只會改到那份複製品。
為了讓修改生效,我們必須把修改後的複製品 return
回來,然後用它來覆蓋掉我們原本的變數。
myCar := NewCar(5, 2)
// 錯誤的呼叫方式,myCar 的狀態不會改變!
// Drive(myCar)
// 正確的呼叫方式:必須接收回傳值!
myCar = Drive(myCar)
這個方法可行,但每次都要 myCar = Drive(myCar)
感覺有點繞口,對吧?有沒有更帥、更專業的做法呢?
當然有!
專業的 Go 開發者會使用一種叫做 方法 (Method) 的技巧。簡單來說,就是把函式「附加」到 struct
身上,讓它成為 struct
專屬的技能。
更酷的是,我們會搭配 指標接收器 (Pointer Receiver),也就是型別前面的那個星號 *
。
func (car Car) ...
-> 這叫「值接收器」,方法拿到的是複製品。func (car *Car) ...
-> 這叫「指標接收器」,方法拿到的是指向原始物件的遙控器 (指標),可以直接修改原始物件!讓我們來改造 Drive
,把它變成 Car
的一個專屬方法:
// Drive 現在是 Car 的一個「方法」
// (car *Car) 就是指標接收器
func (car *Car) Drive() {
if car.battery >= car.batteryDrain {
// 因為有指標,我們可以「直接」修改原始 car 的狀態!
car.battery -= car.batteryDrain
car.distance += car.speed
}
// 因為是直接修改,所以根本不需要回傳任何東西!
}
看看現在的呼叫方式,是不是感覺順多了?
myCar := NewCar(5, 2)
myCar.Drive() // 直接對 myCar 下達「Drive」的命令!
myCar.Drive() // 再跑一次!
這種寫法就像在說:「嘿,myCar
,給我跑起來!」,非常直觀!
這個問題很關鍵,記住這個簡單的規則:
struct
的狀態時:必須使用指標接收器 (*Car
)[3]。這是最重要的原因。struct
很大(有很多欄位),每次都複製一份是很浪費資源的。傳遞指標 (*Car
) 遠比複製整個 struct
(Car
) 來得快。struct
有些方法需要修改(用指標),那最好將所有方法都統一使用指標接收器,這樣程式碼看起來會更整潔。第二篇總結:
struct
時,傳遞的是複製品,修改不會影響原始物件,需要靠 return
來更新。struct
上的專屬函式。*
) 讓方法可以直接修改原始 struct
的狀態,且效能更好,是 Go 語言中的推薦做法。