結構體表示的是實實在在的數據結構,一個結構體類型能夠包含若干個字段,每一個字段一般都須要有確切的名字和類型。結構體也能夠不包含任何字段,即便這樣的結構體也是有意義的,由於,咱們能夠爲結構體關聯上一些方法。編程
方法能夠看作是函數的特殊版本。數據結構
函數是獨立的程序實體。 咱們能夠聲明有名字的函數,也能夠聲明沒有名字的函數,還能夠把函數當作普通的值來傳遞。咱們還能夠把具備相同簽名的函數抽象成獨立的函數類型。dom
方法須要有名字,不能被當作值來看待,最重要的是:方法必須隸屬於某一個類型。 方法所述的類型會經過方法聲明中的接收者(receiver)聲明體現出來。函數
func (接收者名稱 接收者類型) methodName(參數列表)(結果列表)
優化
舉個例子指針
// AnimalCategory 表明動物分類學中的基本分類法。 type AnimalCategory struct { kingdom string // 界。 phylum string // 門。 class string // 綱。 order string // 目。 family string // 科。 genus string // 屬。 species string // 種。 } func (ac AnimalCategory) String() string { return fmt.Sprintf("%s%s%s%s%s%s%s",ac.kingdom, ac.phylum, ac.class, ac.order,ac.family, ac.genus, ac.species) } func main { category := AnimalCategory{species: "cat"} fmt.Printf("The animal category: %s\n", category) }
從String方法的聲明中能夠得知,String方法隸屬於AnimalCategory類型。code
在Go語言中,咱們能夠經過爲一個類型編寫String方法,來自定義該類型的字符串表示形式。對象
調用fmt.Printf函數時,使用佔位符%s和category值自己就能夠打印出category的字符串表示形式,無需顯示調用category的String方法。fmt.Printf函數會自動就尋找String函數。即便一個類型沒有實現String方法,fmt.Printf也能按照默認的格式打印出該類型的字符串表示。繼承
方法隸屬的類型其實並不侷限於結構體類型,可是必須是某個自定義的數據類型,而且不能是任何接口類型。接口
一個數據類型關聯的全部方法,共同組成了該類型的方法集合。同一個方法集合中的方法不能出現重名。而且,若是它們所屬的是一個結構體類型,那麼它們的名稱與該類型中任何字段的名稱也不能重複。
咱們能夠把結構體類型中的一個字段看做是它的一個屬性或者一項數據,再把隸屬於它的一個方法看做是附加在其中數據之上的一個能力或者一項操做。將屬性及其能力(或者說數據及其操做)封裝在一塊兒,是面向對象編程(object-oriented programming)的一個主要原則。
Go 語言攝取了面向對象編程中的不少優秀特性,同時也推薦這種封裝的作法。從這方面看,Go語言實際上是支持面向對象編程的,但它選擇摒棄了一些在實際運用過程當中容易引發程序開發者困惑的特性和規則。
type Animal struct { scientificName string // 學名。 AnimalCategory // 動物基本分類。 }
Go語言規範規定,若是一個字段的聲明中只有字段的類型名而沒有字段的名稱,那麼它就是一個嵌入字段,也能夠被稱爲匿名字段。咱們能夠經過此類型變量的名稱後跟「.」,再後跟嵌入字段類型的方式引用到該字段。也就是說,嵌入字段的類型既是類型也是名稱。
在某個表明變量的標識符的右邊加「.」,再加上字段名或方法名的表達式被稱爲選擇表達式,它用來表示選擇了該變量的某個字段或者方法。
嵌入字段的方法集合會被無條件地合併進被嵌入類型的方法集合中。
只要名稱相同,不管這兩個方法的簽名是否一致,被嵌入類型的方法都會「屏蔽」掉嵌入字段的同名方法。
相似的,因爲咱們一樣能夠像訪問被嵌入類型的字段那樣,直接訪問嵌入字段的字段,因此若是這兩個結構體類型裏存在同名的字段,那麼嵌入字段中的那個字段必定會被「屏蔽」
在這種狀況下,「屏蔽」現象會以嵌入的層級爲依據,嵌入層級越深的字段或方法越可能被「屏蔽」。
若是處於同一個層級的多個嵌入字段擁有同名的字段或方法,那麼從被嵌入類型的值那裏,選擇此名稱的時候就會引起一個編譯錯誤,由於編譯器沒法肯定被選擇的成員究竟是哪個
強調一下,Go 語言中根本沒有繼承的概念,它所作的是經過嵌入字段的方式實現了類型之間的組合。
類型之間的組合採用的是非聲明的方式,咱們不須要顯式地聲明某個類型實現了某個接口,或者一個類型繼承了另外一個類型。
類型組合也是非侵入式的,它不會破壞類型的封裝或加劇類型之間的耦合。咱們要作的只是把類型當作字段嵌入進來,而後不勞而獲地使用嵌入字段所擁有的一切。若是嵌入字段有哪裏不合心意,咱們還能夠用「包裝」或「屏蔽」的方式去調整和優化。
類型間的組合也是靈活的,咱們老是能夠經過嵌入字段的方式把一個類型的屬性和能力「嫁接」給另外一個類型。這時候,被嵌入類型也就天然而然地實現了嵌入字段所實現的接口。
組合要比繼承更加簡潔和清晰,Go 語言能夠垂手可得地經過嵌入多個字段來實現功能強大的類型,卻不會有多重繼承那樣複雜的層次結構和可觀的管理成本。
接口類型之間也能夠組合。在 Go 語言中,接口類型之間的組合甚至更加常見,咱們經常以此來擴展接口定義的行爲或者標記接口的特徵。
方法的接收者類型必須是某個自定義的數據類型,並且不能是接口類型或接口的指針類型。所謂的值方法,就是接收者類型是非指針的自定義數據類型的方法。
func (cat *Cat) SetName(name string) { cat.name = name } func (cat Cat) SetNameOfCopy(name string) { cat.name = name }
方法setName的接收者類型是Cat。表示的Cat類型的指針類型。這時,Cat能夠叫作Cat的基本類型。指針類型的值表示的是指向某個基本類型值的指針。
值方法的接收者是該方法所屬的那個類型值的一個副本。咱們在該方法內對該副本的修改通常都不會體如今原值上,除非這個類型自己是某個引用類型(好比切片或字典)的別名類型。
而指針方法的接收者,是該方法所屬的那個基本類型值的指針值的一個副本。咱們在這樣的方法內對該副本指向的值進行修改,卻必定會體如今原值上。
一個自定義數據類型的方法集合中僅會包含它的全部值方法,而該類型的指針類型的方法集合卻囊括了前者的全部方法,包括全部值方法和全部指針方法
咱們在這樣的基本類型的值上只能調用到它的值方法。可是,Go 語言會適時地爲咱們進行自動地轉譯,使得咱們在這樣的值上也能調用到它的指針方法。
若是一個基本類型和它的指針類型的方法集合是不一樣的,那麼它們具體實現的接口類型的數量就也會有差別,除非這兩個數量都是零。
以上結論能夠結合下面的例子進行體會。
package main import "fmt" type Cat struct { name string // 名字。 scientificName string // 學名。 category string // 動物學基本分類。 } func New(name, scientificName, category string) Cat { return Cat{ name: name, scientificName: scientificName, category: category, } } func (cat *Cat) SetName(name string) { cat.name = name } func (cat Cat) SetNameOfCopy(name string) { cat.name = name } func (cat Cat) Name() string { return cat.name } func (cat Cat) ScientificName() string { return cat.scientificName } func (cat Cat) Category() string { return cat.category } func (cat Cat) String() string { return fmt.Sprintf("%s (category: %s, name: %q)", cat.scientificName, cat.category, cat.name) } func main() { cat := New("little pig", "American Shorthair", "cat") cat.SetName("monster") // (&cat).SetName("monster") fmt.Printf("The cat: %s\n", cat) cat.SetNameOfCopy("little pig") fmt.Printf("The cat: %s\n", cat) type Pet interface { SetName(name string) Name() string Category() string ScientificName() string } _, ok := interface{}(cat).(Pet) fmt.Printf("Cat implements interface Pet: %v\n", ok) _, ok = interface{}(&cat).(Pet) fmt.Printf("*Cat implements interface Pet: %v\n", ok) }
咱們能夠在結構體中嵌入某個類型的指針類型, 它和普通指針相似,默認初始化爲nil,所以在用以前須要人爲初始化,不然可能引發錯誤
空結構體不佔用內存空間,可是具備結構體的一切屬性,如能夠擁有方法,能夠寫入channel。因此當咱們須要使用結構體而又不須要具體屬性時可使用它。