Go微服务实战
上QQ阅读APP看书,第一时间看更新

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节已经介绍过,此处仅仅贴出示例代码,请读者自行阅读。