[A Tour of Go 學習筆記] 07 method
使用官方教學瞭解 Go method
初入 Golang 的世界,首先我們使用官方教學:A Tour of Go
來認識基本的 Golang 使用
這篇介紹 method
method
Go 不是物件導向,所以沒有 class,但是可以用Type
搭配receiver
參數來時做出類似功能
而 method 是一種帶有特殊接收(receiver)參數函數
receiver 出現在它自己的參數列表中,位於宣告字串:func
和方法名稱之間
在範例中Abs
函數擁有一個 Type 是Vertex
並且叫做v
的 receiver
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
/*
>>> 5
*/
方法即函數
method 就只是具有 receiver 的 function
底下就是一個一般寫法的 function,功能並沒有什麼變化
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(Abs(v))
}
/*
>>> 5
*/
method 聲明也可以使用非結構化的 type
只能使用同一個package
作用域中聲明過的的type
當作receiver
如果是聲明在其他package
中,就算是內建的型別,例如int
也不行
package main
import (
"fmt"
"math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
/*
>>> 1.4142135623730951
*/
指針接收者(Pointer receivers)
你也可以使用指針 receiver 來聲明 method
型態前加*
代表是 Pointer receiver,method 的 receiver type 為指針
範例程式中,函數Scale
的 receiver 就聲明為*Vertex
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}
/*
>>> 50
*/
具有 pointer receiver 的 method,例如上面的Scale
可以直接修改變數指向的值,而不是單純的運算
由於 method 經常會修改 receiver,所以實際應用上比較常使用指針作為 receiver
如果把上面範例中的Scale
的 receiver*
刪除,變成一般的 method
則是會把Vertex
的值複製一份,對其進行操作
如果嘗試執行,得到的結果會是5
因為v.Scale(10)
的執行結果不會影響到main
裡面v := Vertex{3, 4}
得到的結果,而直接印出5
Pointers and functions
現在我們要把Abs
和Scale
方法重寫為函數
之前的範例中v.Scale(10)
可以直接調用指針 receiver
而 function 的寫法參數必須使用&
符號聲明傳入指針
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2)
ScaleFunc(&v, 10)
p := &Vertex{4, 3}
p.Scale(3)
ScaleFunc(p, 8)
fmt.Println(v, p)
}
/*
>>> {60 80} &{96 72}
*/
和前面兩個範例做比較,會發現會注意到帶有參數的 functions 必須傳入一個指針,否則
var v Vertex
ScaleFunc(v, 5) // Compile error!
ScaleFunc(&v, 5) // OK
但是帶有 pointer receiver 的 methods 在被呼叫時可以接受一個值或一個指針
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
對於v.Scale(5)
即使v
是個值而非指針,帶有 pointer receiver 的 methods 也能被直接呼叫
也就是說,由於Scale
method 有一個 pointer receiver
為方便起見,Go 會將v.Scale(5)
視為(&v).Scale(5)
同樣的事情也發生在相反的情況,若是對傳值的 function 傳入指針
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Compile error!
但是作為 method 使用的時候,又可以直接傳入指針
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
因為這種情況下p.Abs()
會被解釋為(*p).Abs()
receiver 類型怎麼選(值/指針)
通常來說使用「指針」作為 receiver 是因為
- method 能夠修改指向的值
- 避免每次調用的時候複製資料,若使用的 type 是大型 struct 時效率比較好
在範例中Scale
和Abs
的 receiver 類型為*Vertex
,即使Abs
並不需要修改 receiver
通常來說,所有具有類型的 method 都應該要有值或是指針的 receiver,但不該兩者混用
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)
fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}
/*
>>> Before scaling: &{X:3 Y:4}, Abs: 5
>>> After scaling: &{X:15 Y:20}, Abs: 25
*/