今天我们来了解一下 Go
中函数的另一种形态,带有接收者的函数,我们称为 method
。
method
是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在 func
后面增加了一个 receiver
(也就是 method
所依从的主体)。
用 Rob Pike 的话来说就是:
"A method is a function with an implicit first argument, called a receiver."
method 的语法如下:
func (r ReceiverType) funcName(parameters) (results)
例如下面的 method
来实现面积的求和:
Go
package main
import (
"fmt"
)
type Rectangle struct {
width, height float64
}
// 这样就实现了method
func (r Rectangle) area() float64 {
return r.width * r.height
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
fmt.Println("Area of r1 is: ", r1.area())
fmt.Println("Area of r2 is: ", r2.area())
}
使用 method
有下面的几个特点:
- 虽然
method
的名字一模一样,但是如果接收者不一样,那么method
就不一样。 method
里面可以访问接收者的字段。- 调用
method
通过 . 访问,就像struct
里面访问字段一样。
Receiver
还可以是指针,两者的差别在于,指针作为 Receiver
会对实例对象的内容发生操作,而普通类型作为 Receiver
仅仅是以副本作为操作对象,并不对原实例对象发生操作。
后文对此会有详细论述。
method
只能作用在 struct
上面吗? 当然不是,他可以定义在任何你自定义的类型、内置类型、struct 等各种类型上面。
struct
只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。
type typeName typeLiteral
请看下面这个申明自定义类型的代码。
Go
type ages int
type money float32
type months map[string]int
实际上只是一个定义了一个别名,有点类似于 c
中的 typedef
,例如上面 ages
替代了 int
。
指针作为 receiver
Go
package main
import "fmt"
type Tree struct {
Value int
Left *Tree
Righ *Tree
}
func (t Tree) setValue1(v int) {
t.Value = v
}
func (t *Tree) setValue2(v int) {
t.Value = v
}
func main() {
t1 := Tree{
Value: 0,
Left: nil,
Righ: nil,
}
t1.setValue1(1)
fmt.Println(t1) // {0 <nil> <nil>} 并没有修改原来的值
t1.setValue2(2)
fmt.Println(t1) // {2 <nil> <nil>} 对原来的Tree修改了
t2 := &Tree{
Value: 0,
Left: nil,
Righ: nil,
}
t2.setValue1(1)
fmt.Println(t2) // &{0 <nil> <nil>} // 并没有对原来的值进行修改
t2.setValue2(2)
fmt.Println(t2) // &{2 <nil> <nil>}
}
从上面的例子可以看出来:
-
如果一个
method
的receiver
是*T
, 你可以在一个T
类型的实例变量V
上面调用这个method
,而不需要&V
去调用这个method
。 -
如果一个
method
的receiver
是T
,你可以在一个T
类型的变量P
上面调用这个method
,而不需要*P
去调用这个method
这种特性:让开发者不必关注,调用的指针的 method
还是不是指针的 method
,Go 能推断出你的目的,这对于有多年 C/C++
编程经验的同学来说,真是解决了一个很大的痛苦。
如果你的方法想修改值就将 receiver
改为指针类型,否则就使用实例变量类型。
method 继承
如果匿名字段实现了一个 method
,那么包含这个匿名字段的 struct
也能调用该 method
。
Go
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human // 匿名字段
school string
}
type Employee struct {
Human // 匿名字段
company string
}
// 在 human 上面定义了一个 method
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
method 重写 上面的例子中,如果 Employee
想要实现自己的 SayHi
, 怎么办?
简单,和匿名字段冲突一样的道理,我们可以在 Employee
上面定义一个 method
,重写了匿名字段的方法。
Go
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human // 匿名字段
school string
}
type Employee struct {
Human // 匿名字段
company string
}
// Human 定义 method
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// Employee 的 method 重写 Human 的 method
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
上面的代码设计的是如此的美妙,让人不自觉的为 Go
的设计惊叹!
通过这些内容,我们可以设计出基本的面向对象的程序了。
但是 Go
里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现 (大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。