1. 程式人生 > 實用技巧 >golang學習筆記 --- 結構體(struct)

golang學習筆記 --- 結構體(struct)

結構體(struct)

Go 通過類型別名(alias types)和結構體的形式支援使用者自定義型別,或者叫定製型別。試圖表示一個現實世界中的實體。

結構體由一系列命名的元素組成,這些元素又被稱為欄位,每個欄位都有一個名稱和一個型別。
結構體的目的就是把資料聚集在一起,以便能夠更加便捷地操作這些資料。結構體的概念在 C 語言裡很常見,被稱為 struct。Golang 中的結構體也是 struct。Go 語言中沒有類的概念,因此在 Go 中結構體有著更為重要的地位。結構體是複合型別(composite types),當需要定義一個型別,它由一系列屬性組成,每個屬性都有自己的型別和值的時候,就應該使用結構體,它把資料聚集在一起。然後可以訪問這些資料,就好像它是一個獨立實體的一部分。結構體也是值型別,因此可以通過 new 函式來建立。

定義結構體

在 Golang 中最常用的方法是使用關鍵字 type 和 struct 來定義一個結構體,以關鍵字 type 開始,之後是新型別的名字,最後是關鍵字 struct:

// Person 為使用者定義的一個型別
type Person struct {
    Name  string
    Age     int
    Email string
}

還有一些簡單的寫法,比如:

type T struct { a, b int }

也是合法的,它更適用於簡單的結構體。

結構體裡的欄位都有名字,比如上面例子中的 Name、Age 和 Email 等等。如果一個欄位在程式碼中從來不會被用到,那可以把它命名為 _,即空識別符號。

結構體中的欄位可以是任何型別,甚至是結構體本身,也可以是函式或者介面。可以宣告結構體型別的一個變數,然後像下面這樣給它的欄位賦值:

var p Person
p.Name = "nick"
p.Age = 28

另外,陣列可以看作是一種結構體型別,不過它使用下標而不是具名的欄位。

欄位標記
在定義結構體時還可以為欄位指定一個標記資訊:

type Person struct {
    Name  string `json:"name"`
    Age     int         `json:"age"`
    Email string `json:"email"`
}

這些標記資訊通過反射介面可見,並參與結構體的型別標識,但在其他情況下被忽略。

宣告結構體型別的變數

一旦定義了結構體型別就可以使用這個型別建立值。

使用結構型別宣告變數,並初始化為零值

var nick Person

關鍵字 var 建立了型別為 Person 且名為 nick 的變數。nick 被稱作型別 Person 的一個例項(instance)或物件(object)。注意:當宣告變數時,這個變數對應的值總是會被初始化。這個值要麼用指定的值初始化,要麼用零值(即變數型別的預設值)做初始化。對數值型別來說,零值是 0;對字串來說,零值是空字串;對布林型別,零值是 false。

通過上面的方式宣告結構體型別的變數,結構體裡的每個欄位都會用零值初始化。任何時候,建立一個變數並初始化為零值,習慣上會使用關鍵字 var。這種用法是為了更明確地表示一個變數被設定為零值。

使用結構體字面量宣告變數,並初始化為非零值
如果希望變數被初始化為某個非零值,可以通過結構體字面量和短變數宣告操作符(:=)來建立變數。下面的程式碼展示瞭如何宣告一個 Persion 型別的變數,並使用某個非零值作為初始值。

// 宣告 Person 型別的變數,並初始化所有欄位
nick := Person{
    Name: "nick",
    Age: 28,
    Email: "[email protected]",
}

在第一行中我們給出了一個變數名 nick,之後是短變數宣告操作符。這個操作符是冒號加一個等號 (:=)。一個短變數宣告操作符在一次操作中完成兩件事情:宣告一個變數,並初始化。短變數宣告操作符會使用右側給出的型別資訊作為宣告變數的型別。

短變數宣告操作符(:=)
它的作用是宣告並且賦值一個變數,其好處是不需要寫 var 三個字母,另外不需要寫型別,Golang 語言會自動根據賦值的內容確定型別。

使用短變數宣告操作符也有一些限制,比如不能在函式外面使用,即不能用來宣告全域性變數。另外短變數宣告操作符左邊至少得有一個變數是沒有定義過的。

字面量的兩種寫法
結構體字面量可以對結構體型別進行初始化,比如前面介紹過的示例:

nick := Person{
    Name: "nick",
    Age: 28,
    Email: "[email protected]",
}

這種形式在不同行宣告每個欄位的名字以及對應的值。欄位名與值用冒號分隔,每一行以逗號結尾。這種形式對欄位的宣告順序沒有要求。
第二種形式沒有欄位名,只宣告對應的值,如下面的程式碼:

nick := Person{"nick", 28, "[email protected]"}

每個值也可以分別佔一行,不過習慣上這種形式會寫在一行裡,結尾不需要逗號。這種形式下,值的順序很重要,必須要和結構宣告中欄位的順序一致。

new 函式

new 是一個用來分配記憶體的內建函式,但是與 C++ 不一樣的是,它並不初始化記憶體,只是將其置零。也就是說,new(T) 會為型別 T 分配被置零的記憶體,並且返回它的地址,一個型別為 *T 的值。在 Golang 的術語中,其返回一個指向新分配的型別為 T 的指標,這個指標指向的內容的值為零(zero value)。比如下面的程式碼:

nick := new(Person)

這裡的變數 nick 是一個指標:

fmt.Printf("%T", nick)

輸出的結果為:*main.Student
而通過下面方式宣告的變數:

var nick Person

型別則是:main.Student

使用 new 函式時,宣告變數和分配記憶體並不需要放在一起,可以先宣告一個變數,然後再通過 new 函式為之分配記憶體,比如下面的寫法:
var nick *Person
nick = new (Person)

new 函式的特點是隻能把記憶體初始化為零值並返回其指標,如果要通過字面量初始化該記憶體就需要使用混合字面量語法(composite literal syntax)
&T{...}

比如下面的寫法:

nick := &Person{
    Name:  "nick",
    Age:   28,
    Email: "[email protected]",
}

此時 nick 的型別也是 *Person。因此我們可以得出下面的結論:
表示式 new(Type) 和 &Type{} 是等價的。

選擇器(selector)

在 Golang 中,訪問結構體成員需要使用點號操作符,點號操作符也被稱為選擇器(selector),使用時的格式為:

結構體.成員名

注意:這裡的 "結構體" 是指結構體型別的變數或結構體指標型別的變數。無論變數是一個結構體型別還是一個結構體型別指標,都可以使用相同的選擇器服務來引用結構體的欄位。

匿名欄位和內嵌結構體

結構體可以包含一個或多個匿名(或者稱為內嵌)欄位,即這些欄位沒有顯式的名字。僅指明欄位的型別,此時該型別就是欄位的名字。匿名欄位本身可以是一個結構體型別,即結構體可以包含內嵌的結構體。

匿名欄位
匿名欄位和麵向物件程式設計中的繼承概念相似,可以被用來模擬類似繼承的行為。Golang 中的基礎就是通過內嵌或組合來實現的,所以說在 Golang 中組合比繼承更受歡迎。比如下面的例子:

type test struct {
    name string
    age int
    int // 匿名欄位
}

內嵌結構體
結構體也是一種資料型別,所以它同樣可以作為匿名欄位使用:

type Person struct {
    Name  string
    Age     int
    Email string
}
type Student struct {
    Person
    StudentID int
}

下面的程式碼可以宣告並初始化 Student 型別的變數:

st := Student {
    Person:    Person{"jack", 22, "[email protected]"},
    StudentID: 1000,
}

從這個示例可以看出,內層結構體被簡單地插入或者內嵌進外層結構體。這種簡單的 "繼承" 機制使得 Golang 很輕鬆就能實現從一個或一些型別中繼承部分或全部的實現。

總結

Golang 中沒有類的概念,因此在 Golang 中結構體有著更為重要的地位。所以學習並掌握結構體是入門 Golang 的關鍵步驟。希望本文能夠幫助大家理解 Golang 結構體的基本概念和用法。