Go指针接收者和值接收者

Go指针接收者和值接收者

要讨论这个问题,要明白Go中结构体的接收者实际上就是调用方法的对象,而这个对象有指针的值类型两种情况。

首先要讨论针对单个结构体对象的方法接收者:

  • 当结构体方法使用 指针接收者 时,无论 结构体是指针还是值对象,都可以修改结构体字段值。调用时:
    • 指针接收者 但是 指针对象,直接执行 obj.method()
    • 指针接收者 但是 值对象,值对象先取地址操作再执行 (&obj).method()支持自动寻址
  • 当结构体方法使用 值接收者 时,无论 结构体是指针还是值对象,都无法修改结构体字段值。调用时:
    • 值接收者 但是 指针对象,指针对象先解引用再执行 (*obj).method()
    • 值接收者 但是 值对象,直接执行 obj.method()

下面再讨论当使用 接口 调用实现了该接口方法的 结构体对象方法 时的情况。

PS:需要记住,可以将直接将结构体的 指针对象值对象 赋值给 接口变量!!!

这种情况较为特别,主要是因为接口 懒惰,可以将指向的结构体对象解引用,但是无法自动取地址。

用官方的话来说:The concrete value stored in an interface is not addressable

存储在接口中的具体值(也就是值类型的结构体)是不可寻址的(也就是无法通过&取地址符号来自动寻址)。(这主要针对接口中保存 值类型 的结构体,但是调用的方法却使用了 指针接收器,这种情况下是无法通过 &取地址 获取到想要的指针对象)。

1
2
存储在接口中的具体值是不可寻址的,就像地图元素不可寻址一样。
因此,当您在接口上调用方法时,它必须具有相同的接收器类型,或者必须可以直接从具体类型中辨别出来:指针和值接收器方法可以分别使用指针和值调用,正如您所期望的那样。可以使用指针值调用值接收器方法,因为它们可以首先被取消引用。 然而,指针接收器方法不能用值调用,因为存储在接口中的值没有地址。 将值分配给接口时,编译器确保所有可能的接口方法实际上都可以在该值上调用,因此尝试进行不正确的分配将在编译时失败。

下面看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
type Service interface {
Handle() string
SetName(name string)
}

type ProxyService struct{ name string }
func (s *ProxyService) Handle() string { return "ProxyService " + s.name }
func (s *ProxyService) SetName(name string) { s.name = name }

type SettingService struct{ name string }
func (s SettingService) Handle() string { return "SettingService " + s.name }
func (s SettingService) SetName(name string) { s.name = name }

type BackgroundService struct{ name string }
func (s *BackgroundService) Handle() string { return "BackgroundService " + s.name }
func (s BackgroundService) SetName(name string) { s.name = name }

func handleService(service Service) {
service.SetName("xxx")
fmt.Println(service.Handle())
}

func main() {
proxyService := &ProxyService{name: "test"}
proxyService2 := ProxyService{name: "test"}

settingService := &SettingService{name: "test"}
settingService2 := SettingService{name: "test"}

backService := &BackgroundService{name: "test"}
backService2 := BackgroundService{name: "test"}

proxyService.SetName("test2")
proxyService2.SetName("test2")
settingService.SetName("test2")
settingService2.SetName("test2")
backService.SetName("test2")
backService2.SetName("test2")

fmt.Println(proxyService.name)
fmt.Println(proxyService2.name)
fmt.Println(settingService.name)
fmt.Println(settingService2.name)
fmt.Println(backService.name)
fmt.Println(backService2.name)

handleService(proxyService)
// does not implement 'Service' as the 'Handle' method has a pointer receiver
// proxyService2 为【值对象】,存储在接口中是自动不可寻址的
// 因此无法通过 (&proxyService2).Handle 和 (&proxyService2).SetName 调用 【指针接收器的方法】
// handleService(proxyService2)

handleService(settingService)
// settingService2 为【值对象】,存储在接口中是自动不可寻址的
// 但是可以直接 proxyService2.Handle 和 proxyService2.SetName 调用 【值接收器的方法】
// handleService(proxyService2)
handleService(settingService2)

handleService(backService)
// does not implement 'Service' as the 'Handle' method has a pointer receiver
// 由于 backService2 是【值对象】,存储在接口中是自动不可寻址的
// 因此也就无法通过自动寻址 (&backService2).Handle() 调用 【指针接收者方法】
// 但是可以调用 【值接收者的方法】 backService2.SetName()
// handleService(backService2)
}

输出

1
2
3
4
5
6
7
8
9
10
test2
test2
test
test
test
test
ProxyService xxx
SettingService test
SettingService test
BackgroundService test

参考


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!