为什么 Go 中的作业会创建副本?

Why does assignment in Go create a copy?

提问人:Don Draper 提问时间:8/8/2021 最后编辑:blackgreenDon Draper 更新时间:8/9/2021 访问量:1910

问:

我会稍微澄清一下这个问题。我已经(几乎完全)阅读了 Go 规范、FAQ、Effective Go,当然还有 Tour of Go。

我知道 Go 是一种“按值传递”的语言,甚至设法对这种行为进行推理并理解所有含义。

Go 中的所有作业也会创建副本。在某些情况下,它只是一个值,在某些情况下,它只是一个指针。对于某些数据结构,这有点棘手,因为整个结构是复制的,并且可能包含指向另一个数据结构的隐式指针。

问题是:语言规范中明确指出作业总是创建副本的什么?

我觉得一旦你了解了 Go 中没有引用,它甚至不需要提及,但规范中关于赋值语句的部分甚至没有提到按值传递语义。

我觉得文档中一定有一些东西详细描述了该行为,而由于缺乏一些基本的误解,我没有意识到解释的存在。

go 变量赋值

评论

0赞 Hymns For Disco 8/8/2021
也许这种混淆来自于将“按价值传递”和“复制”混为一谈。Go 是按值传递的,这比“复制”要简单得多,后者是一个非常宽泛的术语。
0赞 Don Draper 8/8/2021
例如,FAQ 文档说“函数总是获取正在传递的事物的副本,就好像有一个赋值语句将值分配给参数一样”。这涵盖了函数调用的行为,但根本没有描述赋值本身。我只是想知道这是否太明显而无法详细记录,或者我在文档中遗漏了一些东西
0赞 Hymns For Disco 8/8/2021
您能否进一步解释为什么某些数据结构在复制方面“有点棘手”?
0赞 Don Draper 8/8/2021
我可能用错了措辞。我的意思是,需要时间才能理解,对于切片,复制的是切片标头。这里没有什么棘手的,只需阅读有关切片和地图如何实现的官方博客文章即可。不过,这与我的问题无关:)
0赞 8/9/2021
Why does assignment in Go create a copy?严格来说,设计决策。认为你的任务标题更好,where in the spec does it mention that the language passes by value;

答:

2赞 Hymns For Disco 8/8/2021 #1

在不赘述太多细节的情况下,规范中的这些摘录应该会提供一些清晰的信息:

变量是用于保存值的存储位置。

变量的值 [...] 是分配给变量的最新值。

在语言层面上,定义值的“复制”并不是真正必要的。正如我们通常所理解的那样,复制的重要含义是,修改“复制到”值不会改变“复制自”值*。从上面的引文中可以推断出这个属性。

  • (这里需要注意的是,“价值”和“数据结构”不是一回事。数据结构可以由多个值组成。
3赞 blackgreen 8/8/2021 #2

语言规范中明确指出赋值总是创建副本的哪些内容?

没有什么明确的,但你也许可以从变量中推断出这一点,这也很好地解决了函数签名的情况:

变量声明,或者对于函数参数和结果,函数声明或函数文本的签名为命名变量保留存储空间。

如果存储是保留的,那么以后当你将一元表达式的结果分配给它时——例如另一个变量——那么它必须是一个副本,否则你就会有内存别名。这就是戴夫·切尼(Dave Cheney)在《Go 中没有传递引用》中所说的。

与 C++ 不同,Go 程序中定义的每个变量都占用唯一的内存位置。

这还有一个更重要的含义,即零值。如果未在变量的声明中提供用于初始化变量的表达式,则为该变量保留的存储将指定零值作为默认值。

-1赞 Burak Serdar 8/8/2021 #3

该规范实际上在这里明确地谈到了这一点:

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

特别:

计算后,调用的参数将按值传递给函数,并且被调用的函数开始执行。当函数返回时,函数的返回参数将按值传递回调用方。

关于赋值:我所知道的大多数语言的所有赋值都会创建值的副本(请参阅注释中的 Python 异常)。RHS 上值的副本将分配给 LHS。如果 RHS 是指针,则该指针的副本将分配给 LHS。

评论

1赞 Don Draper 8/8/2021
Python 中的赋值不会复制任何内容。他们只是创建绑定