2015-12-28 06:49:32 +00:00
|
|
|
|
### 4.2.2. Slice內存技巧
|
|
|
|
|
|
2016-01-02 13:07:14 +00:00
|
|
|
|
讓我們看看更多的例子,比如鏇轉slice、反轉slice或在slice原有內存空間脩改元素。給定一個字符串列表,下面的nonempty函數將在原有slice內存空間之上返迴不包含空字符串的列表:
|
2015-12-29 02:38:02 +00:00
|
|
|
|
|
|
|
|
|
```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"]`
|
|
|
|
|
```
|
|
|
|
|
|
2016-01-06 13:17:08 +00:00
|
|
|
|
因此我們通常會這樣使用nonempty函數:`data = nonempty(data)`。
|
2015-12-29 02:38:02 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2016-01-06 13:17:08 +00:00
|
|
|
|
無論如何實現,以這種方式重用一個slice一般都要求最多爲每個輸入值産生一個輸出值,事實上很多這類算法都是用來過濾或合併序列中相鄰的元素。這種slice用法是比較複雜的技巧,雖然使用到了slice的一些技巧,但是對於某些場合是比較清晰和有效的。
|
2015-12-29 02:38:02 +00:00
|
|
|
|
|
2016-01-06 13:17:08 +00:00
|
|
|
|
一個slice可以用來模擬一個stack。最初給定的空slice對應一個空的stack,然後可以使用append函數將新的值壓入stack:
|
2015-12-29 02:38:02 +00:00
|
|
|
|
|
|
|
|
|
```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
|
|
|
|
|
```
|
|
|
|
|
|
2016-01-06 13:17:08 +00:00
|
|
|
|
要刪除slice中間的某個元素併保存原有的元素順序,可以通過內置的copy函數將後面的子slice向前依次移動一位完成:
|
2015-12-29 02:38:02 +00:00
|
|
|
|
|
|
|
|
|
```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]"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2016-01-06 13:17:08 +00:00
|
|
|
|
如果刪除元素後不用保持原來順序的話,我們可以簡單的用最後一個元素覆蓋被刪除的元素:
|
2015-12-29 02:38:02 +00:00
|
|
|
|
|
|
|
|
|
```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。
|
|
|
|
|
|
2016-01-02 13:07:14 +00:00
|
|
|
|
**練習 4.4:** 編寫一個rotate函數,通過一次循環完成鏇轉。
|
2015-12-29 02:38:02 +00:00
|
|
|
|
|
|
|
|
|
**練習 4.5:** 寫一個函數在原地完成消除[]string中相鄰重複的字符串的操作。
|
|
|
|
|
|
|
|
|
|
**練習 4.6:** 編寫一個函數,原地將一個UTF-8編碼的[]byte類型的slice中相鄰的空格(參考unicode.IsSpace)替換成一個空格返迴
|
|
|
|
|
|
|
|
|
|
**練習 4.7:** 脩改reverse函數用於原地反轉UTF-8編碼的[]byte。是否可以不用分配額外的內存?
|