您好,登錄后才能下訂單哦!
結(jié)構(gòu)體類型表示的是實(shí)實(shí)在在的數(shù)據(jù)結(jié)構(gòu)。一個(gè)結(jié)構(gòu)體類型可以包含若干個(gè)字段,每個(gè)字段通常都需要有確切的名字和類型。
結(jié)構(gòu)體類型也可以不包含任何字段,這樣并不是沒(méi)有意義的,因?yàn)槲覀冞€可以為這些類型關(guān)聯(lián)上一些方法,這里你可以把方法看做是函數(shù)的特殊版本。
方法和函數(shù)不同,它需要有名字,不能被當(dāng)作值來(lái)看待,最重要的是,它必須隸屬于某一個(gè)類型。方法所屬的類型會(huì)通過(guò)其聲明中的接收者(receiver)聲明體現(xiàn)出來(lái)。
方法隸屬的類型其實(shí)并不局限于結(jié)構(gòu)體類型,但必須是某個(gè)自定義的數(shù)據(jù)類型,并且不能是任何接口類型。
接收者聲明就是在關(guān)鍵字func和方法名稱之間的那個(gè)圓括號(hào)包裹起來(lái)的內(nèi)容,其中必須包含確切的名稱和類型字面量。這個(gè)接收者的類型其實(shí)就是當(dāng)前方法所屬的那個(gè)類型,而接收者的名稱,則用于在當(dāng)前方法中引用它所屬的類型的當(dāng)前值。
舉個(gè)例子:
// AnimalCategory 代表動(dòng)物分類學(xué)中的基本分類法
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語(yǔ)言中,我們可以通過(guò)為一個(gè)類型編寫名為String的方法,來(lái)自定義該類型的字符串表示形式。這個(gè)String方法不需要任何參數(shù)聲明,但需要有一個(gè)string類型的結(jié)果聲明。所以在再用fmt包里的函數(shù)時(shí),會(huì)打印出上面自定義的字符串表示形式,而無(wú)需顯示的調(diào)用它的String方法。
我們可以把結(jié)構(gòu)體類型中的一個(gè)字段看作是它的一個(gè)屬性或者一項(xiàng)數(shù)據(jù),再把隸屬于它的一個(gè)方法看作是附加在其中數(shù)據(jù)之上的一個(gè)能力或者一項(xiàng)操作。將屬性及其能力(或者說(shuō)數(shù)據(jù)及其操作)封裝在一起,是面向?qū)ο缶幊蹋╫bject-orientedprogramming)的一個(gè)主要原則。
下面聲明了一個(gè)結(jié)構(gòu)體類型Animal,有兩個(gè)字段,一個(gè)是string類型的scientificName。另一個(gè)字段聲明中只有AnimalCategory,就是上面示例的那個(gè)結(jié)構(gòu)體的名字:
type Animal struct {
scientificName string // 學(xué)名
AnimalCategory // 動(dòng)物基本分類
}
字段聲明AnimalCategory代表了Animal類型的一個(gè)嵌入字段。Go語(yǔ)言規(guī)范規(guī)定,如果一個(gè)字段的聲明中只有字段的類型名而沒(méi)有字段的名稱,那么它就是一個(gè)嵌入字段,也可以被稱為匿名字段。我們可以通過(guò)此類型變量的名稱后跟“.”,再后跟嵌入字段類型的方式引用到該字段。也就是說(shuō),嵌入字段的類型既是類型也是名稱。
強(qiáng)調(diào)一下,Go語(yǔ)言中沒(méi)有繼承的概念,它所做的是通過(guò)嵌入字段的方式實(shí)現(xiàn)了類型之間的組合。
簡(jiǎn)單來(lái)說(shuō),面向?qū)ο缶幊讨械睦^承,其實(shí)是通過(guò)犧牲一定的代碼簡(jiǎn)潔性來(lái)?yè)Q取可擴(kuò)展性,而且這種可擴(kuò)展性是通過(guò)侵入的方式來(lái)實(shí)現(xiàn)的。類型之間的組合采用的是非聲明的方式,我們不需要顯式地聲明某個(gè)類型實(shí)現(xiàn)了某個(gè)接口,或者一個(gè)類型繼承了另一個(gè)類型。
同時(shí),類型組合也是非侵入式的,它不會(huì)破壞類型的封裝或加重類型之間的耦合。我們要做的只是把類型當(dāng)做字段嵌入進(jìn)來(lái),然后坐享其成地使用嵌入字段所擁有的一切。如果嵌入字段有哪里不合心意,我們還可以用“包裝”或“屏蔽”的方式去調(diào)整和優(yōu)化。
方法的接收者類型必須是某個(gè)自定義的數(shù)據(jù)類型(不能是接口)。所謂的值方法,就是接收者類型是非指針的自定義數(shù)據(jù)類型的方法。之前的示例中的方法都是值方法。
下面的這個(gè)就是指針?lè)椒ǎ?/p>
func (a *Animal) SetScientificName(name string) {
a.scientificName = name
}
方法的接受者類型是*Animal,是一個(gè)指針類型。這時(shí)Animal可以被叫做*Animal的基本類型。可以認(rèn)為,指針類型的值就是指向某個(gè)基本類型值的指針。指針?lè)椒?/strong>,就是接收者類型是上述指針類型的方法。
值方法和指針?lè)椒ㄖg的不同點(diǎn):
(&Animal).SetScientificName("Duck")
,即:先取指針值,然后再在改指針值上調(diào)用指針?lè)椒ā? 這個(gè)是驗(yàn)證上述差異的示例:
package main
import "fmt"
type Cat struct {
name string // 名字。
scientificName string // 學(xué)名。
category string // 動(dòng)物學(xué)基本分類。
}
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
}
這里牽涉到了接口的知識(shí)點(diǎn),所以這個(gè)例子和下面的內(nèi)容,下一篇還會(huì)再講一遍。
最后的2行輸出的內(nèi)容,說(shuō)明cat沒(méi)有實(shí)現(xiàn)Pet的接口,而&cat是實(shí)現(xiàn)了Pet的接口。
因?yàn)橐獙?shí)現(xiàn)Pet接口需要實(shí)現(xiàn)接下的那4個(gè)方法。而Cat類型沒(méi)有實(shí)現(xiàn)SetName方法,所以cat沒(méi)有實(shí)現(xiàn)Pet接口。代碼中SetName方法是通過(guò)*Cat實(shí)現(xiàn)的,另外其他的3個(gè)方法都已經(jīng)通過(guò)Cat實(shí)現(xiàn)了,通過(guò)*Cat也能調(diào)用(差異的第2條),所以只有指針?lè)椒▽?shí)現(xiàn)了Pet接口的所有方法。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。