Golang中接收者方法语法糖的使用方法详解

 更新时间:2023年05月17日 08:34:31   作者:人艰不拆_zmc  
这篇文章主要为大家详细介绍了Golang中接收者方法语法糖的使用方法,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的可以了解一下

1、概述

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

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

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

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

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{} 值不能寻址。

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

例如,下面的代码定义了一个整型变量 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 中,当一个变量是可变的(也就是说,该变量是一个具有地址的变量,我们可以对其进行寻址操作),我们可以通过对该变量的指针进行方法调用来执行对该变量的操作,否则就会导致编译错误。

到此这篇关于Golang中接收者方法语法糖的使用方法详解的文章就介绍到这了,更多相关Golang语法糖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang自旋锁的相关介绍

    Golang自旋锁的相关介绍

    自旋锁是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断地判断是否能够被成功获取,知直到获取到锁才会退出循环
    2022-10-10
  • Go语言上下文context底层原理

    Go语言上下文context底层原理

    这篇文章主要介绍了Go语言上下文context底层原理,context是Go中用来进程通信的一种方式,其底层是借助channl与snyc.Mutex实现的,更多相关内容需要的小伙伴可以参加一下
    2022-06-06
  • Go语言编译程序从后台运行,不出现dos窗口的操作

    Go语言编译程序从后台运行,不出现dos窗口的操作

    这篇文章主要介绍了Go语言编译程序从后台运行,不出现dos窗口的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法

    Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法

    对音视频一直是小白,决定沉下心来,好好研究一下音视频知识,下面这篇文章主要给大家介绍了关于Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法,需要的朋友可以参考下
    2022-12-12
  • Golang中interface转string输出打印方法

    Golang中interface转string输出打印方法

    这篇文章主要给大家介绍了关于Golang中interface转string输出打印的相关资料,在go语言中interface转string可以直接使用fmt提供的fmt函数,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • 关于Golang变量初始化/类型推断/短声明的问题

    关于Golang变量初始化/类型推断/短声明的问题

    这篇文章主要介绍了关于Golang变量初始化/类型推断/短声明的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-02-02
  • 使用Go语言实现Yaml编码和解码的方法详解

    使用Go语言实现Yaml编码和解码的方法详解

    在这篇文章中,我们将介绍如何使用Go语言编写代码来实现Yaml编码和解码,文中有详细的代码示例供大家参考,对大家的学习和工作有一定的帮助,需要的朋友可以参考下
    2023-11-11
  • Golang http包构建RESTful API的实现

    Golang http包构建RESTful API的实现

    在Go语言中实现RESTful API可以利用标准库net/http提供的功能,它允许你轻松地创建和处理HTTP请求,本文主要介绍了Golang http包构建RESTful API的实现,感兴趣的可以了解一下
    2024-01-01
  • 本地使用Docker搭建go开发环境的全过程

    本地使用Docker搭建go开发环境的全过程

    最近想学习一下golang,自己之前一直把环境全部安装在docker上,所以这次也想把golang的环境安装在docker上,下面这篇文章主要给大家介绍了关于本地使用Docker搭建go开发环境的相关资料,需要的朋友可以参考下
    2022-07-07
  • Go实现比较时间大小

    Go实现比较时间大小

    这篇文章主要介绍了Go实现比较时间大小的方法和示例,非常的简单实用,有需要的小伙伴可以参考下。
    2015-04-04

最新评论