目录

slice


slice

1 了解切片

数组是切片和映射的基础

与数组一样,切片也是 Go 中的一种数据类型,它表示一系列类型相同的元素。 不过,与数组更重要的区别是切片的大小是动态的,不是固定的。

切片是数组或另一个切片之上的数据结构。 我们将源数组或切片称为基础数组。 通过切片,可访问整个基础数组,也可仅访问部分元素。

切片只有 3 个组件:

指向基础数组中第一个可访问元素的指针。 此元素不一定是数组的第一个元素 array[0]。 切片的长度。 切片中的元素数目。 切片的容量。 切片开头与基础数组结束之间的元素数目。

2 声明和初始化切片

    months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}

目前,切片与数组的区别不大。 可用相同的方式声明这两者。 若要从切片中获取信息,可使用内置函数 len() 和 cap()。 我们将继续使用这些函数来确认切片可具有来自基础数组的后续元素。

3 切片项

Go 支持切片运算符 s[i:p],其中:

  • s 表示数组。
  • i 表示指向要添加到新切片的基础数组(或另一个切片)的第一个元素的指针。 变量 i 对应于数组 array[i] 中索引位置 i 处的元素。 请记住,此元素不一定是基础数组的第一个元素 array[0]。
  • p 表示创建新切片时要使用的基础数组中的元素数目,也表示元素位置。 变量 p 对应于可用于新切片的基础数组中的最后一个元素。 可在位置 array[i+1] 找到基础数组中位置 p 处的元素。 请注意,此元素不一定是基础数组的最后一个元素 array[len(array)-1]。
    months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
    quarter1 := months[0:3]
    quarter2 := months[3:6]
    quarter3 := months[6:9]
    quarter4 := months[9:12]
    fmt.Println(quarter1, len(quarter1), cap(quarter1))
    fmt.Println(quarter2, len(quarter2), cap(quarter2))
    fmt.Println(quarter3, len(quarter3), cap(quarter3))
    fmt.Println(quarter4, len(quarter4), cap(quarter4))

请注意,切片的长度不变,但容量不同。 我们来了解 quarter2 切片。 声明此切片时,你指出希望切片从位置编号 3 开始,最后一个元素位于位置编号 6。 切片长度为 3 个元素,但容量为 9,原因是基础数组有更多元素或位置可供使用,但对切片而言不可见。 例如,如果你尝试打印类似 fmt.Println(quarter2[3]) 的内容,会出现以下错误:panic: runtime error: index out of range [3] with length 3。

4 追加项

我们了解了切片的工作原理,还学习了它们与数组的相似性。 现在,让我们来了解它们与数组之间有何不同。 第一个区别是切片的大小不是固定的,而是动态的。 创建切片后,可向其添加更多元素,这样切片就会扩展。 稍后你将了解基础数组发生的情况。

Go 提供了内置函数 append(slice, element),便于你向切片添加元素。 将要修改的切片和要追加的元素作为值发送给该函数。 然后,append 函数会返回一个新的切片,将其存储在变量中。 对于要更改的切片,变量可能相同。

    var numbers []int
    for i := 0; i < 10; i++ {
        numbers = append(numbers, i)
        fmt.Printf("%d\tcap=%d\t%v\n", i, cap(numbers), numbers)
    }

5 删除项

你可能想知道,删除元素会怎么样呢? Go 没有内置函数用于从切片中删除元素。 可使用上述切片运算符 s[i:p] 来新建一个仅包含所需元素的切片。

    letters := []string{"A", "B", "C", "D", "E"}
    remove := 2

	if remove < len(letters) {

		fmt.Println("Before", letters, "Remove ", letters[remove])

		letters = append(letters[:remove], letters[remove+1:]...)

		fmt.Println("After", letters)
	}

6 创建切片的副本

Go 具有内置函数 copy(dst, src []Type) 用于创建切片的副本。 你需要发送目标切片和源切片。 例如,你可如下例所示创建一个切片副本:

为何要创建副本? 更改切片中的元素时,基础数组将随之更改。 引用该基础数组的任何其他切片都会受到影响。 让我们在代码中看看此过程,然后创建一个切片副本来解决此问题。

使用下述代码确认切片指向数组,而你在切片中所做的每个更改都会影响基础数组。

func main() {
    letters := []string{"A", "B", "C", "D", "E"}
    fmt.Println("Before", letters)

    slice1 := letters[0:2]
    slice2 := letters[1:4]

    slice1[1] = "Z"

    fmt.Println("After", letters)
    fmt.Println("Slice2", slice2)
}

若要解决此问题,你需要创建一个切片副本,它会在后台生成新的基础数组。 可以使用以下代码:

func main() {
    letters := []string{"A", "B", "C", "D", "E"}
    fmt.Println("Before", letters)

    slice1 := letters[0:2]

    slice2 := make([]string, 3)
    copy(slice2, letters[1:4])

    slice1[1] = "Z"

    fmt.Println("After", letters)
    fmt.Println("Slice2", slice2)
}