Go语言核心之美 3.2-slice切片

本文介绍了Go语言中的slice切片,它是一种长度可变的元素序列,与数组紧密相关。slice由指针、长度和容量组成,可以共享底层数组并进行高效操作,如append函数。通过实例展示了如何创建、操作和扩展slice,包括切片操作、反转和就地操作技巧。文章还讨论了slice与数组的区别以及与nil和长度为0的区别,强调了在使用中需要注意的细节。
摘要由CSDN通过智能技术生成

Slice(切片)是长度可变的元素序列(与之对应,上一节中的数组是不可变的),每个元素都有相同的类型。slice类型写作[]T,T是元素类型。slice和数组写法很像,区别在于slice没有指定长度。

数组和slice之间的联系是非常紧密的。slice是很轻量的数据结构,是引用类型,它指向一个底层数组,该数组被称之为slice的底层数组,slice可以访问底层数组的某个子序列,也可以访问整个数组。一个slice由三个部分组成:指针、长度、容量,指针指向了slice中第一个元素对应的底层数组元素的地址,因为slice未必是从数组第一个元素开始,因此slice中的第一个元素未必是数组中的第一个元素;长度是slice中的元素数目,是不能超过容量的;容量一般是从slice中第一个元素对应底层数组中的开始位置,到底层数组的结尾的长度。内置函数len和cap分别返回slice的长度和容量。

多个slice可以共享底层数组,甚至它们引用的部分可以相互重叠。图4.1表示了一个数组,它的元素是一年中各个月份的字符串,还有两个重叠引用了底层数组的slice。数组定义:

months := [...]string{
  1: "January", /* ... */, 12: "December"}

其中一月份是months[1],十二月是months[12]。通常来说,数组第一个元素的索引从0开始,但是月份一般是从1月开始到12月,因此在声明数组时,我们跳过了第0个元素,这里,第0个元素会被默认初始化为""(空字符串)。

下面介绍切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),该操作会创建一个新的slice,slice会引用s中从第i个元素到第j-1个元素,新的slice有j-i个元素。如果省略下标i,写成s[:j],实际上代表s[0:j];如果省略下标j,写成s[i:],代表s[0:len(s)]。因此months[1:13]操作将引用全部月份,和months[1:]操作等价。months[:]则是引用整个数组。下面分别表示第二个季度和北方的夏天:

Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2)     // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]

两个slice有重叠部分:六月份。下面是一个是否包含相同月份的测试(性能不高):

for _, s := range summer {
    for _, q := range Q2 {
        if s == q {
            fmt.Printf("%s appears in both\n", s)
        }
    }
}

如果slice操作访问的下标大于cap(s)将导致panic(越界),如果超过len(s)则意味着扩展slice(不能超过cap(s) ):

fmt.Println(summer[:20]) // panic: out of range

endlessSummer := summer[:5] // 在容量允许内扩展summer
fmt.Println(endlessSummer)  // "[June July August September October]"

另外,字符串的slice操作和[]byte的slice操作是很相似的,它们都表现为x[m:n],并且都返回原始序列的子序列,底层数组也都是原始序列,因此slice操作是常量级别的时间复杂度(只是更新slice中的指向位置,长度,容量)。若x[m:n]的目标是字符串,则生成一个子串;若目标是[]byte,则生成新的[]byte。

因为slice引用包含了指向底层数组的指针,因此向函数传递slice后,函数可以在内部对slice的底层数组进行更改。换而言之,复制slice就是为底层数组创建一个新的引用,代价是非常低的(其实就是复制一个含有三个字段的struct)。下面的reverse函数对[]int类型的slice进行就地反转(无内存分配),它可以用于任意长度的slice:

// reverse reverses a slice of ints in place.
func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

反转数组:

a := [...]int{
  0, 1, 2, 3, 4, 5}
reverse(a[:])
fmt.Println(a) // "[5 4 3 2 1 0]"

如果要将slice中前n个元素和后面所有的元素调换位置的话(以第n个元素为支点,向左旋转),其中一个办法是调用三次reverse函数,第一次是反转前n个元素,然后是反转剩下所有元素,最后是反转整个slice(如果是向右旋转,则将第一个和第三个函数对调位置即可)。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值