結構體嵌套
go中使用結構體嵌套來擴展類型
嵌入到結構體中的字段,完全可以當作自己是自己的字段
import "image/color"
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
ColoredPoint嵌套了Point結構體,從而ColoredPoint就擁有了Point的字段X,Y。 可以直接通過"."操作符來訪問;
如果Point擁有自己的方法,那么ColoredPoint也擁有這些方法,而不需要在自己定義
用這種方式,內嵌可以使我們定義字段特別多的復雜類型,我們可以將字段先按小類型分組,然后定義小類型的方法,之后再把它們組合起來。
讀者如果對基於類來實現面向對象的語言比較熟悉的話,可能會傾向於將Point看作一個基類,而ColoredPoint看作其子類或者繼承類,或者將ColoredPoint看作"is a" Point類型。
但這是錯誤的理解。請注意上面例子中對Distance方法的調用。Distance有一個參數是Point類型,但q並不是一個Point類,所以盡管q有着Point這個內嵌類型,我們也必須要顯式地選擇它。嘗試直接傳q的話你會看到下面這樣的錯誤:---go語言聖經
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "10"
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
一個ColoredPoint並不是一個Point,但他"has a" Point,並且它有從Point類里引入的Distance和ScaleBy方法。如果你喜歡從實現的角度來考慮問題,內嵌字段會指導編譯器去生成額外的包裝方法來委托已經聲明好的方法,和下面的形式是等價的:
func (p ColoredPoint) Distance(q Point) float64 {
return p.Point.Distance(q)
}
func (p *ColoredPoint) ScaleBy(factor float64) {
p.Point.ScaleBy(factor)
}
當Point.Distance被第一個包裝方法調用時,它的接收器值是p.Point,而不是p,當然了,在Point類的方法里,你是訪問不到ColoredPoint的任何字段的。
編譯器查找方法的順序:
- 先在本結構體定義的方法中找
- 找不到,再去子結構體找,然后一直遞歸向下找
如果選擇器有二義性的話編譯器會報錯,比如你有一個字段覺Distance,然后一個方法也叫Distance,那么選擇器就不知道你想使用的是哪個,編譯器就會報錯;
匿名結構體
匿名結構體就是在嵌入時,不指定名稱,這樣子會將匿名結果體的所有方法引入到該類型中;這樣在使用時有很多便利:例如下面對map的操作;
我們知道map時非線程安全的,會存在讀或寫競爭,我們需要在對map的操作時進行加鎖;
package main
// 匿名結構提嵌套,可以引入匿名結構體所有的方法
import (
"fmt"
"sync"
)
type safeMap struct {
sync.Mutex
mapping map[string]string
}
// 獲取map中元素的值
func (m safeMap) Get(key string) (value string) {
// 引入了sync.Mutex的加鎖和解鎖方法
m.Lock()
value = m.mapping[key]
m.Unlock()
return
}
func main() {
m := map[string]string{
"a" : "alpha",
"i" : "integer",
}
cache := safeMap{
mapping: m,
}
fmt.Println(cache.Get("a"))
fmt.Println(cache.Get("i"))
}
因為sync.Mutex字段也被嵌入到了這個struct里,其Lock和Unlock方法也就都被引入到了這個匿名結構中了,這讓我們能夠以一個簡單明了的語法來對其進行加鎖解鎖操作。
