- Go語言並沒有沿襲傳統面向對象編程中的諸多概念
- 封裝:通過方法實現
- 繼承:通過匿名字段實現
- 多態:通過接口實現
一、匿名組合
1. 匿名字段
- 匿名字段(嵌入字段):
- go支持只提供類型,而不寫字段名的方式
- 匿名字段也是結構體時:
- 這個結構體所擁有的全部字段都被隱式地引入了當前定義的這個結構體
//人
type Person struct {
name string
sex byte
age int
}
//學生
type Student struct {
Person // 匿名字段,那麼默認Student就包含了Person的所有字段
id int
addr string
}
2. 初始化
//人
type Person struct {
name string
sex byte
age int
}
//學生
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)
}
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}
4. 同名字段
//人
type Person struct {
name string
sex byte
age int
}
//學生
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)
//默認只會給最外層的成員賦值
//給匿名同名成員賦值,需要顯示調用
s.Person.name = "yoyo"
//Person:{name:yoyo sex:0 age:0} id:0 addr: name:mike}
fmt.Printf("%+v\n", s)
}
5. 其它匿名字段
1. 非結構體類型
- 所有的內置類型和自定義類型都是可以作爲匿名字段的
type mystr string //自定義類型
type Person struct {
name string
sex byte
age int
}
type Student struct {
Person // 匿名字段,結構體類型
int // 匿名字段,內置類型
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)
//成員的操作,打印結果:mike, m, 18, 1, bj
fmt.Printf("%s, %c, %d, %d, %s\n", s1.name, s1.sex, s1.age, s1.int, s1.mystr)
}
2. 結構體類型指針
type Person struct { //人
name string
sex byte
age int
}
type Student struct { //學生
*Person // 匿名字段,結構體指針類型
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)
}
二、方法
-
帶有接收者的函數,我們稱爲方法(method)
-
本質上,一個方法則是一個和特殊類型關聯的函數
-
⽅法總是綁定對象實例,並隱式將實例作爲第⼀實參 (receiver),方法的語法如下:
func (receiver ReceiverType) funcName(parameters) (results)
- 參數 receiver 可任意命名。如⽅法中未曾使⽤,可省略參數名。
- 參數 receiver 類型可以是 T 或 *T。基類型 T 不能是接⼝或指針。
- 不支持重載方法,也就是說,不能定義名字相同但是不同參數的方法。
-
在函數定義時,在其名字之前放上一個變量,即是一個方法
1. 爲類型添加方法
基礎類型作爲接受者
type MyInt int //自定義類型,給int改名爲MyInt
//在函數定義時,在其名字之前放上一個變量,即是一個方法
func (a MyInt) Add(b MyInt) MyInt { //面向對象
return a + b
}
//傳統方式的定義
func Add(a, b MyInt) MyInt { //面向過程
return a + b
}
func main() {
var a MyInt = 1
var b MyInt = 1
//調用func (a MyInt) Add(b MyInt)
fmt.Println("a.Add(b) = ", a.Add(b)) //a.Add(b) = 2
//調用func Add(a, b MyInt)
fmt.Println("Add(a, b) = ", Add(a, b)) //Add(a, b) = 2
結構體作爲接收者
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() //調用func (p Person) PrintInfo()
值語義和引用語義
方法集
- 類型 *T 方法集
- 一個指向自定義類型的值的指針,它的方法集由該類型定義的所有方法組成,無論這些方法接受的是一個值還是一個指針。
- 如果在指針上調用一個接受值的方法,Go語言會聰明地將該指針解引用,並將指針所指的底層值作爲方法的接收者
- 類型 *T ⽅法集包含全部 receiver T + *T ⽅法
- 類型 T 方法集
- 一個自定義類型值的方法集則由爲該類型定義的接收者類型爲值類型的方法組成,但是不包含那些接收者類型爲指針的方法。
- 但這種限制通常並不像這裏所說的那樣,因爲如果我們只有一個值,仍然可以調用一個接收者爲指針類型的方法,這可以藉助於Go語言傳值的地址能力實現
匿名字段
三、接口
1. 概述
- 在Go語言中,接口(interface)是一個自定義類型,接口類型具體描述了一系列方法的集合
2. 接口的使用
1. 定義
- 接⼝命名習慣以 er 結尾
- 接口只有方法聲明,沒有實現,沒有數據字段
- 接口可以匿名嵌入其它接口,或嵌入到結構中
type Humaner interface { SayHi() }
2. 接口實現
- 接口是用來定義行爲的類型。
- 被定義的行爲不由接口直接實現,而是通過方法由用戶定義的類型實現
- 一個實現了這些方法的具體類型是這個接口類型的實例。
3. 接口組合
- 如果一個interface1作爲interface2的一個嵌入字段,那麼interface2隱式的包含了interface1裏面的方法
- 接口轉換:
- 超集接⼝對象可轉換爲⼦集接⼝,反之出錯
4. 空接口:
- 空接口(interface{})不包含任何的方法
- 空接口可以存儲任意類型的數值
- 類似於C語言的void *類型
5. 類型查詢
怎麼反向知道這個變量裏面實際保存了的是哪個類型的對象:comma-ok斷言、switch測試
1. comma-ok斷言
value, ok = element.(T)
- value就是變量的值,ok是一個bool類型,element是interface變量,T是斷言的類型
2. switch測試
該筆記是本人通過學習視頻,做的學習筆記~