在函數聲明時,在其名字以前放上一個變量,便是一個方法。這個附加的參數會將該函數附加到這種類型上,即至關於爲這種類型定義了一個獨佔的方法。程序員
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。express
在方法調用過程當中,接收器參數通常會在方法名以前出現。這和方法聲明是同樣的,都是接收器參數在方法名字以前。下面是例子:編程
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
方法來執行。api
由於每種類型都有其方法的命名空間,咱們在用Distance這個名字的時候,不一樣的Distance調用指向了不一樣類型裏的Distance方法。數組
// 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類型,然而咱們依然能夠爲它定義方法。兩個Distance方法有不一樣的類型。他們兩個方法之間沒有任何關係,儘管Path的Distance方法會在內部調用Point.Distance
方法來計算每一個鏈接鄰接點的線段的長度。app
Go和不少其它的面向對象的語言不太同樣。在Go語言裏,咱們能夠爲一些簡單的數值、字符串、slice、map來定義一些附加行爲很方便。方法能夠被聲明到任意類型,只要不是一個指針或者一個interface(接收者不能是一個指針類型,可是它能夠是任何其餘容許類型的指針)。函數
對於一個給定的類型,其內部的方法都必須有惟一的方法名,可是不一樣的類型卻能夠有一樣的方法名,好比咱們這裏Point和Path就都有Distance這個名字的方法;因此咱們沒有必要非在方法名以前加類型名來消除歧義,好比PathDistance。在上面兩個對Distance名字的方法的調用中,編譯器會根據方法的名字以及接收器來決定具體調用的是哪個函數。this
當調用一個函數時,會對其每個參數值進行拷貝,若是一個函數須要更新一個變量,或者函數的其中一個參數實在太大咱們但願可以避免進行這種默認的拷貝,這種狀況下咱們就須要用到指針了。對應到咱們這裏用來更新接收器的對象的方法,當這個接受者變量自己比較大時,咱們就能夠用其指針而不是對象來聲明方法,以下:編碼
func (p *Point) ScaleBy(factor float64) { p.X *= factor p.Y *= factor }
這個方法的名字是(*Point).ScaleBy
。這裏的括號是必須的;沒有括號的話這個表達式可能會被理解爲*(Point.ScaleBy)
。設計
無論你的method的receiver是指針類型仍是非指針類型,都是能夠經過指針/非指針類型進行調用的,編譯器會幫你作類型轉換。
p := Point{1, 2} pptr := &p p.ScaleBy(2) // implicit (&p) pptr.Distance(q) // implicit (*pptr)
就像一些函數容許nil指針做爲參數同樣,方法理論上也能夠用nil指針做爲其接收器,尤爲當nil對於對象來講是合法的零值時,好比map或者slice。在下面的簡單int鏈表的例子裏,nil表明的是空鏈表:
// An IntList is a linked list of integers. // A nil *IntList represents the empty list. type IntList struct { Value int Tail *IntList } // Sum returns the sum of the list elements. func (list *IntList) Sum() int { if list == nil { return 0 } return list.Value + list.Tail.Sum() }
當你定義一個容許nil做爲接收器的方法的類型時,在類型前面的註釋中指出nil變量表明的意義是頗有必要的,就像咱們上面例子裏作的這樣。
下面的ColoredPoint類型
import "image/color" type Point struct{ X, Y float64 } type ColoredPoint struct { Point Color color.RGBA }
內嵌可使咱們在定義ColoredPoint時獲得一種句法上的簡寫形式,並使其包含Point類型所具備的一切字段和方法。
var cp ColoredPoint cp.X = 1 fmt.Println(cp.Point.X) // "1" cp.Point.Y = 2 fmt.Println(cp.Y) // "2" red := color.RGBA{255, 0, 0, 255} blue := color.RGBA{0, 0, 255, 255} var p = ColoredPoint{Point{1, 1}, red} var q = ColoredPoint{Point{5, 4}, blue} fmt.Println(p.Distance(q.Point)) // "5" p.ScaleBy(2) q.ScaleBy(2) fmt.Println(p.Distance(q.Point)) // "10"
經過內嵌結構體可使咱們定義字段特別多的複雜類型,咱們能夠將字段先按小類型分組,而後定義小類型的方法,以後再把它們組合起來。
內嵌字段會指導編譯器去生成額外的包裝方法來委託已經聲明好的方法,和下面的形式是等價的:
func (p ColoredPoint) Distance(q Point) float64 { return p.Point.Distance(q) } func (p *ColoredPoint) ScaleBy(factor float64) { p.Point.ScaleBy(factor) }
當Point.Distance被第一個包裝方法調用時,它的接收器值是p.Point,而不是p,固然了,在Point類的方法裏,你是訪問不到ColoredPoint的任何字段的。
方法只能在命名類型(像Point)或者指向類型的指針上定義,可是多虧了內嵌,咱們給匿名struct類型來定義方法也有了手段。這個例子中咱們爲變量起了一個更具表達性的名字:cache。由於sync.Mutex類型被嵌入到了這個struct裏,其Lock和Unlock方法也就都被引入到了這個匿名結構中了,這讓咱們可以以一個簡單明瞭的語法來對其進行加鎖解鎖操做。
var cache = struct { sync.Mutex mapping map[string]string }{ mapping: make(map[string]string), } func Lookup(key string) string { cache.Lock() v := cache.mapping[key] cache.Unlock() return v }
咱們常常選擇一個方法,而且在同一個表達式裏執行,好比常見的p.Distance()形式,實際上將其分紅兩步來執行也是可能的。p.Distance叫做「選擇器」,選擇器會返回一個方法"值"->一個將方法(Point.Distance)綁定到特定接收器變量的函數。由於已經在前文中指定過了,這個函數能夠不經過指定其接收器便可被調用,只要傳入函數的參數便可:
p := Point{1, 2} q := Point{4, 6} distanceFromP := p.Distance // method value fmt.Println(distanceFromP(q)) // "5" var origin Point // {0, 0} fmt.Println(distanceFromP(origin)) // "2.23606797749979", sqrt(5) scaleP := p.ScaleBy // method value scaleP(2) // p becomes (2, 4) scaleP(3) // then (6, 12) scaleP(10) // then (60, 120)
當T是一個類型時,方法表達式可能會寫做T.f或者(*T).f,會返回一個函數"值",這種函數會將其第一個參數用做接收器,因此能夠用一般(譯註:不寫選擇器)的方式來對其進行調用:
p := Point{1, 2} q := Point{4, 6} distance := Point.Distance // method expression fmt.Println(distance(p, q)) // "5" fmt.Printf("%T\n", distance) // "func(Point, Point) float64" scale := (*Point).ScaleBy scale(&p, 2) fmt.Println(p) // "{2 4}" fmt.Printf("%T\n", scale) // "func(*Point, float64)" // 譯註:這個Distance其實是指定了Point對象爲接收器的一個方法func (p Point) Distance(), // 但經過Point.Distance獲得的函數須要比實際的Distance方法多一個參數, // 即其須要用第一個額外參數指定接收器,後面排列Distance方法的參數。
當你根據一個變量來決定調用同一個類型的哪一個函數時,方法表達式就顯得頗有用了。你能夠根據選擇來調用接收器各不相同的方法。下面的例子,變量op表明Point類型的addition或者subtraction方法,Path.TranslateBy方法會爲其Path數組中的每個Point來調用對應的方法:
type Point struct{ X, Y float64 } func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} } func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} } type Path []Point func (path Path) TranslateBy(offset Point, add bool) { var op func(p, q Point) Point if add { op = Point.Add } else { op = Point.Sub } for i := range path { // Call either path[i].Add(offset) or path[i].Sub(offset). path[i] = op(path[i], offset) } }
Class
。一個struct類型的字段對同一個包的全部代碼都有可見性,不管你的代碼是寫在一個函數仍是一個方法裏。封裝提供了三方面的優勢。首先,由於調用方不能直接修改對象的變量值,其只須要關注少許的語句而且只要弄懂少許變量的可能的值便可。
第二,隱藏實現的細節,能夠防止調用方依賴那些可能變化的具體實現,這樣使設計包的程序員在不破壞對外的api狀況下能獲得更大的自由。
封裝的第三個優勢也是最重要的優勢,是阻止了外部調用方對對象內部的值任意地進行修改。由於對象內部變量只能夠被同一個包內的函數修改,因此包的做者可讓這些函數確保對象內部的一些值的不變性。好比下面的Counter類型容許調用方來增長counter變量的值,而且容許將這個值reset爲0,可是不容許隨便設置這個值(譯註:由於壓根就訪問不到):
type Counter struct { n int } func (c *Counter) N() int { return c.n } func (c *Counter) Increment() { c.n++ } func (c *Counter) Reset() { c.n = 0 }
只用來訪問或修改內部變量的函數被稱爲setter或者getter,例子以下,好比log包裏的Logger類型對應的一些函數。在命名一個getter方法時,咱們一般會省略掉前面的Get前綴。這種簡潔上的偏好也能夠推廣到各類類型的前綴好比Fetch,Find或者Lookup。
package log type Logger struct { flags int prefix string // ... } func (l *Logger) Flags() int func (l *Logger) SetFlags(flag int) func (l *Logger) Prefix() string func (l *Logger) SetPrefix(prefix string)