結構體類型可以包含若干字段,每個字段通常都需要有確切的名字和類型。也可以不包含任何字段,這樣並不是沒有意義的,因為還可以為這些類型關聯上一些方法,這里可以把方法看作事函數的特殊版本。
函數事獨立的程序實體,可以聲明有名字的函數,也可以聲明沒名字的函數,還可以把它們當作普通的值傳來傳去(把具有相同簽名的函數抽象陳獨立的函數類型,作為一組輸入或輸出的代表)。方法則不同,它需要名字,不能被當作值來看待,更重要的是,它必須隸屬於某一個類型。方法所屬的類型會通過期聲明中的接收者聲明(關鍵字func和方法名稱之間那個圓括號包裹起來的內容,其中必須包含確切的名稱和類型字面量,這個接收者的類型其實就是當前方法所屬的那個類型,而接收者的名稱則用於在當前方法中引用它所屬的類型的當前值)體現出來。
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) }
從String方法的接收者聲明可以看出,它隸屬於AnimalCategory類型。通過該方法的接收者名稱ac,可以在其中引用到當前的任何一個字段或者調用到當前值的任何一個方法(包括string方法自己)。使用時可以這樣表示
category := AnimalCategory{species: "cat"} fmt.Printf("The animal category: %s\n", category)
在Go語言中,可以通過為一個類型編寫名為String的方法,來自定義該類型的字符串表示形式,這個String方法不需要任何參數聲明,但需要一個string類型的結果聲明。正因為如此,在調用fmt.Print函數時,無需顯式調用它的string方法,fmt.Print會自己去尋找它。
方法隸屬的類型並不局限於結構圖,但必須時某個自定義的數據類型,並且不能時任何接口類型。一個數據類型關聯的所有方法,共同組成了該類型的方法集合。同一個方法集合中的方法不能出現重名,並且如果它們所屬的是一個結構圖類型,那么它們的名稱與該類型中任何字段的名稱也不能重復。
可以把結構體類型中的一個字段看作是它的一個屬性或一項數據,再把隸屬於它的一個方法看作是附加再其中數據之上的一個能力或一項操作。將屬性及其能力封裝再一起,是面向對象編程的一個主要原則。
1、結構體嵌入字段
Go語言規范規定,如果一個字段的聲明中只有字段的類型名而沒有字段的名稱,那么它就是一個嵌入字段。可以通過此類型變量的名稱后跟“.”,再后跟嵌入字段類型的方法引用到該字段。
type Animal struct { scientificName string // 學名。 AnimalCategory // 動物基本分類。 } func (a Animal) Category() string { return a.AnimalCategory.String() }
Category方法的接收者類型是Animal,接收者名稱是a。在該方法中,通過表達式a.AnimalCategory選擇到了a這個嵌入字段,然后又選擇了該字段的String方法並調用了它。
把一個結構體類型嵌入到另一個結構體中的,嵌入字段的方法集合會被無條件地合並進被嵌入類型地方法集合中。
animal := Animal{ scientificName: "American Shorthair", AnimalCategory: category, } fmt.Printf("The animal: %s\n", animal)
這里聲明了Animal類型地變量animal並對它進行初始化,把字符串值“American Shorthair”賦值給它的字段scientificName,並把前面聲明過的變量category賦給它的嵌入字段AnimalCategory
那再后面使用fmt.Printf函數相當於調用animal的String方法(雖然還沒有為Animal類型編寫String方法,但這樣做並沒有問題,因為嵌入字段AnimalCategory的String方法會被當做animal的方法調用)
那如果為Animal類型也編寫一個String方法呢?會調用哪一個?
答案是animal的String方法會被調用,AnimalCategory的String方法被屏蔽掉了。注意:只要名稱相同,無論這兩個方法的簽名是否一致,被嵌入類型的方法都會屏蔽掉嵌入字段的同名方法。
同樣,如果兩個結構體類型中存在同名的字段,那嵌入字段中的那個字段一定會被“屏蔽‘。即使是兩個同名的成員,一個是字段,一個是方法,這種屏蔽現象依然會存在。
不過,即使被屏蔽了仍然可以通過鏈式的選擇表達式,選擇到嵌入字段的字段或方法。
當出現多層嵌入時,屏蔽現象會以嵌入的層級為依據,嵌入層級越深的字段或方法越可能被屏蔽。如果同一層級的多個嵌入字段擁有同名的字段或方法,那么被嵌入類型的值那里,選擇此名稱的時候就會引發一個編譯錯誤。
2、Go語言用嵌入字段實現了繼承嗎?
Go語言中根本沒有繼承的概念
它所做的事通過嵌入字段的方式實現了類型之間的組合。
面向對象編程中的繼承事通過犧牲一定的代碼簡潔性來換取可擴展性,這種可擴展性事通過侵入的方式實現的。
而類型之間的組合采用的是非聲明的方式。不需要顯式聲明,而且也非侵入式的。只是通過嵌入字段的方式把一個類型的屬性和能力嫁接給另外一個類型
3、值方法和指針方法都是什么意思?有什么區別
方法的接收者必須是某個自定義的數據類型,而且不能是接口類型或接口的指針類型。
值方法就是接收者是非指針的自定義數據類型的方法。前面的AnimalCategory、Animal聲明的那些方法都是值方法。
type Cat struct { name string Animal } func (cat *Cat) SetName(name string) { cat.name = name }
方法SetName的接收者類型是*Cat(Cat類型的指針類型)。那指針方法,就是接收者類型是指針類型的方法
區別:
1)值方法的接收者是該方法所屬的那個類型值的一個副本,在該方法內對該副本的修改一般不不會體現在原值上(除非這個類型本身是某個引用類型(如切片或字典)的別名類型)。
指針方法的接收者,是該方法所屬那個基本類型值的指針值的一個副本,這樣的方法內對該副本指向值的進行修改,一定會體現在原值上。
(2)一個自定義數據類型的方法集合中僅會包含它的所有值方法
而該類型的指針類型的方法集合卻包含了所有值方法和所有指針方法