在靈胎篇博客中講到函數,函數就是相似與java中的方法,然而go中還有一些升級版的函數,叫方法。java
只不過這種方法在方法名前還有一個括號加參數,只不過被稱呼爲接收者,方法名後面的括號沒有參數,方法接收者在它本身的參數列表內,位於 func
關鍵字和方法名之間。函數
以下:這個就被稱之爲方法,由於方法名前面有接收者spa
package main import ( "math" "fmt" ) type Person struct { x,y float64 } func (p Person) Test() float64 { return math.Sqrt(p.x*p.x+p.y*p.y) } func main() { value := Person{3,4} fmt.Print(value.Test()) }
反而,方法名後有參數,通常被稱呼爲函數,只不過是參數順序不一樣而已,調用的方式也不一樣罷了指針
package main import ( "math" "fmt" ) type Person struct { x,y float64 } func Test(p Person) float64 { return math.Sqrt(p.x*p.x+p.y*p.y) } func main() { value := Person{3,4} fmt.Print(Test(value)) }
不過這個接受者有必定的限制,對struct結構體類型沒有限制要求,可是非結構體類型聲明方法,必須是本包內!!!code
舉例說明:blog
因此方法的接收者對結構體沒有要求,非結構體如數據類型等都要求在同一個package下面才能夠做爲接收者。接口
在靈胎篇第五篇中講到指針,&
操做符會生成一個指向其操做數的指針,*
操做符表示指針指向的底層值,所一當你運行下面的函數的時候,結果是50,開發
package main import ( "math" "fmt" ) type Person struct { x,y float64 } func (v *Person) Scale(f float64) { v.x = v.y * f v.y = v.y * f } func (p Person)Test() float64 { return math.Sqrt(p.x*p.x+p.y*p.y) } func main() { value := Person{3,4} value.Scale(10) fmt.Print(value.Test()) }
當你去掉 Scale方法中接收者的指針的時候,結果會發生變化,由於去掉指針的話,執行那個函數的接收者將是Person結構體的一個副本,並非真正意義上的Person結構體,因此咱們須要用指針‘*’來更改博客
注意點:string
當函數的參數中有指針的狀況下,
func ScaleFunc(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f }
這個狀況下,函數中指針做爲參數,必須用 類型前加&
ScaleFunc(v, 5) // 編譯錯誤! ScaleFunc(&v, 5) // OK
而對於方法,能夠忽略,以下:
func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f }
var v Vertex v.Scale(5) // OK p := &v p.Scale(10) // OK
由於方法中的接收者v.Scale(5)自動轉換(&v).Scale(5)操做
那說一下什麼是反向,就是方法中的接收者和函數的參數不是指針了,而是正常類型,咱們接下來如何操做。也有要求
func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func AbsFunc(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
函數狀況下:
var v Vertex fmt.Println(AbsFunc(v)) // OK fmt.Println(AbsFunc(&v)) // 編譯錯誤!
方法狀況下:
var v Vertex fmt.Println(v.Abs()) // OK p := &v fmt.Println(p.Abs()) // OK
這種狀況下,方法調用 p.Abs()
會被自動轉換爲(*p).ABs(),像極了java的自動裝箱功能和int long的自動轉換功能
因此咱們在開發中,爲何方法用的很是多,函數用的不多的緣由,其次,這樣能夠避免在每次調用方法時複製該值。若值的類型爲大型結構體時,這樣作會更加高效。
接口就是方法的集合,和java中的接口同樣,都須要實現接口
package main import ( "math" "fmt" ) type myFloat float64 type Base interface { test() float64 } func (f myFloat)test() float64 { return math.Sqrt(float64(f)) } func main() { var base Base f := myFloat(25) base = f //f實現接口base fmt.Print(base.test()) }
接下來用指針的接收者操做下
package main import ( "math" "fmt" ) type myFloat float64 type Base interface { test() float64 } func (f *myFloat)test() float64 { return math.Sqrt(25) } func main() { var base Base f := myFloat(25) base = &f fmt.Print(base.test()) }
一樣是結果5,不過咱們上面不該該用數據類型做爲指針接收者,由於須要用結構體,這樣才方便在方法中使用操做業務邏輯。
接口接收者會實現接口!
package main import "fmt" type I interface { M() } type T struct { S string } func (t *T) M() { if t == nil { fmt.Println("<nil>") return } fmt.Println(t.S) }
結果爲:
(<nil>, *main.T) <nil> (&{hello}, *main.T) hello
即使接口內的具體值爲 nil,方法仍然會被 nil 接收者調用,在java中會觸發一個空指針異常,但在 Go 中一般會寫一些方法來優雅地處理它
*注意:* 保存了 nil 具體值的接口其自身並不爲 nil。
package main import "fmt" func main() { var i interface{} describe(i) i = 42 describe(i) i = "hello" describe(i) } func describe(i interface{}) { fmt.Printf("(%v, %T)\n", i, i) }
結果爲:
(<nil>, <nil>) (42, int) (hello, string)
%V 意思是value %T是Type