Go切片学习

引入——数组

在Go中,数组和其他大部分语言一样,是一个固定大小的存储某种类型元素的连续空间。

因为其长度是固定的,故在声明时需要确定它的长度,如下:

 var arr1 [4]int
 arr2 := [3]string{}

声明之后,数组其中的元素默认值为该元素对应的空值,如int——0,string——""

由于数组长度不可变,使用起来不方便,所以便引出了长度可变的切片

切片的数据结构

切片的底层实际也是基于一个数组,它通过维护一个指向数组的指针、一个当前长度值和容量值来实现可变长度的数组

type slice struct {
     array unsafe.Pointer // 指向底层数组的指针
     len   int           // 当前切片的长度
     cap   int           // 当前切片的容量,cap >= len
 }

即一个Slice会对应一个数组,但一个数组可以有多个slice,如下图

slice1和slice2都是对arr数组的引用,它们的修改实际上都是对arr的修改,注意到在slice2我们便看出cap和len的区别,对于slice2,由于其是对arr从下标1到18(左闭右开)的引用,因此其实际长度为18,但其底层实际为一个长度为20的数组,故其cap为20

        

 

        由于切片底层是一个指针,而Go的参数传递均为值传递,故通常我们避免用数组作为参数传递,这样会引起数组的拷贝;而是使用切片作为参数,这样在一定程度上边实现了所谓的引用传递

如何声明一个slice

var slice []int // 声明slice是一个切片,但未给其分配空间,故len和cap均为0
slice0 := []int{} // 同上
slice1 := []int{1,2,3} // len = 3, cap = 3
slice2 := [...]int{1,2,3} // len = 3, cap = 3
slice3 := make([]int, 3) // len = 3, cap = 3

对于slice和slice0,此时它们的值为nil

make([]T, length, capacity)的另一种用法

slice4 := make([]int, 2, 3) // len = 2, cap = 3
fmt.Printf("%v", slice4) // [0 0]
slice5 := make([]int, 4, 3) // invalid argument: length and capacity swapped

添加元素

既然要用长度可变的切片,那么它最常用的操作自然就是添加元素,当向一个切片append一个元素之后,若当前len==cap,则会发生自动扩容,否则修改底层数组,注意这里是修改,因为数组本身的长度不可变,由以下例子分析:

arr := [4]int{1, 2, 3, 4}
slice1 := arr[:2] // slice1: [1 2] len: 2 cap: 4
slice1 = append(slice1, 5)
fmt.Printf("%v\n", slice1)
fmt.Printf("%v\n", arr)
fmt.Println(len(slice1))
fmt.Println(cap(slice1))

输出:

[1 2 5]
[1 2 5 4]
3
4

可以看到,arr[2]因为append而从3变为5,这也印证了slice实际上是对底层数组的引用

append了一个元素之后,slice1的len变为3,而cap仍然为4,说明仍未发生扩容

接着,我们继续append,这回我们将一个切片中的元素逐个append进去,怎么做呢?

可以通过three-dot运算符...

arr := [4]int{1, 2, 3, 4}
slice1 := arr[:2] // slice1: [1 2] len: 2 cap: 4
slice1 = append(slice1, 5)

slice2 := []int{6, 7}
slice1 = append(slice1, slice2...)
fmt.Println(len(slice1))
fmt.Println(cap(slice1))

fmt.Printf("%v\n", arr)
fmt.Printf("%v\n", slice1)

输出:

5
8
[1 2 5 4]
[1 2 5 6 7]
  • 这里有几点需要引起关注:

    1. slice1的cap由原来的4变为8,即发生了扩容

    2. arr的值未发生变化,说明了slice1扩容之后指向的底层数组不再是arr,而是一个cap为8的新数组

扩容

就如前面所看到,当 一个切片的容量无法存储更多元素时,切片会自动扩容,它会生成一个容量更大的新切片,然后把原切片的元素和新元素一起拷贝到新切片中

扩容的通常认为的规则是:

100
100
101
224

不过,本人在尝试的时候,发现并不一定都是遵守这样的规则,比如:

  1. 原切片长度小于 1024 时,新切片的容量会按照原切片的 2 倍扩容

  2. 否则,新切片的容量会按照原切片的 1.5 倍扩容,此时需要注意的是,如果新切片的容量按照原切片的 1.5 倍扩容一次仍然无法存储新元素时,将会不断按照原切片的 1.5 倍扩容,直到新切片的容量可以存储原切片的元素和新元素为止。

arr := [200]int{}
slice := arr[:]
fmt.Println(len(slice))
fmt.Println(cap(slice))
slice = append(slice, 1)
fmt.Println(len(slice))
fmt.Println(cap(slice))

输出:

200
200
201
400

上述的情况是遵守通常规则的,但下面的例子就有点不同了

arr := [100]int{}
slice := arr[:]
fmt.Println(len(slice))
fmt.Println(cap(slice))
slice = append(slice, 1)
fmt.Println(len(slice))
fmt.Println(cap(slice))

输出:

100
100
101
224

此时容量变为224,个人猜测可能是于机器内存对齐之类相关,不过总的原则是长度小于1024时,扩容之后的容量通常会大于等于原有的两倍

删除元素

Go没有给出现成的删除接口,但我们仍然可以通过append来实现,如下例:

slice := []int{1,2,3,4}
slice = append(slice[:2], slice[3:]...)
fmt.Printf("%v", slice)

输出:

[1 2 4]

通过切片的切割拼接来删除指定下标的元素,上例展示的是删除下标为2的元素

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值