> Golang 中的方法是作用在指定的數據類型上的(即:和指定的數據類型綁定),因此自定義類型,都可以有方法,`而不僅僅是 struct`
[toc]
# 1.方法的聲明
Go語言中的方法(Method)是一種作用于特定類型變量的函數。這種特定類型變量叫做接收器(Receiver)。
如果將特定類型理解為結構體或“類”時,接收器的概念就類似于其他語言中的this或者self。
在Go語言中,接收器的類型可以是任何類型,不僅僅是結構體,任何類型都可以擁有方法。
## 1.1 聲明格式
```
func (接收器變量 接收器類型) 方法名(參數列表) (返回參數) {
函數體
}
```
- 接收器變量:接收器中的參數變量名在命名時,官方建議使用接收器類型名的第一個小寫字母,而不是self、this之類的命名。例如,Socket類型的接收器變量應該命名為s,Connector類型的接收器變量應該命名為c等。
- 接收器類型:接收器類型和參數類似,可以是指針類型和非指針類型。
- 方法名、參數列表、返回參數:格式與函數定義一致。
## 1.2 聲明示例
```
type people struct {
Name, Like , Sex string
Height, Weight float32
}
func (p people) walk() {
fmt.Println(p.Name + " 在行走...")
}
func RunStruct() {
var xiaomi people
xiaomi.Name = "小明"
xiaomi.Name = "小明"
p := people{"小明","跑步", "男", 170.0, 62.6}
p.walk()
}
func main() {
RunStruct()
//out: 小明 在行走...
}
```
# 2.接收器分類
## 2.1 指針類型的接收器
理解指針類型的接收器
指針類型的接收器由一個結構體的指針組成,由于指針的特性,調用方法時,修改接收器指針的任意成員變量,在方法結束后,修改都是有效的。
在下面的例子,使用結構體定義一個屬性(Property),為屬性添加SetValue()方法以封裝設置屬性的過程,通過屬性的Value()方法可以重新獲得屬性的數值。使用屬性時,通過SetValue()方法的調用,可以達成修改屬性值的效果。
```
01 package main
02
03 import "fmt"
04
05 // 定義屬性結構
06 type Property struct {
07 value int // 屬性值
08 }
09
10 // 設置屬性值
11 func (p *Property) SetValue(v int) {
12
13 // 修改p的成員變量
14 p.value = v
15 }
16
17 // 取屬性值
18 func (p *Property) GetValue() int {
19 return p.value
20 }
21
22 func main() {
23
24 // 實例化屬性
25 p := new(Property)
26
27 // 設置值
28 p.SetValue(100)
29
30 // 打印值
31 fmt.Println(p.GetValue())
32 // out: 100
33 }
```
代碼說明如下:
- 第6行,定義一個屬性結構,擁有一個整型的成員變量。
- 第11行,定義屬性值的方法。
- 第14行,設置屬性值方法的接收器類型為指針。因此可以修改成員值,即便退出方法,也有效。
- 第18行,定義獲取值的方法。
- 第25行,實例化屬性結構。
- 第28行,設置值。此時成員變量變為100。
- 第31行,獲取成員變量。
## 2.2 非指針類型的接收器
當方法作用于非指針接收器時,Go語言會在代碼運行時將接收器的值復制一份。在非指針接收器的方法中可以獲取接收器的成員值,但修改后無效。
點(Point)使用結構體描述時,為點添加Add()方法,這個方法不能修改Point的成員X、Y變量,而是在計算后返回新的Point對象。Point屬于小內存對象,在函數返回值的復制過程中可以極大地提高代碼運行效率,詳細過程請參考下面的代碼。
```
01 package main
02
03 import (
04 "fmt"
05 )
06
07 // 定義點結構
08 type Point struct {
09 X int
10 Y int
11 }
12
13 // 非指針接收器的加方法
14 func (p Point) Add(other Point) Point {
15
16 // 成員值與參數相加后返回新的結構
17 return Point{p.X + other.X, p.Y + other.Y}
18 }
19
20 func main() {
21
22 // 初始化點
23 p1 := Point{1, 1}
24 p2 := Point{2, 2}
25
26 // 與另外一個點相加
27 result := p1.Add(p2)
28
29 // 輸出結果
30 fmt.Println(result)
31 // out: {3 3}
32 }
```
代碼說明如下:
- 第8行,定義一個點結構,擁有X和Y兩個整型分量。
- 第14行,為Point結構定義一個Add()方法。
- 第23和24行,初始化兩個點p1和p2。
- 第27行,將p1和p2相加后返回結果。
- 第30行,打印結果。
`由于例子中使用了非指針接收器,Add()方法變得類似于只讀的方法,Add()方法內部不會對成員進行任何修改。`
## 2.3 指針和非指針接收器的使用
- 小對象由于值復制時的速度較快,適合使用非指針接收器。
- 大對象因為復制性能較低,適合使用指針接收器,在接收器和參數間傳遞時不進行復制,只是傳遞指針。
# 3. 嵌入結構體擴展類型
結構體允許其成員字段在聲明時沒有字段名而只有類型,這種形式的字段被稱為類型內嵌或匿名字段
類型內嵌的寫法如下:
```
01 type Data struct {
02 int
03 float32
04 bool
05 }
06
07 ins := &Data{
08 int: 10,
09 float32: 3.14,
10 bool: true,
11 }
```
代碼說明如下:
- 第2~4行定義結構體中的匿名字段,類型分別是整型、浮點、布爾。
- 第8~10行將實例化的Data中的字段賦初值。
類型內嵌其實仍然擁有自己的字段名,只是字段名就是其類型本身而已,`結構體要求字段名稱必須唯一,因此一個結構體中同種類型的匿名字段只能有一個`。結構體實例化后,如果匿名的字段類型為結構體,那么可以直接訪問匿名結構體里的所有成員,這種方式被稱為結構體內嵌。
## 3.1 結構內嵌特性
Go語言的結構體內嵌有如下特性。
### 3.1.1 內嵌的結構體可以直接訪問其成員變量
嵌入結構體的成員,可以通過外部結構體的實例直接訪問。如果結構體有多層嵌入結構體,結構體實例訪問任意一級的嵌入結構體成員時都只用給出字段名,而無須像傳統結構體字段一樣,通過一層層的結構體字段訪問到最終的字段。例如,ins.a.b.c的訪問可以簡化為ins.c。
### 3.1.2 內嵌結構體的字段名是它的類型名
內嵌結構體字段仍然可以使用詳細的字段進行一層層訪問,內嵌結構體的字段名就是它的類型名,
代碼如下:
```
...
type people struct {
Name, Sex string
Height, Weight float32
}
type boy struct {
people
Like string
}
func RunStruct() {
var b1 boy
b1.Height = 175
//詳細的字段進行一層層訪問
fmt.Println(b1.people.Height)
}
```
一個結構體只能嵌入一個同類型的成員,無須擔心結構體重名和錯誤賦值的情況,編譯器在發現可能的賦值歧義時會報錯。
## 3.2 使用組合描述對象特性
Go語言的結構體內嵌特性就是一種組合特性,使用組合特性可以快速構建對象的不同特性。
下面的代碼使用Go語言的結構體內嵌實現對象特性組合,請參考代碼6-10。
代碼6-10 人和鳥的特性(具體文件:.../chapter06/humanbird/humanbird.go)
```
01 package main
02
03 import "fmt"
04
05 // 可飛行的
06 type Flying struct{}
07
08 func (f *Flying) Fly() {
09 fmt.Println("can fly")
10 }
11
12 // 可行走的
13 type Walkable struct{}
14
15 func (f *Walkable) Walk() {
16 fmt.Println("can calk")
17 }
18
19 // 人類
20 type Human struct {
21 Walkable // 人類能行走
22 }
23
24 // 鳥類
25 type Bird struct {
26 Walkable // 鳥類能行走
27 Flying // 鳥類能飛行
28 }
29
30 func main() {
31
32 // 實例化鳥類
33 b := new(Bird)
34 fmt.Println("Bird: ")
35 b.Fly()
36 b.Walk()
37
38 // 實例化人類
39 h := new(Human)
40 fmt.Println("Human: ")
41 h.Walk()
42
43 }
//運行結果
Bird:
can fly
can calk
Human:
can calk
```
代碼說明如下:
- 第6行,聲明可飛行結構(Flying)。
- 第8行,為可飛行結構添加飛行方法Fly()。
- 第13行,聲明可行走結構(Walkable)。
- 第15行,為可行走結構添加行走方法Walk()。
- 第20行,聲明人類結構。這個結構嵌入可行走結構(Walkable),讓人類具備“可行走”特性
- 第25行,聲明鳥類結構。這個結構嵌入可行走結構(Walkable)和可飛行結構(Flying),讓鳥類具備既可行走又可飛行的特性。
- 第33行,實例化鳥類結構。
- 第35和36行,調用鳥類可以使用的功能,如飛行和行走。
- 第39行,實例化人類結構。
- 第41行,調用人類能使用的功能,如行走。