提问人:Don Draper 提问时间:8/8/2021 最后编辑:blackgreenDon Draper 更新时间:8/9/2021 访问量:1910
为什么 Go 中的作业会创建副本?
Why does assignment in Go create a copy?
问:
我会稍微澄清一下这个问题。我已经(几乎完全)阅读了 Go 规范、FAQ、Effective Go,当然还有 Tour of Go。
我知道 Go 是一种“按值传递”的语言,甚至设法对这种行为进行推理并理解所有含义。
Go 中的所有作业也会创建副本。在某些情况下,它只是一个值,在某些情况下,它只是一个指针。对于某些数据结构,这有点棘手,因为整个结构是复制的,并且可能包含指向另一个数据结构的隐式指针。
问题是:语言规范中明确指出作业总是创建副本的什么?
我觉得一旦你了解了 Go 中没有引用,它甚至不需要提及,但规范中关于赋值语句的部分甚至没有提到按值传递语义。
我觉得文档中一定有一些东西详细描述了该行为,而由于缺乏一些基本的误解,我没有意识到解释的存在。
答:
在不赘述太多细节的情况下,规范中的这些摘录应该会提供一些清晰的信息:
变量是用于保存值的存储位置。
变量的值 [...] 是分配给变量的最新值。
在语言层面上,定义值的“复制”并不是真正必要的。正如我们通常所理解的那样,复制的重要含义是,修改“复制到”值不会改变“复制自”值*。从上面的引文中可以推断出这个属性。
- (这里需要注意的是,“价值”和“数据结构”不是一回事。数据结构可以由多个值组成。
语言规范中明确指出赋值总是创建副本的哪些内容?
没有什么明确的,但你也许可以从变量中推断出这一点,这也很好地解决了函数签名的情况:
变量声明,或者对于函数参数和结果,函数声明或函数文本的签名为命名变量保留存储空间。
如果存储是保留的,那么以后当你将一元表达式的结果分配给它时——例如另一个变量——那么它必须是一个副本,否则你就会有内存别名。这就是戴夫·切尼(Dave Cheney)在《Go 中没有传递引用》中所说的。
与 C++ 不同,Go 程序中定义的每个变量都占用唯一的内存位置。
这还有一个更重要的含义,即零值。如果未在变量的声明中提供用于初始化变量的表达式,则为该变量保留的存储将指定零值作为默认值。
该规范实际上在这里明确地谈到了这一点:
https://golang.org/ref/spec#Calls
特别:
计算后,调用的参数将按值传递给函数,并且被调用的函数开始执行。当函数返回时,函数的返回参数将按值传递回调用方。
关于赋值:我所知道的大多数语言的所有赋值都会创建值的副本(请参阅注释中的 Python 异常)。RHS 上值的副本将分配给 LHS。如果 RHS 是指针,则该指针的副本将分配给 LHS。
评论
Why does assignment in Go create a copy?
严格来说,设计决策。认为你的任务标题更好,where in the spec does it mention that the language passes by value
;