您好,登錄后才能下訂單哦!
結構體類型表示的是實實在在的數據結構。一個結構體類型可以包含若干個字段,每個字段通常都需要有確切的名字和類型。
結構體類型也可以不包含任何字段,這樣并不是沒有意義的,因為我們還可以為這些類型關聯上一些方法,這里你可以把方法看做是函數的特殊版本。
方法和函數不同,它需要有名字,不能被當作值來看待,最重要的是,它必須隸屬于某一個類型。方法所屬的類型會通過其聲明中的接收者(receiver)聲明體現出來。
方法隸屬的類型其實并不局限于結構體類型,但必須是某個自定義的數據類型,并且不能是任何接口類型。
接收者聲明就是在關鍵字func和方法名稱之間的那個圓括號包裹起來的內容,其中必須包含確切的名稱和類型字面量。這個接收者的類型其實就是當前方法所屬的那個類型,而接收者的名稱,則用于在當前方法中引用它所屬的類型的當前值。
舉個例子:
// 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)
}
在Go語言中,我們可以通過為一個類型編寫名為String的方法,來自定義該類型的字符串表示形式。這個String方法不需要任何參數聲明,但需要有一個string類型的結果聲明。所以在再用fmt包里的函數時,會打印出上面自定義的字符串表示形式,而無需顯示的調用它的String方法。
我們可以把結構體類型中的一個字段看作是它的一個屬性或者一項數據,再把隸屬于它的一個方法看作是附加在其中數據之上的一個能力或者一項操作。將屬性及其能力(或者說數據及其操作)封裝在一起,是面向對象編程(object-orientedprogramming)的一個主要原則。
下面聲明了一個結構體類型Animal,有兩個字段,一個是string類型的scientificName。另一個字段聲明中只有AnimalCategory,就是上面示例的那個結構體的名字:
type Animal struct {
scientificName string // 學名
AnimalCategory // 動物基本分類
}
字段聲明AnimalCategory代表了Animal類型的一個嵌入字段。Go語言規范規定,如果一個字段的聲明中只有字段的類型名而沒有字段的名稱,那么它就是一個嵌入字段,也可以被稱為匿名字段。我們可以通過此類型變量的名稱后跟“.”,再后跟嵌入字段類型的方式引用到該字段。也就是說,嵌入字段的類型既是類型也是名稱。
強調一下,Go語言中沒有繼承的概念,它所做的是通過嵌入字段的方式實現了類型之間的組合。
簡單來說,面向對象編程中的繼承,其實是通過犧牲一定的代碼簡潔性來換取可擴展性,而且這種可擴展性是通過侵入的方式來實現的。類型之間的組合采用的是非聲明的方式,我們不需要顯式地聲明某個類型實現了某個接口,或者一個類型繼承了另一個類型。
同時,類型組合也是非侵入式的,它不會破壞類型的封裝或加重類型之間的耦合。我們要做的只是把類型當做字段嵌入進來,然后坐享其成地使用嵌入字段所擁有的一切。如果嵌入字段有哪里不合心意,我們還可以用“包裝”或“屏蔽”的方式去調整和優化。
方法的接收者類型必須是某個自定義的數據類型(不能是接口)。所謂的值方法,就是接收者類型是非指針的自定義數據類型的方法。之前的示例中的方法都是值方法。
下面的這個就是指針方法:
func (a *Animal) SetScientificName(name string) {
a.scientificName = name
}
方法的接受者類型是*Animal,是一個指針類型。這時Animal可以被叫做*Animal的基本類型。可以認為,指針類型的值就是指向某個基本類型值的指針。指針方法,就是接收者類型是上述指針類型的方法。
值方法和指針方法之間的不同點:
Animal.SetScientificName("Duck")
會自動轉義為(&Animal).SetScientificName("Duck")
,即:先取指針值,然后再在改指針值上調用指針方法。 這個是驗證上述差異的示例:
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) // false
_, ok = interface{}(&cat).(Pet)
fmt.Printf("*Cat implements interface Pet: %v\n", ok) // true
}
這里牽涉到了接口的知識點,所以這個例子和下面的內容,下一篇還會再講一遍。
最后的2行輸出的內容,說明cat沒有實現Pet的接口,而&cat是實現了Pet的接口。
因為要實現Pet接口需要實現接下的那4個方法。而Cat類型沒有實現SetName方法,所以cat沒有實現Pet接口。代碼中SetName方法是通過*Cat實現的,另外其他的3個方法都已經通過Cat實現了,通過*Cat也能調用(差異的第2條),所以只有指針方法實現了Pet接口的所有方法。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。