go slice 实例分析

1,766 阅读10分钟

我们知道go的slice是引用类型,其底层结构有三个变量分别是len,cap和底层数组的指针,今天就用各种实例跑一下这3个变量的变化情况

1.切片初始化

1.1一般初始化方式

a := []int{1,2,3,4,5,6}
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%pa数组指针的地址为%d\n",cap(a),len(a),a,&a,&a[0])

执行结果为:

a的cap为6,a的len为6,a[1 2 3 4 5 6],a的地址为0x1400011e018,a数组指针的地址为1374390779904
  • 初始化时的cap 等于 len 等于初始化长度

1.2当初始化为空切片时

a := []int{}
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p\n",cap(a),len(a),a,&a)

其结果为:

a的cap为0,a的len为0,a[],a的地址为0x1400011e018
  • 初始化为空切片时 ,cap 等于 len 等于0;

1.3采用make的方式进行初始化

a := make([]int,4,5)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p\n",cap(a),len(a),a,&a)

其结果为:

a的cap为5,a的len为4,a[0 0 0 0],a的地址为0x1400011e018
  • 采用make初始化切片有3个参数,类型,len,cap,切片里的值会被初始化成对应类型的零值,cap 参数可以省略,默认和len参数相同;

1.4采用var定义切片:

var a []int
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p\n",cap(a),len(a),a,&a)

其结果为:

a的cap为0,a的len为0,a[],a的地址为0x1400011e018c
  • 采用var定义切片默认len = cap = 0;

2.append操作

2.1 cap足够时

a := make([]int, 3, 6)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
a = append(a,1)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])

其结果为:

a的cap为6,a的len为3,a[0 0 0],a的地址为0x1400011e018,a数组的地址为0x14000130000
a的cap为6,a的len为4,a[0 0 0 1],a的地址为0x1400011e018,a数组的地址为0x14000130000d
  • 当容量足够时,进行append操作直接修改其len并在a数组后进行追加即可;

2.2 cap足够时追加多个数据

a := make([]int, 3, 6)
b := []int{1,2}
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
a = append(a,b...) // 等同于a = append(a,1,2)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])

其结果为:

a的cap为6,a的len为3,a[0 0 0],a的地址为0x14000198000,a数组的地址为0x1400019a000
a的cap为6,a的len为5,a[0 0 0 1 2],a的地址为0x14000198000,a数组的地址为0x1400019a000d
  • **当容量足够时,**进行append操作追加多个数据,结果和多次追加1个数据结果相同;

2.3 cap不足时追加数据

a := make([]int, 3)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
a = append(a,1)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])

其结果为:

a的cap为3,a的len为3,a[0 0 0],a的地址为0x1400000c030,a数组的地址为0x14000016090
a的cap为6,a的len为4,a[0 0 0 1],a的地址为0x1400000c030,a数组的地址为0x14000014240d
  • 当容量不足时,进行append操作,会发生扩容,此时的数组地址发生变化,cap变为原来的2倍(不完全是2倍,可能会因为字节对齐的原因在2倍的基础上略有偏差);

2.4cap不足时追加多个数据扩容2倍后仍然cap不足时

a := make([]int, 4)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
a = append(a,1,2,3,4,5)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])

其结果为:

a的cap为4,a的len为4,a[0 0 0 0],a的地址为0x14000114018,a数组的地址为0x14000124020
a的cap为10,a的len为9,a[0 0 0 0 1 2 3 4 5],a的地址为0x14000114018,a数组的地址为0x1400012a000
  • cap不足时追加多个数据扩容2倍后仍然cap不足时,会继续扩容直到cap>len;(此时不是整倍的扩容了);

2.5 cap追加多个参数时的追加方式

a := make([]int, 3, 4)
b := append(a,1)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
fmt.Printf("b的cap为%d,b的len为%d,b为%d,b的地址为%p,b数组的地址为%p\n",cap(b),len(b),b,&b,&b[0])
a = append(a,2,3)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
fmt.Printf("b的cap为%d,b的len为%d,b为%d,b的地址为%p,b数组的地址为%p\n",cap(b),len(b),b,&b,&b[0])

其结果为:

a的cap为4,a的len为3,a[0 0 0],a的地址为0x1400011e018,a数组的地址为0x1400012e020
b的cap为4,b的len为4,b[0 0 0 1],b的地址为0x1400011e030,b数组的地址为0x1400012e020
a的cap为8,a的len为5,a[0 0 0 2 3],a的地址为0x1400011e018,a数组的地址为0x14000124080
b的cap为4,b的len为4,b[0 0 0 1],b的地址为0x1400011e030,b数组的地址为0x1400012e020
  • 对a进行append时,当a还有空间到那时a的空间不足时,不会在原空间惊醒修改;

3 多切片公用空间

3.1多切片共用相同空间

a := make([]int, 3, 8)
b := append(a,1)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
fmt.Printf("b的cap为%d,b的len为%d,b为%d,b的地址为%p,b数组的地址为%p\n",cap(b),len(b),b,&b,&b[0])
a = append(a,2,3)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
fmt.Printf("b的cap为%d,b的len为%d,b为%d,b的地址为%p,b数组的地址为%p\n",cap(b),len(b),b,&b,&b[0])
b[2] = 10
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
fmt.Printf("b的cap为%d,b的len为%d,b为%d,b的地址为%p,b数组的地址为%p\n",cap(b),len(b),b,&b,&b[0])

其结果为:

a的cap为8,a的len为3,a[0 0 0],a的地址为0x1400011e018,a数组的地址为0x14000124040
b的cap为8,b的len为4,b[0 0 0 1],b的地址为0x1400011e030,b数组的地址为0x14000124040
a的cap为8,a的len为5,a[0 0 0 2 3],a的地址为0x1400011e018,a数组的地址为0x14000124040
b的cap为8,b的len为4,b[0 0 0 2],b的地址为0x1400011e030,b数组的地址为0x14000124040
a的cap为8,a的len为5,a[0 0 10 2 3],a的地址为0x1400011e018,a数组的地址为0x14000124040
b的cap为8,b的len为4,b[0 0 10 2],b的地址为0x1400011e030,b数组的地址为0x14000124040
  • 多切片共用相同的数组空间时,其结果会相互影响但是其len值不会相互影响;

3.2 切片截取

a := make([]int, 3, 8)
a[0] = 0
a[1] = 1
a[2] = 2
b := a[1:4]
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
fmt.Printf("b的cap为%d,b的len为%d,b为%d,b的地址为%p,b数组的地址为%p\n",cap(b),len(b),b,&b,&b[0])
b = append(b,4,5,6,7,8,9)
fmt.Printf("a的cap为%d,a的len为%d,a为%d,a的地址为%p,a数组的地址为%p\n",cap(a),len(a),a,&a,&a[0])
fmt.Printf("b的cap为%d,b的len为%d,b为%d,b的地址为%p,b数组的地址为%p\n",cap(b),len(b),b,&b,&b[0])

其结果为:

a的cap为8,a的len为3,a[0 1 2],a的地址为0x1400000c018,a数组的地址为0x1400001e080
b的cap为7,b的len为3,b[1 2 0],b的地址为0x1400000c030,b数组的地址为0x1400001e088
a的cap为8,a的len为3,a[0 1 2],a的地址为0x1400000c018,a数组的地址为0x1400001e080
b的cap为14,b的len为9,b[1 2 0 4 5 6 7 8 9],b的地址为0x1400000c030,b数组的地址为0x1400005a070
  • 切片进行截取时,数组容量的末尾和原切片数组末尾对齐,数组地址为截取单元的首地址,且可以超过其len的范围进行截取,当其中一个数组发生扩容时,另一个数组的地址不变;

总结

以上便是slice的各种操作时,其cap,len,和底层数组的变化实例,结果和相应的结论。