6. 方法
6.1 方法的聲明
在函數聲明時,在其名字之前放上一個變量,即是一個方法。這個附加的參數會將該函數附加到這種類型上,即相當於爲這種類型定義了一個獨佔的方法。
示例:兩種Distance實現的效果是一樣的
type Point struct {
X, Y float64
}
func Distance(p, q Point) float64 {
return math.Hypot(p.X-q.X, p.Y-q.Y)
}
// 下面的p稱爲方法的接受器
func (p Point) Distance(q Point) float64 {
return math.Hypot(p.X-q.X, p.Y-q.Y)
}
func main() {
p := Point{1, 5}
q := Point{5, 2}
fmt.Println(Distance(p, q))
fmt.Println(p.Distance(q))
}
在go語言中有一個好處就是:可以給同一個包內的任意命名類型定義方法,只要這個命名類型的底層類型。比如:數值、字符串、slice、map
示例,對一個切片定義方法
type Path []Point
func (p Path) Distance() float64 {
sum := 0.0
for i, _ := range p {
if i>0 {
sum += p[i].Distance(p[i-1])
}
}
return sum
}
func main() {
perim := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
fmt.Println(perim.Distance()) // 12
}
6.2 基於指針對象的方法
在函數中,如果我們要對一個對象進行修改,或者給函數傳參的參數對象內容過大,會選用傳遞指針的方式。因爲函數在傳遞的時候是進行值傳遞,也就是將一個參數裏邊的變量在另外一個內存地址重新拷貝一份,然後再在函數中使用,而指針變量的值是一個地址,所以本質上也是進行只拷貝,只不過拷貝了一個地址,編譯器可以通過這個地址去訪問參數對象。
同樣對於對象的方法,也可以實現基於指針對象的方法。
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
使用的時候也是很簡單,就和上一小節的使用方法是一樣的。
但是細分起來有幾種情況:
- 接收器的實際參數和其接收器的形式參數相同,比如兩者都是類型T或者都是類型*T
- 接收器實參是類型T,但接收器形參是類型*T,這種情況下編譯器會隱式地爲我們取變量的地址
- 接收器實參是類型*T,形參是類型T。編譯器會隱式地爲我們解引用,取到指針指向的實際變量
一句話總結就是無論什麼的變量是普通的類型還是指針類型,或者實現的方法的接收器是普通類型還是指針類型,都可以通過點.來訪問定義的方法
可以結合下面例子理解下:
type MyInt int
// 接受器i是一個指針類型
func (i *MyInt) pAdd(x int) MyInt {
// 因爲x和i雖然底層變量是一樣的,但是其實是兩種不同的變量了,所以不能直接相加,詳見2.5類型
return *i + MyInt(x)
}
// 接收器i只是一個普通類型
func (i MyInt) Add(x int) MyInt {
return i + MyInt(x)
}
func main() {
i := MyInt(20)
pi := &i
// 以下都正確
fmt.Println(i.Add(1)) // 21
fmt.Println(i.pAdd(2)) // 22
fmt.Println(pi.Add(3)) // 23
fmt.Println(pi.pAdd(4)) // 24
}
6.3 通過嵌入結構體來擴展類型
go語言中,嵌入結構體能夠達到像其他面向對象的編程語言一樣的繼承。
在下面代碼中結構體Point稱爲基類,通過匿名嵌入可以將Point嵌入到新的結構中,並且Point實現的方法能夠被ColoredPoint所繼承。關於嵌入結構體的其他一些細節可以看4.4節的結構體部分。
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
Point的方法繼承給了ColoredPoint,所以ColoredPoint類型能夠使用Distance函數
func main() {
red := color.RGBA{255, 0,0,1}
green := color.RGBA{0, 255,0,1}
cp1 := ColoredPoint{Point{1, 1}, red}
cp2 := ColoredPoint{Point{4, 5}, green}
fmt.Println(cp1.Distance(cp2.Point))
fmt.Println(cp1.Distance(cp2)) // 錯誤
}
Distance函數是Point方法,參數也是Point類型,所以必須接受一個Point參數,否者會報錯。
當然,也可以爲ColoredPoint定製自己的Distance方法。
func (p *ColoredPoint) Distance(q *ColoredPoint) float64 {
return p.Point.Distance(q.Point)
}
func main() {
red := color.RGBA{255, 0,0,1}
green := color.RGBA{0, 255,0,1}
cp1 := ColoredPoint{Point{1, 1}, red}
cp2 := ColoredPoint{Point{4, 5}, green}
fmt.Println(cp1.Distance(&cp2)) // 5
fmt.Println(cp1.Point.Distance(cp2.Point)) // 5
fmt.Println(cp1.Distance(cp2.Point)) // 錯誤
}
上述代碼中,可以看到ColoredPoint擁有了自己的Distance方法,並且該方法可以接受*ColoredPoint的參數。但是原來的Point的Distance必須通過.Point.來訪問了。
6.4 方法值和方法表達式
定義一個新的變量,這個變量是一個函數的值,可以看作函數的別名,使用的時候可以當做函數一樣來使用。
方法值: 對一個已經聲明的類型對象A,使用另外一個變量B來代替其方法。其接受器依然是A,通常表示爲 B:=A.method
方法表達式: 當T是一個類型時,方法表達式可能會寫作T.f或者(*T).f,會返回一個函數"值",這種函數會將其第一個參數用作接收器,所以可以用通常(譯註:不寫選擇器)的方式來對其進行調用:通常表示爲 op:=T.f或者op:=(*T).f, 其中是(*T).f表示該方法的接收器是一個指針類型。
type Point struct {
X, Y float64
}
// 下面的p稱爲方法的接受器
func (p *Point) Distance(q *Point) float64 {
return math.Hypot(p.X-q.X, p.Y-q.Y)
}
func (p *Point) Add(q *Point) Point {
return Point{p.X+q.X, p.Y+q.Y}
}
func (p *Point) Sub(q *Point) Point {
return Point{p.X-q.X, p.Y-q.Y}
}
type Path []Point
func (path Path) TranslateBy(offset Point, add bool) {
var op func(p *Point, q* Point) Point
// 判斷要加還是減
if add {
// 方法表達式,會將第一個參數作爲接收器
op = (*Point).Add
} else {
op = (*Point).Sub
}
for i := range path {
path[i] = op(&path[i], &offset)
}
}
func main() {
p1 := Point{1,1}
p2 := Point{4, 5}
p1Distance := p1.Distance // 使用方法值
// p1Distance是p1.Distance方法返回的一個值
fmt.Println(p1Distance(&p2))
perim := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
perim.TranslateBy(Point{1, 1}, true)
fmt.Println(perim)
perim.TranslateBy(Point{2, 2}, false)
fmt.Println(perim)
}
6.5 封裝
一個對象的變量或者方法如果對調用方是不可見的話,一般就被定義爲“封裝”。Go使用命名時首字母大小寫來實現可見性。
封裝的好處:
- 因爲調用方不能直接修改對象的變量值,其只需要關注少量的語句並且只要弄懂少量變量的可能的值即可。
- 隱藏實現的細節,可以防止調用方依賴那些可能變化的具體實現,這樣使設計包的程序員在不破壞對外的api情況下能得到更大的自由。
- 是阻止了外部調用方對對象內部的值任意地進行修改。
本文主要參考:《Go語言聖經》
撩我?
搜索我的公衆號:Kyda