8.1 概述
對于面向?qū)ο缶幊痰闹С諫o 語言設(shè)計得非常簡潔而優(yōu)雅隅茎。因為, Go語言并沒有沿襲傳統(tǒng)面向?qū)ο缶幊讨械闹T多概念嫉沽,比如繼承(不支持繼承辟犀,盡管匿名字段的內(nèi)存布局和行為類似繼承,但它并不是繼承)绸硕、虛函數(shù)堂竟、構(gòu)造函數(shù)和析構(gòu)函數(shù)、隱藏的this指針等玻佩。
盡管Go語言中沒有封裝出嘹、繼承、多態(tài)這些概念咬崔,但同樣通過別的方式實現(xiàn)這些特性:
封裝:通過方法實現(xiàn)
繼承:通過匿名字段實現(xiàn)
多態(tài):通過接口實現(xiàn)
8.2 匿名組合
8.2.1 匿名字段
一般情況下税稼,定義結(jié)構(gòu)體的時候是字段名與其類型一一對應(yīng),實際上Go支持只提供類型垮斯,而不寫字段名的方式郎仆,也就是匿名字段,也稱為嵌入字段兜蠕。
當匿名字段也是一個結(jié)構(gòu)體的時候扰肌,那么這個結(jié)構(gòu)體所擁有的全部字段都被隱式地引入了當前定義的這個結(jié)構(gòu)體。
//人
type Person struct {
name string
sex byte
age int
}
//學(xué)生
type Student struct {
Person // 匿名字段牺氨,那么默認Student就包含了Person的所有字段
id int
addr string
}
8.2.2 初始化
//人
type Person struct {
name string
sex byte
age int
}
//學(xué)生
type Student struct {
Person // 匿名字段狡耻,那么默認Student就包含了Person的所有字段
id int
addr string
}
func main() {
//順序初始化
s1 := Student{Person{"mike", 'm', 18}, 1, "sz"}
//s1 = {Person:{name:mike sex:109 age:18} id:1 addr:sz}
fmt.Printf("s1 = %+v\n", s1)
//s2 := Student{"mike", 'm', 18, 1, "sz"} //err
//部分成員初始化1
s3 := Student{Person: Person{"lily", 'f', 19}, id: 2}
//s3 = {Person:{name:lily sex:102 age:19} id:2 addr:}
fmt.Printf("s3 = %+v\n", s3)
//部分成員初始化2
s4 := Student{Person: Person{name: "tom"}, id: 3}
//s4 = {Person:{name:tom sex:0 age:0} id:3 addr:}
fmt.Printf("s4 = %+v\n", s4)
}
8.2.3 成員的操作
var s1 Student //變量聲明
//給成員賦值
s1.name = "mike" //等價于 s1.Person.name = "mike"
s1.sex = 'm'
s1.age = 18
s1.id = 1
s1.addr = "sz"
fmt.Println(s1) //{{mike 109 18} 1 sz}
var s2 Student //變量聲明
s2.Person = Person{"lily", 'f', 19}
s2.id = 2
s2.addr = "bj"
fmt.Println(s2) //{{lily 102 19} 2 bj}
8.2.4 同名字段
//人
type Person struct {
name string
sex byte
age int
}
//學(xué)生
type Student struct {
Person // 匿名字段,那么默認Student就包含了Person的所有字段
id int
addr string
name string //和Person中的name同名
}
func main() {
var s Student //變量聲明
//給Student的name猴凹,還是給Person賦值夷狰?
s.name = "mike"
//{Person:{name: sex:0 age:0} id:0 addr: name:mike}
fmt.Printf("%+v\n", s)
//默認只會給最外層的成員賦值
//給匿名同名成員賦值,需要顯示調(diào)用
s.Person.name = "yoyo"
//Person:{name:yoyo sex:0 age:0} id:0 addr: name:mike}
fmt.Printf("%+v\n", s)
}
8.2.5 其它匿名字段
8.2.5.1 非結(jié)構(gòu)體類型
所有的內(nèi)置類型和自定義類型都是可以作為匿名字段的:
type mystr string //自定義類型
type Person struct {
name string
sex byte
age int
}
type Student struct {
Person // 匿名字段郊霎,結(jié)構(gòu)體類型
int // 匿名字段沼头,內(nèi)置類型
mystr // 匿名字段,自定義類型
}
func main() {
//初始化
s1 := Student{Person{"mike", 'm', 18}, 1, "bj"}
//{Person:{name:mike sex:109 age:18} int:1 mystr:bj}
fmt.Printf("%+v\n", s1)
//成員的操作,打印結(jié)果:mike, m, 18, 1, bj
fmt.Printf("%s, %c, %d, %d, %s\n", s1.name, s1.sex, s1.age, s1.int, s1.mystr)
}
8.2.5.2 結(jié)構(gòu)體指針類型
type Person struct { //人
name string
sex byte
age int
}
type Student struct { //學(xué)生
*Person // 匿名字段进倍,結(jié)構(gòu)體指針類型
id int
addr string
}
func main() {
//初始化
s1 := Student{&Person{"mike", 'm', 18}, 1, "bj"}
//{Person:0xc0420023e0 id:1 addr:bj}
fmt.Printf("%+v\n", s1)
//mike, m, 18
fmt.Printf("%s, %c, %d\n", s1.name, s1.sex, s1.age)
//聲明變量
var s2 Student
s2.Person = new(Person) //分配空間
s2.name = "yoyo"
s2.sex = 'f'
s2.age = 20
s2.id = 2
s2.addr = "sz"
//yoyo 102 20 2 20
fmt.Println(s2.name, s2.sex, s2.age, s2.id, s2.age)
}
8.3 方法
8.3.1 概述
在面向?qū)ο缶幊讨型林粒粋€對象其實也就是一個簡單的值或者一個變量,在這個對象中會包含一些函數(shù)猾昆,這種帶有接收者的函數(shù)陶因,我們稱為方法(method)。 本質(zhì)上垂蜗,一個方法則是一個和特殊類型關(guān)聯(lián)的函數(shù)楷扬。
一個面向?qū)ο蟮某绦驎梅椒▉肀磉_其屬性和對應(yīng)的操作,這樣使用這個對象的用戶就不需要直接去操作對象贴见,而是借助方法來做這些事情烘苹。
在Go語言中,可以給任意自定義類型(包括內(nèi)置類型片部,但不包括指針類型)添加相應(yīng)的方法镣衡。
?法總是綁定對象實例,并隱式將實例作為第?實參 (receiver)档悠,方法的語法如下:
func (receiver ReceiverType) funcName(parameters) (results)
參數(shù) receiver 可任意命名廊鸥。如?法中未曾使?,可省略參數(shù)名站粟。
參數(shù) receiver 類型可以是 T 或 *T黍图。基類型 T 不能是接?或指針奴烙。
不支持重載方法,也就是說剖张,不能定義名字相同但是不同參數(shù)的方法切诀。
8.3.2 為類型添加方法
8.3.2.1 基礎(chǔ)類型作為接收者
type MyInt int //自定義類型,給int改名為MyInt
//在函數(shù)定義時搔弄,在其名字之前放上一個變量幅虑,即是一個方法
func (a MyInt) Add(b MyInt) MyInt { //面向?qū)ο? return a + b
}
//傳統(tǒng)方式的定義
func Add(a, b MyInt) MyInt { //面向過程
return a + b
}
func main() {
var a MyInt = 1
var b MyInt = 1
//調(diào)用func (a MyInt) Add(b MyInt)
fmt.Println("a.Add(b) = ", a.Add(b)) //a.Add(b) = 2
//調(diào)用func Add(a, b MyInt)
fmt.Println("Add(a, b) = ", Add(a, b)) //Add(a, b) = 2
}
通過上面的例子可以看出,面向?qū)ο笾皇菗Q了一種語法形式來表達顾犹。方法是函數(shù)的語法糖倒庵,因為receiver其實就是方法所接收的第1個參數(shù)。
注意:雖然方法的名字一模一樣炫刷,但是如果接收者不一樣擎宝,那么方法就不一樣。
8.3.2.2 結(jié)構(gòu)體作為接收者
方法里面可以訪問接收者的字段浑玛,調(diào)用方法通過點( . )訪問绍申,就像struct里面訪問字段一樣:
type Person struct {
name string
sex byte
age int
}
func (p Person) PrintInfo() { //給Person添加方法
fmt.Println(p.name, p.sex, p.age)
}
func main() {
p := Person{"mike", 'm', 18} //初始化
p.PrintInfo() //調(diào)用func (p Person) PrintInfo()
}
8.3.3 值語義和引用語義
type Person struct {
name string
sex byte
age int
}
//指針作為接收者,引用語義
func (p *Person) SetInfoPointer() {
//給成員賦值
(*p).name = "yoyo"
p.sex = 'f'
p.age = 22
}
//值作為接收者,值語義
func (p Person) SetInfoValue() {
//給成員賦值
p.name = "yoyo"
p.sex = 'f'
p.age = 22
}
func main() {
//指針作為接收者极阅,引用語義
p1 := Person{"mike", 'm', 18} //初始化
fmt.Println("函數(shù)調(diào)用前 = ", p1) //函數(shù)調(diào)用前 = {mike 109 18}
(&p1).SetInfoPointer()
fmt.Println("函數(shù)調(diào)用后 = ", p1) //函數(shù)調(diào)用后 = {yoyo 102 22}
fmt.Println("==========================")
p2 := Person{"mike", 'm', 18} //初始化
//值作為接收者胃碾,值語義
fmt.Println("函數(shù)調(diào)用前 = ", p2) //函數(shù)調(diào)用前 = {mike 109 18}
p2.SetInfoValue()
fmt.Println("函數(shù)調(diào)用后 = ", p2) //函數(shù)調(diào)用后 = {mike 109 18}
}
8.3.4 方法集
類型的方法集是指可以被該類型的值調(diào)用的所有方法的集合。
用實例實例 value 和 pointer 調(diào)用方法(含匿名字段)不受?法集約束筋搏,編譯器編總是查找全部方法仆百,并自動轉(zhuǎn)換 receiver 實參。
8.3.4.1 類型 *T 方法集
一個指向自定義類型的值的指針奔脐,它的方法集由該類型定義的所有方法組成俄周,無論這些方法接受的是一個值還是一個指針。
如果在指針上調(diào)用一個接受值的方法帖族,Go語言會聰明地將該指針解引用栈源,并將指針所指的底層值作為方法的接收者。
類型 *T ?法集包含全部 receiver T + *T ?法:
type Person struct {
name string
sex byte
age int
}
//指針作為接收者竖般,引用語義
func (p *Person) SetInfoPointer() {
(*p).name = "yoyo"
p.sex = 'f'
p.age = 22
}
//值作為接收者甚垦,值語義
func (p Person) SetInfoValue() {
p.name = "xxx"
p.sex = 'm'
p.age = 33
}
func main() {
//p 為指針類型
var p *Person = &Person{"mike", 'm', 18}
p.SetInfoPointer() //func (p) SetInfoPointer()
p.SetInfoValue() //func (*p) SetInfoValue()
(*p).SetInfoValue() //func (*p) SetInfoValue()
}
8.3.4.2 類型 T 方法集
一個自定義類型值的方法集則由為該類型定義的接收者類型為值類型的方法組成,但是不包含那些接收者類型為指針的方法涣雕。
但這種限制通常并不像這里所說的那樣艰亮,因為如果我們只有一個值,仍然可以調(diào)用一個接收者為指針類型的方法挣郭,這可以借助于Go語言傳值的地址能力實現(xiàn)迄埃。
type Person struct {
name string
sex byte
age int
}
//指針作為接收者,引用語義
func (p *Person) SetInfoPointer() {
(*p).name = "yoyo"
p.sex = 'f'
p.age = 22
}
//值作為接收者兑障,值語義
func (p Person) SetInfoValue() {
p.name = "xxx"
p.sex = 'm'
p.age = 33
}
func main() {
//p 為普通值類型
var p Person = Person{"mike", 'm', 18}
(&p).SetInfoPointer() //func (&p) SetInfoPointer()
p.SetInfoPointer() //func (&p) SetInfoPointer()
p.SetInfoValue() //func (p) SetInfoValue()
(&p).SetInfoValue() //func (*&p) SetInfoValue()
}
8.3.5 匿名字段
8.3.5.1 方法的繼承
如果匿名字段實現(xiàn)了一個方法侄非,那么包含這個匿名字段的struct也能調(diào)用該方法。
type Person struct {
name string
sex byte
age int
}
//Person定義了方法
func (p *Person) PrintInfo() {
fmt.Printf("%s,%c,%d\n", p.name, p.sex, p.age)
}
type Student struct {
Person // 匿名字段流译,那么Student包含了Person的所有字段
id int
addr string
}
func main() {
p := Person{"mike", 'm', 18}
p.PrintInfo()
s := Student{Person{"yoyo", 'f', 20}, 2, "sz"}
s.PrintInfo()
}
8.3.5.2 方法的重寫
type Person struct {
name string
sex byte
age int
}
//Person定義了方法
func (p *Person) PrintInfo() {
fmt.Printf("Person: %s,%c,%d\n", p.name, p.sex, p.age)
}
type Student struct {
Person // 匿名字段逞怨,那么Student包含了Person的所有字段
id int
addr string
}
//Student定義了方法
func (s *Student) PrintInfo() {
fmt.Printf("Student:%s,%c,%d\n", s.name, s.sex, s.age)
}
func main() {
p := Person{"mike", 'm', 18}
p.PrintInfo() //Person: mike,m,18
s := Student{Person{"yoyo", 'f', 20}, 2, "sz"}
s.PrintInfo() //Student:yoyo,f,20
s.Person.PrintInfo() //Person: yoyo,f,20
}
8.3.6 表達式
類似于我們可以對函數(shù)進行賦值和傳遞一樣,方法也可以進行賦值和傳遞福澡。
根據(jù)調(diào)用者不同叠赦,方法分為兩種表現(xiàn)形式:方法值和方法表達式。兩者都可像普通函數(shù)那樣賦值和傳參革砸,區(qū)別在于方法值綁定實例除秀,?方法表達式則須顯式傳參。
8.3.6.1 方法值
type Person struct {
name string
sex byte
age int
}
func (p *Person) PrintInfoPointer() {
fmt.Printf("%p, %v\n", p, p)
}
func (p Person) PrintInfoValue() {
fmt.Printf("%p, %v\n", &p, p)
}
func main() {
p := Person{"mike", 'm', 18}
p.PrintInfoPointer() //0xc0420023e0, &{mike 109 18}
pFunc1 := p.PrintInfoPointer //方法值算利,隱式傳遞 receiver
pFunc1() //0xc0420023e0, &{mike 109 18}
pFunc2 := p.PrintInfoValue
pFunc2() //0xc042048420, {mike 109 18}
}
8.3.6.2 方法表達式
type Person struct {
name string
sex byte
age int
}
func (p *Person) PrintInfoPointer() {
fmt.Printf("%p, %v\n", p, p)
}
func (p Person) PrintInfoValue() {
fmt.Printf("%p, %v\n", &p, p)
}
func main() {
p := Person{"mike", 'm', 18}
p.PrintInfoPointer() //0xc0420023e0, &{mike 109 18}
//方法表達式册踩, 須顯式傳參
//func pFunc1(p *Person))
pFunc1 := (*Person).PrintInfoPointer
pFunc1(&p) //0xc0420023e0, &{mike 109 18}
pFunc2 := Person.PrintInfoValue
pFunc2(p) //0xc042002460, {mike 109 18}
}
8.4 接口
8.4.1 概述
在Go語言中,接口(interface)是一個自定義類型笔时,接口類型具體描述了一系列方法的集合棍好。
接口類型是一種抽象的類型,它不會暴露出它所代表的對象的內(nèi)部值的結(jié)構(gòu)和這個對象支持的基礎(chǔ)操作的集合,它們只會展示出它們自己的方法借笙。因此接口類型不能將其實例化扒怖。
Go通過接口實現(xiàn)了鴨子類型(duck-typing):“當看到一只鳥走起來像鴨子、游泳起來像鴨子业稼、叫起來也像鴨子盗痒,那么這只鳥就可以被稱為鴨子”。我們并不關(guān)心對象是什么類型低散,到底是不是鴨子俯邓,只關(guān)心行為。
8.4.2 接口的使用
8.4.2.1 接口定義
type Humaner interface {
SayHi()
}
接?命名習(xí)慣以 er 結(jié)尾
接口只有方法聲明熔号,沒有實現(xiàn)稽鞭,沒有數(shù)據(jù)字段
接口可以匿名嵌入其它接口,或嵌入到結(jié)構(gòu)中
8.4.2.2 接口實現(xiàn)
接口是用來定義行為的類型引镊。這些被定義的行為不由接口直接實現(xiàn)朦蕴,而是通過方法由用戶定義的類型實現(xiàn),一個實現(xiàn)了這些方法的具體類型是這個接口類型的實例弟头。
如果用戶定義的類型實現(xiàn)了某個接口類型聲明的一組方法吩抓,那么這個用戶定義的類型的值就可以賦給這個接口類型的值。這個賦值會把用戶定義的類型的值存入接口類型的值赴恨。
type Humaner interface {
SayHi()
}
type Student struct { //學(xué)生
name string
score float64
}
//Student實現(xiàn)SayHi()方法
func (s *Student) SayHi() {
fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}
type Teacher struct { //老師
name string
group string
}
//Teacher實現(xiàn)SayHi()方法
func (t *Teacher) SayHi() {
fmt.Printf("Teacher[%s, %s] say hi!!\n", t.name, t.group)
}
type MyStr string
//MyStr實現(xiàn)SayHi()方法
func (str MyStr) SayHi() {
fmt.Printf("MyStr[%s] say hi!!\n", str)
}
//普通函數(shù)疹娶,參數(shù)為Humaner類型的變量i
func WhoSayHi(i Humaner) {
i.SayHi()
}
func main() {
s := &Student{"mike", 88.88}
t := &Teacher{"yoyo", "Go語言"}
var tmp MyStr = "測試"
s.SayHi() //Student[mike, 88.880000] say hi!!
t.SayHi() //Teacher[yoyo, Go語言] say hi!!
tmp.SayHi() //MyStr[測試] say hi!!
//多態(tài),調(diào)用同一接口伦连,不同表現(xiàn)
WhoSayHi(s) //Student[mike, 88.880000] say hi!!
WhoSayHi(t) //Teacher[yoyo, Go語言] say hi!!
WhoSayHi(tmp) //MyStr[測試] say hi!!
x := make([]Humaner, 3)
//這三個都是不同類型的元素雨饺,但是他們實現(xiàn)了interface同一個接口
x[0], x[1], x[2] = s, t, tmp
for _, value := range x {
value.SayHi()
}
/*
Student[mike, 88.880000] say hi!!
Teacher[yoyo, Go語言] say hi!!
MyStr[測試] say hi!!
*/
}
通過上面的代碼,你會發(fā)現(xiàn)接口就是一組抽象方法的集合惑淳,它必須由其他非接口類型實現(xiàn)沛膳,而不能自我實現(xiàn)。
8.4.3 接口組合
8.4.3.1 接口嵌入
如果一個interface1作為interface2的一個嵌入字段汛聚,那么interface2隱式的包含了interface1里面的方法。
type Humaner interface {
SayHi()
}
type Personer interface {
Humaner //這里想寫了SayHi()一樣
Sing(lyrics string)
}
type Student struct { //學(xué)生
name string
score float64
}
//Student實現(xiàn)SayHi()方法
func (s *Student) SayHi() {
fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}
//Student實現(xiàn)Sing()方法
func (s *Student) Sing(lyrics string) {
fmt.Printf("Student sing[%s]!!\n", lyrics)
}
func main() {
s := &Student{"mike", 88.88}
var i2 Personer
i2 = s
i2.SayHi() //Student[mike, 88.880000] say hi!!
i2.Sing("學(xué)生哥") //Student sing[學(xué)生哥]!!
}
8.4.3.2 接口轉(zhuǎn)換
超集接?對象可轉(zhuǎn)換為?集接?短荐,反之出錯:
type Humaner interface {
SayHi()
}
type Personer interface {
Humaner //這里像寫了SayHi()一樣
Sing(lyrics string)
}
type Student struct { //學(xué)生
name string
score float64
}
//Student實現(xiàn)SayHi()方法
func (s *Student) SayHi() {
fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}
//Student實現(xiàn)Sing()方法
func (s *Student) Sing(lyrics string) {
fmt.Printf("Student sing[%s]!!\n", lyrics)
}
func main() {
//var i1 Humaner = &Student{"mike", 88.88}
//var i2 Personer = i1 //err
//Personer為超集倚舀,Humaner為子集
var i1 Personer = &Student{"mike", 88.88}
var i2 Humaner = i1
i2.SayHi() //Student[mike, 88.880000] say hi!!
}
8.4.4 空接口
空接口(interface{})不包含任何的方法,正因為如此忍宋,所有的類型都實現(xiàn)了空接口痕貌,因此空接口可以存儲任意類型的數(shù)值。它有點類似于C語言的void *類型糠排。
var v1 interface{} = 1 // 將int類型賦值給interface{}
var v2 interface{} = "abc" // 將string類型賦值給interface{}
var v3 interface{} = &v2 // 將*interface{}類型賦值給interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}
當函數(shù)可以接受任意的對象實例時舵稠,我們會將其聲明為interface{},最典型的例子是標準庫fmt中PrintXXX系列的函數(shù),例如:
func Printf(fmt string, args ...interface{})
func Println(args ...interface{})
8.4.5 類型查詢
我們知道interface的變量里面可以存儲任意類型的數(shù)值(該類型實現(xiàn)了interface)哺徊。那么我們怎么反向知道這個變量里面實際保存了的是哪個類型的對象呢室琢?目前常用的有兩種方法:
comma-ok斷言
switch測試
8.4.5.1 comma-ok斷言
Go語言里面有一個語法,可以直接判斷是否是該類型的變量: value, ok = element.(T)落追,這里value就是變量的值盈滴,ok是一個bool類型,element是interface變量轿钠,T是斷言的類型巢钓。
如果element里面確實存儲了T類型的數(shù)值,那么ok返回true疗垛,否則返回false症汹。
示例代碼:
type Element interface{}
type Person struct {
name string
age int
}
func main() {
list := make([]Element, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"mike", 18}
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is [%s, %d]\n", index, value.name, value.age)
} else {
fmt.Printf("list[%d] is of a different type\n", index)
}
}
/* 打印結(jié)果:
list[0] is an int and its value is 1
list[1] is a string and its value is Hello
list[2] is a Person and its value is [mike, 18]
*/
}
8.4.5.2 switch測試
type Element interface{}
type Person struct {
name string
age int
}
func main() {
list := make([]Element, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"mike", 18}
for index, element := range list {
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is [%s, %d]\n", index, value.name, value.age)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}