Go 语言入门:方法
方法
Methods
Go没有类。但是,您可以在类型上定义方法。 方法是一个具有特殊接收器参数的函数。 接收器出现在自己的参数列表中,位于func关键字和方法名称之间。语法如下
func (t 类型) 方法名(参数名 参数类型 ...) [返回值类型]{
}
一下代码为官网示例;定义了一个 Vertex 类列;包含X Y 两个字段;定义了一个Abs 方法;计算坐标点到原点的距离。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
在这个例子中,Abs方法有一个名为v的Vertex类型的接收器。 请记住:方法只是一个带有接收器参数的函数。下面的Abs是作为一个普通函数编写的,函数本身没有变化。
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(Abs(v))
}
您也可以在非结构类型上声明一个方法。
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
在这个例子中,我们看到一个带有Abs方法的数字类型MyFloat。只能声明具有接收器的方法,该接收器的类型与该方法在同一个包中定义。不能使用类型在另一个包(包括int等内置类型)中定义的接收器来声明方法。
指针接收器
上面的定义方法例子中我们使用的都是值接收器,还可以使用指针接收器声明方法。这意味着接收器类型是类型T的指针T。(此外,T本身不能是int之类的指针。) 例如,此处的“Scale”方法是在“*Vertex”上定义的。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}
带有指针接收器的方法可以修改接收器指向的值(就像Scale在这里所做的那样)。由于方法通常需要修改其接收器,因此指针接收器比值接收器更常见。可以试一下将 Scale 方法改为 值接收器方法看一下是否能起到相同的作用。 对于值接收器,“Scale”方法对原始“Vertex ”值的副本进行操作。(这与任何其他函数参数的行为相同。)Scale方法必须有一个指针接收器才能更改主函数中声明的Vertex值。因此:
- 如果方法内部需要修改结构的字段值,并且对调用方可加则需要用指针接收器。
- 另外如果结构体有很多的字段,使用值接收器时会发生结构体的整体拷贝,为了减少拷贝可以使用指针接收器
- 其他情况,值接收器都可以被使用
- 给定类型上的所有方法都应该具有值或指针接收器,但不能同时具有这两者
指针和函数
下面,我们把Abs和Scale方法被重写为函数应该是怎样的呢?代码如下:
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
可以看到 Abs 函数使用的是值类型参数,Scale 使用的是指针类型参数,因为Scale需要对 参数的值进行修改;如果不使用指针类型参数 Scale 需要通过返回值的形式给调用者返回一个新的结构体;
方法和指针间接寻址
比较前两个程序,您可能会注意到带有指针参数的函数必须使用指针
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2)
ScaleFunc(&v, 10)
p := &Vertex{4, 3}
p.Scale(3)
ScaleFunc(p, 8)
fmt.Println(v, p)
}
而具有指针接收器的方法在被调用时采用值或指针作为接收器:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
对于v.Scale(5)语句,即使v是一个值而不是指针,也会自动调用带有指针接收器的方法。也就是说,为了方便起见,Go将语句v.Scale(5)解释为(&v).Scale(5),因为Scale方法有一个指针接收器。 反过来,采用值参数的函数必须采用该特定类型的值:
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Compile error!
而具有值接收器的方法在被调用时可以采用值或指针作为接收器:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
在这种情况下,方法调用p.Abs()被解释为(*p).Abs()。运行一下下面的代码更好的理解一下函数与方法的异同。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
fmt.Println(AbsFunc(v))
p := &Vertex{4, 3}
fmt.Println(p.Abs())
fmt.Println(AbsFunc(*p))
}