1.方法声明
在普通函数的名字前放一个变量,就是一个方法。这个函数就被绑定这个类型上,所以这个类型独占此方法。不同类型间可以拥有相同方法,互不干扰。
type Count struct {
a, b int
}
// 普通函数
func Add(a, b int) int {
return a + b
}
// Count 类型的方法
func (c Count) Add() int {
return c.a + c.b
}
上面函数的附加参数 c 被称为方法的接收者,一搬用类型的第一个字母来命名。
先声明类型,再通过类型调用方法:
c1 := Count{1, 2}
c2 := Count{3, 6}
fmt.Println(c1.Add()) // 输出:3
fmt.Println(c2.Add()) //
任何自定义的类型,我们都可以为它创建方法:
type Count int
func (c Count) Add(a, b int) int {
return a + b
}
2.指针接收者
因为 golang 的函数是值传递,每个参数都会被拷贝,所以当我们要在函数里面修改一个外部变量或者函数其中一个参数较大(eg:[1000000]int),我们一般会使用指针进行传递。对应到方法这里与之类似,如果接收者变量本身较大或者想要修改类型的字段值,我们就要用到指针。
// 类型前加 * 即为指针接收者
func (c *Count) Add(a, b int) int {
return a + b
}
一般约定,如果类型有一个方法使用了指针接收者,那最好所有的方法都使用指针接收者。
如果一个类型本身是一个指针的话,是不能作为方法的接收者的。
type P *int
func (P) f() {}
编译程序会报错:
# command-line-arguments
./main.go:20:6: invalid receiver type P (P is a pointer type)
想要调用指针类型的方法,只需要提供类型指针即可。
type Count struct {
a, b int
}
func Add(a, b int) int {
return a + b
}
func (c *Count) Add() int {
return c.a + c.b
}
func main() {
c := &Count{1, 2}
fmt.Println(c.Add())
}
或者:
c := Count{1, 2}
fmt.Println(c.Add())
或者:
c := Count{1, 2}
fmt.Println((&c).Add())
有两点需要注意:
1.不管你的方法的接收者是指针类型还是非指针类型,都可以通过指针或非指针类型进行调用,编译器会帮我们做类型转换。
2.在声明一个方法的接收者是指针还是非指针时,我们需要思考两点:第一是这个类型本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝。第二是如果你使用指针类型作为接收者时,这个指针指向的始终是同一块内存地址,所以如果你对这个对象进行了拷贝,可能会出现你意想不到的情况。
3.嵌入结构体扩展类型
import "image/color"
type Point struct{ X, Y float64 }
func (p *Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
type ColoredPoint struct {
Point
Color color.RGBA
}
ColoredPoint 嵌入了 Point 结构,内嵌让 ColoredPoint 拥有了 Point 的所有字段,然后再定义其他自己需要的字段。调用的时候如果嵌入的多个结构体没有重复字段,我们不需要显示指定嵌入类型。
var cp ColoredPoint
cp.X = 1 // 如果嵌入了其他类型也用有 X 字段,那么就必须这么写: cp.Point.X = 1
fmt.Println(cp.Point.X) // "1"
cp.Point.Y = 2
fmt.Println(cp.Y) // "2"
即使 ColoredPoint 里没有声明这些方法,我们可以把 ColoredPoint 类型当作接收者来调用 Point 里的方法,:
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"
Point类的方法也被引入了ColoredPoint 。用这种方式,我们可以定义字段特别多的复杂类型,我们可以将字段先按小类型分组,然后定义小类型的方法,之后再把它们组合起来。
上面的例子中对 Distance 方法的调用,传入的是 Point 类型,所以尽管 q 有 Point 这个内嵌类型,但是记住 q 不是一个 Point 类。如果你强制传入 q ,则会收到下面的错误:
cannot use q (type ColoredPoint) as type Point in argument to p.Point.Distance
当 ColoredPoint 调用 Distance 方法时,它的接收器值是 p.Point,而不是 p。
在类型中内嵌的匿名字段也可能是一个命名类型的指针:
type ColoredPoint struct {
*Point
Color color.RGBA
}
一个 struct 类型也可能会有多个匿名字段。我们将 ColoredPoint 定义为下面这样:
type ColoredPoint struct {
Point
color.RGBA
}
4.Method Value 与 Method Expression
type TZ int
func (a *TZ) Print(i int) {
fmt.Println(i)
}
func main() {
var a TZ
// 通过类型的变量调用方法,称为 Method Value
a.Print(1)
// 直接通过类型调用方法,然后传入接受者作为第一个参数称为 Method Expression
(*TZ).Print(&a, 2)
}