使用指针接收器时,值对象自动取指针的奇怪问题

常见的 Go 教材里都提到,如果在方法中使用指针接受器,仍然可以在值对象上调用该方法,前提是该值可以取指针。


但是,如果我们不将值保存到变量中,而是直接“串联”函数调用,则无法自动取地址。


例如:


package main

import (
"fmt"
"reflect"
)

type Circle struct {
radius float64
}

func NewCircle(radius float64) Circle {
return Circle{radius: 0}
}

func (c *Circle) DummyMethod() {
fmt.Println("Type of receiver:", reflect.TypeOf(c))
}

func main() {
//m := NewCircle(1)
NewCircle(1).DummyMethod() // Chaining function calls results in error.
}

在这里,构造函数 NewCircle() 返回值类型,我们不将其保存到变量中,直接“串联”函数调用。执行以上代码,输出
./test.go:24: cannot call pointer method on NewCircle(1)
./test.go:24: cannot take the address of NewCircle(1)


但是,如果将 NewCircle() 返回的值先保存到变量里,再调用 (c *Circle) DummyMethod(),却可以正常自动取地址:


func main() {
m := NewCircle(1)
m.DummyMethod()
}

输出:Type of receiver: *main.Circle


这里似乎触及了语法糖的内部实现问题?但没有在书籍和文档中找到说明。有没有大佬能解释一下

已邀请:

LockingCoder - 80后

赞同来自: 梁昊

我的想法是,你这里声明的方法是指针接收者,所以要获取到对象的地址才可以,参考C语言里函数的返回值是保存在寄存器中的,但是寄存器中只有对象的值,没有对象的地址,所以无法调用。
我刚才试了一下,如果声明的方法是值接收者,是可以的。
把返回值赋值给一个变量,是会在内存栈中为这个变量分配地址保存对象值的,所以能够获取地址,调用方法。

cholerae

赞同来自: 梁昊

这种问题看语言规范啊。


https://golang.org/ref/spec#Address_operators


看 addressable 的定义。

simple - 分布式 & Java & Go

赞同来自: 梁昊

从我的角度来说,你的问题的核心是:临时对象到底能不能取地址?这应该取决于临时对象的生命周期到底有多长。



  1. 如果你把一个返回值赋值给一个变量,其实这里有一次内存copy,把临时对象的内容copy给了变量,然后临时对象的生命周期就可以结束了。

  2. 从C++的角度来分析一下,在C++11中引入了右值引用,主要就是为了避免函数调用返回临时大对象的copy问题,也延长了临时对象的生命周期(与右值引用相同)。Go语言可能并没有右值引用的逻辑,我猜测函数调用结束过,临时对象的生命周期应该就结束的,所以不让取地址(对象内存地址可能已无效)。

梁昊

赞同来自:

感谢 dalao 分享。的确,不光不能自动取地址,手动取地址也不行:
fmt.Printf("%p\n", &(NewCircle(2)))
(cannot take the address of NewCircle(2))

 



@LockingCoder: 参考C语言里函数的返回值是保存在寄存器中的,但是寄存器中只有对象的值,没有对象的地址,所以无法调用。



在其它地方看到一段话:“然后需要注意的是因为golang支持多值返回,所以是在返回前把返回值压到栈中的,而c语言是把返回值存到寄存器中返回”
看来还是不太一样?..... 不太明白


http://www.cnblogs.com/stormpeach/p/6894949.html
http://www.jb51.net/article/92028.htm
http://blog.csdn.net/zdy0_2004/article/details/53464276

梁昊

赞同来自:

@cholerae 谢谢简单明了的回答,看来 NewCircle(2) 不属于列出的任意一种情况。
不过之前习惯了动态语言"一切皆对象"那一套东西后,很容易想当然以为 NewCircle(2) 也属于变量。


看了 Address Operator 一节后,倒是发现了一个新问题。由于我的问题都比较初级,就不新开贴子了:
语言规范中这一节提到:...x may also be a composite literal. 例如,&(Point{2,3}) 是合法的。


但 《The Go Programming Language》第158-159 页提到:“We cannot call a *Point method on a non addressable Point receiver, because there is no way to obtain the address of a temporary value: Point{1, 2}.ScaleBy(2) // compile error: can't take the address of Point literal”


根据语言规范,Point{1,2} 是 addressable 的,为何还会出现 'can't take the address of Point literal' 呢?


但是手动取地址再调用是可以的:(&(Point{1,2})).ScaleBy(2) 这一点与主贴中的情况不同。

要回复问题请先登录注册