42

在 Google Go 中,我读到字符串是不可变的,好吧,但它是 int 的吗?其他类型呢?作为一个年纪稍大的程序员,我更喜欢可变性,尽管我知道不变性的好处,但我更喜欢危险地生活。

知道哪些类型是可变的或不可变的将非常有帮助。


更新,我最关心的是实际问题取决于类型是可变的还是不可变的。就像 Java 中的典型示例一样,如果您在循环中创建一个字符串并循环 10,000 次,您将获得 10,000 个字符串,这些字符串随后会被垃圾回收。在我工作的一家公司的一个项目中,这实际上是一个严重的问题。

问题是,Go 的不变性在某些情况下会导致同样的问题吗?

它会影响您应该如何对待 var。(或者我认为确实如此)。


再次更新,我也关心其他实际问题。知道某些东西是不可变的意味着我可以编写并行的代码,并且对对象的一个​​引用的更新不应该更新其他引用。但是有时我想做危险的事情,我想要可变性。

这些是可变性与不变性的后果,并影响我如何编写代码。

4

5 回答 5

45

别担心——如果你真的想,Go 会让你在脚下开枪:-)

Go 与 Erlang 不同,这可能是您遇到的问题。

x := 1
x = 2

分配一个变量,x,其值为1,然后将其重新分配给2-- 这里没有分配额外的内存。

正如您所注意到的,字符串是不可变的,因此进行字符串操作可能会导致复制。如果您发现要对字符数据进行就地修改,则可能需要对[]byteviabytes包的变量进行操作。

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
}

因为该函数具有指针的副本,并且可以通过该指针访问底层结构。

于 2011-11-05T17:12:41.160 回答
12

在我看来,首先应该将以下两个概念分开:

  • 整数作为数学对象(即:值)

  • 类型变量int

那么答案是:整数变量是可变的,整数值是不可变的。

这种观点与 Go 规范一致,即字符串是不可变的。显然,字符串变量是可变的。

Go 中的变量(作为一个概念)至少是:

  • 命名变量(例如var i int:)
  • 可通过指针访问的变量

可变 Go 对象:

  • 数组和切片
  • 地图
  • 渠道
  • 从外部范围捕获至少 1 个变量的闭包

不可变的 Go 对象:

  • 接口
  • 布尔值,数值(包括 type 的值int
  • 字符串
  • 指针
  • 函数指针,以及可以简化为函数指针的闭包
  • 具有单个字段的结构

有些人可能认为是可变的,而另一些人可能认为它们是不可变的 Go 对象:

  • 具有多个字段的结构
于 2011-11-05T15:14:42.623 回答
2

是的,不可变这个词在 Go 规范中只出现过一次。那是在讨论的时候type string我认为您应该更多地从AssignabilityAddressability的双重角度来看待它。例如,很明显,Go 将禁止您将变量重新绑定到具有未导出属性的类型的不同值。有点像 C++ 中不提供复制构造函数的类,但在 Go Pimpl中感觉不那么尴尬,通过交流哲学适合 goroutines 的份额。

于 2011-11-05T05:35:12.543 回答
2

“可变性”仅在您谈论某些复合类型时才有意义,即具有“内部”部分的东西,可能可以独立于包含它的东西进行更改。字符串自然是由字符组成的,语言中没有机制可以让我们改变现有字符串中的字符,除非分配一个全新的字符串,所以我们说它是不可变的。

对于 int,谈论可变性并没有什么意义,因为 int 的“组件”是什么?您通过分配一个全新的 int 来更改一个 int,但分配不算作“变异”。

可变性和引用与值类型之间存在一些联系。从语义上讲,不可变引用类型和值类型之间没有区别。为什么?假设 int 实际上是一个指向不可变对象的指针(即*InternalIntObject没有用于更改的函数InternalIntObject)。一旦将这样的指针分配给变量,它将永远表示相同的整数值(不能被共享同一对象的其他人更改),因为该对象是不可变的。这与整数值类型的行为相同。您可以通过赋值运算符分配整数;同样,您可以通过赋值来分配这些指针;结果将是相同的:分配的变量表示与分配给它的整数相同的整数。唯一的区别是比较和算术运算符必须重新定义以取消引用指针来计算结果。

因此,可变性只对引用类型有意义。

至于你问什么,“可变”类型通常被认为是除字符串之外的引用类型:映射、通道、切片(相对于切片指向的数据),以及指向任何东西的指针(因为你可以改变指针指向的位置的值)。

于 2011-11-05T09:08:43.420 回答
1

您的担忧似乎更多是关于分配而不是不变性。不变性肯定会通过使内存无法重用来影响分配。可以想象,一个聪明的编译器可以重用任何它知道的地址不会转义的“不可变”内存。

除了字符串,还要小心接口。分配给接口时必须分配大于字大小的任何内容(优化除外)。此外,在循环体中声明的变量,其地址逃逸,包括通过闭包,每次都必须通过循环分配。但是,否则,分配只是分配。该值只是被复制到由变量表示的内存中。

如果您在循环中使用 make 或 new,或任何产生引用的文字,则必须进行分配(同样,受优化)。

基本上,这一切都归结为尽可能重用内存,并希望编译器在你做不到的时候为你做这件事,如果这样做有意义的话。

于 2011-11-06T05:11:57.823 回答