Golang初级系列教程-结构体方法-Methods on structs
Methods on structs
上一篇文章已经介绍结构体中可以包含属性,同样结构体中可以有方法。方法的定义和普通函数定义相似,唯一的区别是方法需要 attach to
或者说 associated with
(关联)一个结构体。
比如现在想定义一个没有参数,并且返回 int
的普通函数,可能同下面代码类似
func my_func() int {
//code
}
接下来,我们定义一个结构体叫做 my_type
,这时,将如上的函数关联到 my_type
,那么 my_func
就是 my_type
的一个方法。
type my_type struct { }
func (m my_type) my_func() int {
//code
}
让我们扩展之前写的结构体 Rectangle
和 Area
函数。这一次,Area
将只为 Rectangle
服务,成为其一个方法。
package main
import "fmt"
type Rectangle struct {
length, width int
}
func (r Rectangle) Area() int {
return r.length * r.width
}
func main() {
r1 := Rectangle{4, 3}
fmt.Println("Rectangle is: ", r1)
fmt.Println("Rectangle area is: ", r1.Area())
}
Rectangle is: {4 3}
Rectangle area is: 12
许多面向对象语言,都有一个 this
或者 self
隐式的只想当前的实例,但在 Go
中,并不存这样的概念。当定义方法是,都会给定一个变量名,如上面的例子中是 (r Rectangle
),在方法中,可以使用这个变量名。
上面代码中,调用 Area
方法时,Rectangle
实例通过值传递。当然,也可以通过引用传递。对于方法而言,使用引用和值并没有太大的区别。Go
本身会自动识别,并且完成转换。
package main
import "fmt"
type Rectangle struct {
length, width int
}
func (r Rectangle) Area_by_value() int {
return r.length * r.width
}
func (r *Rectangle) Area_by_reference() int {
return r.length * r.width
}
func main() {
r1 := Rectangle{4, 3}
fmt.Println("Rectangle is: ", r1)
fmt.Println("Rectangle area is: ", r1.Area_by_value())
fmt.Println("Rectangle area is: ", r1.Area_by_reference())
fmt.Println("Rectangle area is: ", (&r1).Area_by_value())
fmt.Println("Rectangle area is: ", (&r1).Area_by_reference())
}
Rectangle is: {4 3}
Rectangle area is: 12
Rectangle area is: 12
Rectangle area is: 12
Rectangle area is: 12
上面代码中,一个方法传值,另一个方法传递指针。通过 r1
和 &r1
分别调用两个方法,都能正确执行,源于 Go
内部的处理机制。
让我们继续对 Rectangle
添加一个方法 Perimeter
——计算周长。
package main
import "fmt"
type Rectangle struct {
length, width int
}
func (r Rectangle) Area() int {
return r.length * r.width
}
func (r Rectangle) Perimeter() int {
return 2* (r.length + r.width)
}
func main() {
r1 := Rectangle{4, 3}
fmt.Println("Rectangle is: ", r1)
fmt.Println("Rectangle area is: ", r1.Area())
fmt.Println("Rectangle perimeter is: ", r1.Perimeter())
}
Rectangle is: {4 3}
Rectangle area is: 12
Rectangle perimeter is: 14
你可能已经注意到,这种添加方法的方式非常灵活,那么是不是也可以对 int
或者 time.Time
添加方法呢?答案是否定的。由于, Go
允许和类型定义相同的包内进行方法声明。
func (t time.Time) first5Chars() string {
return time.LocalTime().String()[0:5]
}
cannot define new methods on non-local type time.Time
但是如果你确实想实现这样的功能,那么我们可以利用前面学到的匿名字段。
package main
import "fmt"
import "time"
type myTime struct {
time.Time //anonymous field
}
func (t myTime) first5Chars() string {
return t.Time.String()[0:5]
}
func main() {
m := myTime{*time.LocalTime()} //since time.LocalTime returns an address, we convert it to a value with *
fmt.Println("Full time now:", m.String()) //calling existing String method on anonymous Time field
fmt.Println("First 5 chars:", m.first5Chars()) //calling myTime.first5Chars
}
Full time now: Tue Nov 10 23:00:00 UTC 2009
First 5 chars: Tue N
匿名字段的函数
深入分析上面的代码,想必你已经注意到,调用匿名字段方法的方式。由于 time.Time
是 myTime
的匿名字段,我们可以如同访问 time.Time
一样访问 myTime
,即我们可以调用 myTime.String()
。让我们通过更前面的一个例子更进一步说明。
下面的代码是前面 House
和 Kitchen
的例子。由于匿名字段,允许外围结构体直接访问内部结构体的字段,对于方法同样适用。当前,Kitchen
有一个方法 totalForksAndKnives()
,所以 House
可以直接访问 House.totalForksAndKnives()
。
package main
import "fmt"
type Kitchen struct {
numOfForks int
numOfKnives int
}
func(k Kitchen) totalForksAndKnives() int {
return k.numOfForks + k.numOfKnives
}
type House struct {
Kitchen //anonymous field
}
func main() {
h := House{Kitchen{4, 4}} //the kitchen has 4 forks and 4 knives
fmt.Println("Sum of forks and knives in house: ", h.totalForksAndKnives()) //called on House even though the method is associated with Kitchen
}
Sum of forks and knives in house: 8
Golang一种神奇的语言,让我们一起进步