当前位置:   article > 正文

golang中切片slice的使用须知_golang make slice

golang make slice

这篇文章主要理解一个事情:切片是对数组的引用。

go version
1.14.7

乍一看,这个很好理解,但是却很容易出错。

切片的主要操作:

1、make			创建一个切片。
2、len			切片的长度。
3、cap			切片的容量。
4、append		向切片中填加数据到尾部,实际上是修改底层数组对应位置上的值。
5、a = a[:0]	清空一个切片。
6、a[0]			访问值。
7、copy			复制一个切片。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

切片的三要素:

pointer - 底层数组的某一个元素的地址,不一定是第一个,数组的各个元素的地址是连续的。
len(a)	- 切片的长度。 
cap(a)	- 底层数组的长度。
  • 1
  • 2
  • 3
创建一个切片的步骤为:
m := make([]int,  n)
1、创建一个数组 [n]int 
2、m的pointer为数组的地址,len为n,cap为n。
  • 1
  • 2
  • 3
  • 4
case1

如果 n 为 0 呢?

m := make([]int, 0)
n := make([]int, 0)
t := make([]int, 0)
fmt.Printf("%p\n", m) // 0x5ae008
fmt.Printf("%p\n", n) // 0x5ae008
fmt.Printf("%p\n", t) // 0x5ae008
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

golang在底层为长度等于0的数组指向了同一个地址。

cc := [0]int{}
fmt.Printf("%p\n", &cc) // 0x5ae008
  • 1
  • 2
case2

append一定会导致切片扩容吗?

关于扩容,当底层数组长度不够了,会创建一个新的数组,将原数据拷贝过来,同时切片的pointer为新数组的首地址,每次会扩容多少呢,按照既定的策略来,每个版本可能不一样。

既然有策略存在,那肯定不是每次添加数据都会取扩容的。

那么,以下程序会触发扩容吗?

s1 := make([]int, 0)

fmt.Printf("%p\n", s1) // 0x5ae008

s1 = append(s1, 1)
fmt.Println(len(s1)) // 1
fmt.Println(cap(s1)) // 1
fmt.Printf("%p\n", s1) // 0xc00000a0f8

s1 = append(s1, 10, 11)
fmt.Println(len(s1)) // 3
fmt.Println(cap(s1)) // 4
fmt.Printf("%p\n", s1) // 0xc000014440

s1 = append(s1, 6)
fmt.Println(len(s1)) // 4
fmt.Println(cap(s1)) // 4
fmt.Printf("%p\n", s1) // 0xc000014440 没有发生扩容
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
case3

如何理解切片的容量cap?
切片是构建在数组之上的,切片的pointer指向数组中的某一个元素的地址,直到数组的尾部,这个区间就是切片的容量,也是切片的作用区间

切片的长度,表示切片已经存储了几个元素,可以通过下标来访问。

切片的容量的意义就在于,执行append操作时如果超出了容量,系统需要对数组进行扩容,此时就会将切片的内容复制到新的数组上。

a := make([]int, 0)
a = append(a, 1,2,3,4,5,6)

fmt.Printf("%p\n", a) // 0xc000012330
fmt.Println(len(a)) // 6
fmt.Println(cap(a)) // 6

b := a[:0]
fmt.Printf("%p\n", b) // 0xc000012330
fmt.Println(len(b)) // 0
fmt.Println(cap(b)) // 6

c := a[2:4]
fmt.Printf("%p\n", c) // 0xc000012340 地址在a的后面
fmt.Println(len(c)) // 2
fmt.Println(cap(c)) // 4
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在这里插入图片描述

case4

切片为引用传递
指向同一个底层数组的多个切片会相互影响的,具体影响的范围取决于各个切片的作用区间

a := []int{1,2,3,4,5,6,7,8}
fmt.Printf("%p\n", a) // 0xc00000c540
fmt.Println("------------------------------------")
a1 := a[:3] // [1 2 3]
fmt.Printf("%p\n", a1) // 0xc00000c540
fmt.Println(len(a1)) // 3
fmt.Println(cap(a1)) // 8
fmt.Println("------------------------------------")
a2 := a[:5] // [1 2 3 4 5]
fmt.Printf("%p\n", a2) // 0xc00000c540
fmt.Println(len(a2)) // 5
fmt.Println(cap(a2)) // 8
fmt.Println("------------------------------------")
a3 := a[4:] // [5 6 7 8]
fmt.Printf("%p\n", a3) // 0xc00000c560
fmt.Println(len(a3)) // 4
fmt.Println(cap(a3)) // 4
fmt.Println("------------------------------------")
a1[0] = 10
fmt.Println(a) // [10 2 3 4 5 6 7 8]
fmt.Println(a1) // [10 2 3]
fmt.Println(a2) // [10 2 3 4 5]
fmt.Println(a3) // [5 6 7 8]
fmt.Println("------------------------------------")
a1 = append(a1, 11)
fmt.Println(a) // [10 2 3 11 5 6 7 8]
fmt.Println(a1) // [10 2 3 11]
fmt.Println(a2) // [10 2 3 11 5]
fmt.Println(a3) // [5 6 7 8]
fmt.Println("------------------------------------")
a3 = append(a3, 12) // 已经扩容到新的数组
fmt.Println(a) // [10 2 3 11 5 6 7 8]
fmt.Println(a1) // [10 2 3 11]
fmt.Println(a2) // [10 2 3 11 5]
fmt.Println(a3) // [5 6 7 8 12]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

在函数传参的时候尤其要注意

func main() {
	s := []int{1,2,3,4}
	t1(s)
	fmt.Println(s)
}

func t1(a []int) {
	a[0] = 100
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

打印的结果为[100 2 3 4],原因就是在传参的时候其实就是将变量s先赋值给一个临时变量tmp = s,然后将tmp传到函数,底层的数组不变,所以在切片赋值的时候一定要使用copy方法。

case5

如何做到复制切片,但不需要引用。

func copy(dst, src []Type) int
  • 1

dst 必须是一个初始化了的,且长度大于0的切片,dst和src的类型要一致, copy并不会自动扩容

a := []int{1,2,3,4,5,6,7,8}
fmt.Printf("%p\n", a) // 0xc00000c4c0

var b []int
copy(b, a)
fmt.Printf("%p\n", b) // 0x0
fmt.Println(b) // []

c := make([]int, 0)
copy(c, a)
fmt.Printf("%p\n", c) // 0x5ac008
fmt.Println(c) // []

d := make([]int, len(a))
copy(d, a)
fmt.Printf("%p\n", d) // 0xc00000c500
fmt.Println(d) // [1 2 3 4 5 6 7 8]

e := make([]int, 4)
copy(e, a)
fmt.Printf("%p\n", e) // 0xc000014480
fmt.Println(e) // [1 2 3 4]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
case6
func main() {
	list := []int{2, 4, 8, 2}
	list1 := list[0:3]
	list1[0] = 10
	fixList(list1)
	fmt.Println(list1, list) //打印 [10 4 8] [10 4 8 3]
}

func fixList(param []int) {
	param = append(param, 3)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里要分析一下

fixList(list1) 传入的 list1 的长度为3,传入 fixList 后,param 将会继续指向同样的底层数组,调用 append 操作
之后,param长度变为4,还是可以沿用底层数组,所以最终底层数组为 [10 4 8 3],而 list1长度为3,因此 list1 = [10 4 8]
  • 1
  • 2
copy的使用场景

有时候一个小的切片来自于一个大的切片的一部分(b := a[m:n]),因此它们指向同一个大的底层数组,虽然在此之后大的切片已不再使用,但是底层数组依然不会被释放,因为小的切片还在指向它,所以此时应该使用copy来产生小的切片(copy(b, a)),这样可以减少内存占用。

总结

切片的引用类型具体体现在,他们会共用底层的数组,除非长度超出了,才会创建新的底层数组,这样才会脱离关系。golang 的这种设计是为了节省内存,但是在使用的时候的确会出现问题。只要记住:底层数组,只要长度够就会被共用

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/206433
推荐阅读
相关标签
  

闽ICP备14008679号