在《Golang常用语法糖》这篇博文中我们讲解Golang中常用的12种语法糖,在本文我们主要讲解下接收者方法语法糖。
在介绍Golang接收者方法语法糖前,先简单说下Go 语言的指针 (Pointer),大致上理解如下:
【资料图】
2、接收者方法语法糖注意 1:golang 指针详细介绍请参见《Golang指针隐式间接引用》此篇博文。
在 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{} 值不能寻址。
3、深入测试3.1 示例注意 1:在 Go 中,即使变量没有被显式初始化,编译器仍会为其分配内存空间,因此变量仍然具有内存地址。不过,由于变量没有被初始化,它们在分配后仅被赋予其类型的默认零值,而不是初始值。当然,这些默认值也是存储在变量分配的内存空间中的。
例如,下面的代码定义了一个整型变量
x
,它没有被显式初始化,但是在分配内存时仍然具有一个地址:var x intfmt.Printf("%p\n", &x) // 输出变量 x 的内存地址输出结果类似于:
0xc0000120a0
,表明变量x
的内存地址已经被分配了。但是由于变量没有被初始化,x
的值将为整型的默认值0
。
package maintype 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 literal3.2 问题总结假设
T
类型的方法上接收器既有T
类型的,又有*T
指针类型的,那么就不可以在不能寻址的T
值上调用*T
接收器的方法&B{}
是指针,可寻址B{}
是值,不可寻址b := B{}
b是变量,可寻址4、总结在 Golang 中,当一个变量是可变的(也就是说,该变量是一个具有地址的变量,我们可以对其进行寻址操作),我们可以通过对该变量的指针进行方法调用来执行对该变量的操作,否则就会导致编译错误。
参考:Go 中的那些语法糖
参考:Go 挖坑指南: cannot take the address & cannot call pointer method