Go语言学习 十九 方法

本文最初发表在我的个人博客,查看原文,获得更好的阅读体验


上一篇文章中介绍了关于Go的类型体系。在Go中,可以为结构等类型定义方法。方法就是带有接收者参数的函数。方法接收者位于func关键字和方法名之间。

一 方法的声明

将方法的接收者指定为某一个类型,该方法即成为指定类型的方法。
例如,以下为Point类型定义了一个Abs()方法:

package main

import (
	"fmt"
	"math"
)

func main() {
	p := Point{5, 12}
	v := p.Abs()
	fmt.Println(v)
}

type Point struct {
	X float64
	Y float64
}

/* Abs是一个方法 */
func (p Point) Abs() float64 {
	return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

上述Abs方法同样的功能也可以使用函数实现:

package main

import (
	"fmt"
	"math"
)

func main() {
	p := Point{
		5,
		12,
	}
	v := Abs(p)
	fmt.Println(v)
}

type Point struct {
	X float64
	Y float64
}

/* Abs是一个函数 */
func Abs(p Point) float64 {
	return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

也可以为非结构体类型声明方法:

package main

import (
	"fmt"
)

func main() {
	var t TimeUnit = 2
	fmt.Println(t.Seconds())
}

type Point struct {
	X float64
	Y float64
}

type TimeUnit int

func (a TimeUnit) Seconds() int {
	return int(a) * 60
}

注意,我们只能为同一个包中定义的类型声明方法,这意味着所有的内置类型我们都不能再为它们声明方法。除此之外,可以为任何已命名类型定义方法(指针或接口除外)。

另外,方法的类型是以接收者作为第一个参数的函数的类型。例如,方法Abs具有以下类型:

func(p *Point)

但是,以这种方式声明的函数却不是方法。

二 方法与函数

2.1 指针接收者

上边的示例中,接收者均为值类型。我们也可以为指针接收者声明方法。即对于类型T,接收者的类型可以使用*T。指针接收者的方法可以修改接收者指向的值:

package main

import (
	"fmt"
	"math"
)

func main() {
	v := Point{5, 12}
	fmt.Println(v, v.Abs())

	v.Scale(10) // 值调用
	fmt.Println(v, v.Abs())

	p := &v
	p.Scale(10) // 指针调用
	fmt.Println(p, p.Abs())

}

type Point struct {
	X float64
	Y float64
}

// 值接收者方法
func (p Point) Abs() float64 {
	return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

// 指针接收者方法
func (p *Point) Scale(f float64) { // 注意该行接收者的声明有一个 *
	p.X = p.X * f
	p.Y = p.Y * f
}

2.2 指针与函数

上述方法同样可以改为函数实现:

package main

import (
	"fmt"
	"math"
)

func main() {
	v := Point{5, 12}
	fmt.Println(v, Abs(v))

	// Scale(v, 10) // 值调用编译出错:cannot use v (type Point) as type *Point in argument to Scale
	// fmt.Println(v, Abs(v))

	Scale(&v, 10) // 指针调用
	fmt.Println(v, Abs(v))

}

type Point struct {
	X float64
	Y float64
}

// 值函数
func Abs(p Point) float64 {
	return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

// 指针函数
func Scale(p *Point, f float64) { // 注意该函数第一个参数的声明有一个 *
	p.X = p.X * f
	p.Y = p.Y * f
}

尝试去掉上边第12行的注释看看

2.3 方法与指针重定向

从前两个示例中可以看出,指针参数的函数必须接受一个指针类型的值:

v := Point{5, 12}
// Scale(v, 10) // 值调用编译出错
Scale(&v, 10)   // 指针调用

但在指针接收者方法中,接收者既可以是值又可以是指针:

v := Point{5, 12}
v.Scale(10) // 值调用
p := &v
p.Scale(10) // 指针调用

对于语句v.Scale(10),即便v是个值而非指针,带指针接收者的方法也能被直接调用。也就是说,由于Scale方法有一个指针接收者,方便起见,Go会将语句v.Scale(10)解释为(&v).Scale(10)

反过来也是一样。

接受一个值作为参数的函数必须接受一个指定类型的值:

v := Point{5, 12}
fmt.Println(v, Abs(v))  // 正常编译
fmt.Println(v, Abs(&v)) // 编译出错

而以值为接收者的方法被调用时,接收者既能为值又能为指针:

v := Point{5, 12}
fmt.Println(v.Abs()) // 正常编译
p := &v
fmt.Println(p.Abs()) // 正常编译

这种情况下,方法调用p.Abs()会被解释为(*p).Abs()

2.4 指针和值的区别

以指针或值为接收者的区别在于:值方法可通过指针和值调用,而指针方法只能通过指针来调用。
原因是,指针方法可以修改接收者指向的值,尤其是在比较大的数据结构中,这样做会更高效;而值方法会导致接收值的副本(值复制),所以任何修改都会被丢弃。因此,语言层面不允许这种错误出现。但是有一个例外,即:当值是可寻址的时候,语言就会自动插入取址符来处理一般的通过值调用的指针方法。如上述示例中的v,当使用调用v.Scale(10)时,语言会自动转换为(&v).Scale(10)

通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。

参考:
https://golang.org/ref/spec#Method_expressions
https://golang.org/doc/effective_go.html#pointers_vs_values

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值