【Go基础】切片
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
切片
1. 切片的定义
切片slice是Golang中独有的数据类型。
数组有特定的用处但是有不足之处 运行时长度不可变。切片是随处可变的它构建在数组之上并且提供更强大的能力和便捷。
切片slice是对数组一个片段的引用所以切片是引用数据类型。这个片段可以是整个数组也可以是数组的一部分。
特点
-
切片与其引用的数组共享内存地址
-
虽然切片可变大但是不能超过其引用的数组使用cap(切片)的计算切片的长度。
0 <= len(切片) <= cap(切片) <= len(arr)
-
切片是数组的引用但它是一个结构体值传递。
切片的容量如何计算
切片容量为切片起始位置到数组结束位置的元素个数。假如从数组下标n开始切片那么 cap(切片) = len(数组) - n
2. 切片的声明
2.1 空切片
声明一个数组不指定其大小该数组就是一个切片。
这个切片是一个空切片它的值是 nil
var arr[]int
if arr == nil {
fmt.Println("切片的元素数:", len(arr))// 0
fmt.Println("切片的容量数:", cap(arr))// 0
fmt.Println("切片的所有元素:", slice1)// []
fmt.Println("arr是空切片")// arr是空切片
}
2.2 下标创建切片
切片名 := 数组名[起始索引, 结束索引]
一旦这样声明切片就会将数组 [****起始下标结束下标****中的值引用一份。
var arr [10]int = [10]int{1, 2, 3, 4, 5, 6, 7}
// slice就是一个切片slice将 1~2 下标的值复制一份。
// 这个slice中的元素为: [2, 3]
var slice []int = arr[1:3]
-
如果缺起始下标默认从数组开头区域截取。
-
如果缺结束下表默认从截取至数组结束。
len(切片) 查看切片的大小
len(slice)
cap(切片) 查看切片的容量
cap(slice)
// 0 <= len(slice) <= cap(slice) <= len(arr)
看看切片地址与数组地址到底一样不一样
使用的下标必须是非负且在范围内的若超过范围会出现 panic
2.3 make创建切片
name := make(Type, length, capacity)
切片名 := make(切片类型切片长度切片长度)
// 定义一个切片类型为int数组, 长度为5, 容量为20
slice := make([]int, 5, 20)
该切片最大扩容到 长度 = 容量 = 20。
你是否会有疑惑不是说切片是数组的引用必须引用一个数组吗
For example, make([]int, 0, 10) allocates an underlying array of size
and returns a slice of length and capacity that is backed by this underlying array
关于make的官方解释如上所示 声明一个底层数组返回关于这个数组的切片。 将数组隐藏起来通过切片操作数组。
切片定义后跟正常的数组使用一样可以通过 变量名[下标] 进行操作。
slice2 := make([]int, 5, 20)
fmt.Println(slice2) //此时为空切片
slice2[0] = 1
slice2[1] = 23
slice2[2] = 32
slice2[3] = 154
slice2[4] = 6
fmt.Println(slice2) //此时已经有值
3. 切片的底层
切片的底层是一个结构体有三个变量 指针
、长度
、容量
4. 切片的注意事项
-
切片可以继续切片多个切片指向同一块区域会互相影响。
-
切片不能直接比较无法用
==
判断两个切片是否相等它唯一的等值操作用于和nil
比较。一个nil的切片底层指针是无指向的并且长度、容量都为0。
slice == nil
len(slice) = 0
cap(slice) = 0
一个指向0数组的切片底层是有指向的长度、容量都为0
slice != nil
len(slice) = 0
cap(slice) = 0
所以不能使用 切片是否为nil 来判断切片是否为空应该判断 len(切片) 是否为0
5. 切片的扩容和拷贝
上文说过切片是对数组的引用可以扩容那么它是如何扩容的切片会自动扩容吗
尝试以下能否自动扩容
// 声明一个长度为5容量为20的切片
slice2 := make([]int, 5, 20)
// 将前五个空间占满
slice2[0] = 1
slice2[1] = 23
slice2[2] = 32
slice2[3] = 154
slice2[4] = 6
// 给第六个空间赋值
slice2[5] = 6
发现运行出现bug证明无法自动扩容。
那么该如何扩容 使用 append()
5.1 append()
func append (slice []Type, elems …Type) []Type
第一个参数是想要扩容的切片。
第二个参数是给这个切片扩容的元素要与原切片中的元素类型相同。
返回值是扩容后的切片。
slice2 := make([]int, 5, 20)
slice2[0] = 1
slice2[1] = 23
slice2[2] = 32
slice2[3] = 154
slice2[4] = 6
// 这样是错误的: slice2[5] = 6
slice3 := append(slice2, 6)
fmt.Println(slice3)
这样就给切片扩容了一个元素 6
切片的扩容策略
-
如果新申请的容量(cap)大于2倍的旧容量(old.cap)最终的容量(newcap)就是新容量
-
否则判断 旧容量(old.cap)的长度小于1024如果小于最终容量(newcap)就是旧容量(old.cap)的2倍
-
如果大于等于1024最终容量(newcap)从旧容量开始循环增加 (newcap + 256*3) / 4直至最终容量(newcap)大于等于新容量(cap)
注意 循环增加多少这个要看Go语言版本不同版本可能不同。并且扩容规则针对不同类型也有不同的实现。
func growslice(et *_type, old slice, cap int) slice {
// 此处省略一大段判断条件...
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
newcap = doublecap
} else {
for 0 < newcap && newcap < cap {
newcap += (newcap + 3*threshold) / 4
}
if newcap <= 0 {
newcap = cap
}
}
}
// 下面省略一大段判断条件
}
新申请的容量(cap)大于2倍的旧容量(old.cap)
slice2 := make([]int, 0, 1)
slice2 = append(slice2, 1, 2, 3, 4, 5, 6)
fmt.Println("slice2的长度为:", len(slice2))
fmt.Println("slice2的容量为:", cap(slice2))
容量小于1024最终容量是旧容量的二倍
slice5 := make([]int, 0, 1)
for i := 0; i < 10; i++ {
slice5 = append(slice5, i)
fmt.Printf("切片的长度为: %d\t\t", len(slice5))
fmt.Printf("切片的容量为: %d\n", cap(slice5))
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7rDoNM89-1673752243237)(null)]
累了另外一个不想算了…🥲
5.2 copy()
当多个切片指向同一个数组时它们操作共享空间彼此影响。这时需要使用拷贝函数 copy() 对切片进行值拷贝。
func copy(dst, src []Type) int
dst 目标切片
src 数据来源切片
返回值 拷贝了多少个数据过去
slice2 := make([]int, 5, 20)
slice2[0] = 1
slice2[1] = 23
slice2[2] = 32
slice2[3] = 154
slice2[4] = 6
slice4 := make([]int, 5, 5)
i := copy(slice4, slice2)
fmt.Println("copy的返回值为:", i) // 5
fmt.Println("slice4的值为:", slice4) // [1 23 32 154 6]
copy也被称为深拷贝直接将一个切片指向另一个切片叫浅拷贝