1、Go方法
在灵胎篇博客中讲到函数,函数就是类似与java中的方法,然而go中还有一些升级版的函数,叫方法。
只不过这种方法在方法名前还有一个括号加参数,只不过被称呼为接收者,方法名后面的括号没有参数,方法接收者在它自己的参数列表内,位于 func
关键字和方法名之间。
如下:这个就被称之为方法,因为方法名前面有接收者
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结构体类型没有限制要求,但是非结构体类型声明方法,必须是本包内!!!
举例说明:
所以方法的接收者对结构体没有要求,非结构体如数据类型等都要求在同一个package下面才可以作为接收者。
2、指针接收者
2.1 概念
在灵胎篇第五篇中讲到指针,&
操作符会生成一个指向其操作数的指针,*
操作符表示指针指向的底层值,所一当你运行下面的函数的时候,结果是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结构体,所以我们需要用指针‘*’来更改
2.2 方法与指针重定向
注意点:
当函数的参数中有指针的情况下,
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)操作
2.3 方法与指针重定向(反向)
那说一下什么是反向,就是方法中的接收者和函数的参数不是指针了,而是正常类型,我们接下来如何操作。也有要求
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的自动转换功能
所以我们在开发中,为什么方法用的非常多,函数用的很少的原因,其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。
3、接口
3.1 概念理解
接口就是方法的集合,和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,不过我们上面不应该用数据类型作为指针接收者,因为需要用结构体,这样才方便在方法中使用操作业务逻辑。
接口接收者会实现接口!
3.2 底层值为 nil 的接口值
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。
3.3 空接口
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
请发表评论