Go 有指針。然而卻沒有指針運算,因此它們更象是引用而不是你所知道的來自于 C的指針。指針非常有用。在 Go 中調用函數的時候,得記得變量是值傳遞的。因此,為了修改一個傳遞入函數的值的效率和可能性,有了指針。
通過類型作為前綴來定義一個指針 ’’:var p int。現在 p 是一個指向整數值的指針。所有新定義的變量都被賦值為其類型的零值,而指針也一樣。一個新定義的或者沒有任何指向的指針,有值 nil。在其他語言中,這經常被叫做空(NULL)指針,在 Go 中就是 nil。讓指針指向某些內容,可以使用取址操(&),像這樣:
http://wiki.jikexueyuan.com/project/learn-go-language/images/111.png" alt="pic" />
從指針獲取值是通過在指針變量前置 ’*’ 實現的:
http://wiki.jikexueyuan.com/project/learn-go-language/images/112.png" alt="pic" />
前面已經說了,沒有指針運算,所以如果這樣寫:p++,它表示 (p)++:首先獲取指針指向的值,然后對這個值加一。
Go 同樣也垃圾收集,也就是說無須擔心內存分配和回收。
Go 有兩個內存分配原語,new 和 make。它們應用于不同的類型,做不同的工作,可能有些迷惑人,但是規(guī)則很簡單。下面的章節(jié)展示了在 Go 中如何處理內存分配,并且希望能夠讓 new 和 make 之間的區(qū)別更加清晰。
內建函數 new 本質上說跟其他語言中的同名函數功能一樣:new(T) 分配了零值填充的 T 類型的內存空間,并且返回其地址,一個 *T 類型的值。用 Go 的術語說,它返回了一個指針,指向新分配的類型 T 的零值。記住這點非常重要。
這意味著使用者可以用 new 創(chuàng)建一個數據結構的實例并且可以直接工作。如 bytes.Buffer 的文檔所述 “Buffer 的零值是一個準備好了的空緩沖。” 類似的,sync.Mutex 也沒有明確的構造函數或 Init 方法。取而代之, sync.Mutex 的零值被定義為非鎖定的互斥量。
零值是非常有用的。例如這樣的類型定義,57 頁的”定義自己的類型” 內容。
http://wiki.jikexueyuan.com/project/learn-go-language/images/113.png" alt="pic" />
SyncedBuffer 的值在分配內存或定義之后立刻就可以使用。在這個片段中,p 和 v 都可以在沒有任何更進一步處理的情況下工作。
http://wiki.jikexueyuan.com/project/learn-go-language/images/114.png" alt="pic" />
回到內存分配。內建函數 make(T, args) 與 new(T) 有著不同的功能。它只能創(chuàng)建 slice,map 和 channel,并且返回一個有初始值(非零)的 T 類型,而不是 *T。本質來講,導致這三個類型有所不同的原因是指向數據結構的引用在使用前必須被初始化。
例如,一個 slice,是一個包含指向數據(內部 array)的指針,長度和容量的三項描述符;在這些項目被初始化之前,slice 為 nil。對于 slice,map 和 channel,make 初始化了內部的數據結構,填充適當的值。
例如,make([]int, 10, 100) 分配了 100 個整數的數組,然后用長度 10 和容量 100創(chuàng)建了 slice 結構指向數組的前 10 個元素。區(qū)別是,new([]int) 返回指向新分配的內存的指針,而零值填充的 slice 結構是指向 nil 的 slice 值。
這個例子展示了 new 和 make 的不同。
http://wiki.jikexueyuan.com/project/learn-go-language/images/115.png" alt="pic" />
務必記得 make 僅適用于 map,slice 和 channel,并且返回的不是指針。應當用 new 獲得特定的指針。
new 分配;make 初始化
上面的兩段可以簡單總結為:
- new(T) 返回 *T 指向一個零值 T
- make(T) 返回初始化后的 T
當然 make 僅適用于slice,map 和channel。
有時零值不能滿足需求,必須要有一個用于初始化的構造函數,例如這個來自 os 包的例子。
http://wiki.jikexueyuan.com/project/learn-go-language/images/116.png" alt="pic" />
有許多冗長的內容??梢允褂脧秃下暶魇蛊涓雍啙?,每次只用一個表達式創(chuàng)建一個新的實例。
http://wiki.jikexueyuan.com/project/learn-go-language/images/117.png" alt="pic" />
返回本地變量的地址沒有問題;在函數返回后,相關的存儲區(qū)域仍然存在。
事實上,從復合聲明獲取分配的實例的地址更好,因此可以最終將兩行縮短到一行。
return &File{fd, name, nil, 0}
The items (called of a composite +literal are laid out in order and must all be 所有的項目(稱作字段)都必須按順序全部寫上。然而,通過對元素用字段: 值成對的標識,初始化內容可以按任意順序出現,并且可以省略初始化為零值的字段。因此可以這樣
return &File{fd: fd, name: name}
在特定的情況下,如果復合聲明不包含任何字段,它創(chuàng)建特定類型的零值。表達式 new(File) 和 &File{} 是等價的。
復合聲明同樣可以用于創(chuàng)建 array,slice 和 map,通過指定適當的索引和 map 鍵來標識字段。在這個例子中,無論是 Enone,Eio 還是 Einval 初始化都能很好的工作,只要確保它們不同就好了。
http://wiki.jikexueyuan.com/project/learn-go-language/images/118.png" alt="pic" />
自然,Go 允許定義新的類型,通過關鍵字type 實現:
type foo i n t
創(chuàng)建了一個新的類型 foo 作用跟 int 一樣。創(chuàng)建更加復雜的類型需要用到 struct 關鍵字。這有個在一個數據結構中記錄某人的姓名(string)和年齡(int),并且使其成為一個新的類型的例子:
http://wiki.jikexueyuan.com/project/learn-go-language/images/119.png" alt="pic" />
通常,fmt.Printf("%v\n", a) 的輸出是
&{Pete 42}
這很棒!Go 知道如何打印結構。如果僅想打印某一個,或者某幾個結構中的字段,需要使用 .
http://wiki.jikexueyuan.com/project/learn-go-language/images/120.png" alt="pic" />
之前已經提到結構中的項目被稱為 field。沒有字段的結構:struct {} 或者有四個 c 字段的:
http://wiki.jikexueyuan.com/project/learn-go-language/images/121.png" alt="pic" />
如果省略字段的名字,可以創(chuàng)建匿名字段,例如:
http://wiki.jikexueyuan.com/project/learn-go-language/images/122.png" alt="pic" />
注意首字母大寫的字段可以被導出,也就是說,在其他包中可以進行讀寫。字段名以小寫字母開頭是當前包的私有的。包的函數定義是類似的,參閱第 3 章了解更多細節(jié)。
可以對新定義的類型創(chuàng)建函數以便操作,可以通過兩種途徑:
func doSomething(n1 *NameAge, n2 i n t ) { /* */ }
(你可能已經猜到了)這是函數調用。
2 .創(chuàng)建一個工作在這個類型上的函數(參閱在2.1 中定義的接收方):
func (n1 *NameAge) doSomething(n2 i n t ) { /* */ }
這是方法調用,可以類似這樣使用:
var n *NameAge
n.doSomething(2)
使用函數還是方法是由程序員決定的,但是如果想要滿足接口(參閱下一章)就只能使用方法。如果沒有這方面的需求,那就由個人品味決定了。
使用函數還是方法完全是由程序員說了算,但是若需要滿足接口(參看下一章)就必須使用方法。如果沒有這樣的需求,那就完全由習慣來決定是使用函數還是方法了。
但是下面的內容一定要留意,引用自 [10]:
如果 x 可獲取地址,并且 &x 的方法中包含了 m,x.m() 是 (&x).m() 更短的寫法。
根據上面所述,這意味著下面的情況不是錯誤:
http://wiki.jikexueyuan.com/project/learn-go-language/images/123.png" alt="pic" />
這里 Go 會查找 NameAge 類型的變量n 的方法列表,沒有找到就會再查找 *NameAge 類型的方法列表,并且將其轉化為 (&n).doSomething(2)。
下面的類型定義中有一些微小但是很重要的不同之處。同時可以參閱 [10, section “Type Declarations”]。假設有:
// Mutex 數據類型有兩個方法,Lock 和 Unlock。
http://wiki.jikexueyuan.com/project/learn-go-language/images/124.png" alt="pic" />
現在用兩種不同的風格創(chuàng)建了兩個數據類型。
現在 NewMutux 等同于 Mutex,但是它沒有任何 Mutex 的方法。換句話說,它的方法是空的。
但是 PrintableMutex 已經從 Mutex 繼承了方法集合。如同 [10] 所說:
*PrintableMutex 的方法集合包含了 Lock 和 Unlock 方法,被綁定到其匿名字段 Mutex。
有時需要將一個類型轉換為另一個類型。在 Go 中可以做到,不過有一些規(guī)則。首先,將一個值轉換為另一個是由操作符(看起來像函數:byte())完成的,并且不是所有的轉換都是允許的。
Table 4.1. 合法的轉換,float64 同 float32 類似。注意,為了適配表格的顯示,float32被簡寫為 flt32。
http://wiki.jikexueyuan.com/project/learn-go-language/images/125.png" alt="pic" />
mystring := "hello this is string"
byteslice := []byte(mystring)
轉換到 byte slice,每個 byte 保存字符串對應字節(jié)的整數值。注意 Go 的字符串是 UTF-8 編碼的,一些字符可能是 1、2、3 或者 4 個字節(jié)結尾。
runeslice := []rune(mystring)
轉換到 rune slice,每個 rune 保存 Unicode 編碼的指針。字符串中的每個字符對應一個整數。
b := []byte {'h','e','l','l','o'} // 復合聲明
s := s t r i n g (b)
i := []rune {257,1024,65}
r := s t r i n g (i)
對于數值,定義了下面的轉換:
如何在自定義類型之間進行轉換?這里創(chuàng)建了兩個類型 Foo 和 Bar,而 Bar 是 Foo 的一個別名:
http://wiki.jikexueyuan.com/project/learn-go-language/images/126.png" alt="pic" />
然后:
http://wiki.jikexueyuan.com/project/learn-go-language/images/127.png" alt="pic" />
最后一行會引起錯誤:
cannot use b (type bar) as type foo in assignment(不能使用 b(類型 bar)作為類型 foo 賦值)
這可以通過轉換來修復:
var f foo = foo(b)
注意轉換那些字段不一致的結構是相當困難的。同時注意,轉換 b 到 int 同樣會出錯;整數與有整數字段的結構并不一樣。
TODO(miek):work in progress Go 不是面向對象語言,因此并沒有繼承。但是有時又會需要從已經實現的類型中“繼承”并修改一些方法。在Go 中可以用嵌入一個類型的方式來實現。
Q17. (1) 指針運算
…這里沒有指針運算,因此如果這樣寫:p++,它被解釋為(p)++:
首先解析引用然后增加值。
當像這樣增加一個值的時候,什么類型可以工作?
2 .為什么它不能工作在所有類型上?
Q18. (2) 使用 interface 的 map 函數
Q19. (1) 指針
http://wiki.jikexueyuan.com/project/learn-go-language/images/128.png" alt="pic" />
下面兩行之間的區(qū)別是什么?
var p1 Person
p2 := new(Person)
2 .下面兩個內存分配的區(qū)別是什么?
http://wiki.jikexueyuan.com/project/learn-go-language/images/129.png" alt="pic" />
和
http://wiki.jikexueyuan.com/project/learn-go-language/images/130.png" alt="pic" />
Q20. (1) Linked List
1 .Make use of the package container/list to create a (doubly) linked list. Push the values 1, 2 and 4 to the list and then print it.
2 .Create your own linked list implementation. And perform the same actions as in question 1
Q21. (1) Cat
Q22. (2) 方法調用
http://wiki.jikexueyuan.com/project/learn-go-language/images/131.png" alt="pic" />
k1,k2 和 k3 的類型是什么?
func (p *IntVector) Push(x int) Push 增加 x 到向量的末尾。
那么接受者應當是 *IntVector 類型,為什么上面的代碼(Push 語句)可以正確工作?above (the Push statements) work correct then?
A17. (1) 指針運算
A18. (2) 使用 interface 的 map 函數
http://wiki.jikexueyuan.com/project/learn-go-language/images/132.png" alt="pic" />
A19. (1) 指針
在第一個函數中,x 指向了 t 指向的內容,也就是實際上的參數指向的內容。
因此在第二個函數,我們有了“額外” 的變量存儲了相關值的副本。
A20. (1) Linked List
http://wiki.jikexueyuan.com/project/learn-go-language/images/133.png" alt="pic" />
http://wiki.jikexueyuan.com/project/learn-go-language/images/134.png" alt="pic" />
http://wiki.jikexueyuan.com/project/learn-go-language/images/135.png" alt="pic" />
http://wiki.jikexueyuan.com/project/learn-go-language/images/136.png" alt="pic" />
http://wiki.jikexueyuan.com/project/learn-go-language/images/137.png" alt="pic" />
0 .Include all the packages we need.
A21. (1) Cat
http://wiki.jikexueyuan.com/project/learn-go-language/images/138.png" alt="pic" />
http://wiki.jikexueyuan.com/project/learn-go-language/images/139.png" alt="pic" />
http://wiki.jikexueyuan.com/project/learn-go-language/images/140.png" alt="pic" />
否則,僅僅打印該行內容。
http://wiki.jikexueyuan.com/project/learn-go-language/images/141.png" alt="pic" />
http://wiki.jikexueyuan.com/project/learn-go-language/images/142.png" alt="pic" />
A22. (2) 方法調用
k1 的類型是 vector.IntVector。為什么?這里使用了符號 {},因此獲得了類型的值。變量 k2 是 vector.IntVector,因為獲得了復合語句的地址(&)。而最后的 k3 同樣是 vector.IntVector 類型,因為 new 返回該類型的指針。
當 x 的方法集合包含 m,并且參數列表可以賦值給 m 的參數,方法調用 x.m() 是合法的。如果 x 可以被地址化,而 &x 的方法集合包含 m,x.m() 可以作為 (&x).m() 的省略寫法。
換句話說,由于 k1 可以被地址化,而 *vector.IntVector 具有 Push 方法,調用 k1.Push(2) 被 Go 轉換為 (&k1).Push(2) 來使型系統(tǒng)愉悅(也使你愉悅——現在你已經了解到這一點)