method
func (r ReceiverType) funcName(parameters) (results)
带有接收者的函数,称为method。
method是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在func后面增加了一个receiver(也就是method所依从的主体)。
type Rectangle struct {
width, height float64
}
type Circle struct {
radius float64
}
func (r Rectangle) area() float64 {
return r.width * r.height
}
func (c Circle) area() float64 {
return c.radius * c.radius * math.Pi
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
c1 := Circle{10}
c2 := Circle{25}
fmt.Println("Area of r1 is: ", r1.area()) // 24
fmt.Println("Area of r2 is: ", r2.area()) // 36
fmt.Println("Area of c1 is: ", c1.area()) // 314.1592653589793
fmt.Println("Area of c2 is: ", c2.area()) // 1963.4954084936207
}
从上面的例子中可以看到:
1)虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样;
2)method里面可以访问接收者的字段;
3)调用method通过"."访问,就像struct里面访问字段一样;
上例图示如下:
在上例,method area() 分别属于Rectangle和Circle,于是他们的 Receiver 就变成了Rectangle 和 Circle, 或者说,这个area()方法 是由 Rectangle/Circle 发出的。
需要说明的是,图示中method用虚线标出,意思是此处方法的Receiver是以值传递,而非引用传递。
Receiver还可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。
method的接收者类型
method的接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:在任何你自定义的类型、内置类型、struct等任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。但是接收者不能是一个接口类型。
const (
WHITE = iota
BLACK
BLUE
RED
YELLOW
)
type Color byte // 作为byte的别名
type Box struct {
width, height, depth float64
color Color
}
type BoxList []Box // a slice of boxes
// 返回容量
func (b Box) Volume() float64 {
return b.width * b.height * b.depth
}
// 修改颜色为c
func (b *Box) SetColor(c Color) {
b.color = c
}
// 返回容量最大的颜色
func (bl BoxList) BiggestColor() Color {
v := 0.0
k := Color(WHITE)
for _, b := range bl {
if bv := b.Volume(); bv > v {
v = bv
k = b.color
}
}
return k
}
// 把颜色涂为黑色
func (bl BoxList) PaintItBlack() {
for i := range bl {
bl[i].SetColor(BLACK)
}
}
// 返回Color的具体颜色(字符串格式)
func (c Color) String() string {
strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
return strings[c]
}
func main() {
boxes := BoxList{
Box{4, 4, 4, RED},
Box{10, 10, 1, YELLOW},
Box{1, 1, 20, BLACK},
Box{10, 10, 1, BLUE},
Box{10, 30, 1, WHITE},
Box{20, 20, 20, YELLOW},
}
fmt.Println("boxes length: ", len(boxes)) // 6
fmt.Println("first volume: ", boxes[0].Volume()) // 64
fmt.Println("last color: ", boxes[len(boxes)-1].color.String()) // YELLOW
fmt.Println("biggest color: ", boxes.BiggestColor().String()) // YELLOW
boxes.PaintItBlack()
fmt.Println("second color:", boxes[1].color.String()) // BLACK
fmt.Println("biggest color:", boxes.BiggestColor().String()) // BLACK
}
从上例中,通过const定义了一些常量,定义了一些自定义类型
1)Color作为Byte的别名;
2)定义了一个struct:Box,含有三个长宽高字段和一个颜色属性;
3)定义了一个slice:BoxList,含有Box;
然后以上面的自定义类型为接收者定义了一些method。
注意: Go语言不允许为简单的内置类型添加方法,所以下面定义的方法是非法的。
func Add(a ,b int){ //函数合法
fmt.Println(a+b)
}
func (a int) Add (b int){ //方法非法!不能是内置数据类型
fmt.Println(a+b)
}
这个时候我们需要用Go语言的type,来临时定义一个和int具有同样功能的类型。这个类型不能看成是int类型的别名,它们属于不同的类型,不能直接相互赋值。
修改后如下:
type myInt int
func Add(a ,b int){ //函数
fmt.Println(a+b)
}
func (a myInt) Add (b myInt){ //方法
fmt.Println(a+b)
}
指针作为receiver
上例中SetColor这个method,它的receiver是一个指向Box的指针,你可以使用*Box。
想想为啥要使用指针而不是Box本身呢?
定义SetColor的真正目的是想改变这个Box的颜色,如果不传Box的指针,那么SetColor接受的其实是Box的一个copy,也就是说method内对于颜色值的修改,其实只作用于Box的copy,而不是真正的Box,所以我们需要传入指针。
结合上面的例子:
问题1:SetColor函数里面应该这样定义*b.Color=c,而不是b.Color=c,因为我们需要读取到指针相应的值?
回答1:其实Go里面这两种方式都是正确的,当你用指针去访问相应的字段时(虽然指针没有任何的字段),Go知道你要通过指针去获取这个值。
问题2:PaintItBlack函数里面调用SetColor的时候是不是应该写成(&bl[i]).SetColor(BLACK),因为SetColor的receiver是*Box,而不是Box?
回答2:这两种方式都可以,因为Go知道receiver是指针,他自动帮你转了。
总结来说:
1)如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method,而不需要&V去调用这个method;
2)如果一个method的receiver是T,你可以在一个*T类型的变量P上面调用这个method,而不需要 *P去调用这个method;
method继承
method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。
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("name:%s, phone:%s\n", h.name, h.phone)
}
func main() {
mark := Student{Human{"A", 23, "222-222"}, "MIT"}
sam := Employee{Human{"B", 22, "333-333"}, "Inc"}
mark.SayHi()
sam.SayHi()
}
method重写
如果在上例中,Employee想要实现自己的SayHi,怎么办?
和匿名字段冲突一样的道理,我们可以在Employee上面定义一个method,重写了匿名字段的方法。
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("human name:%s, phone:%s\n", h.name, h.phone)
}
// 在employee的method重写Human的method
func (e *Employee) SayHi() {
fmt.Printf("employee name:%s, phone:%s, company:%s\n", e.name, e.phone, e.company)
}
func main() {
mark := Student{Human{"A", 23, "222-222"}, "MIT"}
sam := Employee{Human{"B", 22, "333-333"}, "Inc"}
mark.SayHi()
sam.SayHi()
}