Go語言學習9-結構體類型

3.Go語言數據類型

書接上篇,咱們瞭解了Go語言的接口類型,如今介紹Go語言的結構體類型。主要以下:git

3.7 結構體

結構體類型既能夠包含若干個命名元素(又稱字段),又能夠與若干個方法相關聯。github

3.7.1 類型表示法

結構體類型的聲明能夠包含若干個字段的聲明。字段聲明左邊的標識符表示了該字段的名稱,右邊的標識符表明了該字段的類型,這兩個標識符之間用空格分隔。json

結構體類型聲明中的每一個字段聲明都獨佔一行。同一個結構體類型聲明中的字段不能出現重名的狀況。數組

結構體類型也分爲命名結構體類型匿名結構體類型緩存

命名結構體類型ide

命名結構體類型以關鍵字type開始,依次包含結構體類型的名稱、關鍵字struct和由花括號括起來的字段聲明列表。以下:函數

type Sequence struct {
    len int
    cap int
    Sortable
    sortableArray  sort.Interface
}

結構體類型的字段的類型能夠是任何數據類型。當字段名稱的首字母是大寫字母時,咱們就能夠在任何位置(包括其餘代碼包)上經過其所屬的結構體類型的值(如下簡稱結構體值)和選擇表達式訪問到它們。不然當字段名稱的首字母是小寫,這些字段就是包級私有的(只有在該結構體聲明所屬的代碼包中才能對它們進行訪問或者給它們賦值)。學習

若是一個字段聲明中只有類型而沒有指定名稱,這個字段就叫作匿名字段。如上結構體 Sequence 中的 Sortable 就是一個匿名字段。匿名字段有時也被稱爲嵌入式的字段或結構體類型的嵌入類型。ui

匿名字段的類型必須由一個數據類型的名稱或者一個與非接口類型對應的指針類型的名稱表明。表明匿名字段類型的非限定名稱將被隱含地做爲該字段的名稱。若是匿名字段是一個指針類型的話,那麼這個指針類型所指的數據類型的非限定名稱(由非限定標識符表明的名稱)就會被做爲該字段的名稱。非限定標識符就是不包含代碼包名稱和點的標識符。指針

匿名類型的隱含名稱的實例,以下:

type Anonymities struct {
    T1
    *T2
    P.T3
    *P.T4
}

這個名爲 Anonymities 的結構體類型包含了4個匿名字段。其中,T1P.T3 爲非指針的數據類型,它們隱含的名稱分別爲 T1T3*T2*P.T4 爲指針類型,它們隱含的名稱分別爲 T2T4**。

注意:匿名字段的隱含名稱也不能與它所屬的結構體類型中的其餘字段名稱重複。

結構體類型中的嵌入字段的類型所附帶的方法都會成爲該結構體類型的方法,結構體類型自動實現了它包含的全部嵌入類型所實現的接口類型。可是嵌入類型的方法的接收者類型仍然是該嵌入類型,而不是被嵌入的結構體類型。當在結構體類型中調用實際上屬於嵌入類型的方法的時候,這一調用會被自動轉發到這個嵌入類型的值上。

如今對 Sequence 的聲明進行改動,以下:

type Sequence struct {
    Sortable
    sorted bool
}

上面的 Sequence 中的匿名字段 Sortable 用來存儲和操做可排序序列,布爾類型的字段 sorted 用來表示類型值是否已經被排序。

假設有一個 Sequence 類型的值 seq,調用 Sortable 接口類型中的方法 Sort,以下:

seq.Sort()

若是 Sequence 類型中也包含了一個與 Sortable 接口類型中的方法 Sort 的名稱和簽名相同的方法,那麼上面的調用必定是對 Sequence 類型值自身附帶的 Sort 方法的調用,而嵌入類型 Sortable 的方法 Sort 被隱藏了。

若是須要在原有的排序操做上添加一些額外功能,能夠這樣聲明一個同名的方法:

func (self *Sequence) Sort() {
    self.Sortable.Sort()
    self.sorted = true
}

這樣聲明的方法實現了對於匿名字段 SortableSort 方法的功能進行無縫擴展的目的。

若是兩個 Sort 方法的名稱相同但簽名不一樣,那麼嵌入類型 Sortable 的方法 Sort 也一樣會被隱藏。這時,在 Sequence 的類型值上調用 Sort 方法的時候,必須依據該 Sequence 結構體類型的 Sort 方法的簽名來編寫調用表達式。以下聲明 Sequence 類型附帶的名爲 Sort 的方法:

func (self *Sequence) Sort(quicksort bool) {
    //省略若干語句
}

可是調用表達式 seq.Sort() 就會形成一個編譯錯誤,由於 Sortable 的無參數的 Sort 方法已經被隱藏了,只能經過 seq.Sort(true)seq.Sort(false) 來對 SequenceSort 方法進行調用。

注意:不管被嵌入類型是否包含了同名的方法,調用表達式 seq.Sortable.Sort() 老是能夠來調用嵌入類 SortableSort 方法。

如今,區別一下嵌入類型是一個非指針的數據類型仍是一個指針類型,假設有結構體類型 S 和非指針類型的數據類型 T,那麼 *S 表示指向 S* 的指針類型,T 表示指向 T** 的指針類型,則:

  1. 若是在 S 中包含了一個嵌入類型 T,那麼 S*S 的方法集合中都會包含接收者類型爲 T* 的方法。除此以外,S** 的方法集合中還會包含接收者類型爲 *T 的方法。

  2. 若是在 S 中包含了一個嵌入類型 *T,那麼 S*S 的方法集合中都會包含接收者類型爲 T** 和 *T 的全部方法。

如今再討論另外一個問題。假設,咱們有一個名爲 List 的結構體類型,而且在它的聲明中嵌入了類型 Sequence,以下:

type List struct {
    Sequence
}

假設有一個 List 類型的值 list,調用嵌入的 Sequence 類型值的字段 sorted,以下:

list.sorted

若是 List 類型也有一個名稱爲 sorted 的字段的話,那麼其中的 Sequence 類型值的字段 sorted 就會被隱藏。

注意: 選擇表達式 list.sorted 只表明了對 List 類型的 sorted 字段的訪問,不論這兩個名稱爲 sorted 的字段的類型是否相同。和上面的相似,這裏選擇表達式 list.Sequence.sorted 老是能夠訪問到嵌入類型 Sequence 的值的 sorted 字段。

對於結構體類型的多層嵌入的規則,有兩點須要說明:

  1. 能夠在被嵌入的結構體類型的值上像調用它本身的字段或方法那樣調用任意深度的嵌入類型值的字段或方法。惟一的前提條件就是這些嵌入類型的字段或方法沒有被隱藏。若是它們被隱藏,也能夠經過相似 list. Sequence.sorted 這樣的表達式進行訪問或調用它們。

  2. 被嵌入的結構體類型的字段或方法能夠隱藏任意深度的嵌入類型的同名字段或方法。任何較淺層次的嵌入類型的字段或方法都會隱藏較深層次的嵌入類型包含的同名的字段或方法。注意,這種隱藏是能夠交叉進行的,即字段能夠隱藏方法,方法也能夠隱藏字段,只要它們的名稱相同便可。

若是在同一嵌入層次中的兩個嵌入類型擁有同名的字段或方法,那麼涉及它們的選擇表達式或調用表達式會由於編譯器不能肯定被選擇或調用的目標而形成一個編譯錯誤。

匿名結構體類型

匿名結構體類型比命名結構體類型少了關鍵字type類型名稱,聲明以下:

struct {
    Sortable
    sorted bool
}

能夠在數組類型、切片類型或字典類型的聲明中,將一個匿名的結構體類型做爲他們的元素的類型。還能夠將匿名結構體類型做爲一個變量的類型,例如:

var anonym struct {
    a int
    b string
}

不過對於上面,更經常使用的作法就是在聲明以匿名結構體類型爲類型的變量的同時對其初始化,例如:

anonym := struct {
    a int
    b string
}{0, "string"}

與命名結構體類型相比,匿名結構體類型更像是「一次性」的類型,它不具備通用性,經常被用在臨時數據存儲和傳遞的場景中。

在Go語言中,能夠在結構體類型聲明中的字段聲明的後面添加一個字符串字面量標籤,以做爲對應字段的附加屬性。例如:

type Person struct {
    Name    string `json:"name"`
    Age     uint8 `json:"age"`
    Address string `json:"addr"`
}

如上的字段的字符串字面量標籤通常有兩個反引號包裹的任意字符串組成。而且,它應該被添加但在與其對應的字段的同一行的最右側。

這種標籤對於使用該結構體類型及其值的代碼來講是不可見的。可是,能夠用標準庫代碼包 reflect 中提供的函數查看到結構體類型中字段的標籤。這種標籤經常會在一些特殊應用場景下使用,好比,標準庫代碼包 encoding/json 中的函數會根據這種標籤的內容肯定與該結構體類型中的字段對應的 JSON 節點的名稱。

3.7.2 值表示法

結構體值通常由複合字面量(類型字面量和花括號構成)來表達。在Go語言中,經常將用於表示結構體值的複合字面量簡稱爲結構體字面量。在同一個結構體字面量中,一個字段名稱只能出現一次。例如:

Sequence{Sortable: SortableStrings{"3", "2", "1"}, sorted: false}

類型 SortableStrings 實現了接口類型 Sortable,這個能夠在Go語言學習筆記4中瞭解到。這裏就能夠把一個 SortableStrings 類型的值賦給 Sortable 字段。

編寫結構體字面量,還能夠忽略字段的名稱,但有以下的兩個限制:

  1. 若是想要省略其中某個或某些鍵值對的鍵,那麼其餘的鍵值對的鍵也必須省略。

    Sequence{ SortableStrings{"3", "2", "1"}, sorted: false} // 這是不合法的
  2. 多個字段值之間的順序應該與結構體類型聲明中的字段聲明的順序一致,而且不可以省略掉任何一字段的賦值。可是不省略字段名稱的字面量卻沒有此限制。例如:
    Sequence{ sorted: false , Sortable: SortableStrings{"3", "2", "1"}} // 合法
    Sequence{SortableStrings{"3", "2", "1"}, false} // 合法   
    Sequence{ Sortable: SortableStrings{"3", "2", "1"}} // 合法,未被明確賦值的字段的值會被其類型的零值填充。
    Sequence{ false , SortableStrings{"3", "2", "1"}} // 不合法,順序不一致,會編譯錯誤
    Sequence{ SortableStrings{"3", "2", "1"}} // 不合法,順序不一致,會編譯錯誤

在Go語言中,能夠在結構體字面量中不指定任何字段的值。例如:

Sequence{} // 這種狀況下,兩個字段都被賦予它們所屬類型的零值。

與數組類型相同,結構體類型屬於值類型。結構體類型的零值就是如上的不爲任何字段賦值的結構體字面量。

3.7.3 屬性和基本操做

一個結構體類型的屬性就是它所包含的字段和與它關聯的方法。在訪問權限容許的狀況下,咱們可使用選擇表達式訪問結構體值中的字段,也可使用調用表達式調用結構體值關聯的方法。

在Go語言中,只存在嵌入而不存在繼承的概念。不能把前面聲明的 List 類型的值賦給一個 Sequence 類型的變量,這樣的賦值語句會形成一個編譯錯誤。在一個結構體類型的別名類型的值上,既不能調用那個結構體類型的方法,也不能調用與那個結構體類型對應的指針類型的方法。別名類型不是它源類型的子類型,但別名類型內部的結構會與它的源類型一致。

對於一個結構體類型的別名類型來講,它擁有源類型的所有字段,但這個別名類型並無繼承與它的源類型關聯的任何方法。

若是隻是將 List 類型做爲 Sequence 類型的一個別名類型,那麼聲明以下:

type List Sequence

此時,List 類型的值的表示方法與 Sequence 類型的值的表示方法同樣,以下:

List{ SortableStrings{"4", "5", "6"}, false}

若是有一個 List 類型的值 List,那麼選擇表達式 list.sorted 訪問的就是這個 List 類型的值的 sorted 字段,一樣,咱們也能夠經過選擇表達式 list.Sortable 訪問這個值的嵌入字段 Sortable。可是這個 List 類型目前卻不包含與它的源類型 Sequence 關聯的方法。

在Go語言中,雖然不少預約義類型都屬於泛型類型(好比數組類型、切片類型、字典類型和通道類型),但卻不支持自定義的泛型類型。爲了使 Sequence 類型可以部分模擬泛型類型的行爲特徵,只是向它嵌入 Sortable 接口類型是不夠的,須要對 Sortable 接口類型進行拓展。以下:

type GenericSeq interface {
    Sortable
    Append(e interface{}) bool
    Set(index int, e interface{}) bool
    Delete(index int) (interface{}, bool)
    ElemValue(index int) interface{}
    ElemType() reflect.Type
    value() interface{}
}

如上的接口類型 GenericSeq 中聲明瞭用於添加、修改、刪除、查詢元素,以及獲取元素類型的方法。一個數據類型要實現 GenericSeq 接口類型,也必須實現 Sortable 接口類型。

如今,將嵌入到 Sequence 類型的 Sortable 接口類型改成 GenericSeq 接口類型,聲明以下:

type Sequence struct {
    GenericSeq
    sorted bool
    elemType reflect.Type
}

在如上的類型聲明中,添加了一個 reflect.Type 類型(即標準庫代碼包 reflect 中的 Type 類型)的字段 elemType,目的用它來緩存 GenericSeq 字段中存儲的值的元素類型。

爲了可以在改變 GenericSeq 字段存儲的值的過程當中及時對字段 sortedelemType 的值進行修改,以下還建立了幾個與 Sequence 類型關聯的方法。聲明以下:

func (self *Sequence) Sort() {
    self.GenericSeq.Sort()
    self.sorted = true
}

func (self *Sequence) Append(e interface{}) bool{
    result := self. GenericSeq.Append(e)
    //省略部分代碼
    self.sorted = true
    //省略部分代碼
    return result
}

func (self *Sequence) Set(index int, e interface{}) bool {
    result := self. GenericSeq.Set(index, e)
    //省略部分代碼
    self.sorted = true
    //省略部分代碼
    return result
}

func (self *Sequence) ElemType() reflect.Type {
    //省略部分代碼
    self.elemType = self.GenericSeq.ElemType()
    //省略部分代碼
    return self.elemType
}

如上的這些方法分別與接口類型 GenericSeqSortable 中聲明的某個方法有着相同的方法名稱和方法簽名。經過這種方式隱藏了 GenericSeq 字段中存儲的值的這些同名方法,並對它們進行了無縫擴展。

GenericSeq 接口類型的實現類型以及 Sequence 類型的完整實現代碼 點擊這裏

相關文章
相關標籤/搜索