Go语言提供了另一种数据类型——切片(Slice),由于切片的数据结构中有指向数组的指针,因此它是一种引用类型。Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速操作数据集合。
切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数append()来实现的,这个函数可以快速且高效地增长切片,也可以通过对切片再次切割,缩小一个切片的大小。因为切片的底层内存也是在连续内存块中分配的,所以,切片还能获得索引、迭代及为垃圾回收优化的好处。
在Go语言中,创建切片的方法有多种,而能否确定切片的容量是创建切片的关键,它决定了该使用哪种方式创建切片。
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。从连续内存区域生成切片是常见的操作。
格式:slice [开始位置 : 结束位置]
// 从数组中生成切片
var a = [3]int{1, 2, 3}
fmt.Println(a) // 1 2 3
fmt.Println(a[1:2]) // 2
fmt.Println(a[:2]) // 1 2
fmt.Println(a[:3]) // 1 2 3
fmt.Println(a[1:]) // 2 3
fmt.Println(a[0:]) // 1 2 3
fmt.Println(a[:]) // 1 2 3
从数组或切片生成新的切片的特性:
(1)取出的元素数量 = 结束位置 - 开始位置。
(2)取出元素不包含结束位置对应的索引,切片最后一个元素使用slice[len(slice)]获取。
(3)当默认开始位置时,表示从连续区域开头到结束位置。
(4)当默认结束位置时,表示从开始位置到整个连续区域末尾。
(5)两者同时默认时,与切片本身等效。
(6)两者同时为0时,等效于空切片,一般用于切片复位。
(7)根据索引位置取切片slice元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误,生成切片时,结束位置可以填写len(slice)但不会报错。
除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此,切片类型也可以被声明。
语法:var name []Type
var strList []string // 声明字符串切片
var numList []int // 声明整型切片
var numListEmp = []int{} // 声明一个空切片
fmt.Println(strList, numList, numListEmp) //输出3个切片
fmt.Println(len(strList), len(numList), len(numListEmp)) //输出3个切片大小
// 切片判定空的结果
fmt.Println(strList == nil) // true
fmt.Println(numList == nil) // true
fmt.Println(numListEmp == nil) // false
如果需要动态地创建一个切片,可以使用make()内建函数。
语法:make( []Type, size, cap )
// 当使用make()函数时,需要传入一个参数,用于指定切片的长度
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
slice := make([]int,5,3)
,否则编译会报错。使用切片字面量创建切片和创建数组类似,只是不需要指定“[]”运算符中的值,初始的长度和容量会根据初始化时提供的元素的个数来确定:
//创建字符串切片
//其长度和容量都是5个元素
slice := []string{ " red ", "blue ", "green ", "yellow ", "pink "}
//创建一个整型切片
//其长度和容量都是3个元素
slice := []int{10,20,30}
当使用切片字面量时,可以设置初始长度和容量,即在初始化时给出所需的长度和容量作为索引(下标)。例如,创建长度和容量都是100个元素的切片。
//使用字符串"1"初始化第100个元素
slice2 := []string{99: "1"}
如果在“[]”运算符中指定了一个值,那么创建的就是数组而不是切片;只有不指定值的时候才会创建切片:
//创建含有3个元素的整型数组
array := [3]int{10,20,30}
//创建长度和容量都是3的整型切片
slice := []int{10,20,30}
有时程序可能需要声明一个值为空的切片(或nil切片),在声明的时候不做任何初始化,就可以创建一个nil切片:
//创建nil整型切片
var slice []int
在Go语言中,nil切片是很常见的创建切片的方法。nil切片多用于标准库和内置函数,在需要描述一个目前暂时不存在的切片时,nil切片十分好用。例如,函数要求返回一个切片但发生异常时,利用初始化,通过声明一个切片可以创建一个nil切片:
//使用make创建空的整型切片
slice := make([]int, 0)
//使用切片字面量创建空的整型切片
slice := [ ]int{}
Go语言的内建函数append()可以为切片动态添加元素。每个切片会指向一片内存空间,这片空间能容纳一定数量的元素。当空间不能容纳足够多的元素时,切片就会进行“扩容”。“扩容”操作往往发生在append()函数调用时。
切片在扩容时,容量的扩展规律是按容量的2倍数进行扩充的:
func main() {
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("len: %d cap: %d pointer:%p\n", len(numbers), cap(numbers), numbers)
}
}
append()函数除了可以添加一个元素外,还可以同时添加多个元素:
func main() {
var student []string
// 向切片中添加一个元素
student = append(student, "a")
fmt.Println(student)
// 向切片中添加多个元素
student = append(student, "b", "c", "d")
fmt.Println(student)
// 向切片中添加切片
team := []string{"e", "f", "g"}
student = append(student, team...)
fmt.Println(student)
}
Go语言的内置函数copy()可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片大小不同,那么就会按照较小的数组切片的元素的个数进行复制。
copy()函数的语法:copy( destSlice, srcSlice []Type) int
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
// 只复制slice1的前3个元素到slice2中
copy(slice2, slice1)
fmt.Println(slice2) // [1 2 3]
// 复制slice2的3个元素到slice1的前3个位置
copy(slice1, slice2)
fmt.Println(slice1) // [1 2 3 4 5]
}
切片复制操作后对切片元素的影响:
func main() {
// 设置元素数量为1000
const elementCount = 1000
// 预分配足够多的元素切片
srcData := make([]int, elementCount)
// 将切片赋值
for i := 0; i < elementCount; i++ {
srcData[i] = i
}
// 引用srcData切片
refData := srcData
// 预分配足够多的元素切片
copyData := make([]int, elementCount)
// 将数据复制到新的切片中
copy(copyData, srcData)
// 修改原始数据的第一个袁术:ref改变,copy不变
srcData[0] = 999
fmt.Println(refData[0]) // 引用,数据改变
fmt.Println(copyData[0]) // 赋值,数据不变
// 赋值原始数据 从4到6(不含6)
copy(copyData, srcData[4:6]) // copyData 前两个元素为 4 5
for i := 0; i < 5; i++ {
fmt.Printf("%d \t", copyData[i])
}
}
Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置可以分为以下三种情况:
删除开头的元素可以直接移动数据指针:
a = []int{1, 2, 3}
a = a[1:] //删除开头1个元素
a = a[N:] //删除开头N个元素
删除开头的元素也可以不移动数据指针,但是需要将后面的数据向开头移动,可以使用append()函数原地完成(所谓原地完成,是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化)
a = []int{1, 2, 3}
a = append(a[:0], a[1:]…) //删除开头1个元素
a = append(a[:0], a[N:]…) //删除开头N个元素
另外,还可以使用copy()函数来删除开头的元素:
a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] //删除开头1个元素
a = a[:copy(a, a[N:])] //删除开头N个元素
对于删除中间的元素,需要对剩余的元素进行一次整体的移动,同样可以用append()函数或copy()函数原地完成:
a = []int{1, 2, 3,…}
a = append(a[:i], a[i+1:]…) //删除中间1个元素
a = append(a[:i], a[i+N:]…) //删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] //删除中间1个元素
a = a[:i+copy(a[i:],a[i+N:])] //删除中间N个元素
删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况。
a = []int{1, 2, 3}
a = a[:len(a)-1] //删除尾部1个元素
a = a[:len(a)-N] //删除尾部N个元素
func main() {
seq := []string{"a", "b", "c", "d", "e"}
// 指定删除位置
index := 2
// 查看删除位置之前的元素和之后的元素
fmt.Println(seq[:index], seq[index+1:]) // [a b] [d e]
//将删除点前后的元素连接起来
seq = append(seq[:index], seq[index+1:]...)
fmt.Println(seq) // [a b d e]
}
注意:Go语言中删除切片元素的本质是,以被删除元素为分界点,将前后两个部分的内存重新连接起来。