介绍
切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数append来实现的,这个函数可以快速且高效的增长切片。还可以通过对切片再次切片来缩小一个切片的大小。
因为切片底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。
切片是一个数组的引用,切片是引用类型
内部实现
切片是有3个字段的数据结构,分别是:指向底层数组的指针、切片可访问元素的个数(长度)、切片允许增长到的元素个数(容量)。一个切片(地址)占用24字节内存
定义切片
1、让切片引用一个已经创建好的数组
直接引用数组,这个数组事先存在,程序员可见
package main
import "fmt"
func main() {
// 定义数组
var intArr [5]int = [...]int{1, 2, 3, 4, 5}
// 以intArr为底层数组,创建切片
slice := intArr[2:4] // 长度为 4-2=2 , 容量为 intArr底层数组的容量5 - 2 = 3
fmt.Println("slice 元素 = ", slice) // 3, 4
fmt.Println("slice 长度 = ", len(slice)) // 2
fmt.Println("slice 容量 = ", cap(slice)) // 3
}
2、通过make创建切片
会自动创建底层数组并维护,对外不可见
var 切片名 []type = make([]type, len, [cap])
默认零值
var slice []int = make([]int, 3) // 类型: []int, 值: [0 0 0]
// 创建一个整形切片
// 长度是3个元素,容量是5个元素
slice := make([]int, 3, 5)
// 创建一个字符串切片
// 长度和容量都是5个元素
slice := make([]string, 5)
func make(Type, size IntegerType) Type
内建函数make分配并初始化一个类型为切片、映射、或通道的对象。其第一个实参为类型,而非值。make的返回类型与其参数相同,而非指向它的指针。其具体结果取决于具体的类型:
- 切片:size指定了其长度。该切片的容量等于其长度。切片支持第二个整数实参可用来指定不同的容 量; 它必须不小于其长度,因此 make([]int, 0, 10) 会分配一个长度为0,容量为10的切片。
- 映射:初始分配的创建取决于size,但产生的映射长度为0。size可以省略,这种情况下就会分配一个 小的起始大小。
- 通道:通道的缓存根据指定的缓存容量初始化。若 size为零或被省略,该信道即为无缓存的。
nil和空切片
- 只声明不初始化,就会创建一个nil切片
使用场景:要求返回一个切片,但是发生异常的时候,可以返回一个nil切片
// 创建nil切片
var slice []int
// nil切片
var slice01 []int
fmt.Printf("slice01 类型: %T, 值: %v \n", slice01, slice01) // slice01 类型: []int, 值: []
fmt.Println(slice01 == nil) // true
- 声明空切片
使用场景:数据库查询返回0个查询结果时
// 使用make创建空的整形切片
slice := make([]int, 0)
// 使用切片字面量创建空的整形切片
slice := []int{}
切片可以继续切片
// 切片,修改
var slice05 []int = []int{5, 6, 7, 8, 9}
fmt.Println("slice05 = ", slice05) // [5 6 7 8 9]
slice06 := slice05[2:4]
fmt.Println("slice06 = ", slice06) // [7 8]
slice06[0] = 22
fmt.Println("修改slice06后: slice05 = ", slice05) // slice05也发生改变 [5 6 22 8 9]
fmt.Println("修改slice06后: slice06 = ", slice06) // [22 8]
计算切片长度和容量
对于底层数组容量是k的切片 slice[i:j] 来说
长度:j - i
容量:k - i
注意: j不能超出原始数组的长度,否则报错
panic: runtime error: slice bounds out of range [:6] with capacity 5
slice[0:]
slice[:]
slice[0:len()]
var slice05 []int = []int{5, 6, 7, 8, 9}
fmt.Println("slice05 = ", slice05)
slice06 := slice05[2:6] // panic: runtime error: slice bounds out of range [:6] with capacity 5
fmt.Println("slice06 = ", slice06)
切片内存分配探究
slice从底层来说是一个数据结构,struct结构体
type slice struct {
ptr *[2]int
len int
cap int
}
首地址,指向底层数组,len和cap指定了底层数组的长度和容量
package main
import "fmt"
func main() {
// 定义数组
var intArr [5]int = [...]int{10, 20, 30, 40, 50}
fmt.Println("intArr = ", intArr) // [10 20 30 40 50]
// 以intArr为底层数组,创建切片
slice := intArr[2:4] // 长度为 4-2=2 , 容量为 intArr底层数组的容量5 - 2 = 3
fmt.Println("slice 元素 = ", slice) // [30 40]
fmt.Println("slice 长度 = ", len(slice)) // 2
fmt.Println("slice 容量 = ", cap(slice)) // 3
// 修改切片元素,会作用到底层数组
slice[1] = 21
fmt.Println("修改后slice 元素 = ", slice) // [30 21]
fmt.Println("修改后intArr = ", intArr) // [10 20 30 21 50]
}
切片遍历
1、常规for循环遍历
// 常规for循环遍历切片
var slice04 []int = []int{10, 20, 30}
for i := 0; i < len(slice04); i++ {
fmt.Printf("slice04[%v] = %v, 地址:%p \n", i, slice04[i], &slice04[i])
}
slice04[0] = 10, 地址:0xc000012198
slice04[1] = 20, 地址:0xc0000121a0
slice04[2] = 30, 地址:0xc0000121a8
2、for range遍历
range创建每个元素的副本,而不是直接返回对该元素的引用
迭代返回变量是一个迭代过程中根据切片依次赋值的新变量
所以v的地址总是相同的
要想获取每个元素的地址,可以使用切片变量和索引值
// range创建每个元素的副本,而不是直接返回对该元素的引用
// 迭代返回变量是一个迭代过程中根据切片依次赋值的新变量
// 所以v的地址总是相同的
// 要想获取每个元素的地址,可以使用切片变量和索引值
for i, v := range slice04 {
fmt.Printf("range - slice04[%v] = %v, 地址:%p \n", i, v, &v)
}
range - slice04[0] = 10, 地址:0xc000014100
range - slice04[1] = 20, 地址:0xc000014100
range - slice04[2] = 30, 地址:0xc000014100
增长切片
func append(slice []Type, elems …Type) []Type
内建函数append将元素追加到切片的末尾。若它有足够的容量,其目标就会重新切片以容纳新的元素。否则,就会分配一个新的基本数组,将被引用的现有的值复制到新数组里,再追加新的值。
append返回更新后的切片,因此必须存储追加后的结果。
// append 追加
var slice07 = make([]int, 3, 5) // 长度3,容量5
fmt.Println("slice07 = ", slice07, "slice07容量", cap(slice07))
// slice07 = [0 0 0] slice07容量 5
// 追加元素
slice07 = append(slice07, 2)
fmt.Println("slice07 = ", slice07) // slice07 = [0 0 0 2]
// 追加切片 slice...
slice07 = append(slice07, slice07...)
fmt.Println("slice07 = ", slice07, "追加后slice07容量", cap(slice07))
// slice07 = [0 0 0 2 0 0 0 2] 追加后slice07容量 10
函数append会智能的处理底层数组的容量增长。再切片容量小于1000个元素时,总是会成倍的增长容量,一旦元素个数超过1000,容量的增长因子会设为1.25,也就是每次增加25%的容量。随着语言的演化,增长算法可能会有所改变。
切片的拷贝操作
使用 copy 内置函数完成拷贝
func copy(dst, src []Type) int
内建函数copy将元素从来源切片复制到目标切片中,也能将字节从字符串复制到字节切片中。copy返回被复制的元素数量,它会是 len(src) 和 len(dst) 中较小的那个。来源和目标的底层内存可以重叠。
copy(para1, para2) 参数的数据类型是切片
para1 是目标切片
// 拷贝切片
var a []int = []int{1, 2, 3}
var b = make([]int, 5) // 长度 容量都是5
c := copy(b, a) // 返回被复制的元素个数,
fmt.Println("c = ", c)
fmt.Println("a = ", a, "容量=", cap(a)) // a = [1 2 3] 容量= 3
fmt.Println("b = ", b, "容量=", cap(b)) // b = [1 2 3 0 0] 容量= 5
c := copy(a, b)
// c = 3
// a = [0 0 0] 容量= 3
// b = [0 0 0 0 0] 容量= 5
切片引用
package main
import "fmt"
// 引用类型
func test(slice []int) {
slice[0] = 100
}
func main() {
var slice = []int{1, 2, 3}
fmt.Println("slice = ", slice) // slice = [1 2 3]
test(slice)
fmt.Println("slice = ", slice) // slice = [100 2 3]
}
字符串切片
string底层是一个 byte 数组
切片用来截取字符串
package main
import "fmt"
func main() {
// string底层是一个 byte 数组,因此string也可以进行切片
str := "xiaobaobao"
// 使用切片 获取baobao
slice := str[4:]
fmt.Printf("slice类型: %T, 值: %v \n", slice, slice)
}
// slice类型: string, 值: baobao
string是不可改变的,也就是说通过string[i] = ‘x’是不行的
str1 := "小笑笑"
arr1 := []rune(str1)
fmt.Printf("arr1类型: %T, 值: %v \n", arr1, arr1)
arr1[1] = '涛'
arr1[2] = '涛'
str3 := string(arr1)
fmt.Printf("str3类型: %T, 值: %v \n", str3, str3)
// arr1类型: []int32, 值: [23567 31505 31505]
// str3类型: string, 值: 小涛涛