在Google Go中,我读到字符串是不可变的,好吧但是是int? 其他类型呢?作为一个稍微老一点的程序员,我更喜欢可变性,即使我知道不变性的好处,我更喜欢生活危险。
了解哪些类型是可变的或不可变的非常有帮助。
更新,我最关心的是实际问题,取决于可变或不可变的类型。与Java中的典型示例一样,如果在循环中创建一个String并循环10,000次,则将创建10,000个String,然后进行垃圾回收。在我工作的公司的项目中,这实际上是一个严重的问题。
问题是,Go的不变性在某些情况下会导致同样的问题吗?
它影响你应该如何对待var。 (或者我认为确实如此)。
再次更新,我也关注其他实际问题。知道某些东西是不可变的意味着我可以编写并行的代码,并且对该对象的一个引用的更新不应该更新其他引用。但有时我想做危险的事情,我想要变性。
这些是可变性与不变性的结果,并影响我编写代码的方式。
答案 0 :(得分:32)
别担心 - 如果你真的想要,Go会让你自己在脚下射击: - )
Go不像Erlang,这可能是你对问题的看法。
x := 1
x = 2
分配一个变量x
,其值为1
,然后将其重新分配给2
- 此处不分配额外的内存。
正如您所注意到的,字符串是不可变的,因此执行字符串操作可能会导致复制。如果您发现要对字符数据进行就地修改,则可能希望通过[]byte
包对bytes
的变量进行操作。
Russ Cox关于此问题的帖子应该回答大部分关于基本数据结构的问题:http://research.swtch.com/2009/11/go-data-structures.html
正如其他评论者所指出的,你会想看看Go函数的值语义 - 起初它们可能有点令人惊讶。
如果您有以下功能:
func (t MyType) myFunc() {
// do something to set a field in t
}
你打电话给你的代码
myVar.myFunc()
您可能会惊讶地发现这不符合您的要求,因为t
中显示的myFunc()
实际上是myVar
的副本 }。
但是,以下将工作:
func (t *myType) myFunc() {
// do something to set a field in t
}
因为该函数具有指针的副本,并且可以通过该指针访问底层结构。
答案 1 :(得分:9)
在我看来,首先应该分开以下两个概念:
整数作为数学对象(即:值)
int
然后答案是:整数变量是可变的,整数值是不可变的。
此视图与Go规范一致,该规范声明字符串是不可变的。显然,字符串变量是可变的。
Go中的变量(作为一个概念)至少是:
var i int
)Mutable Go对象:
不可变的Go对象:
int
类型的值)去某些人可能认为可变的对象,而其他人可能认为它们是不可变的:
答案 2 :(得分:2)
是的, immutable 这个词在Go规范中只出现一次。这是在讨论type string
时。我认为你应该从Assignability和Addressability的双重观点来看待它。例如,Go显然会禁止您将重新绑定变量转换为具有未导出属性的类型的不同值。有点像C ++中没有提供复制构造函数的类,但在Go Pimpl感觉不那么尴尬,通过沟通 哲学来适应goroutines的 分享。
答案 3 :(得分:2)
“可变性”只有当你谈到某种复合类型时才有意义,这种复合类型具有“内部”部分,或许可以独立于包含它的东西而改变。字符串自然由字符组成,并且语言中没有任何机制可以让我们更改现有字符串中的字符,而不是指定一个全新的字符串,所以我们说它是不可变的。
对于一个int,谈论可变性并没有多大意义,因为int的“组件”是什么?您通过分配一个全新的int来更改int,但赋值不算作“变异”。
可变性和参考与价值类型之间存在某种联系。从语义上讲,不可变引用类型和值类型之间没有区别。为什么?假设int实际上是指向不可变对象的指针(即*InternalIntObject
,没有用于更改InternalIntObject
的函数)。一旦将这样的指针分配给变量,它将永远表示相同的整数值(由共享同一对象的其他人不能更改),因为该对象是不可变的。这与整数值类型的行为相同。您可以通过赋值运算符分配整数;同样你可以通过赋值来分配这些指针;结果将是相同的:赋值变量表示与分配给它的整数相同的整数。唯一的区别是比较,并且必须重新定义算术运算符以取消引用指针以计算结果。
因此,可变性仅对参考类型有意义。
至于你提出的问题,“可变”类型通常被认为是引用类型,除了字符串:地图,通道,切片(关于切片指向的数据),还有指向任何东西的指针(因为你可以改变指针指向的位置的值。
答案 4 :(得分:1)
您的关注似乎更多是关于分配而不是不变性。不可变性肯定会影响分配,因为它无法重复使用内存。一个聪明的编译器可以想象地重用任何“不可变”的内存,它知道的地址不会逃脱。
除了字符串外,请注意接口。在分配给界面时,必须分配大于字大小的任何内容(除了优化)。此外,在循环体中声明的变量(包括通过闭包)必须在每次循环时分配。但是,否则,任务只是一项任务。该值只会被复制到变量表示的内存中。
如果在循环中使用make或new,或者在产生引用的任何文字中使用make,则必须进行分配(同样需要进行优化)。
基本上,这一切都归结为尝试尽可能地重用内存,并希望编译器在你不能的情况下为你做这件事,如果它有意义的话。
答案 5 :(得分:-3)
每次都给我相同的地址,所以也许整数是可变的。
package main
import "fmt"
func main() {
var i int
i = 5
fmt.Println(&i)
i = 6
fmt.Println(&i)
var k = 7
i = k
fmt.Println(&i)
}