Slice(切片)代表变长的序列,序列中每个元素都有相同的类型,slice的语法和数组很像,只是没有固定长度而已。

一个slice由三个部分构成:指针、长度和容量。

  • 指针 指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。
  • 长度 对应slice中元素的数目(长度不能超过容量)。
  • 容量 一般是从slice的开始位置到底层数据的结尾位置。

1. 关于slice的长度和容量

关于长度和容量的理解,可以参考下图:
len&cap.png

内置的len和cap函数分别返回slice的长度和容量。

如果切片操作超出cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了slice,因为新slice的长度会变大,举个例子:

months := [...]string{1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December"}
summer := months[6:9]
fmt.Println(summer) // [June July August]
endlessSummer := summer[:5] // extend a slice (within capacity)
fmt.Println(endlessSummer)  // [June July August September October]

2. 关于slice的指针

因为slice值包含指向第一个slice元素的指针因此向函数传递slice将允许在函数内部修改底层数组的元素。换句话说,复制一个slice只是对底层的数组创建了一个新的slice别名。
接着上面的例子:

endlessSummer[0] = "Changed"
fmt.Println(endlessSummer) //[Changed July August September October]
fmt.Println(summer) // [Changed July August]
fmt.Println(months) // [ January February March April May Changed July August September October November December]

虽然修改的是endlessSummer slice,但是summer和months的值也都变了。

3. 理解数组和slice的区别

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。

通过一个例子理解数组和slice的区别:

例子1:

// reverse函数的形参是一个数组(数组有固定长度),调用该函数时,参数需传入数组
func reverse(a [6]int) {
    for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
        a[i], a[j] = a[j], a[i]
    }
}

func main() {
    var i [6]int = [6]int{1, 2, 3, 4, 5, 6}
    reverse(i) // 参数传入数组
    fmt.Println(i) // i的值不变,还是[1 2 3 4 5 6]
}

例子2:改变一下例子1

// reverse函数的形参是一个slice(slice没有固定长度),调用该函数时,参数需传入slice
func reverse(a []int) {
    for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
        a[i], a[j] = a[j], a[i]
    }
}

func main() {
    var i [6]int = [6]int{1, 2, 3, 4, 5, 6}
    reverse(i[:]) // 参数传入slice
    fmt.Println(i) // i值改变了:[6 5 4 3 2 1]
}

例子2的i值变了,是因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。

因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩的动态序列,slice功能也更灵活,但是要理解slice工作原理的话需要先理解数组。

和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。
不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较:

func equal(x, y []string) bool {
    if len(x) != len(y) {
        return false
    }
    for i := range x {
        if x[i] != y[i] {
            return false
        }
    }
    return true
}

4. 创建slice

内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。

make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。在第一种语句中,slice是整个数组的view。在第二个语句中,slice只引用了底层数组的前len个元素,但是容量将包含整个的数组。额外的元素是留给未来的增长用的。