go 用法和本质

118 阅读4分钟

Go 中的 切片(slice) 是一个非常强大且常用的数据结构。它比数组更加灵活,因为它的大小是动态可变的。切片是 Go 语言中处理序列数据的主要方式,它可以用来管理一个数组的子集,也可以动态地调整大小。

1. 切片的基本定义

切片是一个描述数组连续片段的结构体。切片本身并不存储数据,它指向一个底层数组,并包含三个重要信息:

  • 指针:指向切片数据的首元素的指针。
  • 长度len):切片中的元素个数。
  • 容量cap):切片从切片起始位置到底层数组末尾的元素个数。
var s []int // 声明一个切片(零值是 nil)

2. 切片的创建

切片的创建有多种方式:

a. 使用 make 创建切片

make 函数可以创建一个指定大小和容量的切片:

// 创建一个长度为 5,容量为 10 的切片
s := make([]int, 5, 10)

  • 这里 5 是切片的长度,表示切片中包含 5 个元素。
  • 10 是切片的容量,表示切片可以容纳最多 10 个元素,而不需要重新分配底层数组。

b. 从数组或另一个切片中创建

通过切片操作符,可以从数组或已有的切片中创建切片:

arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // 从 arr 数组中获取一个切片,包含元素 arr[1], arr[2], arr[3]

  • 这里 arr[1:4] 会创建一个包含从 arr[1]arr[3] 的元素的切片。

c. 使用字面量创建切片

s := []int{1, 2, 3, 4, 5}

这种方式通过字面量创建了一个长度为 5 的切片,元素为 {1, 2, 3, 4, 5}

3. 切片的基本操作

a. 获取切片的长度和容量

s := []int{1, 2, 3, 4, 5}
fmt.Println(len(s)) // 输出: 5
fmt.Println(cap(s)) // 输出: 5

b. 修改切片的元素

s[0] = 10 // 修改第一个元素
fmt.Println(s) // 输出: [10 2 3 4 5]

c. 切片的扩展(使用 append

切片的一个重要特性是可以通过 append 函数动态地扩展它的容量。append 会根据需要重新分配底层数组并返回新的切片:

s := []int{1, 2, 3}
s = append(s, 4) // 添加元素 4
fmt.Println(s)   // 输出: [1 2 3 4]

s = append(s, 5, 6, 7) // 添加多个元素
fmt.Println(s)         // 输出: [1 2 3 4 5 6 7]

需要注意的是,append 可能会重新分配底层数组,如果切片的容量不足以容纳更多的元素,底层数组会被复制到一个更大的数组。

d. 切片的截取(切片操作)

可以通过切片操作从一个切片中截取出一部分,创建一个新的切片:

s := []int{1, 2, 3, 4, 5}
subSlice := s[1:4] // 包括索引 1 到 3 的元素
fmt.Println(subSlice) // 输出: [2 3 4]

4. 切片的本质

切片是一个 引用类型,它并不存储数据,而是描述了底层数组的一个视图。因此,多个切片可以引用同一个底层数组,它们对数组的修改会相互影响。

a. 底层数组的共享

a := []int{1, 2, 3}
b := a // b 和 a 指向同一个底层数组
b[0] = 10
fmt.Println(a) // 输出: [10 2 3]
fmt.Println(b) // 输出: [10 2 3]

因为 ab 指向同一个底层数组,所以修改 b 会影响到 a

b. 切片与数组的区别

  • 数组 是固定大小的,声明时必须指定大小,并且一旦定义,大小不能更改。
  • 切片 是动态大小的,灵活且方便,可以通过 append 增加元素,且没有固定大小。

切片是 Go 语言中最常用的数据结构之一,它提供了比数组更灵活的操作方式。

5. 切片的扩展和内存分配

当你使用 append 向切片添加新元素时,如果切片的容量不足以容纳新元素,Go 会自动创建一个新的、更大的底层数组,并将原来的数据复制到新的数组中。新的数组大小通常是原来容量的两倍。

这种机制使得切片在增长时具有较好的性能,因为每次扩展时,数组的大小增长会成倍增加,从而减少了复制操作的次数。

6. 切片与垃圾回收

Go 语言的垃圾回收机制会在切片不再使用时回收底层数组的内存。因此,如果你不再使用某个切片,可以将其赋值为 nil,以帮助垃圾回收。

s := []int{1, 2, 3}
s = nil // 切片不再引用底层数组,等待垃圾回收

总结:

  • 切片是 Go 语言中的动态数据结构,它是对数组的引用,支持动态大小。
  • 切片包含 指针、长度和容量 三个信息。
  • 切片的底层是数组,切片修改时会直接影响底层数组。
  • append 函数用于扩展切片,必要时会自动扩展底层数组。
  • 切片是引用类型,多个切片可以共享同一个底层数组。

切片的灵活性和高效性使得它成为 Go 语言中最常用的数据结构之一。