gopl-zh.github.com/ch4/ch4-02-2.md

107 lines
3.4 KiB
Markdown
Raw Normal View History

2016-02-15 03:06:34 +00:00
### 4.2.2. Slice内存技巧
2015-12-28 06:49:32 +00:00
2016-02-15 03:06:34 +00:00
让我们看看更多的例子比如旋转slice、反转slice或在slice原有内存空间修改元素。给定一个字符串列表下面的nonempty函数将在原有slice内存空间之上返回不包含空字符串的列表
2015-12-29 02:38:02 +00:00
2016-01-20 15:36:24 +00:00
<u><i>gopl.io/ch4/nonempty</i></u>
2015-12-29 02:38:02 +00:00
```Go
// 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]
}
```
2016-02-15 03:06:34 +00:00
比较微妙的地方是输入的slice和输出的slice共享一个底层数组。这可以避免分配另一个数组不过原来的数据将可能会被覆盖正如下面两个打印语句看到的那样
2015-12-29 02:38:02 +00:00
```Go
data := []string{"one", "", "three"}
fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]`
fmt.Printf("%q\n", data) // `["one" "three" "three"]`
```
2016-02-15 03:06:34 +00:00
因此我们通常会这样使用nonempty函数`data = nonempty(data)`。
2015-12-29 02:38:02 +00:00
2016-02-15 03:06:34 +00:00
nonempty函数也可以使用append函数实现
2015-12-29 02:38:02 +00:00
```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-02-15 03:06:34 +00:00
无论如何实现以这种方式重用一个slice一般都要求最多为每个输入值产生一个输出值事实上很多这类算法都是用来过滤或合并序列中相邻的元素。这种slice用法是比较复杂的技巧虽然使用到了slice的一些技巧但是对于某些场合是比较清晰和有效的。
2015-12-29 02:38:02 +00:00
2016-02-15 03:06:34 +00:00
一个slice可以用来模拟一个stack。最初给定的空slice对应一个空的stack然后可以使用append函数将新的值压入stack
2015-12-29 02:38:02 +00:00
```Go
stack = append(stack, v) // push v
```
2016-02-15 03:06:34 +00:00
stack的顶部位置对应slice的最后一个元素
2015-12-29 02:38:02 +00:00
```Go
top := stack[len(stack)-1] // top of stack
```
2016-02-15 03:06:34 +00:00
通过收缩stack可以弹出栈顶的元素
2015-12-29 02:38:02 +00:00
```Go
stack = stack[:len(stack)-1] // pop
```
2016-02-15 03:06:34 +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-02-15 03:06:34 +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]
}
```
2016-02-15 03:06:34 +00:00
**练习 4.3** 重写reverse函数使用数组指针代替slice。
2015-12-29 02:38:02 +00:00
2016-02-15 03:06:34 +00:00
**练习 4.4** 编写一个rotate函数通过一次循环完成旋转。
2015-12-29 02:38:02 +00:00
2016-02-15 03:06:34 +00:00
**练习 4.5** 写一个函数在原地完成消除[]string中相邻重复的字符串的操作。
2015-12-29 02:38:02 +00:00
2016-02-15 03:06:34 +00:00
**练习 4.6** 编写一个函数原地将一个UTF-8编码的[]byte类型的slice中相邻的空格参考unicode.IsSpace替换成一个空格返回
2015-12-29 02:38:02 +00:00
2016-02-15 03:06:34 +00:00
**练习 4.7** 修改reverse函数用于原地反转UTF-8编码的[]byte。是否可以不用分配额外的内存