go语言中切片的容量

由于go中的数组类型,其长度是固定的,无法改变。在很多场景下我们需要动态改变数组的长度,因而go中内置了slice类型,称其为数组的一种抽象(可看作是动态数组)。slice与数组很相似,只是长度可变,可追加元素。

切片的初始化:

slice类型的初始化主要有三种方式:

  通过make函数

  通过字面量方式

  对源数组或源切片使用identifier[start:end] 语法生成切片

  如下:  

package main
import (
	"fmt"
)

func main() {
	//make切片
	var s0 []int = make([]int,3,5)  //切片类型、切片长度、切片容量
	fmt.Println(len(s0),cap(s0),s0)
    //--> 3 5 [0 0 0]	
 
	//字面量切片
	var s1 []int = []int{1,2,3}     //切片的元素
	fmt.Println(len(s1),cap(s1),s1)
    //--> 3 3 [1 2 3]

	//从数组切片,这里先定义一个数组a
	var a [10]int
	s2 := a[:3]                     //从数组a的第0个到第3个元素生成切片
	fmt.Println(len(s2),cap(s2),s2)
    //--> 3 10 [0 0 0]

	//从s2切片
	s4 := s2[:2]
	fmt.Println(len(s4),cap(s4),s4)
    //-->2 10 [0 0]
}

大部分情况下我们将切片当作动态数组使用即可,go内置了一些简单函数返回切片的属性、或对切片操作:

append()给切片追加元素:

	primes := [6]int{2, 3, 5, 7, 11, 13}
	var s []int = primes[1:4]
	fmt.Println(s)
    //-->[3 5 7]

	s = append(s,1,2)
	fmt.Println(s)
   //-->[3 5 7 1 2]

copy()将切片2的内容复制给切片1:

	s := []int{1,2}
	s1 := make([]int,len(s),cap(s)*2)

	copy(s1,s)
	fmt.Println(len(s),cap(s),s)
	fmt.Println(len(s1),cap(s1),s1)
//执行结果:
2 2 [1 2]
2 4 [1 2]

len()、cap()方法分别返回切片当前的长度、容量。

capacity初始值:

本篇笔记的第一段代码,也是对capacity的一个试验,我们发现,切片的capacity初始值取决于切片的初始化方式:

1.通过make函数初始化一个切片时,capacity由我们自己定义。

2.通过字面量初始化一个切片时,capacity默认等于该切片的长度。

3.对数组或切片执行array[start:end]操作生成切片时,切片的capacity总等于源数组/源切片的capacity减去start的值,比如array原本的capacity为4,s0 := array[1:],则s0的capacity为3。

capacity自更新:

逐个追加切片元素时:

	primes := [13]int{}

	var s []int = primes[0:12]
	fmt.Println(len(s),cap(s),s)
	s = append(s,1)
	fmt.Println(len(s),cap(s),s)
	s = append(s,1)
	fmt.Println(len(s),cap(s),s)

//执行结果:
12 13 [0 0 0 0 0 0 0 0 0 0 0 0]
13 13 [0 0 0 0 0 0 0 0 0 0 0 0 1]
14 26 [0 0 0 0 0 0 0 0 0 0 0 0 1 1]

如上,切片s的capacity等于数组primes的长度。当我们给切片一次次append元素,直到切片的长度大于capacity时,capacity的值会自动更新,而且是简单的updatedCapacity = capacity X 2

一次追加多个元素时候:

	s := []int{1,2}
	fmt.Println(len(s),cap(s),s)
	
	s = append(s,1,2,4,7,8,2,3,4,5)
	fmt.Println(len(s),cap(s),s)

//执行结果:
2 2 [1 2]
11 12 [1 2 1 2 4 7 8 2 3 4 5]

多次试验会发现,当我们一次性追加多个元素时,capacity会更新为一个接近但大于切片长度的数值,而不是简单地基于原来的值乘以某个正整数。

刚接触的时候,对于切片的容量capacity,我是有点疑惑的,为什么要把它暴露给开发者呢?我们只知道这个属性的值总是大于等于切片的元素个数(即切片的长度)。当使用append函数给切片追加元素时,如果切片的元素个数大于这个capacity,capacity数值会动态更新,变大。后来我在官方的《A Tour of Go》中关于slice的一节看到一段话:

The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.

至此,一切疑惑都解开了:

第一,go中的数组在初始化后,长度是固定的,这是前提。

第二,slice只是基于数组的一种抽象类型,它自身并没有保存什么数据,其底层只是指向了一个数组而已。

第三,假设有个数组长度为L, 切片slice的底层指向该数组,slice的第一个元素是数组的第n个元素,则slice只能访问数组的[n,L]片段而已,L 减n的大小,也就是slice的容量。

从实践意义上,切片长度是当前切片可直接访问的元素的数量,而切片容量则是当前切片扩展后能访问的元素个数:

01	s := []int{1,2,3,4,5}
02	printSlice(s)
03	//下面生成长度为1的切片,其第一个元素指向数组索引为2的元素
04	s = s[2:3]
05	printSlice(s)
06    //下面生成长度为3的切片,其第一个元素指向数组索引为2的元素
07	s = s[0:3]
08	printSlice(s)
09
10  //执行结果:
11  len=5 cap=5 [1 2 3 4 5]
12  len=1 cap=3 [3]
13  len=3 cap=3 [3 4 5]

我们可以像访问一个数组元素一样访问一个切片的元素,但是当索引值大于等于切片长度时,编译器会报错:

panic: runtime error: index out of range

这时候我们可以像第7行代码那样,扩展切片的长度,从而访问数组的其他元素。

要留意的地方:

1.由于slice本质上是对数组片段的引用,当有多个slice指向同一数组时,数组元素的值可能会被多次修改:

    s := make([]int,1,10)
    fmt.Println(s)
-->[0]

    //下面对s执行append时,b的长度为2,没有超出s的容量,b的指针仍指向s
    //此时s的第二个位置的元素,其值被修改为1
    b := append(s,1)
    fmt.Println(b)
-->[0 1]

    //下面对s执行append时,c的长度为2,没有超出s的容量,c的指针仍指向s
    //此时s的第二个位置的元素,其值被修改为2
    c := append(s,2)
    fmt.Println(b)
    fmt.Println(c)
-->[0 2]
   [0 2]

2.有时候我们希望得到一个新的切片,但是对新切片的追加操作不会影响原切片。除了创建一个新的slice然后把目标切片copy()过来之外,我们可以指定新切片的容量,当对其追加元素时,由于长度超过容量,go会给新切片分配新的内存:

    s := make([]int,1,10)
    b := s[0:2:2]
	fmt.Println(b)
-->[0,0]

    //由于切片c长度为3,超出b的容量,go会给切片c分配新内存,以后对s的操作都与c无关
    c := append(b,2)
    fmt.Println(c)
-->[0,0,2]
    s=append(s,3,3)
    fmt.Println(s)
-->[0,3,3]
    fmt.Println(c)
-->[0,0,2]

总结:

实际上,slice的本质是对已有数组的引用。所谓slice,只是对数组的某一截进行封装(或说描述),然后以slice类型呈现给开发者,当我们在slice上对某个元素进行修改时,原数组中相应的元素也被修改。当我们用make函数或字面量方式创建一个slice时,实际上是先创建一个数组,再创建一个基于该数组的切片并返回。

另外,当我们用newSlice=append(oddSlice,1,2)给切片追加元素时候,其实是生成了一个新的切片赋予newSlice,原有的切片oddSlice并没有被修改,原数组也未被修改。

slice类型本身的知识点并不多,理清概念即可。

猜你喜欢

转载自blog.csdn.net/weixin_36094484/article/details/82052049