2011-11-05 92 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,則必須進行分配(同樣需要進行優化)。

    基本上,這都歸結爲儘可能地重用內存,並希望編譯器在你不能的時候爲你做,如果這樣做有道理的話。