4.2 方法
OOP(面向对象编程)无疑是当今世界最流行的语言特性,主流的Java、Python、C++都是面向对象的。Go语言也是支持面向对象的,不过在特征上又和上述语言有明显的不同。Go语言没有继承,但是却有方法,并且方法是Go语言面向对象的主要特征。
Go语言的方法和其他语言不太一样,其方法不再属于一个类,也不是C++那种成员函数,Go语言的方法是关联到类型的,且其存在与类没有任何关系,仅仅和类型有关系,这与Java与Python的方法都必须属于某个类不同。
方法的定义形式如下:
type Rectangle struct{w,h float64} func (r Rectangle) area() float64{ return r.w * r.h }
Go语言的方法定义非常像函数,仅仅是在函数名称前面定义了方法接受者或者叫接收器。本例定义了r,它是上面定义的Rectangle类型的接收器,其主要作用就是代表该方法的调用者。接收器类似于其他语言的self、this等关键字,Go语言里面没有这些关键字。Go语言中的接收器是编程者自己定义的,名称越短越好,一般都是对应类型的首字母。
注意
Go语言的方法也可以称为类型方法,接收器将类型和方法绑定在一起。
先看一个非常简单的示例,熟悉一下Go语言中方法的定义和使用:
book/ch04/4.2/base/main.go
1. package main
2.
3. import "fmt"
4.
5. //定义一个矩形
6. type Rectangle struct{w,h float64}
7. func main() {
8. rec := Rectangle{w:2,h:3}
9. fmt.Println(area(rec.w,rec.h))
10. fmt.Println(rec.area())
11. }
12.
13. //定义一个求矩形面积的函数
14. func area(w,h float64) float64{
15. return w*h
16. }
17.
18. //定义一个矩形类型的方法
19. func (r Rectangle) area() float64 {
20. return r.w*r.h
21. }
22. //以下是程序运行结果
23. 6
24. 6
第6行定义了一个矩形struct。
第19行至第21行定义了一个Rectangle的方法,用于求矩形的面积,接收器为r。第14行至第16行定义了一个与方法同名的函数,也是用于求面积的。
第9行至第10行分别表示打印函数的执行结果和方法的执行结果,从第23行和第24行来看,两者的执行结果一样,但是方法显然更为简洁。
方法的概念本就是从函数演变而来的,最早的函数是C语言的,后来才有了方法的形式。我们可以认为Go语言的方法就是把函数的第一个参数移到了名称的前面。
注意
Go语言里面的方法没有重载的概念。
用户可以给任何自定义的类型定义方法,定义一个或多个都可以,不过要求自定义类型和对应的方法在同一个包中。
注意
给Go语言内置的类型(比如int)定义方法是不可以的,因为要求类型定义和其对应的方法必须在同一个包内。
在调用方法的时候,对应的调用对象和接收器进行数据传递采用的也是值传递方式。如果主调对象的数据量比较大,可以把接收器定义为指针类型,通过指针类型还可以在方法内直接修改调用对象的值。下面通过代码示例体会一下更全面的用法:
book/ch04/4.2/pointer/main.go
1. package main
2.
3. import "fmt"
4.
5. //定义一个矩形
6. type Rectangle struct{w,h float64}
7. func main() {
8. p := &Rectangle{w:2,h:3}
9. fmt.Println(p.area())
10.
11. rec := Rectangle{w:2,h:3}
12. rp := &rec
13. fmt.Println(rp.area())
14.
15. fmt.Println((&rec).area())
16. fmt.Println(rec.area()) //会隐式地加上*rec,合法用法
17.
18. Rectangle{w:2,h:3}.area() //会报错,因为无法通过这种方式获取变量的地址
19. Rectangle{w:2,h:3}.area2() //合法用法,因为area2方法的接收器不是指针类型,采用值传递
20.
21.
22. }
23. //定义一个接收器为指针类型的方法
24. func (r *Rectangle) area() float64 {
25. return r.w*r.h
26. }
27. //定义一个接收器为矩形类型的方法
28. func (r Rectangle) area2() float64 {
29. return r.w*r.h
30. }
第23行至第26行定义了一个接收器为指针类型的方法area,对应的第27行至第30行又定义了接收器为普通对象的方法area2。
第8行至第16行介绍了四种调用area的方法,这些方法都是合法可行的。特别注意第16行的方法,虽然接收器是指针,但是直接用对象本身去调用也不会报错,因为程序会自动隐式地加上“*”。
第18行这种写法会报错,虽然第16行的用法是正确的。这是因为第16行先定义了一个变量,通过这个变量名加上“*”就可以获取变量的地址;而第18行则不然,隐式加“*”会在编译的时候报错。
第19行这种用法不会报错,因为area2的接收器不是指针而是普通对象,也就意味着所有变量的值都会以值传递的方式传到方法内。
注意
如果自定义的类型本身已经是指针类型,例如 type p *int,则不允许为该类型定义方法。对于func (p) f() {...},采用p类型的这种方法定义编译是通不过的。这里主要是为了阅读方便,指针类型会强制要求在接收器处声明,否则不许用。
Go语言不支持继承,所以提供了其他方式达到类似继承的效果,比如通过struct组合可以达到相应的效果。还是通过代码看一下具体用法:
book/ch04/4.2/extends/main.go
1. package main
2.
3. import (
4. "fmt"
5. "image/color"
6. )
7.
8. //定义一个矩形
9. type Rectangle struct{w,h float64}
10. //定义一个彩色矩形
11. type ColorRect struct {
12. Rectangle
13. Color color.RGBA
14. }
15. func main() {
16. var cr ColorRect
17. cr.h = 3
18. cr.w = 2
19. fmt.Println(cr.area())
20. }
21. //定义一个接收器为矩形类型的方法
22. func (r Rectangle) area() float64 {
23. return r.w*r.h
24. }
本例在3.4节已经介绍过,此处仅仅贴出示例代码,请读者自行阅读。