切片
- 切片是围绕动态数据来构建的
- 数组一旦创建就不能更改长度,但是切片可以按需求自动增长和缩小
- 增长是使用内置的append函数来实现的
- 缩小通过对切片的再次切片来实现
切片内部实现
切片分为 data len cap
直接声明和初始化
package main
import "fmt"
func main(){
var s1 []int //声明切片
fmt.Println(s1 )
s1 =append(s1,1) //切片追加 相当于增长切片
s1 =append(s1,2)
fmt.Println(s1)
var s2 = []int{1,2,3} //声明并初始化
fmt.Println(s2)
}
------------------- //输出结果
[]
[1 2]
[1 2 3]
使用make初始化
make([]int,长度,容量)
- 以类型0值+长度的个数填充slice
- 容量不填默认=长度,如果填了不能小于长度,可大于长度
package main
import "fmt"
func main() {
// 使用make初始化一个长度为0的slice
s1 := make([]int, 0) // 不写容量默认跟长度一致
s1 = append(s1, 1)
s1 = append(s1, 2)
s1 = append(s1, 3)
fmt.Println(s1)
fmt.Println(cap(s1)) //4
// 使用make 初始化一个长度为5 容量为5的slice
s2 := make([]int, 5, 5)
s2 = append(s2, 1)
s2 = append(s2, 2)
s2 = append(s2, 3)
fmt.Println(s2) // [0 0 0 0 0 1 2 3]
}
new和make对比
- 简单说 new只分配内存,make用于slice,map,和channel的初始化。
- 对比表格
函数名 | 适用范围 | 返回值 | 填充值 |
---|---|---|---|
new | new可以对所有类型进行内存分配 | new 返回指针 | new 填充零值 |
make | make 只能创建类型(slice map channel) | make 返回引用 | make 填充非零值 |
通过切片创建新的切片
- 语法如下
slice[start:end:cap]
- 其中 start 表示从 slice 的第几个元素开始切
- end 控制切片的长度(end-start)
- cap 控制切片的容量 ,如果没有给定 cap ,slice的长度值,则表示切到底层数组的最尾部
- 新切片的长度 = end-start
- 新切片的容量 = cap-start
- cap不能大于原切片的cap
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Printf("[s1][值:%v][新切片的长度=%d 容量=%d]\n", s1, len(s1), cap(s1))
s2 := s1[2:6]
fmt.Printf("[s2][从索引为2 就是3号往后切4个元素][值:%v][新切片的长度=%d 容量=%d]\n", s2, len(s2), cap(s2))
s3 := s1[5:]
fmt.Printf("[s3][从索引为5 就是6号 切到最后][值:%v][新切片的长度=%d 容量=%d]\n", s3, len(s3), cap(s3))
s4 := s1[:4]
fmt.Printf("[s4][从开头切4个元素][值:%v][新切片的长度=%d 容量=%d]\n", s4, len(s4), cap(s4))
s5 := s1[:]
fmt.Printf("[s5][复制整个切片][值:%v][新切片的长度=%d 容量=%d]\n", s5, len(s5), cap(s5))
s6 := s1[2:6:8]
fmt.Printf("[s6][从索引为2 就是3号往后切4个元素][值:%v][新切片的长度=%d 容量=%d]\n", s6, len(s6), cap(s6))
s7 := s1[2:6:9]
fmt.Printf("[s7][从索引为2 就是3号往后切4个元素][值:%v][新切片的长度=%d 容量=%d]\n", s7, len(s7), cap(s7))
}
改变某一个切片的元素
- 说明所有切片的值都变了
fmt.Printf("[改变某一个切片的元素。看看其他切片会受影响吗]\n")
s6[1] = 80
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(s4)
fmt.Println(s5)
fmt.Println(s6)
fmt.Println(s7)
切片是引用类型,浅拷贝
package main
import "fmt"
func main() {
a1 := []int{1, 2, 3}
a2 := a1
a2[0] = 10
fmt.Println(a1, a2)
a1[2] = 30
fmt.Println(a1, a2)
}
使用copy函数进行深copy
- 它表示把切片 src 中的元素拷贝到切片 dst 中
- 返回值为拷贝成功的元素个数
- 如果 src 比 dst 长,就截断
- 如果 src 比 dst 短,则只拷贝 src 那部分:
package main
import "fmt"
func main() {
a1 := []int{1, 2, 3}
a2 := make([]int, 3)
a3 := make([]int, 5)
copy(a2, a1)
copy(a3, a1)
a2[0] = 10
fmt.Println(a1, a2)
a1[2] = 30
fmt.Println(a1, a2)
fmt.Println(a3)
}
切片作为参数传给函数
- 虽然函数传参是指传递,应该是深拷贝
- 但是slice属于引用类型,浅拷贝,在函数内部的修改还是会影响外部
package main
import "fmt"
func showSlice(s []int) {
fmt.Printf("[传入的切片为:%v]\n", s)
s[2] = 30
}
func main() {
a1 := []int{1, 2, 3}
showSlice(a1)
fmt.Printf("[showSlice处理后的切片为:%v]\n", a1)
}
切片遍历
for range 遍历
- 返回索引和值
- range 创建了每个元素的副本,而不是直接返回对该元素的引用
package main
import "fmt"
func main() {
a1 := []int{10, 20, 30}
// 遍历查看值
for index, num := range a1 {
fmt.Printf("[index :%d,num:%d]\n", index, num)
}
//遍历修改值
for index := range a1 {
a1[index] += 100
}
for index, num := range a1 {
fmt.Printf("[index :%d,num:%d]\n", index, num)
}
}
切片扩容和cap字段作用
- 假设没有cap只有len,怎么扩容
- slice长度为10,len=10,元素已经满了,现在要插入第11个元素
- slice长度扩为20,len=20,此时有用的元素为11个,还有9个空位。
- slice对外界暴露的接口只有ptr和len=20,如果此时再需要插入元素
- slice到底应该扩容还是在原有的基础上直接插入呢,如果直接插入从哪个索引插入
- cap字段的作用就是为了方便扩容
package main
import "fmt"
func main() {
a1 := []int{10, 11, 12, 13}
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
a1 = append(a1, 22)
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
}
- 当这个 append 操作完成后,newSlice 拥有一个全新的底层数组,这个数组的容量是原来的两倍
- append() 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量(随着语言的演化,这种增长算法可能会有所改变)。
package main
import "fmt"
func main() {
a1 := []int{10, 11, 12, 13}
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
a1 = append(a1, 22)
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
a2 := make([]int, 1000)
a1 = append(a1, a2...)
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
a3 := make([]int, 1000)
a1 = append(a1, a3...)
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
}