方法的声明和普通函数类似,只是在函数名字前面多了一个参数,这个参数把这个方法绑定到这个参数对应的类型上,例:
type Point struct{ X, Y float64 }
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
由于主调函数会复制每一个实参变量,如果函数需要更新一个变量,或者如果一个实参太大而我们希望避免复制整个实参,因此我们必须使用指针来传递变量的地址,这也同样适用于更新接收者:将它绑定到指针类型:
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
习惯上遵循如果Point的任何一个方法使用指针接收者,那么所有的Point方法都应该使用指针接收者,
即使有一些方法不需要
不允许本身是指针的类型进行方法声明:
type P *int
func (P) f() {} // 编译错误,非法的接收者类型
通过提供*Point能够调用(*Point).ScaleBy方法
r := &Point{1, 2}
r.ScaleBy(2)
p := Point{1, 2}
pptr := &p
pptr.ScaleBy(2)
(&p).ScaleBy(2)
如果接收者p是Point类型的变量,但方法要求一个*Point接收者,也可以简写为:
p.ScaleBy(2)
实际上编译器会对变量进行&p的隐式转换,只有变量才允许这么做,不能够对一个不能取地址的Point接收者参数调用*Point方法,因为无法获取临时变量的地址
Point{1, 2}.ScaleBy(2) // 编译错误,不能获得Point类型字面量的地址
有三种情况:
p.ScaleBy(2) // 隐式转为(&p)
pptr.Distance(q) // 隐式转为(*pptr)
type IntList struct {
Value int
Tail *IntList
}
func (list *IntList) Sum() int {
if list == nil {
return 0
}
return list.Value + list.Tail.Sum()
}
import "image/color"
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
Point的方法都被纳入到ColoredPoint类型中
与其他面向对象语言不同的是,Point类型并不是ColoredPoint的基类,而是组合
p.Distance(q) // 编译错误:不能将q(ColoredPoint)转换为Point类型
可以将p.Distance
赋予一个方法变量,它是一个函数,把方法(Point.Distance)绑定到一个接收者p上,函数只需要提供实参而不需要提供接收者就能够调用
p := Point{1, 2}
q := Point{3, 4}
distanceFormP := p.Distance
fmt.Println(distanceFromP(q))
和调用一个普通函数不同,在调用方法的时候必须提供接收者,并且按照选择子的语法进行调用,方法表达式写成T.f或者(*T).f,其中T是类型,是一种函数变量,把原来的方法的接收者替换成函数的第一个形参,因此它可以像平常的函数一样调用
p := Point{1, 2}
q := Point{3, 4}
distance := Point.Distance // 方法表达式
fmt.Println(distance(p, q))
Go语言只有一种方式控制命名的可见性:定义的时候,首字母大写的标识符是可以从包中导出的,而首字母没有大写的则不导出
也就是说,要封装一个对象,必须使用结构体
Go语言中封装的单元是包而不是类型,无论是在函数内的代码还是方法内的代码,结构体类型内的字段对于同一个包中的所有代码都是可见的