2011-11-05 46 views
23

在Google Go中,我读到字符串是不可变的,但是int是吗? 其他类型呢?作为一个稍微老一点的程序员,我更喜欢可变性,即使我知道不变性的好处,但我宁愿过着危险的生活。哪些类型在Google Go语言中可变且不可变?

知道什么类型是可变的或不可变的将是非常有益的。


更新,我最关心的是实际问题取决于类型是可变的或不可变的。正如Java中的典型例子,如果您在循环中创建一个字符串并循环10,000次,您将获得10,000个字符串的创建,然后再进行垃圾收集。这实际上是我所在公司的一个项目中的一个严重问题。

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

它影响你应该如何对待变种。 (或者我假设它)。


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

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

+0

我问这个问题的原因是这是一个性能问题,如果我正在编写一个循环运行数百万次,我不想创建大量对象。例如,在Java循环中,我们不希望每次循环都创建一个字符串,所以我们使用StringBuffer。 – Phil

+0

我的假设当然是不可变的类型将会创建大量的对象。 (可能并非如此,具体取决于分配器) – Phil

+0

在某种程度上,因为极其聪明的垃圾回收器可能会重复使用字符串*片段*,您会留下一些聪明的东西(除了连续的单词阵列之外的任何东西)实现。使用'[]字节'然后,注意在全面的utf-8 *字符*(又名符文)具有可变长度。 – ypb

回答

25

不要担心 - 如果你真的想要,Go会让你在脚下自我射击:-)

Go并不像Erlang那样,你可能会问这个问题。

x := 1 
x = 2 

分配一个变量,x,与1的值,然后将其重新分配给2 - 没有额外的内存分配在这里。当你注意到,字符串是不可变的,所以做一个字符串操作可能导致复制。如果您发现要对字符数据进行就地修改,则可能需要通过bytes程序包对[]byte的变量进行操作。

拉斯考克斯的这个应该回答您的大多数有关的基本数据结构的问题后:http://research.swtch.com/2009/11/go-data-structures.html

正如其他评论者所指出的,你要看看围棋的价值功能的语义 - 他们可能会有点起初令人惊讶。

如果您有以下功能:

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 
} 

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

+0

谢谢!这正是我需要调试方法签名中缺少星号的问题的信息。对于我来说令人惊讶的是,Go会让你在这方面轻松地用脚射击自己,当它以如此令人厌恶的方式发出关于以其他方式“帮助”你的声音时。 –

2

是的,单词不可变的在Go规格中出现一次。那是在讨论type string时。我认为你应该从AssignabilityAddressability的双重观点看更多。例如,Go显然会禁止你将变量重新绑定到具有未导出属性的不同类型的值。有点像在C + +的类不提供复制构造,但在Go Pimpl感觉很少尴尬,适合通过哲学的公用事业'分享。

+0

是的,但如果我有一个int,并且我设置了它的值,并且我创建了一个int(不可变)的新实例或者在内存中设置它的值(覆盖,可变)。或者,我是否可以保护自己,甚至不知道是否属于这种情况,并且取决于Go的实施?嗯,也许我可以想一些如何测试这个,不知道。 – Phil

+0

包主 \t导入 “FMT” \t FUNC主(){ \t变种I INT \t I = 5 \t fmt.Println(ⅰ) \t I = 6 \t fmt.Println(ⅰ) \t var k = 7 \t i = k \t fmt。Println(&i) \t} – Phil

+0

@Phil不知道该告诉你什么。也许只有Go是按值传递的,所以你必须明确地使用一个对象的地址,唯一的“参考”类型是无类型的接口{}。/hmmm ...不是太多的老师,moi;} – ypb

-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) 
} 
+3

由相同的逻辑,字符串,实际上任何类型,都是可变的。 'func main(){var_i_ string; i =“foo”; fmt.Println(&i); 我= “栏”; fmt.Println(&i); VAR K = “巴兹”; 我= K; fmt.Println(I) }' – newacct

+0

你怎么解释这是为什么?不是我的地址改变了吗? – Phil

+1

变量的地址不应该改变,这是等价的C:'int main(){char * i; i =“foo”; printf(“%p \ n”, &i); i =“bar”; printf(“%p \ n”,&i); char * k =“baz”; i = k; printf(“%p \ n”,&i); return 0;}'('i' is a字符串(字符指针),但如果你把地址'我'(指针指针),它是一样的)我猜你不是在寻找'我'的地址,而是内部字符缓冲区的地址那'''是指,但在G o字符串是一个不透明的类型,并没有直接的方法来获得这个内部指针,除了可能通过反射 – newacct

2

“善变”原因不仅使当你谈论一些复合型感,一些已经“内部”的部分,或许可以是包含它的东西单独改变。字符串自然是由字符组成的,并且语言中没有任何机制可以让我们更改现有字符串中的字符,并且不会分配全新的字符串,所以我们说它是不可变的。

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

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

可变性因此只对参考类型有意义。

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

+0

关于你的句子“测试是否可变的东西是如果你把这东西给某人,你能改变它的某些方面......“:我认为你写的是一个有效的观点。但在我看来,从命名和组合/重叠角度考虑可变性会更好(从我的观点来看)。也就是说,如何获取对象的“名称”,更改对象以及哪些其他命名对象因此而更改。一个“名字”在这里是一个概念性的东西。 – 2011-11-06 15:07:43

+0

好吧我删除该部分,因为我意识到这实际上是关于引用类型 – newacct

9

我认为,先要分开以下两个概念:

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

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

    该视图与Go规范一致,该规范声明字符串是不可变的。显然,字符串变量是可变的。

    变量(作为一个概念)在围棋至少:

    • 命名变量(如:var i int)通过指针

    易变转到对象

  • 变量访问:

    • 阵列和切片
    • 映射
    • 通道
    • 封闭件,其从外范围捕获至少1可变

    不可变转到对象:

    • 接口
    • 布尔型,数值(包括int类型的值)
    • 指针
    • 函数指针,并关闭其可降低到函数指针具有单场
  • 结构 其中一些人可能会考虑可变的,而其他人可能会认为他们

    转到对象不可改变的:具有

    • 结构的多个字段
  • +1

    'struct'肯定是可变类型的,但我们不觉得它,因为它总是在传递时被复制。所以突变不会被传播。 – Eonil

    1

    您的担心似乎更多地是关于分配而不是不变性。不变性肯定会影响分配,因为不可能重用内存。一个聪明的编译器可以重新使用任何“不可变”的内存,它所知道的地址不会逃脱。

    除了字符串,请小心接口。任何大于字的大小在分配给接口时都必须分配(抛开优化)。此外,在循环体中声明的变量,其地址转义(包括通过闭包)将不得不每次通过循环分配。否则,分配只是一项任务。该值只是被复制到变量表示的内存中。

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

    基本上,这都归结为尽可能地重用内存,并希望编译器在你不能的时候为你做,如果这样做有道理的话。