diff --git a/ch4/ch4-02-1.md b/ch4/ch4-02-1.md index 99976cf..fe3ae2c 100644 --- a/ch4/ch4-02-1.md +++ b/ch4/ch4-02-1.md @@ -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 { ``` 爲了避免重複,和前面相同的代碼併沒有顯示。 - - diff --git a/ch4/ch4-02-2.md b/ch4/ch4-02-2.md index 8455f40..c97fe10 100644 --- a/ch4/ch4-02-2.md +++ b/ch4/ch4-02-2.md @@ -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。是否可以不用分配額外的內存? - diff --git a/ch4/ch4-02.md b/ch4/ch4-02.md index 6af02e5..2940f11 100644 --- a/ch4/ch4-02.md +++ b/ch4/ch4-02.md @@ -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" %} - diff --git a/ch4/ch4-03.md b/ch4/ch4-03.md index 11f28b5..6de5315 100644 --- a/ch4/ch4-03.md +++ b/ch4/ch4-03.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)函數,這樣可以按單詞而不是按行輸入。 - -