gopl-zh.github.com/ch4/ch4-02-2.md
2015-12-29 10:38:02 +08:00

109 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

### 4.2.2. Slice內存技巧
讓我們看看更多的例子比如镟轉slice、反轉slice或在slice原有內存空間脩改元素。給定一個字符串列表下面的nonempty函數將在原有slice內存空間之上返迴不包含空字符串的列表
```Go
gopl.io/ch4/nonempty
// Nonempty is an example of an in-place slice algorithm.
package main
import "fmt"
// nonempty returns a slice holding only the non-empty strings.
// The underlying array is modified during the call.
func nonempty(strings []string) []string {
i := 0
for _, s := range strings {
if s != "" {
strings[i] = s
i++
}
}
return strings[:i]
}
```
比較微妙的地方是輸入的slice和輸出的slice共享一個底層數組。這可以避免分配另一個數組不過原來的數據將可能會被覆蓋正如下面兩個打印語句看到的那樣
```Go
data := []string{"one", "", "three"}
fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]`
fmt.Printf("%q\n", data) // `["one" "three" "three"]`
```
因此我們通常會這樣使用nonempty函數data = nonempty(data)。
nonempty函數也可以使用append函數實現
```Go
func nonempty2(strings []string) []string {
out := strings[:0] // zero-length slice of original
for _, s := range strings {
if s != "" {
out = append(out, s)
}
}
return out
}
```
無論如何實現以這種方式重用一個slice一般要求最多爲每個輸入值産生一個輸出值事實上很多算法都是用來過濾或合併序列中相鄰的元素。這種slice用法是比較複雜的技巧雖然使用到了slice的一些黑魔法但是對於某些場合是比較清晰和有效的。
一個slice可以原來實現一個stack。最初給定的空slice對應一個空的stack然後可以使用append函數將新的值壓入stack
```Go
stack = append(stack, v) // push v
```
stack的頂部位置對應slice的最後一個元素
```Go
top := stack[len(stack)-1] // top of stack
```
通過收縮stack可以彈出棧頂的元素
```Go
stack = stack[:len(stack)-1] // pop
```
要刪除slice中間的某個元素併保存原有的元素順序可以通過內置的copy函數將後面的子slice向前一位複雜完成
```Go
func remove(slice []int, i int) []int {
copy(slice[i:], slice[i+1:])
return slice[:len(slice)-1]
}
func main() {
s := []int{5, 6, 7, 8, 9}
fmt.Println(remove(s, 2)) // "[5 6 8 9]"
}
```
如果刪除元素後不用保存原來順序的話,我們可以簡單的用最後一個元素覆蓋被刪除的元素:
```Go
func remove(slice []int, i int) []int {
slice[i] = slice[len(slice)-1]
return slice[:len(slice)-1]
}
func main() {
s := []int{5, 6, 7, 8, 9}
fmt.Println(remove(s, 2)) // "[5 6 9 8]
}
```
**練習 4.3** 重寫reverse函數使用數組指針代替slice。
**練習 4.4** 編寫一個rotate函數通過一次循環完成镟轉。
**練習 4.5** 寫一個函數在原地完成消除[]string中相鄰重複的字符串的操作。
**練習 4.6** 編寫一個函數原地將一個UTF-8編碼的[]byte類型的slice中相鄰的空格參考unicode.IsSpace替換成一個空格返迴
**練習 4.7** 脩改reverse函數用於原地反轉UTF-8編碼的[]byte。是否可以不用分配額外的內存