• 周五. 11 月 13th, 2026

GO语言方法

由root

10 月 14, 2019 #Golang, #方法

6.1 定义

方法是与对象实例绑定的特殊函数
方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。
可以为当前包,以及除接口和指针以外的任何类型定义方法
type N int
func (n N) toString() string {
	return fmt.Sprintf("%#x", n)
}
func main() {
	var a N = 25
	println(a.toString())
}

方法不支持重载。
receiver参数名没有限制,按惯例选用简短有意义的名称(不推荐this,self).
如方法内部不引用实例,可省略参数名,仅保留类型
type N int
func (N) test() {
    println("hi!")
}
方法可看作特殊的函数,那么receiver的类型自然可以是基础类型或指针类型。这会关系到调用时对象实例是否被复制
type N int
func (n N) value() {
	n++
	fmt.Printf("v:%p, %v\n", &n, n)
}
func (n *N) pointer() {
	(*n)++
	fmt.Printf("p:%p, %v\n", n, *n)
}
func main() {
	var a N = 25
	a.value()
	a.pointer()
	fmt.Printf("a:%p, %v\n", &a, a)
}
可使用实例值或指针调用方法,编译器会根据方法receiver类型自动在基础类型和指针类型间转换, 不能用多级指针调用方法,指针类型的receiver必须是合法指针(包括nil),或能获取实例地址.
如何选择方法的receiver类型
  • 要修改实例状态, 用*T
  • 无须修改状态的小对象或固定值,建议用T
  • 大对象建议用*T,以减少复制成本
  • 引用类型、字符串、函数等指针包装对象,直接用T
  • 若包含Mutex等同步字段,用*T, 避免因复制造成锁操作无效
  • 其他无法确定的情况,都用*T.

6.2 匿名字段

可以像访问匿名字段成员那样调用其方法,由编译器负责查找
type data struct {
	sync.Mutex
	buf [1024]byte
}

func main() {
	d := data{}
	d.Lock()	//编译会处理为sync.(*Mutex).Lock()调用
	defer d.Unlock()
}
方法也会有同名遮蔽问题。但利用这种特性,可实现类似覆盖操作
type user struct {

}
type manager struct {
	user
}
func (user) toString() string {
	return "user"
}
func (m manager) toString() string {
	return m.user.toString() + "; manager"
}
func main() {
	var m manager
	println(m.toString())
	println(m.user.toString())
}
尽管能直接访问匿名字段的成员和方法,但它们依然不属于继承关系。

6.3 方法集

类型有一个与之相关的方法集,这决定了它是否实现某个接口。
  • 类型T方法集包含所有receiver T方法
  • 类型*T方法集包含所有receiver T + *T 方法
  • 匿名嵌入S, T方法集包含所有receiver S方法
  • 匿名嵌入*S, T方法集包含所有receiver S + *S方法
  • 匿名嵌入S 或*S, *T方法集包含所有receiver S + *S方法。

6.4 表达式

通过类型引用的method expression会被还原为普通函数样式,receiver是第一参数,调用时须显式传参。至于类型,可以是T或*T,只要目标方法存在于该类型方法集中即可.
type N int
func (n N) test() {
	fmt.Printf("test.n:%p, %d\n", &n, n)
}
func main() {
	var n N = 25
	fmt.Printf("main.n:%p, %d\n",&n, n)
	f1 := N.test
	f1(n)

	f2 := (*N).test
	f2(&n)
}
尽管*N方法集包装的test方法receiver类型不同,但编译器会保证原定义类型拷贝传值。当然可直接以表达式方式调用
func main() {
    var n N = 25
    N.test(n)
    (*N).test(&n)
}
基于实例或指针引用的method value,参数签名不会改变,依旧按正常方式调用。
当method value被赋值给变量或作为参数传递时,会立即计算并复制该方法执行所需的receiver对象,与其绑定,以便在稍后执行时,能隐式传入receiver参数
type N int
func (n N) test() {
	fmt.Printf("test.n:%p, %v\n", &n, n)
}
func main() {
	var n  N = 100
	p := &n

	n++
	f1 := n.test  //因为test方法的receiver是N类型,所以复制n,等于101

	n++
	f2 := p.test //复制*p, 等于102

	n++
	fmt.Printf("main.n:%p, %v\n", p, n)

	f1()
	f2()
}
编译器会为method value生万一个包装函数,实现间接调用。至于receiver复制,和闭包的实现方法基本相同,打包成funcval,经由DX寄存器传递
当method value作为参数时, 会复制含reeiver在内的整个method value
type N int
func (n N) test() {
	fmt.Printf("test.n:%p, %v\n", &n, n)
}
func call(m func()) {
	m()
}
func main() {
	var n N = 100
	p := &n
	fmt.Printf("main.n:%p, %v\n", p, n)

	n++
	call(n.test)

	n++
	call(p.test)
}
如果目标方法的receiver是指针类型,那么被复制的仅是指针
type N int
func (n *N) test() {
	fmt.Printf("test.n: %p, %v\n", n, *n)
}
func main() {
	var n N = 100
	p := &n

	n++
	f1 := n.test

	n++
	f2 := p.test

	n++
	fmt.Printf("main.n:%p, %v\n", p, n)

	f1()		//延迟调用 n==103
	f2()
}
只要receiver参数类型正确,使用nil同样可以执行

由 root

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注