Go 通過類型別名(alias types)和結(jié)構(gòu)體的形式支持用戶自定義類型蝶俱,或者叫定制類型瘦馍。試圖表示一個(gè)現(xiàn)實(shí)世界中的實(shí)體碳褒。
結(jié)構(gòu)體由一系列命名的元素組成,這些元素又被稱為字段莲组,每個(gè)字段都有一個(gè)名稱和一個(gè)類型诊胞。
結(jié)構(gòu)體的目的就是把數(shù)據(jù)聚集在一起,以便能夠更加便捷地操作這些數(shù)據(jù)锹杈。結(jié)構(gòu)體的概念在 C 語言里很常見撵孤,被稱為 struct。Golang 中的結(jié)構(gòu)體也是 struct竭望。Go 語言中沒有類的概念邪码,因此在 Go 中結(jié)構(gòu)體有著更為重要的地位。結(jié)構(gòu)體是復(fù)合類型(composite types)咬清,當(dāng)需要定義一個(gè)類型闭专,它由一系列屬性組成,每個(gè)屬性都有自己的類型和值的時(shí)候旧烧,就應(yīng)該使用結(jié)構(gòu)體影钉,它把數(shù)據(jù)聚集在一起。然后可以訪問這些數(shù)據(jù)掘剪,就好像它是一個(gè)獨(dú)立實(shí)體的一部分平委。結(jié)構(gòu)體也是值類型,因此可以通過 new 函數(shù)來創(chuàng)建夺谁。
定義結(jié)構(gòu)體
在 Golang 中最常用的方法是使用關(guān)鍵字 type 和 struct 來定義一個(gè)結(jié)構(gòu)體廉赔,以關(guān)鍵字 type 開始肉微,之后是新類型的名字,最后是關(guān)鍵字 struct:
// Person 為用戶定義的一個(gè)類型
type Person struct {
Name string
Age int
Email string
}
還有一些簡(jiǎn)單的寫法蜡塌,比如:
type T struct { a, b int }
也是合法的碉纳,它更適用于簡(jiǎn)單的結(jié)構(gòu)體。
結(jié)構(gòu)體里的字段都有名字岗照,比如上面例子中的 Name村象、Age 和 Email 等等。如果一個(gè)字段在代碼中從來不會(huì)被用到攒至,那可以把它命名為 _,即空標(biāo)識(shí)符躁劣。
結(jié)構(gòu)體中的字段可以是任何類型迫吐,甚至是結(jié)構(gòu)體本身,也可以是函數(shù)或者接口账忘≈景颍可以聲明結(jié)構(gòu)體類型的一個(gè)變量,然后像下面這樣給它的字段賦值:
var p Person
p.Name = "nick"
p.Age = 28
另外鳖擒,數(shù)組可以看作是一種結(jié)構(gòu)體類型溉浙,不過它使用下標(biāo)而不是具名的字段。
字段標(biāo)記
在定義結(jié)構(gòu)體時(shí)還可以為字段指定一個(gè)標(biāo)記信息:
type Person struct {
Name string json:"name"
Age int json:"age"
Email string json:"email"
}
這些標(biāo)記信息通過反射接口可見蒋荚,并參與結(jié)構(gòu)體的類型標(biāo)識(shí)戳稽,但在其他情況下被忽略。
聲明結(jié)構(gòu)體類型的變量
一旦定義了結(jié)構(gòu)體類型就可以使用這個(gè)類型創(chuàng)建值期升。
使用結(jié)構(gòu)類型聲明變量惊奇,并初始化為零值
var nick Person
關(guān)鍵字 var 創(chuàng)建了類型為 Person 且名為 nick 的變量。nick 被稱作類型 Person 的一個(gè)實(shí)例(instance)或?qū)ο?object)播赁。注意:當(dāng)聲明變量時(shí)颂郎,這個(gè)變量對(duì)應(yīng)的值總是會(huì)被初始化。這個(gè)值要么用指定的值初始化容为,要么用零值(即變量類型的默認(rèn)值)做初始化乓序。對(duì)數(shù)值類型來說,零值是 0坎背;對(duì)字符串來說替劈,零值是空字符串;對(duì)布爾類型沼瘫,零值是 false抬纸。
通過上面的方式聲明結(jié)構(gòu)體類型的變量,結(jié)構(gòu)體里的每個(gè)字段都會(huì)用零值初始化耿戚。任何時(shí)候湿故,創(chuàng)建一個(gè)變量并初始化為零值阿趁,習(xí)慣上會(huì)使用關(guān)鍵字 var。這種用法是為了更明確地表示一個(gè)變量被設(shè)置為零值坛猪。
使用結(jié)構(gòu)體字面量聲明變量脖阵,并初始化為非零值
如果希望變量被初始化為某個(gè)非零值,可以通過結(jié)構(gòu)體字面量和短變量聲明操作符(:=)來創(chuàng)建變量墅茉。下面的代碼展示了如何聲明一個(gè) Persion 類型的變量命黔,并使用某個(gè)非零值作為初始值。
// 聲明 Person 類型的變量就斤,并初始化所有字段
nick := Person{
Name: "nick",
Age: 28,
Email: "nickli@xxx.com",
}
在第一行中我們給出了一個(gè)變量名 nick悍募,之后是短變量聲明操作符。這個(gè)操作符是冒號(hào)加一個(gè)等號(hào) (:=)洋机。一個(gè)短變量聲明操作符在一次操作中完成兩件事情:聲明一個(gè)變量坠宴,并初始化。短變量聲明操作符會(huì)使用右側(cè)給出的類型信息作為聲明變量的類型绷旗。
短變量聲明操作符(:=)
它的作用是聲明并且賦值一個(gè)變量喜鼓,其好處是不需要寫 var 三個(gè)字母,另外不需要寫類型衔肢,Golang 語言會(huì)自動(dòng)根據(jù)賦值的內(nèi)容確定類型庄岖。
使用短變量聲明操作符也有一些限制,比如不能在函數(shù)外面使用角骤,即不能用來聲明全局變量隅忿。另外短變量聲明操作符左邊至少得有一個(gè)變量是沒有定義過的。
字面量的兩種寫法
結(jié)構(gòu)體字面量可以對(duì)結(jié)構(gòu)體類型進(jìn)行初始化启搂,比如前面介紹過的示例:
nick := Person{
Name: "nick",
Age: 28,
Email: "nickli@xxx.com",
}
這種形式在不同行聲明每個(gè)字段的名字以及對(duì)應(yīng)的值硼控。字段名與值用冒號(hào)分隔,每一行以逗號(hào)結(jié)尾胳赌。這種形式對(duì)字段的聲明順序沒有要求牢撼。
第二種形式?jīng)]有字段名,只聲明對(duì)應(yīng)的值疑苫,如下面的代碼:
nick := Person{"nick", 28, "nickli@xxx.com"}
每個(gè)值也可以分別占一行熏版,不過習(xí)慣上這種形式會(huì)寫在一行里,結(jié)尾不需要逗號(hào)捍掺。這種形式下撼短,值的順序很重要,必須要和結(jié)構(gòu)聲明中字段的順序一致挺勿。
new 函數(shù)
new 是一個(gè)用來分配內(nèi)存的內(nèi)置函數(shù)曲横,但是與 C++ 不一樣的是,它并不初始化內(nèi)存,只是將其置零禾嫉。也就是說灾杰,new(T) 會(huì)為類型 T 分配被置零的內(nèi)存,并且返回它的地址熙参,一個(gè)類型為 *T 的值艳吠。在 Golang 的術(shù)語中,其返回一個(gè)指向新分配的類型為 T 的指針孽椰,這個(gè)指針指向的內(nèi)容的值為零(zero value)昭娩。比如下面的代碼:
nick := new(Person)
這里的變量 nick 是一個(gè)指針:
fmt.Printf("%T", nick)
輸出的結(jié)果為:*main.Student
而通過下面方式聲明的變量:
var nick Person
類型則是:main.Student
使用 new 函數(shù)時(shí),聲明變量和分配內(nèi)存并不需要放在一起黍匾,可以先聲明一個(gè)變量栏渺,然后再通過 new 函數(shù)為之分配內(nèi)存,比如下面的寫法:
var nick *Person
nick = new (Person)
new 函數(shù)的特點(diǎn)是只能把內(nèi)存初始化為零值并返回其指針膀捷,如果要通過字面量初始化該內(nèi)存就需要使用混合字面量語法(composite literal syntax):
&T{...}
比如下面的寫法:
nick := &Person{
Name: "nick",
Age: 28,
Email: "nickli@xxx.com",
}
此時(shí) nick 的類型也是 *Person迈嘹。因此我們可以得出下面的結(jié)論:
表達(dá)式 new(Type) 和 &Type{} 是等價(jià)的。
選擇器(selector)
在 Golang 中全庸,訪問結(jié)構(gòu)體成員需要使用點(diǎn)號(hào)操作符,點(diǎn)號(hào)操作符也被稱為選擇器(selector)融痛,使用時(shí)的格式為:
結(jié)構(gòu)體.成員名
注意:這里的 "結(jié)構(gòu)體" 是指結(jié)構(gòu)體類型的變量或結(jié)構(gòu)體指針類型的變量壶笼。無論變量是一個(gè)結(jié)構(gòu)體類型還是一個(gè)結(jié)構(gòu)體類型指針,都可以使用相同的選擇器服務(wù)來引用結(jié)構(gòu)體的字段雁刷。
匿名字段和內(nèi)嵌結(jié)構(gòu)體
結(jié)構(gòu)體可以包含一個(gè)或多個(gè)匿名(或者稱為內(nèi)嵌)字段覆劈,即這些字段沒有顯式的名字。僅指明字段的類型沛励,此時(shí)該類型就是字段的名字责语。匿名字段本身可以是一個(gè)結(jié)構(gòu)體類型,即結(jié)構(gòu)體可以包含內(nèi)嵌的結(jié)構(gòu)體目派。
匿名字段
匿名字段和面向?qū)ο缶幊讨械睦^承概念相似坤候,可以被用來模擬類似繼承的行為。Golang 中的基礎(chǔ)就是通過內(nèi)嵌或組合來實(shí)現(xiàn)的企蹭,所以說在 Golang 中組合比繼承更受歡迎白筹。比如下面的例子:
type test struct {
name string
age int
int // 匿名字段
}
內(nèi)嵌結(jié)構(gòu)體
結(jié)構(gòu)體也是一種數(shù)據(jù)類型,所以它同樣可以作為匿名字段使用:
復(fù)制代碼
type Person struct {
Name string
Age int
Email string
}
type Student struct {
Person
StudentID int
}
復(fù)制代碼
下面的代碼可以聲明并初始化 Student 類型的變量:
st := Student {
Person: Person{"jack", 22, "jack@xxx.com"},
StudentID: 1000,
}
從這個(gè)示例可以看出谅摄,內(nèi)層結(jié)構(gòu)體被簡(jiǎn)單地插入或者內(nèi)嵌進(jìn)外層結(jié)構(gòu)體徒河。這種簡(jiǎn)單的 "繼承" 機(jī)制使得 Golang 很輕松就能實(shí)現(xiàn)從一個(gè)或一些類型中繼承部分或全部的實(shí)現(xiàn)。
總結(jié)
Golang 中沒有類的概念送漠,因此在 Golang 中結(jié)構(gòu)體有著更為重要的地位顽照。所以學(xué)習(xí)并掌握結(jié)構(gòu)體是入門 Golang 的關(guān)鍵步驟。希望本文能夠幫助大家理解 Golang 結(jié)構(gòu)體的基本概念和用法闽寡。