wx

关注公众号领取Go学习资料

wx

洋葱小店

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))
}

wx

关注公众号领取Go学习资料

wx

洋葱小店

©2017-2023 鲁ICP备17023316号-1 Powered by Hugo