溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go36-13-結(jié)構(gòu)體及其方法

發(fā)布時(shí)間:2020-06-28 22:47:56 來(lái)源:網(wǎng)絡(luò) 閱讀:522 作者:騎士救兵 欄目:編程語(yǔ)言

結(jié)構(gòu)體及其方法

結(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)化。

值方法和指針?lè)椒?/h3>

方法的接收者類型必須是某個(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):

  1. 值方法的接收者是方法所屬類型的一個(gè)副本。在方法內(nèi)對(duì)副本的修改一般不會(huì)提現(xiàn)在原值上,除非這個(gè)類型本身是某個(gè)引用類型。而指針?lè)椒▋?nèi)對(duì)的修改是一定會(huì)提現(xiàn)在原值上的。
  2. 嚴(yán)格來(lái)講,通過(guò)值只能調(diào)用到值方法,通過(guò)指針只能調(diào)用到指針?lè)椒?。但是,Go會(huì)適時(shí)的進(jìn)行自動(dòng)的轉(zhuǎn)義,使得通過(guò)值也能調(diào)用到它的指針?lè)椒ā1热纾?code>Animal.SetScientificName("Duck")會(huì)自動(dòng)轉(zhuǎn)義為(&Animal).SetScientificName("Duck"),即:先取指針值,然后再在改指針值上調(diào)用指針?lè)椒ā?
  3. 這條和接口相關(guān),一個(gè)類型的方法集合中有哪些方法與它能實(shí)現(xiàn)哪些接口類型是息息相關(guān)的。如果一個(gè)基本類型和它的指針類型的方法集合是不同的,那么它們具體實(shí)現(xiàn)的接口類型的數(shù)量就也會(huì)有差異,除非這兩個(gè)數(shù)量都是零。比如,一個(gè)指針類型實(shí)現(xiàn)了某某接口類型,但它的基本類型卻不一定能夠作為該接口的實(shí)現(xiàn)類型。

這個(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接口的所有方法。

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI