6. 方法
6.1 方法的声明
在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。
示例:两种Distance实现的效果是一样的
type Point struct {
X, Y float64
}
func Distance(p, q Point) float64 {
return math.Hypot(p.X-q.X, p.Y-q.Y)
}
// 下面的p称为方法的接受器
func (p Point) Distance(q Point) float64 {
return math.Hypot(p.X-q.X, p.Y-q.Y)
}
func main() {
p := Point{1, 5}
q := Point{5, 2}
fmt.Println(Distance(p, q))
fmt.Println(p.Distance(q))
}
在go语言中有一个好处就是:可以给同一个包内的任意命名类型定义方法,只要这个命名类型的底层类型。比如:数值、字符串、slice、map
示例,对一个切片定义方法
type Path []Point
func (p Path) Distance() float64 {
sum := 0.0
for i, _ := range p {
if i>0 {
sum += p[i].Distance(p[i-1])
}
}
return sum
}
func main() {
perim := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
fmt.Println(perim.Distance()) // 12
}
6.2 基于指针对象的方法
在函数中,如果我们要对一个对象进行修改,或者给函数传参的参数对象内容过大,会选用传递指针的方式。因为函数在传递的时候是进行值传递,也就是将一个参数里边的变量在另外一个内存地址重新拷贝一份,然后再在函数中使用,而指针变量的值是一个地址,所以本质上也是进行只拷贝,只不过拷贝了一个地址,编译器可以通过这个地址去访问参数对象。
同样对于对象的方法,也可以实现基于指针对象的方法。
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
使用的时候也是很简单,就和上一小节的使用方法是一样的。
但是细分起来有几种情况:
- 接收器的实际参数和其接收器的形式参数相同,比如两者都是类型T或者都是类型*T
- 接收器实参是类型T,但接收器形参是类型*T,这种情况下编译器会隐式地为我们取变量的地址
- 接收器实参是类型*T,形参是类型T。编译器会隐式地为我们解引用,取到指针指向的实际变量
一句话总结就是无论什么的变量是普通的类型还是指针类型,或者实现的方法的接收器是普通类型还是指针类型,都可以通过点.来访问定义的方法
可以结合下面例子理解下:
type MyInt int
// 接受器i是一个指针类型
func (i *MyInt) pAdd(x int) MyInt {
// 因为x和i虽然底层变量是一样的,但是其实是两种不同的变量了,所以不能直接相加,详见2.5类型
return *i + MyInt(x)
}
// 接收器i只是一个普通类型
func (i MyInt) Add(x int) MyInt {
return i + MyInt(x)
}
func main() {
i := MyInt(20)
pi := &i
// 以下都正确
fmt.Println(i.Add(1)) // 21
fmt.Println(i.pAdd(2)) // 22
fmt.Println(pi.Add(3)) // 23
fmt.Println(pi.pAdd(4)) // 24
}
6.3 通过嵌入结构体来扩展类型
go语言中,嵌入结构体能够达到像其他面向对象的编程语言一样的继承。
在下面代码中结构体Point称为基类,通过匿名嵌入可以将Point嵌入到新的结构中,并且Point实现的方法能够被ColoredPoint所继承。关于嵌入结构体的其他一些细节可以看4.4节的结构体部分。
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
Point的方法继承给了ColoredPoint,所以ColoredPoint类型能够使用Distance函数
func main() {
red := color.RGBA{255, 0,0,1}
green := color.RGBA{0, 255,0,1}
cp1 := ColoredPoint{Point{1, 1}, red}
cp2 := ColoredPoint{Point{4, 5}, green}
fmt.Println(cp1.Distance(cp2.Point))
fmt.Println(cp1.Distance(cp2)) // 错误
}
Distance函数是Point方法,参数也是Point类型,所以必须接受一个Point参数,否者会报错。
当然,也可以为ColoredPoint定制自己的Distance方法。
func (p *ColoredPoint) Distance(q *ColoredPoint) float64 {
return p.Point.Distance(q.Point)
}
func main() {
red := color.RGBA{255, 0,0,1}
green := color.RGBA{0, 255,0,1}
cp1 := ColoredPoint{Point{1, 1}, red}
cp2 := ColoredPoint{Point{4, 5}, green}
fmt.Println(cp1.Distance(&cp2)) // 5
fmt.Println(cp1.Point.Distance(cp2.Point)) // 5
fmt.Println(cp1.Distance(cp2.Point)) // 错误
}
上述代码中,可以看到ColoredPoint拥有了自己的Distance方法,并且该方法可以接受*ColoredPoint的参数。但是原来的Point的Distance必须通过.Point.来访问了。
6.4 方法值和方法表达式
定义一个新的变量,这个变量是一个函数的值,可以看作函数的别名,使用的时候可以当做函数一样来使用。
方法值: 对一个已经声明的类型对象A,使用另外一个变量B来代替其方法。其接受器依然是A,通常表示为 B:=A.method
方法表达式: 当T是一个类型时,方法表达式可能会写作T.f或者(*T).f,会返回一个函数"值",这种函数会将其第一个参数用作接收器,所以可以用通常(译注:不写选择器)的方式来对其进行调用:通常表示为 op:=T.f或者op:=(*T).f, 其中是(*T).f表示该方法的接收器是一个指针类型。
type Point struct {
X, Y float64
}
// 下面的p称为方法的接受器
func (p *Point) Distance(q *Point) float64 {
return math.Hypot(p.X-q.X, p.Y-q.Y)
}
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 *Point, q* Point) Point
// 判断要加还是减
if add {
// 方法表达式,会将第一个参数作为接收器
op = (*Point).Add
} else {
op = (*Point).Sub
}
for i := range path {
path[i] = op(&path[i], &offset)
}
}
func main() {
p1 := Point{1,1}
p2 := Point{4, 5}
p1Distance := p1.Distance // 使用方法值
// p1Distance是p1.Distance方法返回的一个值
fmt.Println(p1Distance(&p2))
perim := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
perim.TranslateBy(Point{1, 1}, true)
fmt.Println(perim)
perim.TranslateBy(Point{2, 2}, false)
fmt.Println(perim)
}
6.5 封装
一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为“封装”。Go使用命名时首字母大小写来实现可见性。
封装的好处:
- 因为调用方不能直接修改对象的变量值,其只需要关注少量的语句并且只要弄懂少量变量的可能的值即可。
- 隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,这样使设计包的程序员在不破坏对外的api情况下能得到更大的自由。
- 是阻止了外部调用方对对象内部的值任意地进行修改。
本文主要参考:《Go语言圣经》
撩我?
搜索我的公众号:Kyda