总结: 感觉方法就是c++类的函数成员。
- 定义一个方法的形式就是普通的go函数定义前面再加个接收者参数,也就是这个方法属于的struct。
- 然后这个struct的定义和这个方法的声明必须在同一个包里面。
- 还有就是接收者参数可以是传值,也可以是传指针,传值只能改变副本的字段,而传指针可以改变原来那个struct的字段,再加个go的指向struct的指针可以隐式间接引用。
- 不管你接收者是指针还是值,你用指针还是值去调用这个方法,go的编译器都会给你重写这个调用过程,使得适用这个方法(比如接收者是指针,而你用值去调用,那么编译器会 (&p).func )。
定义:方法只是个带接收者参数的函数。
理解:跟c++里面类的函数成员一个意思吧?不同struct的方法能够同名
tips:接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法(除非给它一个别名)。
package main
import (
"fmt"
)
type Myfloat float64
type Myint int
func (f Myfloat) Abs() Myfloat{
if f<0 {
return -f
} else{
return f
}
}
func (f Myint) Abs() int{
if f<0 {
return int(f)
} else{
return int(-f)
}
}
func main(){
var a Myfloat = 2.5
var b Myint = 2
fmt.Println(a.Abs(),"\n",b.Abs()) // 输出2.5 和-2
}
指针接收者
你可以为指针接收者声明方法。
这意味着对于某类型 T
,接收者的类型可以用 *T
的文法。(此外,T
不能是像 *int
这样的指针。(why 不可以,只要给个别名,在同一个包里面不就OK了吗?下面的例子就是)
指针接收者的方法可以修改接收者指向的值(而值接受者就不行,就跟c++的传值和传引用一样)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。
tips:一个指向结构体的指针
p
,我们可以通过(*p).X
来访问其字段X
。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写p.X
就可以。
若使用值接收者,那么 Scale
方法会对原始 Vertex
值的副本进行操作。(对于函数的其它参数也是如此。)Scale
方法必须用指针接受者来更改 main
函数中声明的 Vertex
的值。
使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。
// go-指南的例子
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f // 隐式间接引用
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}
// 给内置类型 一个别名 实现指针接收者
type Myint int
func (f Myfloat) Abs() Myfloat{
if f<0 {
return -f
} else{
return f
}
}
func (f *Myint) Double() {
*f=2*(*f)
}
func main(){
var a Myint = 2
a.Double()
fmt.Println(a)
}
方法与指针重定向
带指针参数的函数必须接受一个指针:
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
var v Vertex
ScaleFunc(v, 5) // 编译错误!
ScaleFunc(&v, 5) // OK
而以指针为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
对于语句 v.Scale(5)
,即便 v
是个值而非指针,带指针接收者的方法也能被直接调用。 也就是说,由于 Scale
方法有一个指针接收者,为方便起见,Go 会将语句 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()
。