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