Go 切片
切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。
数组生成切片
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
从连续内存区域生成切片是常见的操作,格式如下:slice [开始位置 : 结束位置]
,切片结果不包括结束位置
从数组生成切片:
package main
import "fmt"
func main() {
var a = [5]int{1, 2, 3, 4, 5}
fmt.Println(a[1:3]) // [2 3]
}
从数组或切片生成新的切片拥有如下特性:
- 切片位置为:0 - len(数组);
- 取出元素不包含结束位置对应的索引;
- 当缺省开始位置时,表示从连续区域开头到结束位置;
- 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
- 两者同时缺省时,与切片本身等效;
- 两者同时为 0 时,等效于空切片,一般用于切片复位。
直接声明新的切片
除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:var name []Type
,其中 name 表示切片的变量名,Type 表示切片对应的元素类型。
package main
import "fmt"
func main() {
// 声明字符串切片
var strList []string
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
// 输出3个切片
fmt.Println(strList, numList, numListEmpty) // [] [] []
// 输出3个切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty)) // 0 0 0
// 切片判定空的结果
fmt.Println(strList == nil) // true
fmt.Println(numList == nil) // true
fmt.Println(numListEmpty == nil) // false
}
切片是动态结构,只能与 nil 判定相等,不能互相判定相等。声明新的切片后,可以使用 append() 函数向切片中添加元素。
使用 make() 函数构造切片
如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:make( []Type, size, cap )
其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。
package main
import "fmt"
func main() {
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b) // [0 0] [0 0]
fmt.Println(len(a), len(b)) // 2 2
}
其中 a 和 b 均是预分配 2 个元素的切片,只是 b 的内部存储空间已经分配了 10 个,但实际使用了 2 个元素。
容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2。
使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。
append()
Go语言的内建函数 append() 可以为切片动态添加元素
package main
import "fmt"
func main() {
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
fmt.Println(a) // [1 1 2 3 1 2 3]
}
除了在切片的尾部追加,我们还可以在切片的开头添加元素
package main
import "fmt"
func main() {
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片
fmt.Println(a) // [-3 -2 -1 0 1 2 3]
}
在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。
从切片中删除元素
Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。
删除头部元素
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
slice = slice[1:] // 删除开头1个元素
fmt.Println(slice) // [2 3]
}
也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化)
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
slice = append(slice[:0], slice[1:]...) // 删除开头1个元素
fmt.Println(slice) // [2 3]
}
删除尾部元素
package main
import "fmt"
func main() {
// 删除尾部一个元素
slice := []int{1, 2, 3, 4, 5, 6}
// slice = slice[:len(slice)-1]
slice = append(slice[:len(slice)-1], slice[:0]...)
fmt.Println(slice)
}
删除任意位置元素
package main
import "fmt"
func main() {
slice := []int{1, 2, 3, 4, 5, 6}
index := 3
slice = append(slice[:index], slice[index+1:]...)
fmt.Println(slice)
}
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5}
b := a[1:4]
c := b[2:4]
fmt.Println(c) // [4 5]
}
Reslice
- Reslice时索引以被slice的切片为准
- 索引不可以超过被slice的切片的容量cap()值
- 索引越界不会导致底层数组的重新分配而是引发错误
Append
- 可以在slice 尾部追加元素
- 可以将一个slice追加在另一个slice尾部
- 如果最终长度未超过追加到slice的容量则返回原始slice
- 如果超过追加到的slice的容量则将重新分配数组并拷贝原始数据