跳到主要内容

Go 切片(Slice)

切片是一种动态数组,声明长度默认为 0 的数组即为一个切片。

var stringSlice []string

创建切片

使用切片字面量

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

使用 make 函数

使用 make 函数创建切片,可以指定长度和容量。

numbers := make([]int, 3, 5)
fmt.Println(numbers) // 输出: [0 0 0]

从数组创建切片

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

添加元素

使用 append 函数可以向切片添加元素。

var names []string
names = append(names, "Alice", "Bob", "Charlie")
fmt.Println(names) // 输出: [Alice Bob Charlie]

注意:

  1. append 返回一个新的切片,需要用变量接收。
  2. 新的切片必须赋值给原切片,才能实现增加元素的效果。
  3. append 返回的是一个新的引用。

展开操作

可以使用 ... 将一个切片的元素展开,并添加到另一个切片中。

func main() {
students := []string{"John", "Paul", "George", "Ringo"}
moreStudents := []string{"Mick", "Keith", "Charlie", "Ronnie"}

students = append(students, moreStudents...)

for index, name := range students {
fmt.Println(index, name)
}
}

复制切片

使用 copy 函数可以复制切片。

sourceSlice := []int{1, 2, 3}
destinationSlice := make([]int, len(sourceSlice))
copy(destinationSlice, sourceSlice)
fmt.Println(destinationSlice) // 输出: [1 2 3]

注意:

  1. copy 不会扩充目标切片的长度,目标切片必须有足够的长度。
  2. copy 返回被复制的元素数量。

删除元素

要从切片中删除元素,可以使用切片的截取和 append

numbers := []int{1, 2, 3, 4, 5}
// 删除索引为 2 的元素
numbers = append(numbers[:2], numbers[3:]...)
fmt.Println(numbers) // 输出: [1 2 4 5]

访问和修改元素

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

// 访问索引为 2 的元素
fmt.Println(numbers[2]) // 输出: 3

// 修改索引为 2 的元素
numbers[2] = 10
fmt.Println(numbers) // 输出: [1 2 10 4 5]

获取切片的长度和容量

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

fmt.Println(len(numbers)) // 长度: 5
fmt.Println(cap(numbers)) // 容量: 5

截取切片

切片的截取操作通过更新切片的指针、长度和容量来完成。

array := []string{"a", "b", "c", "d", "e", "f", "g", "h"}

// 截取索引从 1 到 5 的元素(不包含索引 5)
subSlice := array[1:5]
fmt.Println(subSlice) // 输出: [b c d e]

截取到末尾

subSlice := array[1:]
fmt.Println(subSlice) // 输出: [b c d e f g h]

从起始位置截取

subSlice := array[:4]
fmt.Println(subSlice) // 输出: [a b c d]

截取整个切片

subSlice := array[:]
fmt.Println(subSlice) // 输出: [a b c d e f g h]

注意:新的切片变量指向同一个底层数组。

array := []string{"a", "b", "c", "d", "e"}
subSlice := array[:3]
array[0] = "z"
fmt.Println(subSlice) // 输出: [z b c]
fmt.Println(array) // 输出: [z b c d e]

当修改底层数组的元素时,所有引用该数组的切片都会受影响。

遍历切片

可以使用 for 循环或 for range 语句遍历切片。

students := []string{"John", "Paul", "George", "Ringo"}

// 使用传统的 for 循环
for i := 0; i < len(students); i++ {
fmt.Println(i, students[i])
}

// 使用 for range 循环
for index, name := range students {
fmt.Println(index, name)
}

切片的容量扩展

当使用 append 向切片添加元素,且超过其容量时,Go 会自动分配更大的底层数组。

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

numbers = append(numbers, 4)
fmt.Println(len(numbers), cap(numbers)) // 输出: 4 6

切片的容量增长策略是指数型增长,以减少内存分配的次数。

切片的内存共享

由于切片共享底层数组,在某些情况下可能需要避免内存共享带来的副作用。

func main() {
data := []int{1, 2, 3, 4, 5}
subData := data[2:4] // [3 4]

subData[0] = 100
fmt.Println(data) // 输出: [1 2 100 4 5]
fmt.Println(subData) // 输出: [100 4]
}

如果想要创建切片的副本,可以使用 copy 函数。

func main() {
data := []int{1, 2, 3, 4, 5}
subData := make([]int, 2)
copy(subData, data[2:4]) // 复制 [3 4] 到 subData

subData[0] = 100
fmt.Println(data) // 输出: [1 2 3 4 5]
fmt.Println(subData) // 输出: [100 4]
}

切片与数组的区别

  • 数组是定长的,长度是类型的一部分,长度固定后不能改变。
  • 切片是变长的,可以动态增加或减少元素,长度可变。
  • 切片是对底层数组的一个引用,操作切片会影响底层数组。

最佳实践

  • 在函数参数中,尽量使用切片而非数组,以获得更大的灵活性。
  • 当需要避免底层数组的副作用时,可以使用 copy 创建切片的副本。
  • 遍历切片时,使用 for range 循环更加简洁。