Go 切片的操作

121 阅读4分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。

复习切片的结构

  • ptr:指向底层数组的指针
  • len:切片长度
  • cap:切片容量,底层数组最大长度

make

  • 切片可以用内建函数 make 来创建,这也是创建动态数组的方式
  • make 函数会分配一个元素为零值的数组并返回一个指向(引用)了它的切片

语法格式

func make([]T, len, cap) []T
  • []T代表创建的切元素的类型
  • len:切片长度长度
  • cap:可选的切片容量,不指定 cap 则默认为 len 的长度

具体的🌰

package main

import "fmt"

func main() {
	var s1 = make([]int, 5)
	printSlice(s1)

	s2 := make([]int, 5, 10)
	printSlice(s2)

    // reslice
	b := make([]int, 0, 5) // len(b)=0, cap(b)=5
	printSlice(b)
	
    // 向后扩展
	b = b[:cap(b)] // len(b)=5, cap(b)=5
	printSlice(b)
	
    // ptr 指向了第二个元素,所以 cap 也变成
	b = b[1:] // len(b)=4, cap(b)=4
	printSlice(b)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

运行结果

len=5 cap=5 [0 0 0 0 0]
len=5 cap=10 [0 0 0 0 0]
len=0 cap=5 []
len=5 cap=5 [0 0 0 0 0]
len=4 cap=4 [0 0 0 0]

make 的原理

s := make([]int, 5)

append

将元素追加到切片的末尾,并返回切片

语法格式

func append(slice []Type, elems ...Type) []Type
  • slice是一个元素类型为Type的切片,其余类型为Type的值将会追加到切片的末尾
  • 返回值是一个包含原切片所有元素加上新添加元素的切片
  • slice的底层数组太小,添加元素时超过 cap,系统会重新分配更大的底层数组,返回的切片会指向这个新分配的数组
  • 若切片有足够的容量(cap),会直接在原切片末尾追加元素

返回值

  • 返回更新后的切片
  • 必须接收 append 后的返回值
  • 因为如果系统分配了更大的新底层数组,那么切片的指针需要重新指向新分配的数组
// slice 切片追加元素 elem1, elem2
slice = append(slice, elem1, elem2)

// slice 切片追加另一个切片 anotherSlice
slice = append(slice, anotherSlice...)

具体的🌰

package main

import "fmt"

func main() {
	var s []int
	printSlice(s)

	// 添加一个空切片
	s = append(s, 0)
	printSlice(s)

	// 这个切片会按需增长
	s = append(s, 1)
	printSlice(s)

	// 可以一次性添加多个元素
	s = append(s, 2, 3)
	printSlice(s)

	s = append(s, 2, 3, 4)
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

运行结果

len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=4 cap=4 [0 1 2 3]
len=7 cap=8 [0 1 2 3 2 3 4]

切片的 cap 不足以添加所有元素时,系统会自动创建一个新的、更大容量的切片,然后将原有切片的内容复制到新的切片,最后再返回

再来看一个🌰

package main

import "fmt"

func main() {
	// 定义一个数组
	var arr = [5]int{1, 2, 3, 4, 5}

	s1 := arr[:3]
	s2 := append(s1, 44, 55)
	s3 := append(s2, 66)
	s4 := append(s3, 77)

	fmt.Println(s2, s3, s4)
	fmt.Println(arr)

	printSlice(s1)
	printSlice(s2)
	printSlice(s3)
	printSlice(s4)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

运行结果

[1 2 3 44 55] [1 2 3 44 55 66] [1 2 3 44 55 66 77]
[1 2 3 44 55]
len=3 cap=5 [1 2 3]
len=5 cap=5 [1 2 3 44 55]
len=6 cap=10 [1 2 3 44 55 66]
len=7 cap=10 [1 2 3 44 55 66 77]
  • 切片 s1 本来就指向数组 arr,s2 指向 s1,所以它们的容量还是 5
  • s1、s2 添加元素后切片大小没有大于 cap,所以切片 s1、s2还是指向 arr 数组,而添加的元素也会同步修改数组 arr 对应位置的值
  • s3、s4 添加元素后切片大小已经大于 cap,所以会分配更大的底层数组,s3、s4 会重新指向新的数组,所以添加的元素自然不会影响数组 arr

特殊情况

将字符追加到字节数组之后是合法的

slice = append([]byte("hello "), "world"...)

将切片追加到另一个切片尾部

package main

import "fmt"

func main() {
	a := []string{"John", "Paul"}
	b := []string{"George", "Ringo", "Pete"}
    
	// 等价写法 append(a, b[0], b[1], b[2])
	a = append(a, b...)	// b后面加了 ...
	printSlice(a)
}

func printSlice(s []string) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

运行结果

len=5 cap=5 [John Paul George Ringo Pete]

copy

  • 将元素从源切片复制到目标切片
  • 作为一种特殊情况,它还会将字节从字符串复制到字节切片
  • 源和目标可能会重叠
  • 函数返回复制的元素数,会是 len(src) 和 len(dst) 中的最小值

语法格式

func copy(dst, src []Type) int

具体的🌰

package main

import "fmt"

func main() {

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

	s1 := arr[:3]
	s2 := make([]int, 5)

	printSlice(s1)
	printSlice(s2)

	copy(s2, s1)
	printSlice(s2)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

运行结果

len=3 cap=5 [1 2 3]
len=5 cap=5 [0 0 0 0 0]
len=5 cap=5 [1 2 3 0 0]