Go36-13-結構體及其方法

結構體及其方法

結構體類型表示的是實實在在的數據結構。一個結構體類型可以包含若干個字段,每個字段通常都需要有確切的名字和類型。
結構體類型也可以不包含任何字段,這樣並不是沒有意義的,因爲我們還可以爲這些類型關聯上一些方法,這里你可以把方法看做是函數的特殊版本。
方法和函數不同,它需要有名字,不能被當作值來看待,最重要的是,它必須隸屬於某一個類型。方法所屬的類型會通過其聲明中的接收者(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的基本類型。可以認爲,指針類型的值就是指向某個基本類型值的指針。指針方法,就是接收者類型是上述指針類型的方法。

值方法和指針方法之間的不同點:

  1. 值方法的接收者是方法所屬類型的一個副本。在方法內對副本的修改一般不會提現在原值上,除非這個類型本身是某個引用類型。而指針方法內對的修改是一定會提現在原值上的。
  2. 嚴格來講,通過值只能調用到值方法,通過指針只能調用到指針方法。但是,Go會適時的進行自動的轉義,使得通過值也能調用到它的指針方法。比如,Animal.SetScientificName("Duck")會自動轉義爲(&Animal).SetScientificName("Duck"),即:先取指針值,然後再在改指針值上調用指針方法。
  3. 這條和接口相關,一個類型的方法集合中有哪些方法與它能實現哪些接口類型是息息相關的。如果一個基本類型和它的指針類型的方法集合是不同的,那麼它們具體實現的接口類型的數量就也會有差異,除非這兩個數量都是零。比如,一個指針類型實現了某某接口類型,但它的基本類型卻不一定能夠作爲該接口的實現類型。

這個是驗證上述差異的示例:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章