Slice使用技巧

Slice的使用方式有很多例如旋转slice、反转slice或在slice原有内存空间修改元素。 给定一个字符串列表,下面的nonempty函数将在原有slice内存空间之上返回不包含空字符串的列表:

  1. // Nonempty is an example of an in-place slice algorithm.
  2. package main
  3. import "fmt"
  4. // nonempty returns a slice holding only the non-empty strings.
  5. // The underlying array is modified during the call.
  6. func nonempty(strings []string) []string {
  7. i := 0
  8. for _, s := range strings {
  9. if s != "" {
  10. strings[i] = s
  11. i++
  12. }
  13. }
  14. return strings[:i]
  15. }

比较微妙的地方是,输入的slice和输出的slice共享一个底层数组。这可以避免分配另一个数 组,不过原来的数据将可能会被覆盖,正如下面两个打印语句看到的那样:

  1. data := []string{"1", "", "3"}
  2. fmt.Printf("%q\n", nonempty(data)) // `["1" "3"]`
  3. fmt.Printf("%q\n", data)
  4. // `["1" "3" "3"]`

因此我们通常会这样使用nonempty函数:data = nonempty(data)。 nonempty函数也可以使用append函数实现:

  1. func nonempty2(strings []string) []string {
  2. out := strings[:0] // zero-length slice of original
  3. for _, s := range strings {
  4. if s != "" {
  5. out = append(out, s)
  6. }
  7. }
  8. return out
  9. }

无论如何实现,以这种方式重用一个slice一般都要求最多为每个输入值产生一个输出值,事实上很多这类算法都是用来过滤或合并序列中相邻的元素。这种slice用法是比较复杂的技巧,虽然使用到了slice的一些技巧,但是对于某些场合是比较清晰和有效的。

一个slice可以用来模拟一个stack。最初给定的空slice对应一个空的stack,然后可以使用append函数将新的值压入stack:

  1. stack = append(stack, v) // push v

stack的顶部位置对应slice的最后一个元素:

  1. top := stack[len(stack)-1] // top of stack

通过收缩stack可以弹出栈顶的元素:

  1. stack = stack[:len(stack)-1] // pop

要删除slice中间的某个元素并保存原有的元素顺序,可以通过内置的copy函数将后面的子slice向前依次移动一位完成:

  1. func remove(slice []int, i int) []int {
  2. copy(slice[i:], slice[i+1:])
  3. return slice[:len(slice)-1]
  4. }
  5. func main() {
  6. s := []int{5, 6, 7, 8, 9}
  7. fmt.Println(remove(s, 2)) // "[5 6 8 9]"
  8. }

如果删除元素后不用保持原来顺序的话,我们可以简单的用最后一个元素覆盖被删除的元素:

  1. func remove(slice []int, i int) []int {
  2. slice[i] = slice[len(slice)-1]
  3. return slice[:len(slice)-1]
  4. }
  5. func main() {
  6. s := []int{5, 6, 7, 8, 9}
  7. fmt.Println(remove(s, 2)) // "[5 6 9 8]
  8. }

如果要反转整个slice的元素:

  1. s := []int{0, 1, 2, 3, 4, 5}
  2. reverse(s)
  3. fmt.Println(s) // "[5 4 3 2 1 0]"

在slice中也有许多的认知的错误需要避免的,我们结合一个例子详细的分析.

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. arr1 := []int{1, 2, 3, 4}
  7. arr2 := arr1[:2]
  8. arr2 = append(arr2, 5)
  9. fmt.Println("the arr1 value:", arr1)
  10. fmt.Println("the arr2 value:", arr2)
  11. }

运行输出:

  1. the arr1 value: [1 2 5 4]
  2. the arr2 value: [1 2 5]

本来以为arr1会输出的是[1 2 3 4],但是输出的却是[1 2 5 4],之所以产生这样的原因是,slice有个特性是允许多个slice指向同一个底层数组,这是一个有用的特性,在很多场景下都能通过这个特性实现 no copy而提高效率。但共享同时意味着不安全。因为slice是有长度和容量的,在没有扩容的情况下,改变了arr1中结构.

为了避免这样的情况的发生我们通常是建议使用make去指定len和cap,但是如果你用这样的写法也是可以,这里需要使用到slice的截取的特性,slice[0:1:1] ([起始index,终止index,cap终止index]).

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. arr1 := []int{1, 2, 3, 4}
  7. arr2 := arr1[:2:2]
  8. arr2 = append(arr2, 5)
  9. fmt.Println("the arr1 value:", arr1)
  10. fmt.Println("the arr2 value:", arr2)
  11. }

运行:

  1. the arr1 value: [1 2 3 4]
  2. the arr2 value: [1 2 5]

这里输出的就是对应的,[1 2 3 4].