mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-11-24 15:18:57 +00:00
Merge branch 'master' of https://github.com/golang-china/gopl-zh
This commit is contained in:
commit
f732abf07f
@ -42,9 +42,9 @@ func appendInt(x []int, y int) []int {
|
||||
|
||||
如果沒有足夠的增長空間的話,appendInt函數則會先分配一個足夠大的slice用於保存新的結果,先將輸入的x複製到新的空間,然後添加y元素。結果z和輸入的x引用的將是不同的底層數組。
|
||||
|
||||
雖然通過循環複製元素更直接,不過內置的copy函數可以方便地將一個slice複製另一個相同類型的slice。copy函數的第一個參數是要複製的目標slice,第二個參數是源slice,目標和源的位置順序和dst = src賦值語句是一致的。兩個slice可以共享同一個底層數組,甚至有重疊也沒有問題。copy函數將返迴成功複製的元素的個數(我們這里沒有用到),等於兩個slice中較小的長度,所以我們不用擔心覆蓋會超出目的slice的范圍。
|
||||
雖然通過循環複製元素更直接,不過內置的copy函數可以方便地將一個slice複製另一個相同類型的slice。copy函數的第一個參數是要複製的目標slice,第二個參數是源slice,目標和源的位置順序和`dst = src`賦值語句是一致的。兩個slice可以共享同一個底層數組,甚至有重疊也沒有問題。copy函數將返迴成功複製的元素的個數(我們這里沒有用到),等於兩個slice中較小的長度,所以我們不用擔心覆蓋會超出目標slice的范圍。
|
||||
|
||||
爲了效率,新分配的數組一般略大於保存x和y所需要的最低大小。通過在每次擴展數組時直接將長度翻倍從而避免了多次內存分配,也確保了添加單個元素操的平均時間是一個常數時間。這個程序演示了效果:
|
||||
爲了提高內存使用效率,新分配的數組一般略大於保存x和y所需要的最低大小。通過在每次擴展數組時直接將長度翻倍從而避免了多次內存分配,也確保了添加單個元素操的平均時間是一個常數時間。這個程序演示了效果:
|
||||
|
||||
```Go
|
||||
func main() {
|
||||
@ -76,17 +76,17 @@ func main() {
|
||||
|
||||
![](../images/ch4-02.png)
|
||||
|
||||
在下一次迭代時i=4,現在沒有新的空餘的空間了,因此appendInt函數分配一個容量爲8的底層數組,將x的4個元素[0 1 2 3]複製到新空間的開頭,然後添加新的元素i,新元素的值是4。新的y的長度是5,容量是8;後面有3個空閒的位置,三次迭代都不需要分配新的空間。當前迭代中,y和x是對應不用底層數組的view。這次操作如圖4.3所示。
|
||||
在下一次迭代時i=4,現在沒有新的空餘的空間了,因此appendInt函數分配一個容量爲8的底層數組,將x的4個元素[0 1 2 3]複製到新空間的開頭,然後添加新的元素i,新元素的值是4。新的y的長度是5,容量是8;後面有3個空閒的位置,三次迭代都不需要分配新的空間。當前迭代中,y和x是對應不同底層數組的view。這次操作如圖4.3所示。
|
||||
|
||||
![](../images/ch4-03.png)
|
||||
|
||||
內置的append函數可能使用比appendInt更複雜的內存擴展策略。因此,通常我們併不知道append調用是否導致了內存的分配,因此我們也不能確認新的slice和原始的slice是否引用的是相同的底層數組空間。同樣,我們不能確認在原先的slice上的操作是否會影響到新的slice。因此,通常是將append返迴的結果直接賦值給輸入的slice變量:
|
||||
內置的append函數可能使用比appendInt更複雜的內存擴展策略。因此,通常我們併不知道append調用是否導致了內存的重新分配,因此我們也不能確認新的slice和原始的slice是否引用的是相同的底層數組空間。同樣,我們不能確認在原先的slice上的操作是否會影響到新的slice。因此,通常是將append返迴的結果直接賦值給輸入的slice變量:
|
||||
|
||||
```Go
|
||||
runes = append(runes, r)
|
||||
```
|
||||
|
||||
更新slice變量不僅對調用append函數是必要的,實際上對應任何可能導致長度、容量或底層數組變化的操作都是必要的。要正確地使用slice,需要記住盡管底層數組的元素是間接訪問,但是slice本身的指針、長度和容量是直接訪問的。要更新這些信息需要像上面例子那樣一個顯式的賦值操作。從這個角度看,slice併不是一個純粹的引用類型,它實際上是一個類似下面結構體的聚合類型:
|
||||
更新slice變量不僅對調用append函數是必要的,實際上對應任何可能導致長度、容量或底層數組變化的操作都是必要的。要正確地使用slice,需要記住盡管底層數組的元素是間接訪問的,但是slice對應結構體本身的指針、長度和容量部分是直接訪問的。要更新這些信息需要像上面例子那樣一個顯式的賦值操作。從這個角度看,slice併不是一個純粹的引用類型,它實際上是一個類似下面結構體的聚合類型:
|
||||
|
||||
```Go
|
||||
type IntSlice struct {
|
||||
@ -119,5 +119,3 @@ func appendInt(x []int, y ...int) []int {
|
||||
```
|
||||
|
||||
爲了避免重複,和前面相同的代碼併沒有顯示。
|
||||
|
||||
|
||||
|
@ -32,7 +32,7 @@ fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]`
|
||||
fmt.Printf("%q\n", data) // `["one" "three" "three"]`
|
||||
```
|
||||
|
||||
因此我們通常會這樣使用nonempty函數:data = nonempty(data)。
|
||||
因此我們通常會這樣使用nonempty函數:`data = nonempty(data)`。
|
||||
|
||||
nonempty函數也可以使用append函數實現:
|
||||
|
||||
@ -48,9 +48,9 @@ func nonempty2(strings []string) []string {
|
||||
}
|
||||
```
|
||||
|
||||
無論如何實現,以這種方式重用一個slice一般要求最多爲每個輸入值産生一個輸出值,事實上很多算法都是用來過濾或合併序列中相鄰的元素。這種slice用法是比較複雜的技巧,雖然使用到了slice的一些黑魔法,但是對於某些場合是比較清晰和有效的。
|
||||
無論如何實現,以這種方式重用一個slice一般都要求最多爲每個輸入值産生一個輸出值,事實上很多這類算法都是用來過濾或合併序列中相鄰的元素。這種slice用法是比較複雜的技巧,雖然使用到了slice的一些技巧,但是對於某些場合是比較清晰和有效的。
|
||||
|
||||
一個slice可以原來實現一個stack。最初給定的空slice對應一個空的stack,然後可以使用append函數將新的值壓入stack:
|
||||
一個slice可以用來模擬一個stack。最初給定的空slice對應一個空的stack,然後可以使用append函數將新的值壓入stack:
|
||||
|
||||
```Go
|
||||
stack = append(stack, v) // push v
|
||||
@ -68,7 +68,7 @@ top := stack[len(stack)-1] // top of stack
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
```
|
||||
|
||||
要刪除slice中間的某個元素併保存原有的元素順序,可以通過內置的copy函數將後面的子slice向前一位複雜完成:
|
||||
要刪除slice中間的某個元素併保存原有的元素順序,可以通過內置的copy函數將後面的子slice向前依次移動一位完成:
|
||||
|
||||
```Go
|
||||
func remove(slice []int, i int) []int {
|
||||
@ -82,7 +82,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
如果刪除元素後不用保存原來順序的話,我們可以簡單的用最後一個元素覆蓋被刪除的元素:
|
||||
如果刪除元素後不用保持原來順序的話,我們可以簡單的用最後一個元素覆蓋被刪除的元素:
|
||||
|
||||
```Go
|
||||
func remove(slice []int, i int) []int {
|
||||
@ -105,4 +105,3 @@ func main() {
|
||||
**練習 4.6:** 編寫一個函數,原地將一個UTF-8編碼的[]byte類型的slice中相鄰的空格(參考unicode.IsSpace)替換成一個空格返迴
|
||||
|
||||
**練習 4.7:** 脩改reverse函數用於原地反轉UTF-8編碼的[]byte。是否可以不用分配額外的內存?
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 4.2. Slice
|
||||
|
||||
Slice(切片)代表變長的序列,序列中每個元素都有相同的類型。一個slice類型一般寫作[]T,其中T代表slice中元素的類型;語法和數組很像,隻是沒有固定長度而已。
|
||||
Slice(切片)代表變長的序列,序列中每個元素都有相同的類型。一個slice類型一般寫作[]T,其中T代表slice中元素的類型;slice的語法和數組很像,隻是沒有固定長度而已。
|
||||
|
||||
數組和slice之間有着緊密的聯繫。一個slice是一個輕量級的數據結構,提供了訪問數組子序列(或者全部)元素的功能,因爲slice的底層確實引用一個數組對象。一個slice由三個部分構成:指針、長度和容量。指針指向第一個slice元素對應的底層數組元素的地址,要註意的是slice的第一個元素併不一定就是數組的第一個元素。長度對應slice中元素的數目;長度不能超過容量,容量一般是從slice的開始位置到底層數據的結尾位置。內置的len和cap函數分别返迴slice的長度和容量。
|
||||
數組和slice之間有着緊密的聯繫。一個slice是一個輕量級的數據結構,提供了訪問數組子序列(或者全部)元素的功能,而且slice的底層確實引用一個數組對象。一個slice由三個部分構成:指針、長度和容量。指針指向第一個slice元素對應的底層數組元素的地址,要註意的是slice的第一個元素併不一定就是數組的第一個元素。長度對應slice中元素的數目;長度不能超過容量,容量一般是從slice的開始位置到底層數據的結尾位置。內置的len和cap函數分别返迴slice的長度和容量。
|
||||
|
||||
多個slice之間可以共享底層的數據,併且引用的數組部分區間可能重疊。圖4.1顯示了表示一年中每個月份名字的字符串數組,還有重疊引用了該數組的兩個slice。數組這樣定義
|
||||
|
||||
@ -129,4 +129,3 @@ make([]T, len, cap) // same as make([]T, cap)[:len]
|
||||
{% include "./ch4-02-1.md" %}
|
||||
|
||||
{% include "./ch4-02-2.md" %}
|
||||
|
||||
|
@ -27,7 +27,7 @@ ages["alice"] = 31
|
||||
ages["charlie"] = 34
|
||||
```
|
||||
|
||||
因此,另一種創建空的map的表達式是map[string]int{}。
|
||||
因此,另一種創建空的map的表達式是`map[string]int{}`。
|
||||
|
||||
Map中的元素通過key對應的下標語法訪問:
|
||||
|
||||
@ -48,7 +48,7 @@ delete(ages, "alice") // remove element ages["alice"]
|
||||
ages["bob"] = ages["bob"] + 1 // happy birthday!
|
||||
```
|
||||
|
||||
而且x += y和x++等簡短賦值語法也可以用在map上,所以上面的代碼可以改寫成
|
||||
而且`x += y`和`x++`等簡短賦值語法也可以用在map上,所以上面的代碼可以改寫成
|
||||
|
||||
```Go
|
||||
ages["bob"] += 1
|
||||
@ -176,7 +176,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
Go程序員將這種忽略value的map當作一個字符串集合,併非所有map[string]bool類型value都是無關緊要的;有一些則可能會同時包含tue和false的值。
|
||||
Go程序員將這種忽略value的map當作一個字符串集合,併非所有`map[string]bool`類型value都是無關緊要的;有一些則可能會同時包含tue和false的值。
|
||||
|
||||
有時候我們需要一個map或set的key是slice類型,但是map的key必鬚是可比較的,但是slice併不滿足這個條件。不過,我們可以通過兩個步驟繞過這個限製。第一步,定義一個輔助函數k,將slice轉爲map對應的string類型的key,確保隻有x和y相等時k(x) == k(y)才成立。然後創建一個key爲string類型的map,在每次對map操作時先用k輔助函數將slice轉化爲string類型。
|
||||
|
||||
@ -292,5 +292,3 @@ func hasEdge(from, to string) bool {
|
||||
**練習 4.8:** 脩改charcount程序,使用unicode.IsLetter等相關的函數,統計字母、數字等Unicode中不同的字符類别。
|
||||
|
||||
**練習 4.9:** 編寫一個程序wordfreq程序,報告輸入文本中每個單詞出現的頻率。在第一次調用Scan前先調用input.Split(bufio.ScanWords)函數,這樣可以按單詞而不是按行輸入。
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user