前言

数组的长度在定义之后无法再次修改, 数组是值类型, 每次传递都将产生一份副本.

显然这种数据结构无法完全满足我们的真实需求. 所以, Go语言提供了切片(Slice)来弥补数组的不足.

[]TT

数组和Slice之间有着紧密的联系. 一个Slice是一个轻量级的数据结构, 提供了访问数组子序列(或者全部)元素的功能, 而且Slice的底层确实引用一个数组对象.

一个Slice由三个部分构成: 指针, 长度容量.

指针指向第一个Slice元素对应的底层数组元素的地址, 要注意的是Slice的第一个元素并不一定就是数组的第一个元素.

切片并不是数组或数组指针, 它通过内部指针和相关属性引用数组片段, 以实现变长方案.

多个Slice之间可以共享底层的数据, 并且引用的数组部分区间可能重叠.

Slice并不是真正意义上的动态数组, 而是一个引用类型, Slice总是指向一个底层数组.

所以, 为什么要有切片?

  • 数组的容量固定, 不能自动扩展.
  • 数组为值传递. 数组作为函数参数时, 会将整个数组值拷贝一份给形参.

在Go语言中, 我们几乎可以在所有的场景中, 使用切片来替换数组使用.

切片的本质

  • 不是一个数组的指针, 是一种数据结构, 用来操作数组内部元素.

创建切片

[][]...

可以使用创建数组的方式对切片进行初始化:

arr := [6]int{1, 2, 3, 4, 5, 6}
s := arr[1: 3: 5]
切片名称[low: high: max]
low: 起始下标位置
high: 结束下标位置    len = high - low
max: 容量    cap = max - low

我们可以看一下这个切片的长度以及容量:

package main

import "fmt"

func main() {
    arr := [6]int{1, 2, 3, 4, 5, 6}
    s := arr[1: 3: 5]
    fmt.Println(s)
    fmt.Println("len(s) = ", len(s))
    fmt.Println("cap(s) = ", cap(s))
}

结果:

[2 3]
len(s) =  2
cap(s) =  4

当然, 在截取原数组时, 我们也可以忽略容量, 此时容量 = 原数组长度 - low.

package main

import "fmt"

func main() {
    arr := [6]int{1, 2, 3, 4, 5, 6}

    s := arr[1:3]
    fmt.Println(s)
    fmt.Println("len(s) = ", len(s))
    fmt.Println("cap(s) = ", cap(s))
}

结果:

[2 3]
len(s) =  2
cap(s) =  5
s2
package main

import "fmt"

func main() {
    arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    s := arr[1: 5: 8]
    fmt.Println(s)
    fmt.Println("len(s) = ", len(s))
    fmt.Println("cap(s) = ", cap(s))

    s2 := s[: 6]
    fmt.Println(s)
    fmt.Println("len(s2) = ", len(s2))
    fmt.Println("cap(s2) = ", cap(s2))
}

结果:

[2 3 4 5]
len(s) =  4
cap(s) =  7
[2 3 4 5 6 7]
len(s2) =  6
cap(s2) =  7
s2
s3
package main

import "fmt"

func main(){
    arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    s := arr[2: 5]  // [3, 4, 5]
    fmt.Println("s = ", s)

    s2 := arr[2: 7]  // [3, 4, 5, 6, 7]
    fmt.Println("s2 = ", s2)

    s3 := s[2: 7]
    fmt.Println("s3 = ", s3)
}

结果:

s =  [3 4 5]
s2 =  [3 4 5 6 7]
s3 =  [5 6 7 8 9]

有数组时我们可以截取数组来创建切片, 下面我们来看一下没有数组时创建切片的方法.

常用的切片创建方法:

  1. 自动推导类型创建Slice
s := []int{1, 2, 3, 4}  // 创建有 4 个元素的切片
make(切片类型, 长度, 容量)
s := make([]int, 5, 10)  // len(s) = 5, cap(s) = 10
  1. make时, 没有指定容量, 那么容量 = 长度.
s := make([]int, 5)  // len(s) = 5, cap(s) = 5
package main

import "fmt"

func main(){
    // 自动推导赋初始值
    s1 := []int{1, 2, 4, 8}
    fmt.Println("s1 = ", s1, "len(s1) = ", len(s1), "cap(s1) = ", cap(s1))

    // make创建切片, 指定容量
    s2 := make([]int, 5, 10)
    fmt.Println("s2 = ", s2, "len(s2) = ", len(s2), "cap(s2) = ", cap(s2))

    // make创建切片, 不指定容量
    s3 := make([]int, 5)
    fmt.Println("s3 = ", s3, "len(s3) = ", len(s3), "cap(s3) = ", cap(s3))

}
Slicemapchannel

切片做函数参数

切片在做函数参数时, 传引用(地址).

package main

import "fmt"

func foo(s []int){  // 切片做函数参数
    s[0] = -1    // 直接修改 main 中的 slice
}

func main(){
    slice := []int{1, 2, 3, 4}
    fmt.Println(slice)

    foo(slice)  // 传引用

    fmt.Println(slice)
}

结果:

[1 2 3 4]
[-1 2 3 4]

常用操作函数

append函数

append()

append函数会智能的将底层数组的容量增长, 一旦超过原底层数组容量, 通常以2倍(1024以下)容量重新分配底层数组, 并复制原来的数据. 因此, 使用append给切片做扩充时, 切片的地址可能发生变化. 但是数据都被重新保存了, 不影响使用.

李培冠博客

欢迎访问我的个人网站:

李培冠博客:lpgit.com