Golang接收者方法语法糖

科技资讯 投稿 23900 0 评论

Golang接收者方法语法糖

1、概述

在介绍Golang接收者方法语法糖前,先简单说下Go 语言的指针 (Pointer,大致上理解如下:

    变量名前的 & 符号,是取变量的内存地址,不是取值;
  • 数据类型前的 * 符号,代表要储存的是对应数据类型的内存地址,不是存值;
  • 变量名前的 * 符号,代表从内存地址中取值 (Dereferencing。

2、接收者方法语法糖

在 Go 中,对于自定义类型 T,为它定义方法时,其接收者可以是类型 T 本身,也可能是 T 类型的指针 *T。

type Instance struct{} func (ins *Instance Foo( string { return "" }

在上例中,我们定义了 Instance 的 Foo 方法时,其接收者是一个指针类型(*Instance)。

func main( { var _ = Instance{}.Foo( //编译错误:cannot call pointer method on Instance{},变量是不可变的(该变量没有地址,不能对其进行寻址操作) }

因此,如果我们用 Instance 类型本身 Instance{} 值去调用 Foo 方法,将会得到以上错误。

type Instance struct{} func (ins Instance Foo( string { return "" } func main( { var _ = Instance{}.Foo( // 编译通过 }

此时,如果我们将 Foo 方法的接收者改为 Instance 类型,就没有问题。

这说明,定义类型 T 的函数方法时,其接收者类型决定了之后什么样的类型对象能去调用该函数方法。但,实际上真的是这样吗?

type Instance struct{} func (ins *Instance String( string { return "" } func main( { var ins Instance _ = ins.String( // 编译器会自动获取 ins 的地址并将其转换为指向 Instance 类型的指针_ = (&ins.String( }

实际上,即使是我们在实现 Foo 方法时的接收者是指针类型,上面 ins 调用的使用依然没有问题。

Ins 值属于 Instance 类型,而非 *Instance,却能调用 Foo 方法,这是为什么呢?这其实就是 Go 编译器提供的语法糖!

当一个变量可变时(也就是说,该变量是一个具有地址的变量,我们可以对其进行寻址操作),我们对类型 T 的变量直接调用 *T 方法是合法的,因为 Go 编译器隐式地获取了它的地址。变量可变意味着变量可寻址,因此,上文提到的 Instance{}.Foo( 会得到编译错误,就在于 Instance{} 值不能寻址。

例如,下面的代码定义了一个整型变量 x,它没有被显式初始化,但是在分配内存时仍然具有一个地址:

var x int fmt.Printf("%p\n", &x // 输出变量 x 的内存地址

输出结果类似于:0xc0000120a0,表明变量 x 的内存地址已经被分配了。但是由于变量没有被初始化,x 的值将为整型的默认值 0

3、深入测试

3.1 示例

package main type B struct { Id int } func New( B { return B{} } func New2( *B { return &B{} } func (b *B Hello( { return } func (b B World( { return } func main( { // 方法的接收器为 *T 类型 New(.Hello( // 编译不通过 b1 := New( b1.Hello( // 编译通过 b2 := B{} b2.Hello( // 编译通过 (B{}.Hello( // 编译不通过 B{}.Hello( // 编译不通过 New2(.Hello( // 编译通过 b3 := New2( b3.Hello( // 编译通过 b4 := &B{} // 编译通过 b4.Hello( // 编译通过 (&B{}.Hello( // 编译通过 // 方法的接收器为 T 类型 New(.World( // 编译通过 b5 := New( b5.World( // 编译通过 b6 := B{} b6.World( // 编译通过 (B{}.World( // 编译通过 B{}.World( // 编译通过 New2(.World( // 编译通过 b7 := New2( b7.World( // 编译通过 b8 := &B{} // 编译通过 b8.World( // 编译通过 (&B{}.World( // 编译通过 }

输出结果:

./main.go:25:10: cannot call pointer method on New( ./main.go:25:10: cannot take the address of New( ./main.go:33:10: cannot call pointer method on B literal ./main.go:33:10: cannot take the address of B literal ./main.go:34:8: cannot call pointer method on B literal ./main.go:34:8: cannot take the address of B literal

3.2 问题总结

假设 T 类型的方法上接收器既有 T 类型的,又有 *T 指针类型的,那么就不可以在不能寻址的 T 值上调用 *T 接收器的方法
    &B{} 是指针,可寻址
  • B{} 是值,不可寻址
  • b := B{} b是变量,可寻址

4、总结 

在 Golang 中,当一个变量是可变的(也就是说,该变量是一个具有地址的变量,我们可以对其进行寻址操作),我们可以通过对该变量的指针进行方法调用来执行对该变量的操作,否则就会导致编译错误。

参考:Go 挖坑指南: cannot take the address & cannot call pointer method

编程笔记 » Golang接收者方法语法糖

赞同 (113) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽