go slice的切片操作浅析

本文探讨了Go语言中slice的切片操作,包括定义、切片操作的边界问题及不同情况下切片间的关联。文章详细阐述了切片操作遵循的左闭右开原则,并通过代码示例解释了切片在不同边界条件下的行为,如获取len和cap下的切片,以及超过原切片len和cap的切片操作。总结指出,不超过原切片cap的切片共享底层数组,而超过cap的切片会创建新的底层数组。最后,文章提醒了在使用append时需要注意的新旧slice指向问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天和大家分享一下go的slice类型的切片操作。

何为go-slice和切片操作

首先,我们先来了解下go的Slice,slice是一个可变长指向底层数组的数据结构,由长度,容量指向底层数组的指针组成。

定义:

s := make([]T,len,cap)
   T:slice的具体类型
   Len:slice的长度
   Cap:slice的扩展,0<=len<=cap

声明(示例为其中一种):

a := []int{1,2,3}//声明和定义,此处也可看作同时定义了一个匿名的数组,变量a指向了这个数组的地址

slice有一个特性,多个slice之间可以共享底层数据,并且引用的数组部分区间可能重叠,通常可用slice的切片操作生成指向原slice的指针地址,长度(0<=len<=cap)可灵活调整的新切片,当然另一个方法是赋值(指针地址,长度容量和原slice一致),暂且不展开细说。今天着重说一下slice的切片操作。

切片操作定义:

slice [开始位置i : 结束位置j]
   Slice:表示目标的切片对象
   开始位置i:对目标切片开始索引(下标),0<=i<=cap,可省略,默认0
   结束位置j:对目标切片结束索引(下标),i<=j<=cap,可省略,默认len

slice的切片操作遵循左闭右开原则,取出元素的数量j-i

切片操作的边界问题

先说一下切片操作容易混淆和忽略的边界问题,简单来说就是两可两不可。

一 不可:获取len下标的值不可以
一   可:获取len开始的切片可以,将会得到新切片
二不可:获取超过==len<=cap下标的值不可以
二  可:获取==len<=cap的切片可以

下面我分别用代码实验证明上面的结论:

1.获取len下标的值

代码示例:

func main(){
    a := []int{1, 2, 3}
    fmt.Println(a, len(a), cap(a)) //[1 2 3] 3 3

    //获取len下标的值
    b := a[3]
    fmt.Println(b)

}

运行结果:

[1 2 3] 3 3
panic: runtime error: index out of range

2.获取len长度的切片

代码示例:

func main(){
   a := []int{1, 2, 3}
   fmt.Println(a, len(a), cap(a)) 

   //获取len长度的切片
   b := a[len(a):]
   fmt.Println(b, len(b), cap(b))
}

运行结果:

[1 2 3] 3 3
[] 0 0

3.生成一个len<cap的slice

为了验证结果更清晰,与上述产生明显对比,我需要生成一个len<cap的slice
代码示例:

func main(){
   a := []int{1, 2, 3}//长度3容量 3
   b := make([]int, 0, 6)//声明一个长度为0容量为6的切片指针变量b
   b = append(b, a...)//a切片赋值给b,ab指向同一底层数组 ->改变值互不影响即对应底层数组不同
   fmt.Println(b, len(b), cap(b))
   fmt.Println(a, len(a), cap(a))

   //各自向a,b新增字段或者改变对应下标的值,看看会不会互相影响
   //新增字段
   a = append(a, 4)
   fmt.Println(a, b) 
   b = append(a, 5)
   fmt.Println(a, b)

   //改变对应下标的值啊
   a[1] = 8
   fmt.Println(a, b)
}

运行结果:

[1 2 3] 3 6
[1 2 3] 3 3
[1 2 3 4] [1 2 3]
[1 2 3 4] [1 2 3 4 5]
[1 8 3 4] [1 8 3 4 5]

4.获取cap下标的值

代码示例:

func main(){
   a := []int{1, 2, 3}//长度3容量 3
   b := make([]int, 0, 6)
   b = append(b, a...)
   fmt.Println(b, len(b), cap(b))

   //获取cap下标的值
   c := b[cap[(b)]
   fmt.Println(c)
}

运行结果:

[1 2 3] 3 6
panic: runtime error: index out of range

5.获取cap开始的切片

代码示例:

func main(){
  a := []int{1, 2, 3}//长度3容量 3
  b := make([]int, 0, 6)
  b = append(b, a...)
  fmt.Println(b, len(b), cap(b))

  c := b[cap(b):]
  fmt.Println(c, len(c), cap(c))
}

运行结果:

[1 2 3] 3 6
panic: runtime error: slice bounds out of range

原因是切片的默认下标是len的值,而cap>len违背的切片左右值i<=j的规定

6.获取cap开始cap结束的切片

代码示例:

func main(){
   a := []int{1, 2, 3}//长度3容量 3
   b := make([]int, 0, 6)
   b = append(b, a...)
   fmt.Println(b, len(b), cap(b))

   c := b[cap(b):cap(b)]
   fmt.Println(c, len(c), cap(c))

   d := b[len(b):cap(b)]
   fmt.Println(d, len(d), cap(d))
}

运行结果:

[1 2 3] 3 6
[] 0 0
[0 0 0] 3 3

切片的默认值是切片对应类型的”零值“,例如:string的”零值“是空字符串”".

切片操作前后切片对比

Slice是指针类型,多个slice之间可以共享底层的数据,修改数据会相互影响,只有变动导致底层数组扩展之后,才会指向不同地址的底层数组。
我分下面几种情况说明两个切片是否指向同一底层数组

1.不超过原切片len的切片

用切片操作在原切片得到的切片,在不超过原切片的len的情况下,实验代码如下:

func main() {
    a := make([]int, 3, 8)
    a[1] = 1
    a[2] = 2
    fmt.Println(a, len(a), cap(a))
    b := a[:len(a)]
    fmt.Println(b, len(b), cap(b))
    b[1] = 4
    fmt.Println(a, b)
}

运行结果:

[0 1 2] 3 8
[0 1 2] 3 8
[0 4 2] [0 4 2]

B切片通过切片操作从a获取,在未超过切片a的len的情况下,a改变会影响b,故可以说明ab指向同一底层数组。
结论:在不超过原切片的len的情况下,append数据会影响原切片的值,即和原切片指向同一个数组

2.超过原切片的len,但是没有超过cap的切片

用切片操作在原切片得到的切片,超过原切片的len,但是没有超过cap,代码如下:

func main() {
    a := make([]int, 3, 8)
    a[1] = 1
    a[2] = 2
    fmt.Println(a, len(a), cap(a))
    c := a[len(a):cap(a)]
    fmt.Println(c, len(c), cap(c))
    a = append(a, 8)
    fmt.Println(a, len(a), cap(a))
    fmt.Println(c, len(c), cap(c))
}

运行结果:

[0 1 2] 3 8
[0 0 0 0 0] 5 5
[0 1 2 8] 4 8
[8 0 0 0 0] 5 5

c切片通过切片操作从a获取,超过原切片a的len,但是没有超过a切片的cap,追加a的值也会影响c,说明此时ac指向同一底层数组。
结论:超过原切片的len,但是没有超过cap,不会开辟新的底层数组。

3.超过原切片cap的切片

用切片操作在原切片得到的切片,超过原切片的cap(没办法直接得到,我们利用切片的append得到),代码如下:
代码示例:

func main() {
    a := make([]int, 3, 8)
    a[1] = 4
    a[2] = 2
    fmt.Println(a, len(a), cap(a))
    c := a[len(a):cap(a)]
    fmt.Println(c, len(c), cap(c))
    a = append(a, 8)
    fmt.Println(a, len(a), cap(a))
    fmt.Println(c, len(c), cap(c))
    c = append(c, 10)
    fmt.Println(a, len(a), cap(a))
    fmt.Println(c, len(c), cap(c))
    c[0] = 22
    fmt.Println(a, len(a), cap(a))
    fmt.Println(c, len(c), cap(c))
    a = append(a, 11)
    fmt.Println(a, len(a), cap(a))
    fmt.Println(c, len(c), cap(c))
}

运行结果:

[0 4 2] 3 8
[0 0 0 0 0] 5 5
[0 4 2 8] 4 8
[8 0 0 0 0] 5 5
[0 4 2 8] 4 8
[8 0 0 0 0 10] 6 10
[0 4 2 8] 4 8
[22 0 0 0 0 10] 6 10
[0 4 2 8 11] 5 8
[22 0 0 0 0 10] 6 10

c切片通过切片操作从a获取,因为c添加了参数,超过原切片的cap,而修改c的值不会影响a,a,c的底层数组变成了两个截然不同的数组。
结论:超过原切片cap的切片,会开辟新的底层数组

总结

不超过原切片的cap,不同的切片都对应相同的数组。变动导致扩容会开辟一个新的数组作为该切片的指向数组,此时原切片和现切片改变值互不影响,操作导致扩容才会导致切片指向不同底层数组

拓展

由此解释一点,slice的append通常写法都是会将返回值(新的slice)赋值给原slice,而不是直接使用原slice,因为我们无法确定,经过append操作,新旧两个slice是否指向同一底层数组(不确定是否会造成内存重新分配),也没法保证,对旧slice的操作不会影响新的,故对slice的append操作的返回值通常会赋值给输入slice。

这就是本次分享的全部内容,欢迎大家捉虫和讨论~

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值