struct概述
結(jié)構(gòu)體是go語(yǔ)言最重要的數(shù)據(jù)結(jié)構(gòu)之一,go和其它編程語(yǔ)言不一樣,它沒(méi)有類的概念卵佛,類比過(guò)來(lái)struct就相當(dāng)于其它語(yǔ)言中的類,因此十分重要饼灿。
結(jié)構(gòu)體這部分涉及到的知識(shí)點(diǎn)頁(yè)比較多疑苫,此文偏長(zhǎng),請(qǐng)耐心閱讀抽高。
1. 認(rèn)識(shí)結(jié)構(gòu)體
直接說(shuō)語(yǔ)法往往非车勒担枯燥因苹,在正式開(kāi)始前,我們先來(lái)看一段簡(jiǎn)單的結(jié)構(gòu)體代碼鞭呕,建立整體感知蛤育,后續(xù)我們?cè)僖灰患?xì)說(shuō)其中的知識(shí)點(diǎn)。
package main
import "fmt"
// Person結(jié)構(gòu)體 - 相當(dāng)于類
type Person struct {
Name string // 字段Name 類型為string
Age int8 // 字段Age 類型為int8
}
// 實(shí)例方法
func (p Person) GetName() {
fmt.Printf("My name: %s\n", p.Name)
}
func main() {
p := Person{
Name: "zhangsan",
Age: 18,
}
p.GetName()
}
// My name: zhangsan
看到了吧,還是很簡(jiǎn)單的,跟著注釋你大概已經(jīng)看懂了如何使用葫松。
下面我們拆分成知識(shí)點(diǎn)細(xì)細(xì)分析
1.1 如何定義
它按照如下方式定義(PS: 它還可以代標(biāo)簽瓦糕,為簡(jiǎn)單起見(jiàn),這里暫且不討論)
// 如下格式
type 結(jié)構(gòu)體名 struct {
字段名1 字段類型1
字段名2 字段類型2
.....
}
1.2 實(shí)例化
主要有幾種方式:
var p = new(Person) // 這里返回的是實(shí)力化后的地址 注意地址哈
var p Person // 它是值類型 所以這種相當(dāng)于零值初始化
var p = Person{} // 這種和上面等效
// 這是一種最標(biāo)準(zhǔn)的賦值方式 把每個(gè)字段名都寫(xiě)了出來(lái)
p := Person{
Name: "zhangsan",
Age: 18,
}
// 可以省略字段名
p := Person{"zhangsan", 18}
實(shí)際例化后我們可以通過(guò)obj.字段名
的方式調(diào)出值腋么,如上例中p.Name
1.3 方法
結(jié)構(gòu)體方法,對(duì)應(yīng)到面向?qū)ο笳Z(yǔ)言中就是實(shí)例方法.
在上例中,如下部分:
// 實(shí)例方法
func (p Person) GetName() {
fmt.Printf("My name: %s\n", p.Name)
}
// 在這里 p Person稱為接收者 后續(xù)為方法名
// 定義后 我們可以同 obj.方法名調(diào)用
方法和函數(shù)有什么主要區(qū)別呢咕娄?
方法它有接收者,而函數(shù)沒(méi)有
1.4 接收者
接收者既可以是值也可以是指針類型,我們看下:
package main
import "fmt"
// Person結(jié)構(gòu)體 - 相當(dāng)于類
type Person struct {
Name string // 字段Name 類型為string
Age int8 // 字段Age 類型為int8
}
// 值接收者
func (p Person) GetName() {
fmt.Printf("My name: %s\n", p.Name)
}
// 指針接收者
func (p *Person) GetAge() {
fmt.Printf("My age: %d\n", p.Age)
}
func main() {
p1 := Person{Name: "張三", Age: 18} // 值
p2 := &Person{Name: "李四", Age: 16} // 指針
// 值對(duì)象可以同時(shí)調(diào)用 值接收者 和 指針接收者方法
p1.GetName()
p1.GetAge()
fmt.Println("---------分割線-------")
// 指針對(duì)象可以同時(shí)調(diào)用 值接收者 和 指針接收者方法
p2.GetName()
p2.GetAge()
}
// My name: 張三
// My age: 18
// ---------分割線-------
// My name: 李四
// My age: 16
我們可以發(fā)現(xiàn)党晋,無(wú)論接收者是值類型還是指針類型,它們?cè)谡{(diào)用上卻不會(huì)有任何區(qū)別谭胚,這是因?yàn)間o編譯器會(huì)悄悄自動(dòng)幫我轉(zhuǎn)換, nice!
1.5 指針接收者or值接收者
那么什么時(shí)候使用值接收者啥時(shí)候用指針接收者呢?
- 在go中一般約定,同一個(gè)struct接收者類型保持一致(要么全是指針接收者,要么全是值接收者)
- 值接收者: 結(jié)構(gòu)體相對(duì)較形床!(拷貝成本不高)灾而,不需要改變結(jié)構(gòu)體內(nèi)部值場(chǎng)景
- 指針接收者: 結(jié)構(gòu)體比較大(拷貝成本高),需要改變結(jié)構(gòu)體內(nèi)部值場(chǎng)景
2. 匿名字段及嵌套
匿名字段可以說(shuō)是結(jié)構(gòu)體最有用的功能,使用的地方比比皆是,下面我們來(lái)看下
2.1 匿名字段
所謂匿名字段指的是在結(jié)構(gòu)體中字段名可以不用顯示寫(xiě)出來(lái),比如:
package main
import "fmt"
type Data struct {
uint8 // 沒(méi)有結(jié)構(gòu)體字段名 只有類型名
// 此時(shí)字段名 == 類型名
}
func main() {
d := Data{8}
// 直接通過(guò)類型名調(diào)用
fmt.Println(d.uint8)
}
// 8
關(guān)鍵點(diǎn)在于字段名 == 類型名
2.2 結(jié)構(gòu)體嵌套
在開(kāi)始之前我們來(lái)看下兩個(gè)結(jié)構(gòu)體
// 人結(jié)構(gòu)體
type Person struct {
Name string // 姓名
Age int8 // 年齡
}
// 結(jié)構(gòu)體
type Student struct {
ID int // 學(xué)生id
Name string // 姓名
Age int8 // 年齡
Score float32 // 分?jǐn)?shù)
}
我們會(huì)發(fā)現(xiàn)學(xué)生結(jié)構(gòu)體和人結(jié)構(gòu)體相比只多了兩個(gè)字段(ID
和Score
)分別定義有點(diǎn)浪費(fèi)扳剿?
另外人和學(xué)生有許多相似的地方,某些時(shí)候Person結(jié)構(gòu)體中的方法,Student同樣也需要旁趟,如果分別寫(xiě)兩份相同的方法,也很浪費(fèi)庇绽?
好啦锡搜!在go中可以通過(guò)嵌套
解決,直接看代碼
package main
import "fmt"
type Person struct {
Name string // 姓名
Age int8 // 年齡
}
// person結(jié)構(gòu)體方法
func (p Person) GetName() {
fmt.Printf("My name: %s\n", p.Name)
}
type Student struct {
ID int // 學(xué)生id
Score float32 // 分?jǐn)?shù)
Person // 嵌套Person結(jié)構(gòu)體 這里是匿名字段
}
func (s Student) GetScore() {
fmt.Printf("My score: %v\n", s.Score)
}
func main() {
p := Student{
ID: 1,
Score: 98,
Person: Person{ // 這里是匿名字段 字段名 == 字段類型
Name: "zhangsan",
Age: 18,
},
}
// 調(diào)用嵌套結(jié)構(gòu)體字段
fmt.Printf("My age: %d\n", p.Age) // 直接調(diào)用 嵌套結(jié)構(gòu)體字段
fmt.Printf("My age p.Person.age: %d\n", p.Person.Age) // 通過(guò)匿名字段間接調(diào)用
p.GetScore() // 調(diào)用自己方法
p.GetName() // 直接調(diào)用嵌套結(jié)構(gòu)體字段
p.Person.GetName() // 通過(guò)匿名字段間接調(diào)用
}
// My age: 18
// My age p.Person.age: 18
// My score: 98
// My name: zhangsan
// My name: zhangsan
上面的注釋已經(jīng)非常詳細(xì),這里總結(jié)下規(guī)律:
匿名結(jié)構(gòu)體嵌套瞧掺,會(huì)有如下效果:
- 匿名結(jié)構(gòu)體中字段,當(dāng)前結(jié)構(gòu)體可以直接調(diào)用
- 匿名結(jié)構(gòu)體方法,當(dāng)前結(jié)構(gòu)體可以直接調(diào)用
本質(zhì)是:go在字段查找時(shí),現(xiàn)在本結(jié)構(gòu)體中找耕餐,如果找不到則到匿名結(jié)構(gòu)體中查找;方法同理
2.3 匿名結(jié)構(gòu)體嵌套經(jīng)典使用
數(shù)據(jù)庫(kù)表設(shè)計(jì)中:
我們可以把常用的字段抽出來(lái)成一個(gè)結(jié)構(gòu)體辟狈,其它結(jié)構(gòu)體只需要引入就可以擴(kuò)展其中字段以及方法肠缔,比如:
package main
import (
"fmt"
"time"
)
type BaseTable struct {
ID int
CreatedAt time.Time
UpdatedAt time.Time
}
type User struct {
Name string
BaseTable // 擴(kuò)展User
}
3. 方法值和方法表達(dá)式
方法值和方法表達(dá)式類似于函數(shù)表達(dá)式,我們可以將函數(shù)表達(dá)式當(dāng)作變量傳遞,方法值和方法表達(dá)式也是一樣,文字上不太容易明白,直接看代碼
package main
import (
"fmt"
)
type Person struct {
Name string
Age int8
}
func (p Person) GetName() {
fmt.Printf("My name: %s\n", p.Name)
}
func main() {
p := Person{Name: "zhangsan", Age: 18}
// 方法值
getName := p.GetName
getName() // 調(diào)用方法值
fmt.Println("--------分割線-------")
// 方法表達(dá)方式
pGetName := Person.GetName
pGetName(p) // 方法表達(dá)式需要傳遞接收者
}
// My name: zhangsan
// --------分割線-------
// My name: zhangsan
它可以做為變量取出,因此可以實(shí)現(xiàn)復(fù)雜精巧場(chǎng)景下的使用哼转,舉例這里不做舉例,方法值和方法表達(dá)式的區(qū)別在于:
方法表達(dá)式需要把接收者做為參數(shù)傳入