经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
Golang接收者方法语法糖
来源:cnblogs  作者:人艰不拆_zmc  时间:2023/5/17 8:52:42  对本文有异议

1、概述

在《Golang常用语法糖》这篇博文中我们讲解Golang中常用的12种语法糖,在本文我们主要讲解下接收者方法语法糖。

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

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

注意 1:golang 指针详细介绍请参见《Golang指针隐式间接引用》此篇博文。

2、接收者方法语法糖

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

  1. type Instance struct{}
  2.  
  3. func (ins *Instance) Foo() string {
  4. return ""
  5. }

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

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

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

  1. type Instance struct{}
  2.  
  3. func (ins Instance) Foo() string {
  4. return ""
  5. }
  6.  
  7. func main() {
  8. var _ = Instance{}.Foo() // 编译通过
  9. }

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

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

  1. type Instance struct{}
  2.  
  3. func (ins *Instance) String() string {
  4. return ""
  5. }
  6.  
  7. func main() {
  8. var ins Instance
  9. _ = ins.String() // 编译器会自动获取 ins 的地址并将其转换为指向 Instance 类型的指针_ = (&ins).String()
  10. }

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

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

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

注意 1:在 Go 中,即使变量没有被显式初始化,编译器仍会为其分配内存空间,因此变量仍然具有内存地址。不过,由于变量没有被初始化,它们在分配后仅被赋予其类型的默认零值,而不是初始值。当然,这些默认值也是存储在变量分配的内存空间中的。

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

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

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

3、深入测试

3.1 示例

  1. package main
  2.  
  3. type B struct {
  4. Id int
  5. }
  6.  
  7. func New() B {
  8. return B{}
  9. }
  10.  
  11. func New2() *B {
  12. return &B{}
  13. }
  14.  
  15. func (b *B) Hello() {
  16. return
  17. }
  18.  
  19. func (b B) World() {
  20. return
  21. }
  22.  
  23. func main() {
  24. // 方法的接收器为 *T 类型
  25. New().Hello() // 编译不通过
  26.  
  27. b1 := New()
  28. b1.Hello() // 编译通过
  29.  
  30. b2 := B{}
  31. b2.Hello() // 编译通过
  32.  
  33. (B{}).Hello() // 编译不通过
  34. B{}.Hello() // 编译不通过
  35.  
  36. New2().Hello() // 编译通过
  37.  
  38. b3 := New2()
  39. b3.Hello() // 编译通过
  40.  
  41. b4 := &B{} // 编译通过
  42. b4.Hello() // 编译通过
  43.  
  44. (&B{}).Hello() // 编译通过
  45.  
  46. // 方法的接收器为 T 类型
  47. New().World() // 编译通过
  48.  
  49. b5 := New()
  50. b5.World() // 编译通过
  51.  
  52. b6 := B{}
  53. b6.World() // 编译通过
  54.  
  55. (B{}).World() // 编译通过
  56. B{}.World() // 编译通过
  57.  
  58. New2().World() // 编译通过
  59.  
  60. b7 := New2()
  61. b7.World() // 编译通过
  62.  
  63. b8 := &B{} // 编译通过
  64. b8.World() // 编译通过
  65.  
  66. (&B{}).World() // 编译通过
  67. }

输出结果:

  1. ./main.go:25:10: cannot call pointer method on New()
  2. ./main.go:25:10: cannot take the address of New()
  3. ./main.go:33:10: cannot call pointer method on B literal
  4. ./main.go:33:10: cannot take the address of B literal
  5. ./main.go:34:8: cannot call pointer method on B literal
  6. ./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 中的那些语法糖

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

原文链接:https://www.cnblogs.com/zhangmingcheng/p/17395367.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号