Golang的切片Slice小结

声明

var identifier []type
var slice1 []type = make([]type, len)
var slice1 []type = make([]type, len, cap) 

s :=[]int{0,1}//s := [len]int{} 和数组有二义性不允许

s := make([]int, len) //len可以是变量

var numbers []int //这样的 slice == nil,这也是slice唯一合法的比较操作

底层实现!!!

切片,一开始看感觉和c++的vector很像,但实际上还是有区别的,slice的底层比vector简单得多,slice更像是对数组的一层封装而vector是对线性内存空间进行操作

切片内部结构:

struct Slice
{   
    byte*    array;       // actual data
    uintgo    len;        // number of elements
    uintgo    cap;        // allocated number of elements
};
//所以unsafe.Sizeof(切片)是 24

当把 slice 作为参数,本身传递的是值,但因为 byte* array指针指向的还是原底层数组,所以对参数的修改也会影响到外部。
但如果对 slice 本身做 append,而且导致 slice 进行了扩容,实际扩容的是函数内复制的一份切片,对于函数外面的切片没有变化。

细品上面这句话,如需要扩容则会开辟新的内存并且copy原来的arr,这时append返回的slice的底层数组和原来的不是同一个

如果不需要扩容那么还是在原有的底层数组进行操作

len和cap函数

和vector的size和capacity类似,len切片<=cap切片<=len底层数组

fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) //打印一个空切片  len=0 cap=0 slice=[]

切片操作

可以通过切片操作来引用数组/切片

s := arr[s:l] //将 arr 中[s,l)的元素创建为一个新的切片
s := arr[:l] //[0,l)
s := arr[s:]//[s, end)

s := arr[:] //相当于s引用arr

注意,对于slice,切片操作返回一个原始字节系列的子序列,底层都是共享之前的底层数组,因此切片操作对应常量时间复杂度

所以上图中一旦修改了Q2或者summer,mouths也会被改变

append和copy

func main() {
   var numbers []int
   printSlice(numbers) //len=0 cap=0 slice=[]

   /* 允许追加空切片 */
   numbers = append(numbers, 0)
   printSlice(numbers) //len=1 cap=1 slice=[0]

   /* 向切片添加一个元素 */
   numbers = append(numbers, 1)
   printSlice(numbers) //len=2 cap=2 slice=[0 1]

   /* 同时添加多个元素 */
   numbers = append(numbers, 2,3,4)
   printSlice(numbers) // len=5 cap=6 slice=[0 1 2 3 4]

   /* 创建切片 numbers1 是之前切片的两倍容量*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)

   /* 拷贝 numbers 的内容到 numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)  //len=5 cap=12 slice=[0 1 2 3 4]
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

append切片

s = append(s, t...) //t should be slice

比较

slice唯一合法的比较操作是和nil比较

如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断,因为一个零值的slice等于nil。

扩容

可以通过查看$GOROOT/src/runtime/slice.go源码,其中扩容相关代码如下:

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
    newcap = cap
} else {
    if old.len < 1024 {
        newcap = doublecap
    } else {
        // Check 0 < newcap to detect overflow
        // and prevent an infinite loop.
        for 0 < newcap && newcap < cap {
            newcap += newcap / 4
        }
        // Set newcap to the requested cap when
        // the newcap calculation overflowed.
        if newcap <= 0 {
            newcap = cap
        }
    }
}

从上面的代码可以看出以下内容:

  • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如intstring类型的处理方式就不一样。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值