iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 12
1
自我挑戰組

30天學會Golang系列 第 12

day12 - 方法

大家好,今天是鐵人賽第十二天。昨天我們談到物件是用來封裝資料和行為,go語言可以透過定義及宣告結構型別來封裝物件資料。而今天要講的是,go語言中的物件行為要如何定義及操作。行為在物件導向程式中極為重要,它的本質其實就是物件裡的函式,我們通常稱作方法。

方法(Method)

我覺得go語言最特別的地方就是方法,和以往我學過的語言都不一樣。以Java/C#來說,方法的定義會寫在類別之中;雖然go語言沒有類別,但方法也不是寫在結構內,而是寫在外面,並且會透過接受者(receiver)來指定方法給一個結構型別。下面先來看個範例,了解方法如何定義:

// 定義一個英雄結構型別,欄位有名稱、生命、力量
type Hero struct {
	name          string
	health, power int
}

// 定義一個英雄攻擊英雄的方法
func (h1 *Hero) Attack(h2 *Hero) {
	h2.health = h2.health - h1.power
}

func main() {
	// 召喚鋼鐵人
	var tony = &Hero{"IronMan", 1000, 250}
	fmt.Println("Create Hero1:", *tony)

    // 召喚奇異博士
	var stephen = &Hero{"Dr.Strange", 1000, 777}
	fmt.Println("Create Hero2:", *stephen)

    // 命令鋼鐵人去打奇異博士
	tony.Attack(stephen)
	fmt.Println("After attack:", *tony, *stephen)
}

執行結果:

Create Hero1: {IronMan 1000 250}
Create Hero2: {Dr.Strange 1000 777}
After attack: {IronMan 1000 250} {Dr.Strange 750 777}

範例中定義的方法 Attack,指定可以被Hero結構呼叫使用,並且傳入另一個Hero結構,然後進行內部的運算。

接收者

Go語言的接收者像是其他語言中的 thisself,它就是一個連結呼叫物件的變數。我們會在 func 和方法名稱之間定義它,語法如下:

func (p Point) AddX(val float64) {
	p.X = p.X + val
}

type Point struct {
	X, Y float64
}

func main() {
	var p = &Point{1.1, 2.5}
	p.AddX(1.2)
	fmt.Println(p)
}
// 執行結果: {1.1 2.5}

上面範例中的發現一件有趣的事,我明明就是宣告一個指標變數,怎麼執行方法卻沒有修改資料?

這是因為方法是用傳值接受者,當go看到指標變數呼叫的方法是傳值接受者,這時候go會自動幫我們轉換:

p.AddX(1.2)       // 這是語法糖
(*p).AddX(1.2)  // 實際的語法

因此,我們將上面的範例改成傳指標接受者,如下:

func (p *Point) AddX(val float64) {   // <-- 這行 Point 加上 *
	p.X = p.X + val
}

type Point struct {
	X, Y float64
}

func main() {
	var p = &Point{1.1, 2.5}
	p.AddX(1.2)
	fmt.Println(p)
}
// 執行結果: {2.3 2.5}

這次資料就有被修改了,由於指標變數呼叫的方法是傳指標接受者,go也就不用幫我們轉換了。

另外,這個情形也是可以倒過來,範例如下:

func (p *Point) AddX(val float64) {
	p.X = p.X + val
}

type Point struct {
	X, Y float64
}

func main() {
	var p = Point{1.1, 2.5}  // <-- 這行刪除了 &
	p.AddX(1.2)
	fmt.Println(p)
}
// 執行結果: {2.3 2.5}

這個範例很神奇的是,我們宣告的 p 是一般變數,它不是指標。但是當它在呼叫方法後,竟然修改了資料。原因就和前一個範例剛好相反,當go看到一般變數呼叫的方法是傳指標接收者,這時候go也會自動幫我們轉換:

p.AddX(1.2)        // 這是語法糖
(&p).AddX(1.2)  // 實際的語法

方法與函式

Go語言的方法與函式有異曲同工之妙,我們其實也可以用函式來模擬方法。

func AddX(p *Point, val float64) {
	p.X = p.X + val
}

type Point struct {
	X, Y float64
}

func main() {
	var p = Point{1.1, 2.5}
	AddX(&p, 1.2)
	fmt.Println(p)
}
// 執行結果: {2.3 2.5}

這樣寫好像也能定義物件行為,但有種說不出來的感覺。事實上,方法就是這樣的一個函式,只是程式語言通常會提供一個比較特別或比較潮的語法。

另外一點,使用函式的話就沒有上面提到的語法糖了,傳入時必須自己取值(*)或取址(&) 。

其他型別與方法

go語言中除了結構以外,其他的型別也是可以定義方法。但是go規定接收者只能是同個package裡的型別,因此我們就必須使用型別別名,如下:

// 定義浮點數型別的別名
type MyFloat float64

func (a *MyFloat) Add(b float64) {
	*a = *a + MyFloat(b)
}

func main() {
	var a = MyFloat(2.2)
	a.Add(1.1)
	fmt.Println(a)
}
// 執行結果: 3.3000000000000003

如果忘記了型別別名,可以參考 day4-基本型別 拉到最下面。

小結

今天介紹了go語言中的方法,可以透過接受者指定結構或其他型別。到目前為止,我覺得go語言最難學的地方還是在指標,幾乎每個地方都有分指標寫法和一般寫法。如果觀念不夠好,寫出來的程式一定Bug一堆。


上一篇
day11 - 結構
下一篇
day13 - 內嵌
系列文
30天學會Golang30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
yichin
iT邦新手 5 級 ‧ 2021-10-07 22:58:42

你好~
type MyFloat float64
這裡不是定義 alias 而是定義一個新 type。
創造了一個以 float64 為基礎的新 type 才能夠在上面定義 methods

我要留言

立即登入留言