限制切片的容量

在创建切片时,使用第三个索引选项引可以用来控制新切片的容量。其目的并不是要增加容量,而是要限制容量。允许限制新切片的容量为底层数组提供了一定的保护,可以更好地控制追加操作。

  1. // 创建长度和容量都是 5 的字符串切片
  2. fruit := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
  3. // 将第三个元素切片,并限制容量
  4. // 其长度为 1 个元素,容量为 2 个元素
  5. myFruit := fruit[2:3:4]
  6. fmt.Println(myFruit)
  7. fmt.Println(len(myFruit))
  8. fmt.Println(cap(myFruit))

这个切片操作执行后,新切片里从底层数组引用了 1 个元素,容量是 2 个元素。具体来说,新切片引用了 Plum 元素,并将容量扩展到 Banana 元素:

限制切片的容量 - 图1

如果设置的容量比可用的容量还大,就会得到一个运行时错误:

  1. myFruit := fruit[2:3:6]

:::danger panic: runtime error: slice bounds out of range [::6] with capacity 5

:::

内置函数 append()在操作切片时会首先使用可用容量。一旦没有可用容量,就会分配一个新的底层数组。这导致很容易忘记切片间正在共享同一个底层数组。一旦发生这种情况,对切片进行修改,很可能会导致随机且奇怪的问题,这种问题一般都很难调查。

如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个 append 操作创建新的底层数组,与原有的底层数组分离。这样就可以安全地进行后续的修改操作了:

  1. myFruit := fruit[2:3:3]
  2. // 向 myFruit 追加新字符串
  3. myFruit = append(myFruit, "Kiwi")

这里,我们限制了 myFruit 的容量为 1。当我们第一次对 myFruit 调用 append() 函数的时候,会创建一个新的底层数组,这个数组包括 2 个元素,并将水果 Plum 复制进来,再追加新水果 Kiwi,并返回一个引用了这个底层数组的新切片。

因为新的切片 myFruit 拥有了自己的底层数组,所以杜绝了可能发生的问题。我们可以继续向新切片里追加水果,而不用担心会不小心修改了其他切片里的水果。可以通过下图来理解此时内存中的数据结构:

限制切片的容量 - 图2

将一个切片追加到另一个切片

内置函数append() 也是一个可变参数的函数。这意味着可以在一次调用中传递多个值。如果使用 运算符,可以将一个切片的所有元素追加到另一个切片里:

  1. // 创建两个切片,并分别用两个整数进行初始化
  2. num1 := []int{1, 2}
  3. num2 := []int{3, 4}
  4. // 将两个切片追加在一起,并显示结果
  5. fmt.Printf("%v\n", append(num1, num2...))

输出的结果为:

[1 2 3 4]

在返回的新的切片中,切片 num2 里的所有值都追加到了切片 num1 中的元素后面。