## 6.1. 方法聲明
在函數聲明時,在其名字之前放上一個變量,卽是一個方法。這個附加的參數會將該函數附加到這種類型上,卽相當於爲這種類型定義了一個獨占的方法。
下面來寫我們第一個方法的例子,這個例子在package geometry下:
```go
gopl.io/ch6/geometry
package geometry
import "math"
type Point struct{ X, Y float64 }
// traditional function
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
```
上面的代碼里那個附加的參數p,叫做方法的接收器(receiver),早期的面向對象語言留下的遺産將調用一個方法稱爲“向一個對象發送消息”。
在Go語言中,我們併不會像其它語言那樣用this或者self作爲接收器;我們可以任意的選擇接收器的名字。由於接收器的名字經常會被使用到,所以保持其在方法間傳遞時的一致性和簡短性是不錯的主意。這里的建議是可以使用其類型的第一個字母,比如這里使用了Point的首字母p。
在方法調用過程中,接收器參數一般會在方法名之前出現。這和方法聲明是一樣的,都是接收器參數在方法名字之前。下面是例子:
```Go
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // "5", function call
fmt.Println(p.Distance(q)) // "5", method call
```
可以看到,上面的兩個函數調用都是Distance,但是卻沒有發生衝突。第一個Distance的調用實際上用的是包級别的函數geometry.Distance,而第二個則是使用剛剛聲明的Point,調用的是Point類下聲明的Point.Distance方法。
這種p.Distance的表達式叫做選擇器,因爲他會選擇合適的對應p這個對象的Distance方法來執行。選擇器也會被用來選擇一個struct類型的字段,比如p.X。由於方法和字段都是在同一命名空間,所以如果我們在這里聲明一個X方法的話,編譯器會報錯,因爲在調用p.X時會有歧義(譯註:這里確實挺奇怪的)。
因爲每種類型都有其方法的命名空間,我們在用Distance這個名字的時候,不同的Distance調用指向了不同類型里的Distance方法。讓我們來定義一個Path類型,這個Path代表一個線段的集合,併且也給這個Path定義一個叫Distance的方法。
```Go
// A Path is a journey connecting the points with straight lines.
type Path []Point
// Distance returns the distance traveled along the path.
func (path Path) Distance() float64 {
sum := 0.0
for i := range path {
if i > 0 {
sum += path[i-1].Distance(path[i])
}
}
return sum
}
```
Path是一個命名的slice類型,而不是Point那樣的struct類型,然而我們依然可以爲它定義方法。在能夠給任意類型定義方法這一點上,Go和很多其它的面向對象的語言不太一樣。因此在Go語言里,我們爲一些簡單的數值、字符串、slice、map來定義一些附加行爲很方便。方法可以被聲明到任意類型,隻要不是一個指針或者一個interface。
兩個Distance方法有不同的類型。他們兩個方法之間沒有任何關繫,盡管Path的Distance方法會在內部調用Point.Distance方法來計算每個連接鄰接點的線段的長度。
讓我們來調用一個新方法,計算三角形的週長:
```Go
perim := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
fmt.Println(perim.Distance()) // "12"
```
在上面兩個對Distance名字的方法的調用中,編譯器會根據方法的名字以及接收器來決定具體調用的是哪一個函數。第一個例子中path[i-1]數組中的類型是Point,因此Point.Distance這個方法被調用;在第二個例子中perim的類型是Path,因此Distance調用的是Path.Distance。
對於一個給定的類型,其內部的方法都必鬚有唯一的方法名,但是不同的類型卻可以有同樣的方法名,比如我們這里Point和Path就都有Distance這個名字的方法;所以我們沒有必要非在方法名之前加類型名來消除歧義,比如PathDistance。這里我們已經看到了方法比之函數的一些好處:方法名可以簡短。當我們在包外調用的時候這種好處就會被放大,因爲我們可以使用這個短名字,而可以省略掉包的名字,下面是例子:
```Go
import "gopl.io/ch6/geometry"
perim := geometry.Path{{1, 1}, {5, 1}, {5, 4}, {1, 1}}
fmt.Println(geometry.PathDistance(perim)) // "12", standalone function
fmt.Println(perim.Distance()) // "12", method of geometry.Path
```
譯註:如果我們要用方法去計算perim的distance,還需要去寫全geometry的包名,和其函數名,但是因爲Path這個變量定義了一個可以直接用的Distance方法,所以我們可以直接寫perim.Distance()。相當於可以少打很多字,作者應該是這個意思。因爲在Go里包外調用函數需要帶上包名,還是挺麻煩的。
- 前言
- Go語言起源
- Go語言項目
- 本書的組織
- 更多的信息
- 致謝
- 入門
- Hello, World
- 命令行參數
- 査找重複的行
- GIF動畵
- 獲取URL
- 併發獲取多個URL
- Web服務
- 本章要點
- 程序結構
- 命名
- 聲明
- 變量
- 賦值
- 類型
- 包和文件
- 作用域
- 基礎數據類型
- 整型
- 浮點數
- 複數
- 布爾型
- 字符串
- 常量
- 複合數據類型
- 數組
- Slice
- Map
- 結構體
- JSON
- 文本和HTML模闆
- 函數
- 函數聲明
- 遞歸
- 多返迴值
- 錯誤
- 函數值
- 匿名函數
- 可變參數
- Deferred函數
- Panic異常
- Recover捕獲異常
- 方法
- 方法聲明
- 基於指針對象的方法
- 通過嵌入結構體來擴展類型
- 方法值和方法表達式
- 示例: Bit數組
- 封裝
- 接口
- 接口是合約
- 接口類型
- 實現接口的條件
- flag.Value接口
- 接口值
- sort.Interface接口
- http.Handler接口
- error接口
- 示例: 表達式求值
- 類型斷言
- 基於類型斷言識别錯誤類型
- 通過類型斷言査詢接口
- 類型分支
- 示例: 基於標記的XML解碼
- 補充幾點
- Goroutines和Channels
- Goroutines
- 示例: 併發的Clock服務
- 示例: 併發的Echo服務
- Channels
- 併發的循環
- 示例: 併發的Web爬蟲
- 基於select的多路複用
- 示例: 併發的字典遍歷
- 併發的退出
- 示例: 聊天服務
- 基於共享變量的併發
- 競爭條件
- sync.Mutex互斥鎖
- sync.RWMutex讀寫鎖
- 內存同步
- sync.Once初始化
- 競爭條件檢測
- 示例: 併發的非阻塞緩存
- Goroutines和線程
- 包和工具
- 包簡介
- 導入路徑
- 包聲明
- 導入聲明
- 包的匿名導入
- 包和命名
- 工具
- 測試
- go test
- 測試函數
- 測試覆蓋率
- 基準測試
- 剖析
- 示例函數
- 反射
- 爲何需要反射?
- reflect.Type和reflect.Value
- Display遞歸打印
- 示例: 編碼S表達式
- 通過reflect.Value脩改值
- 示例: 解碼S表達式
- 獲取結構體字段標識
- 顯示一個類型的方法集
- 幾點忠告
- 底層編程
- unsafe.Sizeof, Alignof 和 Offsetof
- unsafe.Pointer
- 示例: 深度相等判斷
- 通過cgo調用C代碼
- 幾點忠告
- 附録