简单易懂,解析Go语言中的Slice切片

2. slice 切片

2.1 初始化

var初始化一个nil切片,不分配内存,(并不是空切片);make 可以指定长度和容量;字面量可以根据长度自动设定长度

若未指定容量,则容量默认等于长度

func main() {
    
     // len cap 值已标注在后面
   var v1 []int				    // 0 0
   v2 := make([]int, 0)		    // 0 0
   v3 := make([]int, 5)		    // 5 5
   v4 := make([]int, 5, 10)	    // 5 10
   v5 := []int{
    
    }				// 0 0
   v6 := []int{
    
    1, 2, 3, 4, 5}	// 5 5
   v7 := *new([]int)            // 0 0 nil切片,不是空切片
}

还可以从数组,切片截取来进行初始化。

  • 切片长度根据截取长度定。
  • 若从切片截取,容量保持跟原切片一致;从数组截取,容量为数组长度 - 起始截取位置;
  • 切片截取时,只考虑容量,不考虑长度;即不超过原数组/切片容量就可以截取
func main() {
    
    
   a := [10]int{
    
    1, 2, 3, 4, 5}
   s1 := a[1:6]
   s2 := s1[0:9]
   s3 := s1[0:10] //panic: runtime error: slice bounds out of range [:10] with capacity 9
   fmt.Println(len(a), cap(a))
   fmt.Println(len(s1), cap(s1))
   fmt.Println(len(s2), cap(s2))
   /*
   10 10
   5 9
   9 9
    */
}

2.2 源代码

切片依托于底层数组实现

type slice struct {
    
    
	array unsafe.Pointer // 指向底层数组
	len   int            // 切片长度
	cap   int            // 容量
}

由于指向的是底层数组,所以在使用数组/切片创建切片的时候,切片会与原数组/切片共用一部分内存,在对切片进行修改的时候,有可能会将原数据一起修改

但扩容之后,数据会被复制到新切片中,此时底层数组就不一样了,不会发生上述情况

//未发生扩容
func main() {
    
    
	a := [10]int{
    
    1, 2, 3, 4, 5, 6, 7}
	s1 := a[1:5]
	s2 := s1[0:6]
	s1 = append(s1, 10)
	s2[2] = 100
	fmt.Println(a) 
	fmt.Println(s1)
	fmt.Println(s2)
	/*
	[1 2 3 100 5 10 7 0 0 0]
	[2 3 100 5 10]
	[2 3 100 5 10 7]
	 */
}
//发生扩容
func main() {
    
    
   a := [10]int{
    
    1, 2, 3, 4, 5, 6, 7}
   s1 := a[1:5]
   s2 := s1[0:9]
   s1 = append(s1, 10)
   s2 = append(s2, 10)
   s2[2] = 100
   fmt.Println(a)
   fmt.Println(s1)
   fmt.Println(s2)
}
/*  可以看到修改 100 没有改变原数据
[1 2 3 4 5 10 7 0 0 0]
[2 3 4 5 10]
[2 3 100 5 10 7 0 0 0 10]
 */

2.3 拷贝

  • 使用copy(new,old)可以拷贝切片,并且此时两个切片底层数组地址不同。
  • 拷贝时,去两个切片长度的最小值进行拷贝,拷贝过程中不会发生扩容操作。
func main() {
    
    
	a := []int{
    
    1, 2, 3, 4, 5}
	b := make([]int, 3)
	c := make([]int, 10)
	copy(b, a)
	copy(c, a)
	b[0] = 100
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	/*
	[1 2 3 4 5]
	[100 2 3]           //修改不会影响原切片,并且复制不会发生扩容操作
	[1 2 3 4 5 0 0 0 0 0]
	 */
}

2.4 扩容

  • slice扩容时,会先创建一个大数组,再将原数组数据复制进去,最后再执行append操作。
  • 1.18前,大于等于1024,每次扩容25%; 小于1024,每次扩容一倍
  • 1.18后,大于256,扩容后的容量计算公式如下:newcap = oldcap+(oldcap+threshold*3)/4; 小于256,每次扩容一倍
  • 过渡更加平滑,避免了2-1.25的突变
  • 实际扩容后的容量不严格等于计算结果,还要考虑到内存对齐等问题
//扩容后地址改变
func main() {
    
    
	a := make([]int, 1, 2)
	fmt.Println(&a[0])
	a = append(a, 1)        //未发生扩容
	fmt.Println(&a[0])
	a = append(a, 2)        //发生扩容
	fmt.Println(&a[0])
	/*
	0xc00000a0d0
	0xc00000a0d0
	0xc0000101c0        //扩容后的地址发生了改变
	 */
}
// 扩容示例
func main() {
    
    
	a := make([]int, 256) //第一档
	b := make([]int, 257) //第二档 
	c := make([]int, 512)
	d := make([]int, 1024)
	a = append(a, 1)
	b = append(b, 2)
	c = append(c, 3)
	d = append(d, 4)
	fmt.Println(len(a), cap(a))
	fmt.Println(len(b), cap(b))
	fmt.Println(len(c), cap(c))
	fmt.Println(len(d), cap(d))
}
/*          b扩容后计算结果应为 513.25,向上取整 514 , 但实际结果为 608 这其中就经历了内存对齐
257 512
258 608
513 848
1025 1536
 */

2.5 切片表达式

  • 简单表达式[low:high] 表示截取[low,high);low,high均可省略
  • 扩展表达式[low:high:capmax] 只可省略low;capmax用来限制新切片容量,避免对high后底层数组的元素进行修改
  • 作用于字符串时,生成的结果仍为字符串;扩展表达式不能用于字符串
//扩展表达式
func main() {
    
    
	a := [10]int{
    
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	b := a[:5]
	b = append(b, 5555)
	c := a[:5:5]        //限制切片容量为5 添加元素就会触发扩容操作,改变底层数组指向
	c = append(c, 6666)
	fmt.Println(a) // [1 2 3 4 5 5555 7 8 9 10]
}
//用于字符串
func main() {
    
    
	a := "lonelysnow"
	b := a[6:]
	//c := a[0:6:6] // invalid operation: 3-index slice of string
	fmt.Println(b)
	fmt.Println(reflect.TypeOf(b)) // 查看截取的类型
}
/*
   snow
   string
 */

猜你喜欢

转载自blog.csdn.net/lonely__snow/article/details/145745054
今日推荐